├── .github ├── dependabot.yaml └── workflows │ ├── dart.yaml │ └── publish.yaml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── lib ├── mailbox.dart ├── posix.dart ├── primitives.dart ├── sendable.dart ├── src │ └── bindings │ │ ├── pthread.dart │ │ └── winapi.dart └── windows.dart ├── pubspec.yaml └── test ├── mailbox_test.dart └── primitives_test.dart /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | # Dependabot configuration file. 2 | version: 2 3 | 4 | updates: 5 | - package-ecosystem: github-actions 6 | directory: / 7 | schedule: 8 | interval: monthly 9 | labels: 10 | - autosubmit 11 | groups: 12 | github-actions: 13 | patterns: 14 | - "*" 15 | -------------------------------------------------------------------------------- /.github/workflows/dart.yaml: -------------------------------------------------------------------------------- 1 | name: Dart 2 | 3 | on: 4 | schedule: 5 | # “At 00:00 (UTC) on Sunday.” 6 | - cron: '0 0 * * 0' 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | # Declare default permissions as read only. 13 | permissions: read-all 14 | 15 | jobs: 16 | analyze: 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | matrix: 20 | os: [ubuntu-latest] 21 | sdk: [dev, stable] 22 | 23 | steps: 24 | # These are the latest versions of the github actions; dependabot will 25 | # send PRs to keep these up-to-date. 26 | - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 27 | - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 28 | with: 29 | sdk: ${{ matrix.sdk }} 30 | 31 | - name: Install dependencies 32 | run: dart pub get 33 | 34 | - name: Verify formatting 35 | run: dart format --output=none --set-exit-if-changed . 36 | 37 | - name: Analyze project source 38 | run: dart analyze --fatal-infos 39 | 40 | test: 41 | needs: analyze 42 | runs-on: ${{ matrix.os }} 43 | strategy: 44 | matrix: 45 | os: [ubuntu-latest, macos-latest, windows-latest] 46 | sdk: [dev, stable] 47 | steps: 48 | - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 49 | - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 50 | with: 51 | sdk: ${{ matrix.sdk }} 52 | 53 | - name: Install dependencies 54 | run: dart pub get 55 | 56 | - name: Run tests 57 | run: dart test 58 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | # A CI configuration to auto-publish pub packages. 2 | 3 | name: Publish 4 | 5 | on: 6 | pull_request: 7 | branches: [ main ] 8 | push: 9 | tags: [ 'v[0-9]+.[0-9]+.[0-9]+' ] 10 | 11 | jobs: 12 | publish: 13 | if: ${{ github.repository_owner == 'dart-lang' }} 14 | uses: dart-lang/ecosystem/.github/workflows/publish.yaml@main 15 | permissions: 16 | id-token: write # Required for authentication using OIDC 17 | pull-requests: write # Required for writing the pull request note 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub. 2 | .dart_tool/ 3 | .packages 4 | 5 | # Conventional directory for build outputs. 6 | build/ 7 | 8 | # Omit committing pubspec.lock for library packages; see 9 | # https://dart.dev/guides/libraries/private-files#pubspeclock. 10 | pubspec.lock 11 | 12 | # VSCode configuration files 13 | .vscode/ 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.3.0 2 | 3 | - Add a closed state to `Mailbox`. 4 | 5 | ## 0.2.0 6 | 7 | - Lower SDK lower bound to 3.0.0. 8 | 9 | ## 0.1.0 10 | 11 | - Initial version. 12 | - Expose `Mutex` and `ConditionVariable` 13 | - Implement `Mailbox`. 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023, the Dart project authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided 12 | with the distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived 15 | from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!NOTE] 2 | > The source-of-truth for this package has moved to 3 | https://github.com/dart-lang/labs/tree/main/pkgs/native_synchronization. 4 | 5 | ## package:native_synchronization 6 | 7 | This package exposes a portable interface for low-level thread 8 | synchronization primitives like `Mutex` and `ConditionVariable`. 9 | 10 | It also provides some slightly more high-level synchronization primitives 11 | like `Mailbox` built on top of low-level primitives. 12 | 13 | ## Status: experimental 14 | 15 | **NOTE**: This package is currently experimental and published under the 16 | [labs.dart.dev](https://dart.dev/dart-team-packages) pub publisher in order to 17 | solicit feedback. 18 | 19 | For packages in the labs.dart.dev publisher we generally plan to either graduate 20 | the package into a supported publisher (dart.dev, tools.dart.dev) after a period 21 | of feedback and iteration, or discontinue the package. These packages have a 22 | much higher expected rate of API and breaking changes. 23 | 24 | Your feedback is valuable and will help us evolve this package. For general 25 | feedback, suggestions, and comments, please file an issue in the 26 | [bug tracker](https://github.com/dart-lang/native_synchronization/issues). 27 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:dart_flutter_team_lints/analysis_options.yaml 2 | -------------------------------------------------------------------------------- /lib/mailbox.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:ffi'; 6 | import 'dart:typed_data'; 7 | 8 | import 'package:ffi/ffi.dart'; 9 | 10 | import 'package:native_synchronization/primitives.dart'; 11 | import 'package:native_synchronization/sendable.dart'; 12 | 13 | final class _MailboxRepr extends Struct { 14 | external Pointer buffer; 15 | 16 | @Int32() 17 | external int bufferLength; 18 | 19 | @Int32() 20 | external int state; 21 | } 22 | 23 | class _SendableMailbox { 24 | final int address; 25 | final Sendable mutex; 26 | final Sendable condVar; 27 | 28 | _SendableMailbox( 29 | {required this.address, required this.mutex, required this.condVar}); 30 | } 31 | 32 | /// Mailbox communication primitive. 33 | /// 34 | /// This synchronization primitive allows a single producer to send messages 35 | /// to one or more consumers. Producer uses [put] to place a message into 36 | /// a mailbox which consumers can then [take] out. 37 | /// 38 | /// [Mailbox] object can not be directly sent to other isolates via a 39 | /// `SendPort`, but it can be converted to a `Sendable` via 40 | /// `asSendable` getter. 41 | /// 42 | /// [Mailbox] object is owned by an isolate which created them. 43 | class Mailbox { 44 | final Pointer<_MailboxRepr> _mailbox; 45 | final Mutex _mutex; 46 | final ConditionVariable _condVar; 47 | 48 | static const _stateEmpty = 0; 49 | static const _stateFull = 1; 50 | static const _stateClosed = 2; 51 | 52 | static final finalizer = Finalizer((Pointer<_MailboxRepr> mailbox) { 53 | calloc.free(mailbox.ref.buffer); 54 | calloc.free(mailbox); 55 | }); 56 | 57 | Mailbox() 58 | : _mailbox = calloc.allocate(sizeOf<_MailboxRepr>()), 59 | _mutex = Mutex(), 60 | _condVar = ConditionVariable() { 61 | finalizer.attach(this, _mailbox); 62 | } 63 | 64 | Mailbox._fromSendable(_SendableMailbox sendable) 65 | : _mailbox = Pointer.fromAddress(sendable.address), 66 | _mutex = sendable.mutex.materialize(), 67 | _condVar = sendable.condVar.materialize(); 68 | 69 | /// Place a message into the mailbox if has space for it. 70 | /// 71 | /// If mailbox already contains a message or mailbox is closed then [put] will 72 | /// throw [StateError]. 73 | void put(Uint8List message) { 74 | final buffer = message.isEmpty ? nullptr : _toBuffer(message); 75 | _mutex.runLocked(() { 76 | if (_mailbox.ref.state != _stateEmpty) { 77 | throw StateError('Mailbox is closed or full'); 78 | } 79 | 80 | _mailbox.ref.state = _stateFull; 81 | _mailbox.ref.buffer = buffer; 82 | _mailbox.ref.bufferLength = message.length; 83 | 84 | _condVar.notify(); 85 | }); 86 | } 87 | 88 | /// Close a mailbox. 89 | /// 90 | /// If mailbox already contains a message then [close] will drop the message. 91 | void close() => _mutex.runLocked(() { 92 | if (_mailbox.ref.state == _stateFull && _mailbox.ref.bufferLength > 0) { 93 | malloc.free(_mailbox.ref.buffer); 94 | } 95 | 96 | _mailbox.ref.state = _stateClosed; 97 | _mailbox.ref.buffer = nullptr; 98 | _mailbox.ref.bufferLength = 0; 99 | 100 | _condVar.notify(); 101 | }); 102 | 103 | /// Take a message from the mailbox. 104 | /// 105 | /// If mailbox is empty then [take] will synchronously block until message 106 | /// is available or mailbox is closed. If mailbox is closed then [take] will 107 | /// throw [StateError]. 108 | Uint8List take() => _mutex.runLocked(() { 109 | while (_mailbox.ref.state == _stateEmpty) { 110 | _condVar.wait(_mutex); 111 | } 112 | 113 | if (_mailbox.ref.state == _stateClosed) { 114 | throw StateError('Mailbox is closed'); 115 | } 116 | 117 | final result = _toList(_mailbox.ref.buffer, _mailbox.ref.bufferLength); 118 | 119 | _mailbox.ref.state = _stateEmpty; 120 | _mailbox.ref.buffer = nullptr; 121 | _mailbox.ref.bufferLength = 0; 122 | return result; 123 | }); 124 | 125 | static final _emptyResponse = Uint8List(0); 126 | 127 | static Uint8List _toList(Pointer buffer, int length) { 128 | if (length == 0) { 129 | return _emptyResponse; 130 | } 131 | 132 | // TODO: remove feature detection once 3.1 becomes stable. 133 | // ignore: omit_local_variable_types 134 | final Uint8List Function(int) asTypedList = buffer.asTypedList; 135 | if (asTypedList is Uint8List Function(int, 136 | {Pointer finalizer})) { 137 | return asTypedList(length, finalizer: malloc.nativeFree); 138 | } 139 | 140 | final result = Uint8List(length); 141 | result.setRange(0, length, buffer.asTypedList(length)); 142 | malloc.free(buffer); 143 | return result; 144 | } 145 | 146 | static Pointer _toBuffer(Uint8List list) { 147 | final buffer = malloc.allocate(list.length); 148 | buffer.asTypedList(list.length).setRange(0, list.length, list); 149 | return buffer; 150 | } 151 | 152 | Sendable get asSendable => Sendable.wrap( 153 | Mailbox._fromSendable, 154 | _SendableMailbox( 155 | address: _mailbox.address, 156 | mutex: _mutex.asSendable, 157 | condVar: _condVar.asSendable)); 158 | } 159 | -------------------------------------------------------------------------------- /lib/posix.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | part of 'primitives.dart'; 6 | 7 | class _PosixMutex extends Mutex { 8 | /// This is maximum value of `sizeof(pthread_mutex_t)` across all supported 9 | /// platforms. 10 | static const _sizeInBytes = 64; 11 | 12 | final Pointer _impl; 13 | 14 | static final _finalizer = Finalizer>((ptr) { 15 | pthread_mutex_destroy(ptr); 16 | malloc.free(ptr); 17 | }); 18 | 19 | _PosixMutex() 20 | : _impl = malloc.allocate(_PosixMutex._sizeInBytes), 21 | super._() { 22 | if (pthread_mutex_init(_impl, nullptr) != 0) { 23 | malloc.free(_impl); 24 | throw StateError('Failed to initialize mutex'); 25 | } 26 | _finalizer.attach(this, _impl); 27 | } 28 | 29 | _PosixMutex.fromAddress(int address) 30 | : _impl = Pointer.fromAddress(address), 31 | super._(); 32 | 33 | @override 34 | void _lock() { 35 | if (pthread_mutex_lock(_impl) != 0) { 36 | throw StateError('Failed to lock mutex'); 37 | } 38 | } 39 | 40 | @override 41 | void _unlock() { 42 | if (pthread_mutex_unlock(_impl) != 0) { 43 | throw StateError('Failed to unlock mutex'); 44 | } 45 | } 46 | 47 | @override 48 | int get _address => _impl.address; 49 | } 50 | 51 | class _PosixConditionVariable extends ConditionVariable { 52 | /// This is maximum value of `sizeof(pthread_cond_t)` across all supported 53 | /// platforms. 54 | static const _sizeInBytes = 64; 55 | 56 | final Pointer _impl; 57 | 58 | static final _finalizer = Finalizer>((ptr) { 59 | pthread_cond_destroy(ptr); 60 | malloc.free(ptr); 61 | }); 62 | 63 | _PosixConditionVariable() 64 | : _impl = malloc.allocate(_PosixConditionVariable._sizeInBytes), 65 | super._() { 66 | if (pthread_cond_init(_impl, nullptr) != 0) { 67 | malloc.free(_impl); 68 | throw StateError('Failed to initialize condition variable'); 69 | } 70 | _finalizer.attach(this, _impl); 71 | } 72 | 73 | _PosixConditionVariable.fromAddress(int address) 74 | : _impl = Pointer.fromAddress(address), 75 | super._(); 76 | 77 | @override 78 | void notify() { 79 | if (pthread_cond_signal(_impl) != 0) { 80 | throw StateError('Failed to signal condition variable'); 81 | } 82 | } 83 | 84 | @override 85 | void wait(covariant _PosixMutex mutex) { 86 | if (pthread_cond_wait(_impl, mutex._impl) != 0) { 87 | throw StateError('Failed to wait on a condition variable'); 88 | } 89 | } 90 | 91 | @override 92 | int get _address => _impl.address; 93 | } 94 | -------------------------------------------------------------------------------- /lib/primitives.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// This library contains native synchronization primitives such as [Mutex] 6 | /// and [ConditionVariable] implemented on top of low-level primitives 7 | /// provided by the OS. 8 | /// 9 | /// See OS specific documentation for more details: 10 | /// 11 | /// * POSIX man pages (Linux, Android, Mac OS X and iOS X) 12 | /// * `pthread_mutex_lock` and `pthread_mutex_unlock`, 13 | /// * `pthread_cond_wait` and `pthread_cond_signal`. 14 | /// * Windows 15 | /// * [Slim Reader/Writer (SRW) Locks](https://learn.microsoft.com/en-us/windows/win32/sync/slim-reader-writer--srw--locks), 16 | /// * [Condition Variables](https://learn.microsoft.com/en-us/windows/win32/sync/condition-variables), 17 | library; 18 | 19 | import 'dart:ffi'; 20 | import 'dart:io'; 21 | 22 | import 'package:ffi/ffi.dart'; 23 | import 'package:native_synchronization/sendable.dart'; 24 | 25 | import 'package:native_synchronization/src/bindings/pthread.dart'; 26 | import 'package:native_synchronization/src/bindings/winapi.dart'; 27 | 28 | part 'posix.dart'; 29 | part 'windows.dart'; 30 | 31 | /// A *mutex* synchronization primitive. 32 | /// 33 | /// Mutex can be used to synchronize access to a native resource shared between 34 | /// multiple threads. 35 | /// 36 | /// [Mutex] object can not be directly sent to other isolates via a `SendPort`, 37 | /// but it can be converted to a `Sendable` via `asSendable` getter. 38 | /// 39 | /// Mutex objects are owned by an isolate which created them. 40 | sealed class Mutex implements Finalizable { 41 | Mutex._(); 42 | 43 | factory Mutex() => Platform.isWindows ? _WindowsMutex() : _PosixMutex(); 44 | 45 | /// Acquire exclusive ownership of this mutex. 46 | /// 47 | /// If this mutex is already acquired then an attempt to acquire it 48 | /// blocks the current thread until the mutex is released by the 49 | /// current owner. 50 | /// 51 | /// **Warning**: attempting to hold a mutex across asynchronous suspension 52 | /// points will lead to undefined behavior and potentially crashes. 53 | void _lock(); 54 | 55 | /// Release exclusive ownership of this mutex. 56 | /// 57 | /// It is an error to release ownership of the mutex if it was not 58 | /// previously acquired. 59 | void _unlock(); 60 | 61 | /// Run the given synchronous `action` under a mutex. 62 | /// 63 | /// This function takes exclusive ownership of the mutex, executes `action` 64 | /// and then releases the mutex. It returns the value returned by `action`. 65 | /// 66 | /// **Warning**: you can't combine `runLocked` with an asynchronous code. 67 | R runLocked(R Function() action) { 68 | _lock(); 69 | try { 70 | return action(); 71 | } finally { 72 | _unlock(); 73 | } 74 | } 75 | 76 | Sendable get asSendable => Sendable.wrap( 77 | Platform.isWindows ? _WindowsMutex.fromAddress : _PosixMutex.fromAddress, 78 | _address); 79 | 80 | int get _address; 81 | } 82 | 83 | /// A *condition variable* synchronization primitive. 84 | /// 85 | /// Condition variable can be used to synchronously wait for a condition to 86 | /// occur. 87 | /// 88 | /// [ConditionVariable] object can not be directly sent to other isolates via a 89 | /// `SendPort`, but it can be converted to a `Sendable` 90 | /// object via [asSendable] getter. 91 | /// 92 | /// [ConditionVariable] objects are owned by an isolate which created them. 93 | sealed class ConditionVariable implements Finalizable { 94 | ConditionVariable._(); 95 | 96 | factory ConditionVariable() => Platform.isWindows 97 | ? _WindowsConditionVariable() 98 | : _PosixConditionVariable(); 99 | 100 | /// Block and wait until another thread calls [notify]. 101 | /// 102 | /// `mutex` must be a [Mutex] object exclusively held by the current thread. 103 | /// It will be released and the thread will block until another thread 104 | /// calls [notify]. 105 | void wait(Mutex mutex); 106 | 107 | /// Wake up at least one thread waiting on this condition variable. 108 | void notify(); 109 | 110 | Sendable get asSendable => Sendable.wrap( 111 | Platform.isWindows 112 | ? _WindowsConditionVariable.fromAddress 113 | : _PosixConditionVariable.fromAddress, 114 | _address); 115 | 116 | int get _address; 117 | } 118 | -------------------------------------------------------------------------------- /lib/sendable.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | abstract final class Sendable { 6 | static Sendable wrap(T Function(U) make, U data) { 7 | return _SendableImpl._(make, data); 8 | } 9 | 10 | T materialize(); 11 | } 12 | 13 | final class _SendableImpl implements Sendable { 14 | final U _data; 15 | final T Function(U v) _make; 16 | 17 | _SendableImpl._(this._make, this._data); 18 | 19 | @override 20 | T materialize() => _make(_data); 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/bindings/pthread.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | // ignore_for_file: non_constant_identifier_names, camel_case_types 6 | 7 | import 'dart:ffi'; 8 | 9 | final class pthread_mutex_t extends Opaque {} 10 | 11 | final class pthread_cond_t extends Opaque {} 12 | 13 | @Native, Pointer)>() 14 | external int pthread_mutex_init( 15 | Pointer mutex, Pointer attrs); 16 | 17 | @Native)>() 18 | external int pthread_mutex_lock(Pointer mutex); 19 | 20 | @Native)>() 21 | external int pthread_mutex_unlock(Pointer mutex); 22 | 23 | @Native)>() 24 | external int pthread_mutex_destroy(Pointer cond); 25 | 26 | @Native, Pointer)>() 27 | external int pthread_cond_init( 28 | Pointer cond, Pointer attrs); 29 | 30 | @Native, Pointer)>() 31 | external int pthread_cond_wait( 32 | Pointer cond, Pointer mutex); 33 | 34 | @Native)>() 35 | external int pthread_cond_destroy(Pointer cond); 36 | 37 | @Native)>() 38 | external int pthread_cond_signal(Pointer cond); 39 | -------------------------------------------------------------------------------- /lib/src/bindings/winapi.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | // ignore_for_file: non_constant_identifier_names, camel_case_types 6 | 7 | import 'dart:ffi'; 8 | 9 | final class SRWLOCK extends Opaque {} 10 | 11 | final class CONDITION_VARIABLE extends Opaque {} 12 | 13 | @Native)>() 14 | external void InitializeSRWLock(Pointer lock); 15 | 16 | @Native)>() 17 | external void AcquireSRWLockExclusive(Pointer lock); 18 | 19 | @Native)>() 20 | external void ReleaseSRWLockExclusive(Pointer mutex); 21 | 22 | @Native)>() 23 | external void InitializeConditionVariable(Pointer condVar); 24 | 25 | @Native< 26 | Int Function( 27 | Pointer, Pointer, Uint32, Uint32)>() 28 | external int SleepConditionVariableSRW(Pointer condVar, 29 | Pointer srwLock, int timeOut, int flags); 30 | 31 | @Native)>() 32 | external void WakeConditionVariable(Pointer condVar); 33 | -------------------------------------------------------------------------------- /lib/windows.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | part of 'primitives.dart'; 6 | 7 | class _WindowsMutex extends Mutex { 8 | static const _sizeInBytes = 8; // `sizeof(SRWLOCK)` 9 | 10 | final Pointer _impl; 11 | 12 | static final _finalizer = Finalizer>((ptr) { 13 | malloc.free(ptr); 14 | }); 15 | 16 | _WindowsMutex() 17 | : _impl = malloc.allocate(_WindowsMutex._sizeInBytes), 18 | super._() { 19 | InitializeSRWLock(_impl); 20 | _finalizer.attach(this, _impl); 21 | } 22 | 23 | _WindowsMutex.fromAddress(int address) 24 | : _impl = Pointer.fromAddress(address), 25 | super._(); 26 | 27 | @override 28 | void _lock() => AcquireSRWLockExclusive(_impl); 29 | 30 | @override 31 | void _unlock() => ReleaseSRWLockExclusive(_impl); 32 | 33 | @override 34 | int get _address => _impl.address; 35 | } 36 | 37 | class _WindowsConditionVariable extends ConditionVariable { 38 | static const _sizeInBytes = 8; // `sizeof(CONDITION_VARIABLE)` 39 | 40 | final Pointer _impl; 41 | 42 | static final _finalizer = Finalizer>((ptr) { 43 | malloc.free(ptr); 44 | }); 45 | 46 | _WindowsConditionVariable() 47 | : _impl = malloc.allocate(_WindowsConditionVariable._sizeInBytes), 48 | super._() { 49 | InitializeConditionVariable(_impl); 50 | _finalizer.attach(this, _impl); 51 | } 52 | 53 | _WindowsConditionVariable.fromAddress(int address) 54 | : _impl = Pointer.fromAddress(address), 55 | super._(); 56 | 57 | @override 58 | void notify() { 59 | WakeConditionVariable(_impl); 60 | } 61 | 62 | @override 63 | void wait(covariant _WindowsMutex mutex) { 64 | const infinite = 0xFFFFFFFF; 65 | const exclusive = 0; 66 | if (SleepConditionVariableSRW(_impl, mutex._impl, infinite, exclusive) == 67 | 0) { 68 | throw StateError('Failed to wait on a condition variable'); 69 | } 70 | } 71 | 72 | @override 73 | int get _address => _impl.address; 74 | } 75 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: native_synchronization 2 | description: Low level synchronization primitives built on dart:ffi. 3 | version: 0.3.0 4 | repository: https://github.com/dart-lang/native_synchronization 5 | 6 | environment: 7 | sdk: ">=3.0.0 <4.0.0" 8 | 9 | dependencies: 10 | ffi: ^2.1.0 11 | 12 | dev_dependencies: 13 | dart_flutter_team_lints: ^1.0.0 14 | test: ^1.16.0 15 | -------------------------------------------------------------------------------- /test/mailbox_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | import 'dart:isolate'; 7 | import 'dart:typed_data'; 8 | 9 | import 'package:native_synchronization/mailbox.dart'; 10 | import 'package:native_synchronization/sendable.dart'; 11 | import 'package:test/test.dart'; 12 | 13 | void main() { 14 | Future startHelperIsolate(Sendable sendableMailbox) { 15 | return Isolate.run(() { 16 | sleep(const Duration(milliseconds: 500)); 17 | final mailbox = sendableMailbox.materialize(); 18 | mailbox.put(Uint8List(42)..[41] = 42); 19 | return 'success'; 20 | }); 21 | } 22 | 23 | test('mailbox', () async { 24 | final mailbox = Mailbox(); 25 | final helperResult = startHelperIsolate(mailbox.asSendable); 26 | final value = mailbox.take(); 27 | expect(value, isA()); 28 | expect(value.length, equals(42)); 29 | expect(value[41], equals(42)); 30 | expect(await helperResult, equals('success')); 31 | }); 32 | 33 | Future startHelperIsolateClose(Sendable sendableMailbox) { 34 | return Isolate.run(() { 35 | sleep(const Duration(milliseconds: 500)); 36 | final mailbox = sendableMailbox.materialize(); 37 | try { 38 | mailbox.take(); 39 | } catch (_) { 40 | return 'success'; 41 | } 42 | return 'failed'; 43 | }); 44 | } 45 | 46 | test('mailbox close', () async { 47 | final mailbox = Mailbox(); 48 | mailbox.put(Uint8List(42)..[41] = 42); 49 | mailbox.close(); 50 | final helperResult = startHelperIsolateClose(mailbox.asSendable); 51 | expect(await helperResult, equals('success')); 52 | }); 53 | } 54 | -------------------------------------------------------------------------------- /test/primitives_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:ffi'; 6 | import 'dart:io'; 7 | import 'dart:isolate'; 8 | 9 | import 'package:ffi/ffi.dart'; 10 | import 'package:native_synchronization/primitives.dart'; 11 | import 'package:native_synchronization/sendable.dart'; 12 | import 'package:test/test.dart'; 13 | 14 | void main() { 15 | group('mutex', () { 16 | test('simple', () { 17 | final mutex = Mutex(); 18 | expect(mutex.runLocked(() => 42), equals(42)); 19 | }); 20 | 21 | Future spawnHelperIsolate( 22 | int ptrAddress, Sendable sendableMutex) { 23 | return Isolate.run(() { 24 | final ptr = Pointer.fromAddress(ptrAddress); 25 | final mutex = sendableMutex.materialize(); 26 | 27 | while (true) { 28 | sleep(Duration(milliseconds: 10)); 29 | if (mutex.runLocked(() { 30 | if (ptr.value == 2) { 31 | return true; 32 | } 33 | ptr.value = 0; 34 | sleep(Duration(milliseconds: 500)); 35 | ptr.value = 1; 36 | return false; 37 | })) { 38 | break; 39 | } 40 | } 41 | 42 | return 'success'; 43 | }); 44 | } 45 | 46 | test('isolate', () async { 47 | await using((arena) async { 48 | final ptr = arena.allocate(1); 49 | final mutex = Mutex(); 50 | 51 | final helperResult = spawnHelperIsolate(ptr.address, mutex.asSendable); 52 | 53 | while (true) { 54 | final sw = Stopwatch()..start(); 55 | if (mutex.runLocked(() { 56 | if (sw.elapsedMilliseconds > 300 && ptr.value == 1) { 57 | ptr.value = 2; 58 | return true; 59 | } 60 | return false; 61 | })) { 62 | break; 63 | } 64 | await Future.delayed(const Duration(milliseconds: 10)); 65 | } 66 | expect(await helperResult, equals('success')); 67 | }); 68 | }); 69 | }); 70 | 71 | group('condvar', () { 72 | Future spawnHelperIsolate( 73 | int ptrAddress, 74 | Sendable sendableMutex, 75 | Sendable sendableCondVar) { 76 | return Isolate.run(() { 77 | final ptr = Pointer.fromAddress(ptrAddress); 78 | final mutex = sendableMutex.materialize(); 79 | final condVar = sendableCondVar.materialize(); 80 | 81 | return mutex.runLocked(() { 82 | ptr.value = 1; 83 | while (ptr.value == 1) { 84 | condVar.wait(mutex); 85 | } 86 | return ptr.value == 2 ? 'success' : 'failure'; 87 | }); 88 | }); 89 | } 90 | 91 | test('isolate', () async { 92 | await using((arena) async { 93 | final ptr = arena.allocate(1); 94 | final mutex = Mutex(); 95 | final condVar = ConditionVariable(); 96 | 97 | final helperResult = spawnHelperIsolate( 98 | ptr.address, mutex.asSendable, condVar.asSendable); 99 | 100 | while (true) { 101 | final success = mutex.runLocked(() { 102 | if (ptr.value == 1) { 103 | ptr.value = 2; 104 | condVar.notify(); 105 | return true; 106 | } 107 | return false; 108 | }); 109 | if (success) { 110 | break; 111 | } 112 | await Future.delayed(const Duration(milliseconds: 20)); 113 | } 114 | 115 | expect(await helperResult, equals('success')); 116 | }); 117 | }); 118 | }); 119 | } 120 | --------------------------------------------------------------------------------