blob: 776c331a20bbbc59edb7911935319c15359936b8 [file] [log] [blame]
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
//
// Explicit pool used for managing resources.
// @dart = 2.9
import "dart:async";
import 'dart:convert';
import 'dart:ffi';
import 'dart:typed_data';
import 'package:ffi/ffi.dart' as packageFfi;
import 'package:ffi/ffi.dart' show Utf8;
/// Manages native resources.
///
/// Primary implementations are [Pool] and [Unmanaged].
abstract class ResourceManager {
/// Allocates memory on the native heap.
///
/// The native memory is under management by this [ResourceManager].
///
/// For POSIX-based systems, this uses malloc. On Windows, it uses HeapAlloc
/// against the default public heap. Allocation of either element size or count
/// of 0 is undefined.
///
/// Throws an ArgumentError on failure to allocate.
Pointer<T> allocate<T extends NativeType>({int count: 1});
}
/// Manages native resources.
class Pool implements ResourceManager {
/// Native memory under management by this [Pool].
final List<Pointer<NativeType>> _managedMemoryPointers = [];
/// Callbacks for releasing native resources under management by this [Pool].
final List<Function()> _managedResourceReleaseCallbacks = [];
/// Allocates memory on the native heap.
///
/// The native memory is under management by this [Pool].
///
/// For POSIX-based systems, this uses malloc. On Windows, it uses HeapAlloc
/// against the default public heap. Allocation of either element size or count
/// of 0 is undefined.
///
/// Throws an ArgumentError on failure to allocate.
Pointer<T> allocate<T extends NativeType>({int count: 1}) {
final p = Unmanaged().allocate<T>(count: count);
_managedMemoryPointers.add(p);
return p;
}
/// Registers [resource] in this pool.
///
/// Executes [releaseCallback] on [releaseAll].
T using<T>(T resource, Function(T) releaseCallback) {
_managedResourceReleaseCallbacks.add(() => releaseCallback(resource));
return resource;
}
/// Registers [releaseResourceCallback] to be executed on [releaseAll].
void onReleaseAll(Function() releaseResourceCallback) {
_managedResourceReleaseCallbacks.add(releaseResourceCallback);
}
/// Releases all resources that this [Pool] manages.
void releaseAll() {
for (final c in _managedResourceReleaseCallbacks) {
c();
}
_managedResourceReleaseCallbacks.clear();
for (final p in _managedMemoryPointers) {
Unmanaged().free(p);
}
_managedMemoryPointers.clear();
}
}
/// Creates a [Pool] to manage native resources.
///
/// If the isolate is shut down, through `Isolate.kill()`, resources are _not_ cleaned up.
R using<R>(R Function(Pool) f) {
final p = Pool();
try {
return f(p);
} finally {
p.releaseAll();
}
}
/// Creates a zoned [Pool] to manage native resources.
///
/// Pool is availabe through [currentPool].
///
/// Please note that all throws are caught and packaged in [RethrownError].
///
/// If the isolate is shut down, through `Isolate.kill()`, resources are _not_ cleaned up.
R usePool<R>(R Function() f) {
final p = Pool();
try {
return runZoned(() => f(),
zoneValues: {#_pool: p},
onError: (error, st) => throw RethrownError(error, st));
} finally {
p.releaseAll();
}
}
/// The [Pool] in the current zone.
Pool get currentPool => Zone.current[#_pool];
class RethrownError {
dynamic original;
StackTrace originalStackTrace;
RethrownError(this.original, this.originalStackTrace);
toString() => """RethrownError(${original})
${originalStackTrace}""";
}
/// Does not manage it's resources.
class Unmanaged implements ResourceManager {
/// Allocates memory on the native heap.
///
/// For POSIX-based systems, this uses malloc. On Windows, it uses HeapAlloc
/// against the default public heap. Allocation of either element size or count
/// of 0 is undefined.
///
/// Throws an ArgumentError on failure to allocate.
Pointer<T> allocate<T extends NativeType>({int count = 1}) =>
packageFfi.allocate(count: count);
/// Releases memory on the native heap.
///
/// For POSIX-based systems, this uses free. On Windows, it uses HeapFree
/// against the default public heap. It may only be used against pointers
/// allocated in a manner equivalent to [allocate].
///
/// Throws an ArgumentError on failure to free.
///
void free(Pointer pointer) => packageFfi.free(pointer);
}
/// Does not manage it's resources.
final Unmanaged unmanaged = Unmanaged();
extension Utf8InPool on String {
/// Convert a [String] to a Utf8-encoded null-terminated C string.
///
/// If 'string' contains NULL bytes, the converted string will be truncated
/// prematurely. Unpaired surrogate code points in [string] will be preserved
/// in the UTF-8 encoded result. See [Utf8Encoder] for details on encoding.
///
/// Returns a malloc-allocated pointer to the result.
///
/// The memory is managed by the [Pool] passed in as [pool].
Pointer<Utf8> toUtf8(ResourceManager pool) {
final units = utf8.encode(this);
final Pointer<Uint8> result = pool.allocate<Uint8>(count: units.length + 1);
final Uint8List nativeString = result.asTypedList(units.length + 1);
nativeString.setAll(0, units);
nativeString[units.length] = 0;
return result.cast();
}
}
extension Utf8Helpers on Pointer<Utf8> {
/// Returns the length of a null-terminated string -- the number of (one-byte)
/// characters before the first null byte.
int strlen() {
final Pointer<Uint8> array = this.cast<Uint8>();
final Uint8List nativeString = array.asTypedList(_maxSize);
return nativeString.indexWhere((char) => char == 0);
}
/// Creates a [String] containing the characters UTF-8 encoded in [this].
///
/// [this] must be a zero-terminated byte sequence of valid UTF-8
/// encodings of Unicode code points. It may also contain UTF-8 encodings of
/// unpaired surrogate code points, which is not otherwise valid UTF-8, but
/// which may be created when encoding a Dart string containing an unpaired
/// surrogate. See [Utf8Decoder] for details on decoding.
///
/// Returns a Dart string containing the decoded code points.
String contents() {
final int length = strlen();
return utf8.decode(Uint8List.view(
this.cast<Uint8>().asTypedList(length).buffer, 0, length));
}
}
const int _kMaxSmi64 = (1 << 62) - 1;
const int _kMaxSmi32 = (1 << 30) - 1;
final int _maxSize = sizeOf<IntPtr>() == 8 ? _kMaxSmi64 : _kMaxSmi32;