[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:
Daco Harkes 2021-01-15 18:04:12 +00:00 committed by commit-bot@chromium.org
parent bdb60329e9
commit 73bd44d363
6 changed files with 321 additions and 126 deletions

View file

@ -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.");
} }

View file

@ -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.

View file

@ -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.

View file

@ -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.");
} }

View file

@ -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.

View file

@ -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.