mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 17:35:46 +00:00
[vm/ffi] Improve Pool
samples
Addressing comments from https://dart-review.googlesource.com/c/sdk/+/177706/20 Change-Id: I0cf023a5613978eebcb4aca84c9db24796687602 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/179180 Commit-Queue: Daco Harkes <dacoharkes@google.com> Reviewed-by: Lasse R.H. Nielsen <lrn@google.com>
This commit is contained in:
parent
bdb60329e9
commit
73bd44d363
|
@ -11,30 +11,47 @@ import 'package:ffi/ffi.dart';
|
||||||
|
|
||||||
import '../calloc.dart';
|
import '../calloc.dart';
|
||||||
|
|
||||||
/// Keeps track of all allocated memory and frees all allocated memory on
|
/// An [Allocator] which frees all allocations at the same time.
|
||||||
/// [releaseAll].
|
|
||||||
///
|
///
|
||||||
/// Wraps an [Allocator] to do the actual allocation and freeing.
|
/// The pool allows you to allocate heap memory, but ignores calls to [free].
|
||||||
|
/// Instead you call [releaseAll] to release all the allocations at the same
|
||||||
|
/// time.
|
||||||
|
///
|
||||||
|
/// Also allows other resources to be associated with the pool, through the
|
||||||
|
/// [using] method, to have a release function called for them when the pool is
|
||||||
|
/// released.
|
||||||
|
///
|
||||||
|
/// An [Allocator] can be provided to do the actual allocation and freeing.
|
||||||
|
/// Defaults to using [calloc].
|
||||||
class Pool implements Allocator {
|
class Pool implements Allocator {
|
||||||
/// The [Allocator] used for allocation and freeing.
|
/// The [Allocator] used for allocation and freeing.
|
||||||
final Allocator _wrappedAllocator;
|
final Allocator _wrappedAllocator;
|
||||||
|
|
||||||
Pool(this._wrappedAllocator);
|
|
||||||
|
|
||||||
/// Native memory under management by this [Pool].
|
/// Native memory under management by this [Pool].
|
||||||
final List<Pointer<NativeType>> _managedMemoryPointers = [];
|
final List<Pointer<NativeType>> _managedMemoryPointers = [];
|
||||||
|
|
||||||
/// Callbacks for releasing native resources under management by this [Pool].
|
/// Callbacks for releasing native resources under management by this [Pool].
|
||||||
final List<Function()> _managedResourceReleaseCallbacks = [];
|
final List<Function()> _managedResourceReleaseCallbacks = [];
|
||||||
|
|
||||||
/// Allocates memory on the native heap by using the allocator supplied to
|
bool _inUse = true;
|
||||||
/// the constructor.
|
|
||||||
|
/// Creates a pool of allocations.
|
||||||
|
///
|
||||||
|
/// The [allocator] is used to do the actual allocation and freeing of
|
||||||
|
/// memory. It defaults to using [calloc].
|
||||||
|
Pool([Allocator allocator = calloc]) : _wrappedAllocator = allocator;
|
||||||
|
|
||||||
|
/// Allocates memory and includes it in the pool.
|
||||||
|
///
|
||||||
|
/// Uses the allocator provided to the [Pool] constructor to do the
|
||||||
|
/// allocation.
|
||||||
///
|
///
|
||||||
/// Throws an [ArgumentError] if the number of bytes or alignment cannot be
|
/// Throws an [ArgumentError] if the number of bytes or alignment cannot be
|
||||||
/// satisfied.
|
/// satisfied.
|
||||||
@override
|
@override
|
||||||
Pointer<T> allocate<T extends NativeType>(int numBytes, {int? alignment}) {
|
Pointer<T> allocate<T extends NativeType>(int byteCount, {int? alignment}) {
|
||||||
final p = _wrappedAllocator.allocate<T>(numBytes, alignment: alignment);
|
_ensureInUse();
|
||||||
|
final p = _wrappedAllocator.allocate<T>(byteCount, alignment: alignment);
|
||||||
_managedMemoryPointers.add(p);
|
_managedMemoryPointers.add(p);
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
@ -43,6 +60,8 @@ class Pool implements Allocator {
|
||||||
///
|
///
|
||||||
/// Executes [releaseCallback] on [releaseAll].
|
/// Executes [releaseCallback] on [releaseAll].
|
||||||
T using<T>(T resource, Function(T) releaseCallback) {
|
T using<T>(T resource, Function(T) releaseCallback) {
|
||||||
|
_ensureInUse();
|
||||||
|
releaseCallback = Zone.current.bindUnaryCallback(releaseCallback);
|
||||||
_managedResourceReleaseCallbacks.add(() => releaseCallback(resource));
|
_managedResourceReleaseCallbacks.add(() => releaseCallback(resource));
|
||||||
return resource;
|
return resource;
|
||||||
}
|
}
|
||||||
|
@ -53,59 +72,102 @@ class Pool implements Allocator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Releases all resources that this [Pool] manages.
|
/// Releases all resources that this [Pool] manages.
|
||||||
void releaseAll() {
|
///
|
||||||
for (final c in _managedResourceReleaseCallbacks) {
|
/// If [reuse] is `true`, the pool can be used again after resources
|
||||||
c();
|
/// have been released. If not, the default, then the [allocate]
|
||||||
|
/// and [using] methods must not be called after a call to `releaseAll`.
|
||||||
|
void releaseAll({bool reuse = false}) {
|
||||||
|
if (!reuse) {
|
||||||
|
_inUse = false;
|
||||||
|
}
|
||||||
|
while (_managedResourceReleaseCallbacks.isNotEmpty) {
|
||||||
|
_managedResourceReleaseCallbacks.removeLast()();
|
||||||
}
|
}
|
||||||
_managedResourceReleaseCallbacks.clear();
|
|
||||||
for (final p in _managedMemoryPointers) {
|
for (final p in _managedMemoryPointers) {
|
||||||
_wrappedAllocator.free(p);
|
_wrappedAllocator.free(p);
|
||||||
}
|
}
|
||||||
_managedMemoryPointers.clear();
|
_managedMemoryPointers.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Does nothing, invoke [releaseAll] instead.
|
||||||
@override
|
@override
|
||||||
void free(Pointer<NativeType> pointer) => throw UnsupportedError(
|
void free(Pointer<NativeType> pointer) {}
|
||||||
"Individually freeing Pool allocated memory is not allowed");
|
|
||||||
|
void _ensureInUse() {
|
||||||
|
if (!_inUse) {
|
||||||
|
throw StateError(
|
||||||
|
"Pool no longer in use, `releaseAll(reuse: false)` was called.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a [Pool] to manage native resources.
|
/// Runs [computation] with a new [Pool], and releases all allocations at the end.
|
||||||
///
|
///
|
||||||
/// If the isolate is shut down, through `Isolate.kill()`, resources are _not_ cleaned up.
|
/// If [R] is a [Future], all allocations are released when the future completes.
|
||||||
R using<R>(R Function(Pool) f, [Allocator wrappedAllocator = calloc]) {
|
///
|
||||||
final p = Pool(wrappedAllocator);
|
/// If the isolate is shut down, through `Isolate.kill()`, resources are _not_
|
||||||
|
/// cleaned up.
|
||||||
|
R using<R>(R Function(Pool) computation,
|
||||||
|
[Allocator wrappedAllocator = calloc]) {
|
||||||
|
final pool = Pool(wrappedAllocator);
|
||||||
|
bool isAsync = false;
|
||||||
try {
|
try {
|
||||||
return f(p);
|
final result = computation(pool);
|
||||||
|
if (result is Future) {
|
||||||
|
isAsync = true;
|
||||||
|
return (result.whenComplete(pool.releaseAll) as R);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
} finally {
|
} finally {
|
||||||
p.releaseAll();
|
if (!isAsync) {
|
||||||
|
pool.releaseAll();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a zoned [Pool] to manage native resources.
|
/// Creates a zoned [Pool] to manage native resources.
|
||||||
///
|
///
|
||||||
/// Pool is availabe through [currentPool].
|
/// The pool is availabe through [zonePool].
|
||||||
///
|
|
||||||
/// 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.
|
/// If the isolate is shut down, through `Isolate.kill()`, resources are _not_ cleaned up.
|
||||||
R usePool<R>(R Function() f, [Allocator wrappedAllocator = calloc]) {
|
R withZonePool<R>(R Function() computation,
|
||||||
final p = Pool(wrappedAllocator);
|
[Allocator wrappedAllocator = calloc]) {
|
||||||
|
final pool = Pool(wrappedAllocator);
|
||||||
|
var poolHolder = [pool];
|
||||||
|
bool isAsync = false;
|
||||||
try {
|
try {
|
||||||
return runZoned(() => f(),
|
return runZoned(() {
|
||||||
zoneValues: {#_pool: p},
|
final result = computation();
|
||||||
onError: (error, st) => throw RethrownError(error, st));
|
if (result is Future) {
|
||||||
|
isAsync = true;
|
||||||
|
result.whenComplete(pool.releaseAll);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}, zoneValues: {#_pool: poolHolder});
|
||||||
} finally {
|
} finally {
|
||||||
p.releaseAll();
|
if (!isAsync) {
|
||||||
|
pool.releaseAll();
|
||||||
|
poolHolder.remove(pool);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The [Pool] in the current zone.
|
/// A zone-specific [Pool].
|
||||||
Pool get currentPool => Zone.current[#_pool];
|
///
|
||||||
|
/// Asynchronous computations can share a [Pool]. Use [withZonePool] to create
|
||||||
class RethrownError {
|
/// a new zone with a fresh [Pool], and that pool will then be released
|
||||||
dynamic original;
|
/// automatically when the function passed to [withZonePool] completes.
|
||||||
StackTrace originalStackTrace;
|
/// All code inside that zone can use `zonePool` to access the pool.
|
||||||
RethrownError(this.original, this.originalStackTrace);
|
///
|
||||||
toString() => """RethrownError(${original})
|
/// The current pool must not be accessed by code which is not running inside
|
||||||
${originalStackTrace}""";
|
/// a zone created by [withZonePool].
|
||||||
|
Pool get zonePool {
|
||||||
|
final List<Pool>? poolHolder = Zone.current[#_pool];
|
||||||
|
if (poolHolder == null) {
|
||||||
|
throw StateError("Not inside a zone created by `usePool`");
|
||||||
|
}
|
||||||
|
if (!poolHolder.isEmpty) {
|
||||||
|
return poolHolder.single;
|
||||||
|
}
|
||||||
|
throw StateError("Pool as already been cleared with releaseAll.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
//
|
//
|
||||||
// Sample illustrating resource management with an explicit pool.
|
// Sample illustrating resource management with an explicit pool.
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:ffi';
|
import 'dart:ffi';
|
||||||
|
|
||||||
import 'package:expect/expect.dart';
|
import 'package:expect/expect.dart';
|
||||||
|
@ -12,7 +13,7 @@ import 'pool.dart';
|
||||||
import 'utf8_helpers.dart';
|
import 'utf8_helpers.dart';
|
||||||
import '../dylib_utils.dart';
|
import '../dylib_utils.dart';
|
||||||
|
|
||||||
main() {
|
main() async {
|
||||||
final ffiTestDynamicLibrary =
|
final ffiTestDynamicLibrary =
|
||||||
dlopenPlatformSpecific("ffi_test_dynamic_library");
|
dlopenPlatformSpecific("ffi_test_dynamic_library");
|
||||||
|
|
||||||
|
@ -108,6 +109,24 @@ main() {
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
print("Caught exception: $e");
|
print("Caught exception: $e");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [using] waits with releasing its resources until after [Future]s
|
||||||
|
/// complete.
|
||||||
|
List<int> freed = [];
|
||||||
|
freeInt(int i) {
|
||||||
|
freed.add(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> myFutureInt = using((Pool pool) {
|
||||||
|
return Future.microtask(() {
|
||||||
|
pool.using(1, freeInt);
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Expect.isTrue(freed.isEmpty);
|
||||||
|
await myFutureInt;
|
||||||
|
Expect.equals(1, freed.single);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents some opaque resource being managed by a library.
|
/// Represents some opaque resource being managed by a library.
|
||||||
|
|
|
@ -12,7 +12,7 @@ import 'pool.dart';
|
||||||
import 'utf8_helpers.dart';
|
import 'utf8_helpers.dart';
|
||||||
import '../dylib_utils.dart';
|
import '../dylib_utils.dart';
|
||||||
|
|
||||||
main() {
|
main() async {
|
||||||
final ffiTestDynamicLibrary =
|
final ffiTestDynamicLibrary =
|
||||||
dlopenPlatformSpecific("ffi_test_dynamic_library");
|
dlopenPlatformSpecific("ffi_test_dynamic_library");
|
||||||
|
|
||||||
|
@ -20,9 +20,9 @@ main() {
|
||||||
Void Function(Pointer<Void>, Pointer<Void>, IntPtr),
|
Void Function(Pointer<Void>, Pointer<Void>, IntPtr),
|
||||||
void Function(Pointer<Void>, Pointer<Void>, int)>("MemMove");
|
void Function(Pointer<Void>, Pointer<Void>, int)>("MemMove");
|
||||||
|
|
||||||
// To ensure resources are freed, wrap them in a [using] call.
|
// To ensure resources are freed, wrap them in a [withZonePool] call.
|
||||||
usePool(() {
|
withZonePool(() {
|
||||||
final p = currentPool<Int64>(2);
|
final p = zonePool<Int64>(2);
|
||||||
p[0] = 24;
|
p[0] = 24;
|
||||||
MemMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), sizeOf<Int64>());
|
MemMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), sizeOf<Int64>());
|
||||||
print(p[1]);
|
print(p[1]);
|
||||||
|
@ -31,24 +31,23 @@ main() {
|
||||||
|
|
||||||
// Resources are freed also when abnormal control flow occurs.
|
// Resources are freed also when abnormal control flow occurs.
|
||||||
try {
|
try {
|
||||||
usePool(() {
|
withZonePool(() {
|
||||||
final p = currentPool<Int64>(2);
|
final p = zonePool<Int64>(2);
|
||||||
p[0] = 25;
|
p[0] = 25;
|
||||||
MemMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), 8);
|
MemMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), 8);
|
||||||
print(p[1]);
|
print(p[1]);
|
||||||
Expect.equals(25, p[1]);
|
Expect.equals(25, p[1]);
|
||||||
throw Exception("Some random exception");
|
throw Exception("Some random exception");
|
||||||
});
|
});
|
||||||
} on RethrownError catch (e) {
|
} catch (e) {
|
||||||
// Note that exceptions are wrapped when using zones.
|
print("Caught exception: ${e}");
|
||||||
print("Caught exception: ${e.original}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// In a pool multiple resources can be allocated, which will all be freed
|
// In a pool multiple resources can be allocated, which will all be freed
|
||||||
// at the end of the scope.
|
// at the end of the scope.
|
||||||
usePool(() {
|
withZonePool(() {
|
||||||
final p = currentPool<Int64>(2);
|
final p = zonePool<Int64>(2);
|
||||||
final p2 = currentPool<Int64>(2);
|
final p2 = zonePool<Int64>(2);
|
||||||
p[0] = 1;
|
p[0] = 1;
|
||||||
p[1] = 2;
|
p[1] = 2;
|
||||||
MemMove(p2.cast<Void>(), p.cast<Void>(), 2 * sizeOf<Int64>());
|
MemMove(p2.cast<Void>(), p.cast<Void>(), 2 * sizeOf<Int64>());
|
||||||
|
@ -59,10 +58,10 @@ main() {
|
||||||
// If the resource allocation happens in a different scope, it is in the
|
// If the resource allocation happens in a different scope, it is in the
|
||||||
// same zone, so it's lifetime is automatically managed by the pool.
|
// same zone, so it's lifetime is automatically managed by the pool.
|
||||||
f1() {
|
f1() {
|
||||||
return currentPool<Int64>(2);
|
return zonePool<Int64>(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
usePool(() {
|
withZonePool(() {
|
||||||
final p = f1();
|
final p = f1();
|
||||||
final p2 = f1();
|
final p2 = f1();
|
||||||
p[0] = 1;
|
p[0] = 1;
|
||||||
|
@ -73,8 +72,8 @@ main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Using Strings.
|
// Using Strings.
|
||||||
usePool(() {
|
withZonePool(() {
|
||||||
final p = "Hello world!".toUtf8(currentPool);
|
final p = "Hello world!".toUtf8(zonePool);
|
||||||
print(p.contents());
|
print(p.contents());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -91,24 +90,42 @@ main() {
|
||||||
void Function(Pointer<SomeResource>)>("ReleaseResource");
|
void Function(Pointer<SomeResource>)>("ReleaseResource");
|
||||||
|
|
||||||
// Using an FFI call to release a resource.
|
// Using an FFI call to release a resource.
|
||||||
usePool(() {
|
withZonePool(() {
|
||||||
final r = currentPool.using(allocateResource(), releaseResource);
|
final r = zonePool.using(allocateResource(), releaseResource);
|
||||||
useResource(r);
|
useResource(r);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Using an FFI call to release a resource with abnormal control flow.
|
// Using an FFI call to release a resource with abnormal control flow.
|
||||||
try {
|
try {
|
||||||
usePool(() {
|
withZonePool(() {
|
||||||
final r = currentPool.using(allocateResource(), releaseResource);
|
final r = zonePool.using(allocateResource(), releaseResource);
|
||||||
useResource(r);
|
useResource(r);
|
||||||
|
|
||||||
throw Exception("Some random exception");
|
throw Exception("Some random exception");
|
||||||
});
|
});
|
||||||
// Resource has been freed.
|
// Resource has been freed.
|
||||||
} on RethrownError catch (e) {
|
} catch (e) {
|
||||||
// Note that exceptions are wrapped when using zones.
|
print("Caught exception: ${e}");
|
||||||
print("Caught exception: ${e.original}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [using] waits with releasing its resources until after [Future]s
|
||||||
|
/// complete.
|
||||||
|
|
||||||
|
List<int> freed = [];
|
||||||
|
freeInt(int i) {
|
||||||
|
freed.add(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> myFutureInt = withZonePool(() {
|
||||||
|
return Future.microtask(() {
|
||||||
|
zonePool.using(1, freeInt);
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Expect.isTrue(freed.isEmpty);
|
||||||
|
await myFutureInt;
|
||||||
|
Expect.equals(1, freed.single);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents some opaque resource being managed by a library.
|
/// Represents some opaque resource being managed by a library.
|
||||||
|
|
|
@ -13,30 +13,47 @@ import 'package:ffi/ffi.dart';
|
||||||
|
|
||||||
import '../calloc.dart';
|
import '../calloc.dart';
|
||||||
|
|
||||||
/// Keeps track of all allocated memory and frees all allocated memory on
|
/// An [Allocator] which frees all allocations at the same time.
|
||||||
/// [releaseAll].
|
|
||||||
///
|
///
|
||||||
/// Wraps an [Allocator] to do the actual allocation and freeing.
|
/// The pool allows you to allocate heap memory, but ignores calls to [free].
|
||||||
|
/// Instead you call [releaseAll] to release all the allocations at the same
|
||||||
|
/// time.
|
||||||
|
///
|
||||||
|
/// Also allows other resources to be associated with the pool, through the
|
||||||
|
/// [using] method, to have a release function called for them when the pool is
|
||||||
|
/// released.
|
||||||
|
///
|
||||||
|
/// An [Allocator] can be provided to do the actual allocation and freeing.
|
||||||
|
/// Defaults to using [calloc].
|
||||||
class Pool implements Allocator {
|
class Pool implements Allocator {
|
||||||
/// The [Allocator] used for allocation and freeing.
|
/// The [Allocator] used for allocation and freeing.
|
||||||
final Allocator _wrappedAllocator;
|
final Allocator _wrappedAllocator;
|
||||||
|
|
||||||
Pool(this._wrappedAllocator);
|
|
||||||
|
|
||||||
/// Native memory under management by this [Pool].
|
/// Native memory under management by this [Pool].
|
||||||
final List<Pointer<NativeType>> _managedMemoryPointers = [];
|
final List<Pointer<NativeType>> _managedMemoryPointers = [];
|
||||||
|
|
||||||
/// Callbacks for releasing native resources under management by this [Pool].
|
/// Callbacks for releasing native resources under management by this [Pool].
|
||||||
final List<Function()> _managedResourceReleaseCallbacks = [];
|
final List<Function()> _managedResourceReleaseCallbacks = [];
|
||||||
|
|
||||||
/// Allocates memory on the native heap by using the allocator supplied to
|
bool _inUse = true;
|
||||||
/// the constructor.
|
|
||||||
|
/// Creates a pool of allocations.
|
||||||
|
///
|
||||||
|
/// The [allocator] is used to do the actual allocation and freeing of
|
||||||
|
/// memory. It defaults to using [calloc].
|
||||||
|
Pool([Allocator allocator = calloc]) : _wrappedAllocator = allocator;
|
||||||
|
|
||||||
|
/// Allocates memory and includes it in the pool.
|
||||||
|
///
|
||||||
|
/// Uses the allocator provided to the [Pool] constructor to do the
|
||||||
|
/// allocation.
|
||||||
///
|
///
|
||||||
/// Throws an [ArgumentError] if the number of bytes or alignment cannot be
|
/// Throws an [ArgumentError] if the number of bytes or alignment cannot be
|
||||||
/// satisfied.
|
/// satisfied.
|
||||||
@override
|
@override
|
||||||
Pointer<T> allocate<T extends NativeType>(int numBytes, {int alignment}) {
|
Pointer<T> allocate<T extends NativeType>(int byteCount, {int alignment}) {
|
||||||
final p = _wrappedAllocator.allocate<T>(numBytes, alignment: alignment);
|
_ensureInUse();
|
||||||
|
final p = _wrappedAllocator.allocate<T>(byteCount, alignment: alignment);
|
||||||
_managedMemoryPointers.add(p);
|
_managedMemoryPointers.add(p);
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
@ -45,6 +62,8 @@ class Pool implements Allocator {
|
||||||
///
|
///
|
||||||
/// Executes [releaseCallback] on [releaseAll].
|
/// Executes [releaseCallback] on [releaseAll].
|
||||||
T using<T>(T resource, Function(T) releaseCallback) {
|
T using<T>(T resource, Function(T) releaseCallback) {
|
||||||
|
_ensureInUse();
|
||||||
|
releaseCallback = Zone.current.bindUnaryCallback(releaseCallback);
|
||||||
_managedResourceReleaseCallbacks.add(() => releaseCallback(resource));
|
_managedResourceReleaseCallbacks.add(() => releaseCallback(resource));
|
||||||
return resource;
|
return resource;
|
||||||
}
|
}
|
||||||
|
@ -55,59 +74,102 @@ class Pool implements Allocator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Releases all resources that this [Pool] manages.
|
/// Releases all resources that this [Pool] manages.
|
||||||
void releaseAll() {
|
///
|
||||||
for (final c in _managedResourceReleaseCallbacks) {
|
/// If [reuse] is `true`, the pool can be used again after resources
|
||||||
c();
|
/// have been released. If not, the default, then the [allocate]
|
||||||
|
/// and [using] methods must not be called after a call to `releaseAll`.
|
||||||
|
void releaseAll({bool reuse = false}) {
|
||||||
|
if (!reuse) {
|
||||||
|
_inUse = false;
|
||||||
|
}
|
||||||
|
while (_managedResourceReleaseCallbacks.isNotEmpty) {
|
||||||
|
_managedResourceReleaseCallbacks.removeLast()();
|
||||||
}
|
}
|
||||||
_managedResourceReleaseCallbacks.clear();
|
|
||||||
for (final p in _managedMemoryPointers) {
|
for (final p in _managedMemoryPointers) {
|
||||||
_wrappedAllocator.free(p);
|
_wrappedAllocator.free(p);
|
||||||
}
|
}
|
||||||
_managedMemoryPointers.clear();
|
_managedMemoryPointers.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Does nothing, invoke [releaseAll] instead.
|
||||||
@override
|
@override
|
||||||
void free(Pointer<NativeType> pointer) => throw UnsupportedError(
|
void free(Pointer<NativeType> pointer) {}
|
||||||
"Individually freeing Pool allocated memory is not allowed");
|
|
||||||
|
void _ensureInUse() {
|
||||||
|
if (!_inUse) {
|
||||||
|
throw StateError(
|
||||||
|
"Pool no longer in use, `releaseAll(reuse: false)` was called.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a [Pool] to manage native resources.
|
/// Runs [computation] with a new [Pool], and releases all allocations at the end.
|
||||||
///
|
///
|
||||||
/// If the isolate is shut down, through `Isolate.kill()`, resources are _not_ cleaned up.
|
/// If [R] is a [Future], all allocations are released when the future completes.
|
||||||
R using<R>(R Function(Pool) f, [Allocator wrappedAllocator = calloc]) {
|
///
|
||||||
final p = Pool(wrappedAllocator);
|
/// If the isolate is shut down, through `Isolate.kill()`, resources are _not_
|
||||||
|
/// cleaned up.
|
||||||
|
R using<R>(R Function(Pool) computation,
|
||||||
|
[Allocator wrappedAllocator = calloc]) {
|
||||||
|
final pool = Pool(wrappedAllocator);
|
||||||
|
bool isAsync = false;
|
||||||
try {
|
try {
|
||||||
return f(p);
|
final result = computation(pool);
|
||||||
|
if (result is Future) {
|
||||||
|
isAsync = true;
|
||||||
|
return (result.whenComplete(pool.releaseAll) as R);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
} finally {
|
} finally {
|
||||||
p.releaseAll();
|
if (!isAsync) {
|
||||||
|
pool.releaseAll();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a zoned [Pool] to manage native resources.
|
/// Creates a zoned [Pool] to manage native resources.
|
||||||
///
|
///
|
||||||
/// Pool is availabe through [currentPool].
|
/// The pool is availabe through [zonePool].
|
||||||
///
|
|
||||||
/// 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.
|
/// If the isolate is shut down, through `Isolate.kill()`, resources are _not_ cleaned up.
|
||||||
R usePool<R>(R Function() f, [Allocator wrappedAllocator = calloc]) {
|
R withZonePool<R>(R Function() computation,
|
||||||
final p = Pool(wrappedAllocator);
|
[Allocator wrappedAllocator = calloc]) {
|
||||||
|
final pool = Pool(wrappedAllocator);
|
||||||
|
var poolHolder = [pool];
|
||||||
|
bool isAsync = false;
|
||||||
try {
|
try {
|
||||||
return runZoned(() => f(),
|
return runZoned(() {
|
||||||
zoneValues: {#_pool: p},
|
final result = computation();
|
||||||
onError: (error, st) => throw RethrownError(error, st));
|
if (result is Future) {
|
||||||
|
isAsync = true;
|
||||||
|
result.whenComplete(pool.releaseAll);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}, zoneValues: {#_pool: poolHolder});
|
||||||
} finally {
|
} finally {
|
||||||
p.releaseAll();
|
if (!isAsync) {
|
||||||
|
pool.releaseAll();
|
||||||
|
poolHolder.remove(pool);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The [Pool] in the current zone.
|
/// A zone-specific [Pool].
|
||||||
Pool get currentPool => Zone.current[#_pool];
|
///
|
||||||
|
/// Asynchronous computations can share a [Pool]. Use [withZonePool] to create
|
||||||
class RethrownError {
|
/// a new zone with a fresh [Pool], and that pool will then be released
|
||||||
dynamic original;
|
/// automatically when the function passed to [withZonePool] completes.
|
||||||
StackTrace originalStackTrace;
|
/// All code inside that zone can use `zonePool` to access the pool.
|
||||||
RethrownError(this.original, this.originalStackTrace);
|
///
|
||||||
toString() => """RethrownError(${original})
|
/// The current pool must not be accessed by code which is not running inside
|
||||||
${originalStackTrace}""";
|
/// a zone created by [withZonePool].
|
||||||
|
Pool get zonePool {
|
||||||
|
final List<Pool> poolHolder = Zone.current[#_pool];
|
||||||
|
if (poolHolder == null) {
|
||||||
|
throw StateError("Not inside a zone created by `usePool`");
|
||||||
|
}
|
||||||
|
if (!poolHolder.isEmpty) {
|
||||||
|
return poolHolder.single;
|
||||||
|
}
|
||||||
|
throw StateError("Pool as already been cleared with releaseAll.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import 'pool.dart';
|
||||||
import 'utf8_helpers.dart';
|
import 'utf8_helpers.dart';
|
||||||
import '../dylib_utils.dart';
|
import '../dylib_utils.dart';
|
||||||
|
|
||||||
main() {
|
main() async {
|
||||||
final ffiTestDynamicLibrary =
|
final ffiTestDynamicLibrary =
|
||||||
dlopenPlatformSpecific("ffi_test_dynamic_library");
|
dlopenPlatformSpecific("ffi_test_dynamic_library");
|
||||||
|
|
||||||
|
@ -110,6 +110,24 @@ main() {
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
print("Caught exception: $e");
|
print("Caught exception: $e");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [using] waits with releasing its resources until after [Future]s
|
||||||
|
/// complete.
|
||||||
|
List<int> freed = [];
|
||||||
|
freeInt(int i) {
|
||||||
|
freed.add(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> myFutureInt = using((Pool pool) {
|
||||||
|
return Future.microtask(() {
|
||||||
|
pool.using(1, freeInt);
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Expect.isTrue(freed.isEmpty);
|
||||||
|
await myFutureInt;
|
||||||
|
Expect.equals(1, freed.single);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents some opaque resource being managed by a library.
|
/// Represents some opaque resource being managed by a library.
|
||||||
|
|
|
@ -14,7 +14,7 @@ import 'pool.dart';
|
||||||
import 'utf8_helpers.dart';
|
import 'utf8_helpers.dart';
|
||||||
import '../dylib_utils.dart';
|
import '../dylib_utils.dart';
|
||||||
|
|
||||||
main() {
|
main() async {
|
||||||
final ffiTestDynamicLibrary =
|
final ffiTestDynamicLibrary =
|
||||||
dlopenPlatformSpecific("ffi_test_dynamic_library");
|
dlopenPlatformSpecific("ffi_test_dynamic_library");
|
||||||
|
|
||||||
|
@ -22,9 +22,9 @@ main() {
|
||||||
Void Function(Pointer<Void>, Pointer<Void>, IntPtr),
|
Void Function(Pointer<Void>, Pointer<Void>, IntPtr),
|
||||||
void Function(Pointer<Void>, Pointer<Void>, int)>("MemMove");
|
void Function(Pointer<Void>, Pointer<Void>, int)>("MemMove");
|
||||||
|
|
||||||
// To ensure resources are freed, wrap them in a [using] call.
|
// To ensure resources are freed, wrap them in a [withZonePool] call.
|
||||||
usePool(() {
|
withZonePool(() {
|
||||||
final p = currentPool<Int64>(2);
|
final p = zonePool<Int64>(2);
|
||||||
p[0] = 24;
|
p[0] = 24;
|
||||||
MemMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), sizeOf<Int64>());
|
MemMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), sizeOf<Int64>());
|
||||||
print(p[1]);
|
print(p[1]);
|
||||||
|
@ -33,24 +33,23 @@ main() {
|
||||||
|
|
||||||
// Resources are freed also when abnormal control flow occurs.
|
// Resources are freed also when abnormal control flow occurs.
|
||||||
try {
|
try {
|
||||||
usePool(() {
|
withZonePool(() {
|
||||||
final p = currentPool<Int64>(2);
|
final p = zonePool<Int64>(2);
|
||||||
p[0] = 25;
|
p[0] = 25;
|
||||||
MemMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), 8);
|
MemMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), 8);
|
||||||
print(p[1]);
|
print(p[1]);
|
||||||
Expect.equals(25, p[1]);
|
Expect.equals(25, p[1]);
|
||||||
throw Exception("Some random exception");
|
throw Exception("Some random exception");
|
||||||
});
|
});
|
||||||
} on RethrownError catch (e) {
|
} catch (e) {
|
||||||
// Note that exceptions are wrapped when using zones.
|
print("Caught exception: ${e}");
|
||||||
print("Caught exception: ${e.original}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// In a pool multiple resources can be allocated, which will all be freed
|
// In a pool multiple resources can be allocated, which will all be freed
|
||||||
// at the end of the scope.
|
// at the end of the scope.
|
||||||
usePool(() {
|
withZonePool(() {
|
||||||
final p = currentPool<Int64>(2);
|
final p = zonePool<Int64>(2);
|
||||||
final p2 = currentPool<Int64>(2);
|
final p2 = zonePool<Int64>(2);
|
||||||
p[0] = 1;
|
p[0] = 1;
|
||||||
p[1] = 2;
|
p[1] = 2;
|
||||||
MemMove(p2.cast<Void>(), p.cast<Void>(), 2 * sizeOf<Int64>());
|
MemMove(p2.cast<Void>(), p.cast<Void>(), 2 * sizeOf<Int64>());
|
||||||
|
@ -61,10 +60,10 @@ main() {
|
||||||
// If the resource allocation happens in a different scope, it is in the
|
// If the resource allocation happens in a different scope, it is in the
|
||||||
// same zone, so it's lifetime is automatically managed by the pool.
|
// same zone, so it's lifetime is automatically managed by the pool.
|
||||||
f1() {
|
f1() {
|
||||||
return currentPool<Int64>(2);
|
return zonePool<Int64>(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
usePool(() {
|
withZonePool(() {
|
||||||
final p = f1();
|
final p = f1();
|
||||||
final p2 = f1();
|
final p2 = f1();
|
||||||
p[0] = 1;
|
p[0] = 1;
|
||||||
|
@ -75,8 +74,8 @@ main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Using Strings.
|
// Using Strings.
|
||||||
usePool(() {
|
withZonePool(() {
|
||||||
final p = "Hello world!".toUtf8(currentPool);
|
final p = "Hello world!".toUtf8(zonePool);
|
||||||
print(p.contents());
|
print(p.contents());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -93,24 +92,42 @@ main() {
|
||||||
void Function(Pointer<SomeResource>)>("ReleaseResource");
|
void Function(Pointer<SomeResource>)>("ReleaseResource");
|
||||||
|
|
||||||
// Using an FFI call to release a resource.
|
// Using an FFI call to release a resource.
|
||||||
usePool(() {
|
withZonePool(() {
|
||||||
final r = currentPool.using(allocateResource(), releaseResource);
|
final r = zonePool.using(allocateResource(), releaseResource);
|
||||||
useResource(r);
|
useResource(r);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Using an FFI call to release a resource with abnormal control flow.
|
// Using an FFI call to release a resource with abnormal control flow.
|
||||||
try {
|
try {
|
||||||
usePool(() {
|
withZonePool(() {
|
||||||
final r = currentPool.using(allocateResource(), releaseResource);
|
final r = zonePool.using(allocateResource(), releaseResource);
|
||||||
useResource(r);
|
useResource(r);
|
||||||
|
|
||||||
throw Exception("Some random exception");
|
throw Exception("Some random exception");
|
||||||
});
|
});
|
||||||
// Resource has been freed.
|
// Resource has been freed.
|
||||||
} on RethrownError catch (e) {
|
} catch (e) {
|
||||||
// Note that exceptions are wrapped when using zones.
|
print("Caught exception: ${e}");
|
||||||
print("Caught exception: ${e.original}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [using] waits with releasing its resources until after [Future]s
|
||||||
|
/// complete.
|
||||||
|
|
||||||
|
List<int> freed = [];
|
||||||
|
freeInt(int i) {
|
||||||
|
freed.add(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<int> myFutureInt = withZonePool(() {
|
||||||
|
return Future.microtask(() {
|
||||||
|
zonePool.using(1, freeInt);
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Expect.isTrue(freed.isEmpty);
|
||||||
|
await myFutureInt;
|
||||||
|
Expect.equals(1, freed.single);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents some opaque resource being managed by a library.
|
/// Represents some opaque resource being managed by a library.
|
||||||
|
|
Loading…
Reference in a new issue