mirror of
https://github.com/dart-lang/sdk
synced 2024-10-02 23:59:16 +00:00
Update to the latest package:ffi
Remove SDK copies of `arena.dart` which is now in `package:ffi`. Change-Id: Ic4808c473043be7d34cd1334406897a935c19263 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/204020 Auto-Submit: Nate Bosch <nbosch@google.com> Commit-Queue: Daco Harkes <dacoharkes@google.com> Reviewed-by: Daco Harkes <dacoharkes@google.com>
This commit is contained in:
parent
b3b2abb9eb
commit
8b2cacf9ca
2
DEPS
2
DEPS
|
@ -109,7 +109,7 @@ vars = {
|
|||
"dartdoc_rev" : "b733d4952dbd25374d55e28476a5f44bd60ed63f",
|
||||
"devtools_rev" : "b3bf672474a2bff82f33e1176aa803539baa0d60+1",
|
||||
"jsshell_tag": "version:88.0",
|
||||
"ffi_rev": "f3346299c55669cc0db48afae85b8110088bf8da",
|
||||
"ffi_rev": "4dd32429880a57b64edaf54c9d5af8a9fa9a4ffb",
|
||||
"fixnum_rev": "16d3890c6dc82ca629659da1934e412292508bba",
|
||||
"file_rev": "0e09370f581ab6388d46fda4cdab66638c0171a1",
|
||||
"glob_rev": "a62acf590598f458d3198d9f2930c1c9dd4b1379",
|
||||
|
|
|
@ -1,182 +0,0 @@
|
|||
// 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 arena used for managing resources.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:ffi';
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
/// An [Allocator] which frees all allocations at the same time.
|
||||
///
|
||||
/// The arena 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 arena, through the
|
||||
/// [using] method, to have a release function called for them when the arena is
|
||||
/// released.
|
||||
///
|
||||
/// An [Allocator] can be provided to do the actual allocation and freeing.
|
||||
/// Defaults to using [calloc].
|
||||
class Arena implements Allocator {
|
||||
/// The [Allocator] used for allocation and freeing.
|
||||
final Allocator _wrappedAllocator;
|
||||
|
||||
/// Native memory under management by this [Arena].
|
||||
final List<Pointer<NativeType>> _managedMemoryPointers = [];
|
||||
|
||||
/// Callbacks for releasing native resources under management by this [Arena].
|
||||
final List<void Function()> _managedResourceReleaseCallbacks = [];
|
||||
|
||||
bool _inUse = true;
|
||||
|
||||
/// Creates a arena of allocations.
|
||||
///
|
||||
/// The [allocator] is used to do the actual allocation and freeing of
|
||||
/// memory. It defaults to using [calloc].
|
||||
Arena([Allocator allocator = calloc]) : _wrappedAllocator = allocator;
|
||||
|
||||
/// Allocates memory and includes it in the arena.
|
||||
///
|
||||
/// Uses the allocator provided to the [Arena] constructor to do the
|
||||
/// allocation.
|
||||
///
|
||||
/// Throws an [ArgumentError] if the number of bytes or alignment cannot be
|
||||
/// satisfied.
|
||||
@override
|
||||
Pointer<T> allocate<T extends NativeType>(int byteCount, {int? alignment}) {
|
||||
_ensureInUse();
|
||||
final p = _wrappedAllocator.allocate<T>(byteCount, alignment: alignment);
|
||||
_managedMemoryPointers.add(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
/// Registers [resource] in this arena.
|
||||
///
|
||||
/// Executes [releaseCallback] on [releaseAll].
|
||||
///
|
||||
/// Returns [resource] again, to allow for easily inserting
|
||||
/// `arena.using(resource, ...)` where the resource is allocated.
|
||||
T using<T>(T resource, void Function(T) releaseCallback) {
|
||||
_ensureInUse();
|
||||
releaseCallback = Zone.current.bindUnaryCallback(releaseCallback);
|
||||
_managedResourceReleaseCallbacks.add(() => releaseCallback(resource));
|
||||
return resource;
|
||||
}
|
||||
|
||||
/// Registers [releaseResourceCallback] to be executed on [releaseAll].
|
||||
void onReleaseAll(void Function() releaseResourceCallback) {
|
||||
_managedResourceReleaseCallbacks.add(releaseResourceCallback);
|
||||
}
|
||||
|
||||
/// Releases all resources that this [Arena] manages.
|
||||
///
|
||||
/// If [reuse] is `true`, the arena can be used again after resources
|
||||
/// have been released. If not, the default, then the [allocate]
|
||||
/// and [using] methods must not be called after a call to `releaseAll`.
|
||||
///
|
||||
/// If any of the callbacks throw, [releaseAll] is interrupted, and should
|
||||
/// be started again.
|
||||
void releaseAll({bool reuse = false}) {
|
||||
if (!reuse) {
|
||||
_inUse = false;
|
||||
}
|
||||
// The code below is deliberately wirtten to allow allocations to happen
|
||||
// during `releaseAll(reuse:true)`. The arena will still be guaranteed
|
||||
// empty when the `releaseAll` call returns.
|
||||
while (_managedResourceReleaseCallbacks.isNotEmpty) {
|
||||
_managedResourceReleaseCallbacks.removeLast()();
|
||||
}
|
||||
for (final p in _managedMemoryPointers) {
|
||||
_wrappedAllocator.free(p);
|
||||
}
|
||||
_managedMemoryPointers.clear();
|
||||
}
|
||||
|
||||
/// Does nothing, invoke [releaseAll] instead.
|
||||
@override
|
||||
void free(Pointer<NativeType> pointer) {}
|
||||
|
||||
void _ensureInUse() {
|
||||
if (!_inUse) {
|
||||
throw StateError(
|
||||
'Arena no longer in use, `releaseAll(reuse: false)` was called.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs [computation] with a new [Arena], and releases all allocations at the
|
||||
/// end.
|
||||
///
|
||||
/// If the return value of [computation] is a [Future], all allocations are
|
||||
/// released when the future completes.
|
||||
///
|
||||
/// If the isolate is shut down, through `Isolate.kill()`, resources are _not_
|
||||
/// cleaned up.
|
||||
R using<R>(R Function(Arena) computation,
|
||||
[Allocator wrappedAllocator = calloc]) {
|
||||
final arena = Arena(wrappedAllocator);
|
||||
bool isAsync = false;
|
||||
try {
|
||||
final result = computation(arena);
|
||||
if (result is Future) {
|
||||
isAsync = true;
|
||||
return (result.whenComplete(arena.releaseAll) as R);
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
if (!isAsync) {
|
||||
arena.releaseAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a zoned [Arena] to manage native resources.
|
||||
///
|
||||
/// The arena is availabe through [zoneArena].
|
||||
///
|
||||
/// If the isolate is shut down, through `Isolate.kill()`, resources are _not_ cleaned up.
|
||||
R withZoneArena<R>(R Function() computation,
|
||||
[Allocator wrappedAllocator = calloc]) {
|
||||
final arena = Arena(wrappedAllocator);
|
||||
var arenaHolder = [arena];
|
||||
bool isAsync = false;
|
||||
try {
|
||||
return runZoned(() {
|
||||
final result = computation();
|
||||
if (result is Future) {
|
||||
isAsync = true;
|
||||
result.whenComplete(arena.releaseAll);
|
||||
}
|
||||
return result;
|
||||
}, zoneValues: {#_arena: arenaHolder});
|
||||
} finally {
|
||||
if (!isAsync) {
|
||||
arena.releaseAll();
|
||||
arenaHolder.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A zone-specific [Arena].
|
||||
///
|
||||
/// Asynchronous computations can share a [Arena]. Use [withZoneArena] to create
|
||||
/// a new zone with a fresh [Arena], and that arena will then be released
|
||||
/// automatically when the function passed to [withZoneArena] completes.
|
||||
/// All code inside that zone can use `zoneArena` to access the arena.
|
||||
///
|
||||
/// The current arena must not be accessed by code which is not running inside
|
||||
/// a zone created by [withZoneArena].
|
||||
Arena get zoneArena {
|
||||
final List<Arena>? arenaHolder = Zone.current[#_arena];
|
||||
if (arenaHolder == null) {
|
||||
throw StateError('Not inside a zone created by `useArena`');
|
||||
}
|
||||
if (arenaHolder.isNotEmpty) {
|
||||
return arenaHolder.single;
|
||||
}
|
||||
throw StateError('Arena has already been cleared with releaseAll.');
|
||||
}
|
|
@ -9,8 +9,8 @@ import "dart:isolate";
|
|||
import 'dart:ffi';
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
import 'arena.dart';
|
||||
import '../dylib_utils.dart';
|
||||
|
||||
void main() {
|
||||
|
|
|
@ -8,8 +8,8 @@ import 'dart:async';
|
|||
import 'dart:ffi';
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
import 'arena.dart';
|
||||
import 'utf8_helpers.dart';
|
||||
import '../dylib_utils.dart';
|
||||
|
||||
|
|
|
@ -1,208 +0,0 @@
|
|||
// Copyright (c) 2021, 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.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:ffi';
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
// TODO(dacoharkes): After merging into `package:ffi`, roll package in DEPS
|
||||
// and remove arena.dart.
|
||||
import 'arena.dart';
|
||||
|
||||
void main() async {
|
||||
test('sync', () async {
|
||||
List<int> freed = [];
|
||||
void freeInt(int i) {
|
||||
freed.add(i);
|
||||
}
|
||||
|
||||
using((Arena arena) {
|
||||
arena.using(1234, freeInt);
|
||||
expect(freed.isEmpty, true);
|
||||
});
|
||||
expect(freed, [1234]);
|
||||
});
|
||||
|
||||
test('async', () async {
|
||||
/// Calling [using] waits with releasing its resources until after
|
||||
/// [Future]s complete.
|
||||
List<int> freed = [];
|
||||
void freeInt(int i) {
|
||||
freed.add(i);
|
||||
}
|
||||
|
||||
Future<int> myFutureInt = using((Arena arena) {
|
||||
return Future.microtask(() {
|
||||
arena.using(1234, freeInt);
|
||||
return 1;
|
||||
});
|
||||
});
|
||||
|
||||
expect(freed.isEmpty, true);
|
||||
await myFutureInt;
|
||||
expect(freed, [1234]);
|
||||
});
|
||||
|
||||
test('throw', () {
|
||||
/// [using] waits with releasing its resources until after [Future]s
|
||||
/// complete.
|
||||
List<int> freed = [];
|
||||
void freeInt(int i) {
|
||||
freed.add(i);
|
||||
}
|
||||
|
||||
// Resources are freed also when abnormal control flow occurs.
|
||||
var didThrow = false;
|
||||
try {
|
||||
using((Arena arena) {
|
||||
arena.using(1234, freeInt);
|
||||
expect(freed.isEmpty, true);
|
||||
throw Exception('Exception 1');
|
||||
});
|
||||
} on Exception {
|
||||
expect(freed.single, 1234);
|
||||
didThrow = true;
|
||||
}
|
||||
expect(didThrow, true);
|
||||
});
|
||||
|
||||
test(
|
||||
'allocate',
|
||||
() {
|
||||
final countingAllocator = CountingAllocator();
|
||||
// To ensure resources are freed, wrap them in a [using] call.
|
||||
using((Arena arena) {
|
||||
final p = arena<Int64>(2);
|
||||
p[1] = p[0];
|
||||
}, countingAllocator);
|
||||
expect(countingAllocator.freeCount, 1);
|
||||
},
|
||||
);
|
||||
|
||||
test('allocate throw', () {
|
||||
// Resources are freed also when abnormal control flow occurs.
|
||||
bool didThrow = false;
|
||||
final countingAllocator = CountingAllocator();
|
||||
try {
|
||||
using((Arena arena) {
|
||||
final p = arena<Int64>(2);
|
||||
p[0] = 25;
|
||||
throw Exception('Exception 2');
|
||||
}, countingAllocator);
|
||||
} on Exception {
|
||||
expect(countingAllocator.freeCount, 1);
|
||||
didThrow = true;
|
||||
}
|
||||
expect(didThrow, true);
|
||||
});
|
||||
|
||||
test('toNativeUtf8', () {
|
||||
final countingAllocator = CountingAllocator();
|
||||
using((Arena arena) {
|
||||
final p = 'Hello world!'.toNativeUtf8(allocator: arena);
|
||||
expect(p.toDartString(), 'Hello world!');
|
||||
}, countingAllocator);
|
||||
expect(countingAllocator.freeCount, 1);
|
||||
});
|
||||
|
||||
test('zone', () async {
|
||||
List<int> freed = [];
|
||||
void freeInt(int i) {
|
||||
freed.add(i);
|
||||
}
|
||||
|
||||
withZoneArena(() {
|
||||
zoneArena.using(1234, freeInt);
|
||||
expect(freed.isEmpty, true);
|
||||
});
|
||||
expect(freed.length, 1);
|
||||
expect(freed.single, 1234);
|
||||
});
|
||||
|
||||
test('zone async', () async {
|
||||
/// [using] waits with releasing its resources until after [Future]s
|
||||
/// complete.
|
||||
List<int> freed = [];
|
||||
void freeInt(int i) {
|
||||
freed.add(i);
|
||||
}
|
||||
|
||||
Future<int> myFutureInt = withZoneArena(() {
|
||||
return Future.microtask(() {
|
||||
zoneArena.using(1234, freeInt);
|
||||
return 1;
|
||||
});
|
||||
});
|
||||
|
||||
expect(freed.isEmpty, true);
|
||||
await myFutureInt;
|
||||
expect(freed.length, 1);
|
||||
expect(freed.single, 1234);
|
||||
});
|
||||
|
||||
test('zone throw', () {
|
||||
/// [using] waits with releasing its resources until after [Future]s
|
||||
/// complete.
|
||||
List<int> freed = [];
|
||||
void freeInt(int i) {
|
||||
freed.add(i);
|
||||
}
|
||||
|
||||
// Resources are freed also when abnormal control flow occurs.
|
||||
bool didThrow = false;
|
||||
try {
|
||||
withZoneArena(() {
|
||||
zoneArena.using(1234, freeInt);
|
||||
expect(freed.isEmpty, true);
|
||||
throw Exception('Exception 3');
|
||||
});
|
||||
} on Exception {
|
||||
expect(freed.single, 1234);
|
||||
didThrow = true;
|
||||
}
|
||||
expect(didThrow, true);
|
||||
expect(freed.single, 1234);
|
||||
});
|
||||
|
||||
test('allocate during releaseAll', () {
|
||||
final countingAllocator = CountingAllocator();
|
||||
final arena = Arena(countingAllocator);
|
||||
|
||||
arena.using(arena<Uint8>(), (Pointer discard) {
|
||||
arena<Uint8>();
|
||||
});
|
||||
|
||||
expect(countingAllocator.allocationCount, 1);
|
||||
expect(countingAllocator.freeCount, 0);
|
||||
|
||||
arena.releaseAll(reuse: true);
|
||||
|
||||
expect(countingAllocator.allocationCount, 2);
|
||||
expect(countingAllocator.freeCount, 2);
|
||||
});
|
||||
}
|
||||
|
||||
/// Keeps track of the number of allocates and frees for testing purposes.
|
||||
class CountingAllocator implements Allocator {
|
||||
final Allocator wrappedAllocator;
|
||||
|
||||
int allocationCount = 0;
|
||||
int freeCount = 0;
|
||||
|
||||
CountingAllocator([this.wrappedAllocator = calloc]);
|
||||
|
||||
@override
|
||||
Pointer<T> allocate<T extends NativeType>(int byteCount, {int? alignment}) {
|
||||
allocationCount++;
|
||||
return wrappedAllocator.allocate(byteCount, alignment: alignment);
|
||||
}
|
||||
|
||||
@override
|
||||
void free(Pointer<NativeType> pointer) {
|
||||
freeCount++;
|
||||
return wrappedAllocator.free(pointer);
|
||||
}
|
||||
}
|
|
@ -7,8 +7,8 @@
|
|||
import 'dart:ffi';
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
import 'arena.dart';
|
||||
import 'utf8_helpers.dart';
|
||||
import '../dylib_utils.dart';
|
||||
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
// 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.
|
||||
|
||||
import "dart:async";
|
||||
import "dart:ffi";
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
/// [Arena] manages allocated C memory.
|
||||
///
|
||||
/// Arenas are zoned.
|
||||
class Arena {
|
||||
Arena();
|
||||
|
||||
List<Pointer<Void>> _allocations = [];
|
||||
|
||||
/// Bound the lifetime of [ptr] to this [Arena].
|
||||
T scoped<T extends Pointer>(T ptr) {
|
||||
_allocations.add(ptr.cast());
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/// Frees all memory pointed to by [Pointer]s in this arena.
|
||||
void finalize() {
|
||||
for (final ptr in _allocations) {
|
||||
calloc.free(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
/// The last [Arena] in the zone.
|
||||
factory Arena.current() {
|
||||
return Zone.current[#_currentArena];
|
||||
}
|
||||
}
|
||||
|
||||
/// Bound the lifetime of [ptr] to the current [Arena].
|
||||
T scoped<T extends Pointer>(T ptr) => Arena.current().scoped(ptr);
|
||||
|
||||
class RethrownError {
|
||||
dynamic original;
|
||||
StackTrace originalStackTrace;
|
||||
RethrownError(this.original, this.originalStackTrace);
|
||||
toString() => """RethrownError(${original})
|
||||
${originalStackTrace}""";
|
||||
}
|
||||
|
||||
/// Runs the [body] in an [Arena] freeing all memory which is [scoped] during
|
||||
/// execution of [body] at the end of the execution.
|
||||
R runArena<R>(R Function(Arena) body) {
|
||||
Arena arena = Arena();
|
||||
try {
|
||||
return runZoned(() => body(arena),
|
||||
zoneValues: {#_currentArena: arena},
|
||||
onError: (error, st) => throw RethrownError(error, st));
|
||||
} finally {
|
||||
arena.finalize();
|
||||
}
|
||||
}
|
|
@ -1,184 +0,0 @@
|
|||
// 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 arena used for managing resources.
|
||||
|
||||
// @dart = 2.9
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:ffi';
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
/// An [Allocator] which frees all allocations at the same time.
|
||||
///
|
||||
/// The arena 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 arena, through the
|
||||
/// [using] method, to have a release function called for them when the arena is
|
||||
/// released.
|
||||
///
|
||||
/// An [Allocator] can be provided to do the actual allocation and freeing.
|
||||
/// Defaults to using [calloc].
|
||||
class Arena implements Allocator {
|
||||
/// The [Allocator] used for allocation and freeing.
|
||||
final Allocator _wrappedAllocator;
|
||||
|
||||
/// Native memory under management by this [Arena].
|
||||
final List<Pointer<NativeType>> _managedMemoryPointers = [];
|
||||
|
||||
/// Callbacks for releasing native resources under management by this [Arena].
|
||||
final List<void Function()> _managedResourceReleaseCallbacks = [];
|
||||
|
||||
bool _inUse = true;
|
||||
|
||||
/// Creates a arena of allocations.
|
||||
///
|
||||
/// The [allocator] is used to do the actual allocation and freeing of
|
||||
/// memory. It defaults to using [calloc].
|
||||
Arena([Allocator allocator = calloc]) : _wrappedAllocator = allocator;
|
||||
|
||||
/// Allocates memory and includes it in the arena.
|
||||
///
|
||||
/// Uses the allocator provided to the [Arena] constructor to do the
|
||||
/// allocation.
|
||||
///
|
||||
/// Throws an [ArgumentError] if the number of bytes or alignment cannot be
|
||||
/// satisfied.
|
||||
@override
|
||||
Pointer<T> allocate<T extends NativeType>(int byteCount, {int alignment}) {
|
||||
_ensureInUse();
|
||||
final p = _wrappedAllocator.allocate<T>(byteCount, alignment: alignment);
|
||||
_managedMemoryPointers.add(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
/// Registers [resource] in this arena.
|
||||
///
|
||||
/// Executes [releaseCallback] on [releaseAll].
|
||||
///
|
||||
/// Returns [resource] again, to allow for easily inserting
|
||||
/// `arena.using(resource, ...)` where the resource is allocated.
|
||||
T using<T>(T resource, void Function(T) releaseCallback) {
|
||||
_ensureInUse();
|
||||
releaseCallback = Zone.current.bindUnaryCallback(releaseCallback);
|
||||
_managedResourceReleaseCallbacks.add(() => releaseCallback(resource));
|
||||
return resource;
|
||||
}
|
||||
|
||||
/// Registers [releaseResourceCallback] to be executed on [releaseAll].
|
||||
void onReleaseAll(void Function() releaseResourceCallback) {
|
||||
_managedResourceReleaseCallbacks.add(releaseResourceCallback);
|
||||
}
|
||||
|
||||
/// Releases all resources that this [Arena] manages.
|
||||
///
|
||||
/// If [reuse] is `true`, the arena can be used again after resources
|
||||
/// have been released. If not, the default, then the [allocate]
|
||||
/// and [using] methods must not be called after a call to `releaseAll`.
|
||||
///
|
||||
/// If any of the callbacks throw, [releaseAll] is interrupted, and should
|
||||
/// be started again.
|
||||
void releaseAll({bool reuse = false}) {
|
||||
if (!reuse) {
|
||||
_inUse = false;
|
||||
}
|
||||
// The code below is deliberately wirtten to allow allocations to happen
|
||||
// during `releaseAll(reuse:true)`. The arena will still be guaranteed
|
||||
// empty when the `releaseAll` call returns.
|
||||
while (_managedResourceReleaseCallbacks.isNotEmpty) {
|
||||
_managedResourceReleaseCallbacks.removeLast()();
|
||||
}
|
||||
for (final p in _managedMemoryPointers) {
|
||||
_wrappedAllocator.free(p);
|
||||
}
|
||||
_managedMemoryPointers.clear();
|
||||
}
|
||||
|
||||
/// Does nothing, invoke [releaseAll] instead.
|
||||
@override
|
||||
void free(Pointer<NativeType> pointer) {}
|
||||
|
||||
void _ensureInUse() {
|
||||
if (!_inUse) {
|
||||
throw StateError(
|
||||
'Arena no longer in use, `releaseAll(reuse: false)` was called.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs [computation] with a new [Arena], and releases all allocations at the
|
||||
/// end.
|
||||
///
|
||||
/// If the return value of [computation] is a [Future], all allocations are
|
||||
/// released when the future completes.
|
||||
///
|
||||
/// If the isolate is shut down, through `Isolate.kill()`, resources are _not_
|
||||
/// cleaned up.
|
||||
R using<R>(R Function(Arena) computation,
|
||||
[Allocator wrappedAllocator = calloc]) {
|
||||
final arena = Arena(wrappedAllocator);
|
||||
bool isAsync = false;
|
||||
try {
|
||||
final result = computation(arena);
|
||||
if (result is Future) {
|
||||
isAsync = true;
|
||||
return (result.whenComplete(arena.releaseAll) as R);
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
if (!isAsync) {
|
||||
arena.releaseAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a zoned [Arena] to manage native resources.
|
||||
///
|
||||
/// The arena is availabe through [zoneArena].
|
||||
///
|
||||
/// If the isolate is shut down, through `Isolate.kill()`, resources are _not_ cleaned up.
|
||||
R withZoneArena<R>(R Function() computation,
|
||||
[Allocator wrappedAllocator = calloc]) {
|
||||
final arena = Arena(wrappedAllocator);
|
||||
var arenaHolder = [arena];
|
||||
bool isAsync = false;
|
||||
try {
|
||||
return runZoned(() {
|
||||
final result = computation();
|
||||
if (result is Future) {
|
||||
isAsync = true;
|
||||
result.whenComplete(arena.releaseAll);
|
||||
}
|
||||
return result;
|
||||
}, zoneValues: {#_arena: arenaHolder});
|
||||
} finally {
|
||||
if (!isAsync) {
|
||||
arena.releaseAll();
|
||||
arenaHolder.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A zone-specific [Arena].
|
||||
///
|
||||
/// Asynchronous computations can share a [Arena]. Use [withZoneArena] to create
|
||||
/// a new zone with a fresh [Arena], and that arena will then be released
|
||||
/// automatically when the function passed to [withZoneArena] completes.
|
||||
/// All code inside that zone can use `zoneArena` to access the arena.
|
||||
///
|
||||
/// The current arena must not be accessed by code which is not running inside
|
||||
/// a zone created by [withZoneArena].
|
||||
Arena get zoneArena {
|
||||
final List<Arena> arenaHolder = Zone.current[#_arena];
|
||||
if (arenaHolder == null) {
|
||||
throw StateError('Not inside a zone created by `useArena`');
|
||||
}
|
||||
if (arenaHolder.isNotEmpty) {
|
||||
return arenaHolder.single;
|
||||
}
|
||||
throw StateError('Arena has already been cleared with releaseAll.');
|
||||
}
|
|
@ -11,8 +11,8 @@ import "dart:isolate";
|
|||
import 'dart:ffi';
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
import 'arena.dart';
|
||||
import '../dylib_utils.dart';
|
||||
|
||||
void main() {
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
import 'dart:ffi';
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
import 'arena.dart';
|
||||
import 'utf8_helpers.dart';
|
||||
import '../dylib_utils.dart';
|
||||
|
||||
|
|
|
@ -1,210 +0,0 @@
|
|||
// Copyright (c) 2021, 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.
|
||||
//
|
||||
// @dart = 2.9
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:ffi';
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
// TODO(dacoharkes): After merging into `package:ffi`, roll package in DEPS
|
||||
// and remove arena.dart.
|
||||
import 'arena.dart';
|
||||
|
||||
void main() async {
|
||||
test('sync', () async {
|
||||
List<int> freed = [];
|
||||
void freeInt(int i) {
|
||||
freed.add(i);
|
||||
}
|
||||
|
||||
using((Arena arena) {
|
||||
arena.using(1234, freeInt);
|
||||
expect(freed.isEmpty, true);
|
||||
});
|
||||
expect(freed, [1234]);
|
||||
});
|
||||
|
||||
test('async', () async {
|
||||
/// Calling [using] waits with releasing its resources until after
|
||||
/// [Future]s complete.
|
||||
List<int> freed = [];
|
||||
void freeInt(int i) {
|
||||
freed.add(i);
|
||||
}
|
||||
|
||||
Future<int> myFutureInt = using((Arena arena) {
|
||||
return Future.microtask(() {
|
||||
arena.using(1234, freeInt);
|
||||
return 1;
|
||||
});
|
||||
});
|
||||
|
||||
expect(freed.isEmpty, true);
|
||||
await myFutureInt;
|
||||
expect(freed, [1234]);
|
||||
});
|
||||
|
||||
test('throw', () {
|
||||
/// [using] waits with releasing its resources until after [Future]s
|
||||
/// complete.
|
||||
List<int> freed = [];
|
||||
void freeInt(int i) {
|
||||
freed.add(i);
|
||||
}
|
||||
|
||||
// Resources are freed also when abnormal control flow occurs.
|
||||
var didThrow = false;
|
||||
try {
|
||||
using((Arena arena) {
|
||||
arena.using(1234, freeInt);
|
||||
expect(freed.isEmpty, true);
|
||||
throw Exception('Exception 1');
|
||||
});
|
||||
} on Exception {
|
||||
expect(freed.single, 1234);
|
||||
didThrow = true;
|
||||
}
|
||||
expect(didThrow, true);
|
||||
});
|
||||
|
||||
test(
|
||||
'allocate',
|
||||
() {
|
||||
final countingAllocator = CountingAllocator();
|
||||
// To ensure resources are freed, wrap them in a [using] call.
|
||||
using((Arena arena) {
|
||||
final p = arena<Int64>(2);
|
||||
p[1] = p[0];
|
||||
}, countingAllocator);
|
||||
expect(countingAllocator.freeCount, 1);
|
||||
},
|
||||
);
|
||||
|
||||
test('allocate throw', () {
|
||||
// Resources are freed also when abnormal control flow occurs.
|
||||
bool didThrow = false;
|
||||
final countingAllocator = CountingAllocator();
|
||||
try {
|
||||
using((Arena arena) {
|
||||
final p = arena<Int64>(2);
|
||||
p[0] = 25;
|
||||
throw Exception('Exception 2');
|
||||
}, countingAllocator);
|
||||
} on Exception {
|
||||
expect(countingAllocator.freeCount, 1);
|
||||
didThrow = true;
|
||||
}
|
||||
expect(didThrow, true);
|
||||
});
|
||||
|
||||
test('toNativeUtf8', () {
|
||||
final countingAllocator = CountingAllocator();
|
||||
using((Arena arena) {
|
||||
final p = 'Hello world!'.toNativeUtf8(allocator: arena);
|
||||
expect(p.toDartString(), 'Hello world!');
|
||||
}, countingAllocator);
|
||||
expect(countingAllocator.freeCount, 1);
|
||||
});
|
||||
|
||||
test('zone', () async {
|
||||
List<int> freed = [];
|
||||
void freeInt(int i) {
|
||||
freed.add(i);
|
||||
}
|
||||
|
||||
withZoneArena(() {
|
||||
zoneArena.using(1234, freeInt);
|
||||
expect(freed.isEmpty, true);
|
||||
});
|
||||
expect(freed.length, 1);
|
||||
expect(freed.single, 1234);
|
||||
});
|
||||
|
||||
test('zone async', () async {
|
||||
/// [using] waits with releasing its resources until after [Future]s
|
||||
/// complete.
|
||||
List<int> freed = [];
|
||||
void freeInt(int i) {
|
||||
freed.add(i);
|
||||
}
|
||||
|
||||
Future<int> myFutureInt = withZoneArena(() {
|
||||
return Future.microtask(() {
|
||||
zoneArena.using(1234, freeInt);
|
||||
return 1;
|
||||
});
|
||||
});
|
||||
|
||||
expect(freed.isEmpty, true);
|
||||
await myFutureInt;
|
||||
expect(freed.length, 1);
|
||||
expect(freed.single, 1234);
|
||||
});
|
||||
|
||||
test('zone throw', () {
|
||||
/// [using] waits with releasing its resources until after [Future]s
|
||||
/// complete.
|
||||
List<int> freed = [];
|
||||
void freeInt(int i) {
|
||||
freed.add(i);
|
||||
}
|
||||
|
||||
// Resources are freed also when abnormal control flow occurs.
|
||||
bool didThrow = false;
|
||||
try {
|
||||
withZoneArena(() {
|
||||
zoneArena.using(1234, freeInt);
|
||||
expect(freed.isEmpty, true);
|
||||
throw Exception('Exception 3');
|
||||
});
|
||||
} on Exception {
|
||||
expect(freed.single, 1234);
|
||||
didThrow = true;
|
||||
}
|
||||
expect(didThrow, true);
|
||||
expect(freed.single, 1234);
|
||||
});
|
||||
|
||||
test('allocate during releaseAll', () {
|
||||
final countingAllocator = CountingAllocator();
|
||||
final arena = Arena(countingAllocator);
|
||||
|
||||
arena.using(arena<Uint8>(), (Pointer discard) {
|
||||
arena<Uint8>();
|
||||
});
|
||||
|
||||
expect(countingAllocator.allocationCount, 1);
|
||||
expect(countingAllocator.freeCount, 0);
|
||||
|
||||
arena.releaseAll(reuse: true);
|
||||
|
||||
expect(countingAllocator.allocationCount, 2);
|
||||
expect(countingAllocator.freeCount, 2);
|
||||
});
|
||||
}
|
||||
|
||||
/// Keeps track of the number of allocates and frees for testing purposes.
|
||||
class CountingAllocator implements Allocator {
|
||||
final Allocator wrappedAllocator;
|
||||
|
||||
int allocationCount = 0;
|
||||
int freeCount = 0;
|
||||
|
||||
CountingAllocator([this.wrappedAllocator = calloc]);
|
||||
|
||||
@override
|
||||
Pointer<T> allocate<T extends NativeType>(int byteCount, {int alignment}) {
|
||||
allocationCount++;
|
||||
return wrappedAllocator.allocate(byteCount, alignment: alignment);
|
||||
}
|
||||
|
||||
@override
|
||||
void free(Pointer<NativeType> pointer) {
|
||||
freeCount++;
|
||||
return wrappedAllocator.free(pointer);
|
||||
}
|
||||
}
|
|
@ -9,8 +9,8 @@
|
|||
import 'dart:ffi';
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
import 'arena.dart';
|
||||
import 'utf8_helpers.dart';
|
||||
import '../dylib_utils.dart';
|
||||
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
// 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.
|
||||
|
||||
// @dart = 2.9
|
||||
|
||||
import "dart:async";
|
||||
import "dart:ffi";
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
/// [Arena] manages allocated C memory.
|
||||
///
|
||||
/// Arenas are zoned.
|
||||
class Arena {
|
||||
Arena();
|
||||
|
||||
List<Pointer<Void>> _allocations = [];
|
||||
|
||||
/// Bound the lifetime of [ptr] to this [Arena].
|
||||
T scoped<T extends Pointer>(T ptr) {
|
||||
_allocations.add(ptr.cast());
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/// Frees all memory pointed to by [Pointer]s in this arena.
|
||||
void finalize() {
|
||||
for (final ptr in _allocations) {
|
||||
calloc.free(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
/// The last [Arena] in the zone.
|
||||
factory Arena.current() {
|
||||
return Zone.current[#_currentArena];
|
||||
}
|
||||
}
|
||||
|
||||
/// Bound the lifetime of [ptr] to the current [Arena].
|
||||
T scoped<T extends Pointer>(T ptr) => Arena.current().scoped(ptr);
|
||||
|
||||
class RethrownError {
|
||||
dynamic original;
|
||||
StackTrace originalStackTrace;
|
||||
RethrownError(this.original, this.originalStackTrace);
|
||||
toString() => """RethrownError(${original})
|
||||
${originalStackTrace}""";
|
||||
}
|
||||
|
||||
/// Runs the [body] in an [Arena] freeing all memory which is [scoped] during
|
||||
/// execution of [body] at the end of the execution.
|
||||
R runArena<R>(R Function(Arena) body) {
|
||||
Arena arena = Arena();
|
||||
try {
|
||||
return runZoned(() => body(arena),
|
||||
zoneValues: {#_currentArena: arena},
|
||||
onError: (error, st) => throw RethrownError(error, st));
|
||||
} finally {
|
||||
arena.finalize();
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue