// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. // // Explicit pool used for managing resources. import "dart:async"; import 'dart:ffi'; import 'package:ffi/ffi.dart'; /// An [Allocator] which frees all allocations at the same time. /// /// 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 { /// The [Allocator] used for allocation and freeing. final Allocator _wrappedAllocator; /// Native memory under management by this [Pool]. final List> _managedMemoryPointers = []; /// Callbacks for releasing native resources under management by this [Pool]. final List _managedResourceReleaseCallbacks = []; bool _inUse = true; /// 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 /// satisfied. @override Pointer allocate(int byteCount, {int? alignment}) { _ensureInUse(); final p = _wrappedAllocator.allocate(byteCount, alignment: alignment); _managedMemoryPointers.add(p); return p; } /// Registers [resource] in this pool. /// /// Executes [releaseCallback] on [releaseAll]. T using(T resource, Function(T) releaseCallback) { _ensureInUse(); releaseCallback = Zone.current.bindUnaryCallback(releaseCallback); _managedResourceReleaseCallbacks.add(() => releaseCallback(resource)); return resource; } /// Registers [releaseResourceCallback] to be executed on [releaseAll]. void onReleaseAll(Function() releaseResourceCallback) { _managedResourceReleaseCallbacks.add(releaseResourceCallback); } /// Releases all resources that this [Pool] manages. /// /// If [reuse] is `true`, the pool 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`. void releaseAll({bool reuse = false}) { if (!reuse) { _inUse = false; } while (_managedResourceReleaseCallbacks.isNotEmpty) { _managedResourceReleaseCallbacks.removeLast()(); } for (final p in _managedMemoryPointers) { _wrappedAllocator.free(p); } _managedMemoryPointers.clear(); } /// Does nothing, invoke [releaseAll] instead. @override void free(Pointer pointer) {} void _ensureInUse() { if (!_inUse) { throw StateError( "Pool no longer in use, `releaseAll(reuse: false)` was called."); } } } /// Runs [computation] with a new [Pool], and releases all allocations at the end. /// /// If [R] 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 Function(Pool) computation, [Allocator wrappedAllocator = calloc]) { final pool = Pool(wrappedAllocator); bool isAsync = false; try { final result = computation(pool); if (result is Future) { isAsync = true; return (result.whenComplete(pool.releaseAll) as R); } return result; } finally { if (!isAsync) { pool.releaseAll(); } } } /// Creates a zoned [Pool] to manage native resources. /// /// The pool is availabe through [zonePool]. /// /// If the isolate is shut down, through `Isolate.kill()`, resources are _not_ cleaned up. R withZonePool(R Function() computation, [Allocator wrappedAllocator = calloc]) { final pool = Pool(wrappedAllocator); var poolHolder = [pool]; bool isAsync = false; try { return runZoned(() { final result = computation(); if (result is Future) { isAsync = true; result.whenComplete(pool.releaseAll); } return result; }, zoneValues: {#_pool: poolHolder}); } finally { if (!isAsync) { pool.releaseAll(); poolHolder.remove(pool); } } } /// A zone-specific [Pool]. /// /// Asynchronous computations can share a [Pool]. Use [withZonePool] to create /// a new zone with a fresh [Pool], and that pool will then be released /// automatically when the function passed to [withZonePool] completes. /// All code inside that zone can use `zonePool` to access the pool. /// /// The current pool must not be accessed by code which is not running inside /// a zone created by [withZonePool]. Pool get zonePool { final List? 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."); }