| // 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'; | |
| void main() async { | |
| test('sync', () async { | |
| final freed = <int>[]; | |
| 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. | |
| final freed = <int>[]; | |
| void freeInt(int i) { | |
| freed.add(i); | |
| } | |
| final 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. | |
| final freed = <int>[]; | |
| 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. | |
| var 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 { | |
| final freed = <int>[]; | |
| 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. | |
| final freed = <int>[]; | |
| void freeInt(int i) { | |
| freed.add(i); | |
| } | |
| final 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. | |
| final freed = <int>[]; | |
| void freeInt(int i) { | |
| freed.add(i); | |
| } | |
| // Resources are freed also when abnormal control flow occurs. | |
| var 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('zone future error', () async { | |
| var caughtError = false; | |
| var uncaughtError = false; | |
| Future<int> asyncFunction() async { | |
| throw Exception('Exception 4'); | |
| } | |
| final future = runZonedGuarded( | |
| () { | |
| return withZoneArena(asyncFunction).catchError((error) { | |
| caughtError = true; | |
| return 5; | |
| }); | |
| }, | |
| (error, stackTrace) { | |
| uncaughtError = true; | |
| }, | |
| ); | |
| final result = (await Future.wait([future!])).single; | |
| expect(result, 5); | |
| expect(caughtError, true); | |
| expect(uncaughtError, false); | |
| }); | |
| 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); | |
| }); | |
| test('onReleaseAll and free', () { | |
| var released = false; | |
| final arena = Arena(); | |
| arena.onReleaseAll(() { | |
| released = true; | |
| }); | |
| final ptr = arena<Uint8>(); | |
| arena.free(ptr); // no-op | |
| arena.releaseAll(); | |
| expect(released, isTrue); | |
| expect(() => arena.allocate<Uint8>(1), throwsStateError); | |
| }); | |
| test('zoneArena errors outside zone and after release', () { | |
| expect(() => zoneArena, throwsA(isA<StateError>())); | |
| late Zone capturedZone; | |
| withZoneArena(() { | |
| capturedZone = Zone.current; | |
| }); | |
| expect(() => capturedZone.run(() => zoneArena), throwsA(isA<StateError>())); | |
| }); | |
| } | |
| /// 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); | |
| } | |
| } |