Skip to content
This repository has been archived by the owner on Jan 17, 2024. It is now read-only.

Commit

Permalink
Add Arena Allocator (#103)
Browse files Browse the repository at this point in the history
  • Loading branch information
dcharkes authored May 25, 2021
1 parent d0d197b commit 90c7f0b
Show file tree
Hide file tree
Showing 5 changed files with 394 additions and 2 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 1.1.0

Adds the `arena` allocator.

## 1.0.0

Bumping the version of this package to `1.0.0`.
Expand Down
3 changes: 2 additions & 1 deletion lib/ffi.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// 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.

export 'src/allocation.dart' show calloc, malloc;
export 'src/arena.dart';
export 'src/utf8.dart';
export 'src/utf16.dart';
export 'src/allocation.dart' show calloc, malloc;
183 changes: 183 additions & 0 deletions lib/src/arena.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// 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.');
}
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: ffi
version: 1.0.0
version: 1.1.0
homepage: https://github.com/dart-lang/ffi
description: Utilities for working with Foreign Function Interface (FFI) code.

Expand Down
Loading

0 comments on commit 90c7f0b

Please sign in to comment.