Skip to content

Commit f3c0ccb

Browse files
authored
Merge pull request #3 from dart-lang/merge-native_synchronization-package
Merge native synchronization package
2 parents ad4cda6 + 44d4a91 commit f3c0ccb

File tree

16 files changed

+863
-0
lines changed

16 files changed

+863
-0
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
name: package:native_synchronization
2+
permissions: read-all
3+
4+
on:
5+
pull_request:
6+
branches: [ main ]
7+
paths:
8+
- '.github/workflows/native_synchronization.yml'
9+
- 'pkgs/native_synchronization/**'
10+
push:
11+
branches: [ main ]
12+
paths:
13+
- '.github/workflows/native_synchronization.yml'
14+
- 'pkgs/native_synchronization/**'
15+
schedule:
16+
- cron: '0 0 * * 0' # weekly
17+
18+
jobs:
19+
analyze:
20+
runs-on: ${{ matrix.os }}
21+
defaults:
22+
run:
23+
working-directory: pkgs/native_synchronization
24+
strategy:
25+
matrix:
26+
os: [ubuntu-latest]
27+
sdk: [dev, stable]
28+
29+
steps:
30+
# These are the latest versions of the github actions; dependabot will
31+
# send PRs to keep these up-to-date.
32+
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
33+
- uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672
34+
with:
35+
sdk: ${{ matrix.sdk }}
36+
37+
- name: Install dependencies
38+
run: dart pub get
39+
40+
- name: Verify formatting
41+
run: dart format --output=none --set-exit-if-changed .
42+
43+
- name: Analyze project source
44+
run: dart analyze --fatal-infos
45+
46+
test:
47+
needs: analyze
48+
runs-on: ${{ matrix.os }}
49+
defaults:
50+
run:
51+
working-directory: pkgs/native_synchronization
52+
strategy:
53+
matrix:
54+
os: [ubuntu-latest, macos-latest, windows-latest]
55+
sdk: [dev, stable]
56+
steps:
57+
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
58+
- uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672
59+
with:
60+
sdk: ${{ matrix.sdk }}
61+
62+
- name: Install dependencies
63+
run: dart pub get
64+
65+
- name: Run tests
66+
run: dart test
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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/
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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`.

pkgs/native_synchronization/LICENSE

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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.

pkgs/native_synchronization/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[![package:native_synchronization](https://github.com/dart-lang/labs/actions/workflows/native_synchronization.yml/badge.svg)](https://github.com/dart-lang/labs/actions/workflows/native_synchronization.yml)
2+
[![pub package](https://img.shields.io/pub/v/native_synchronization.svg)](https://pub.dev/packages/native_synchronization)
3+
[![package publisher](https://img.shields.io/pub/publisher/native_synchronization.svg)](https://pub.dev/packages/native_synchronization/publisher)
4+
5+
This package exposes a portable interface for low-level thread
6+
synchronization primitives like `Mutex` and `ConditionVariable`.
7+
8+
It also provides some slightly more high-level synchronization primitives
9+
like `Mailbox` built on top of low-level primitives.
10+
11+
## Status: experimental
12+
13+
**NOTE**: This package is currently experimental and published under the
14+
[labs.dart.dev](https://dart.dev/dart-team-packages) pub publisher in order to
15+
solicit feedback.
16+
17+
For packages in the labs.dart.dev publisher we generally plan to either graduate
18+
the package into a supported publisher (dart.dev, tools.dart.dev) after a period
19+
of feedback and iteration, or discontinue the package. These packages have a
20+
much higher expected rate of API and breaking changes.
21+
22+
Your feedback is valuable and will help us evolve this package. For general
23+
feedback, suggestions, and comments, please file an issue in the
24+
[bug tracker](https://github.com/dart-lang/native_synchronization/issues).
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include: package:dart_flutter_team_lints/analysis_options.yaml
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
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<Uint8> 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> mutex;
26+
final Sendable<ConditionVariable> 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<Mailbox>` 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<Uint8> 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<NativeFinalizerFunction> 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<Uint8> _toBuffer(Uint8List list) {
147+
final buffer = malloc.allocate<Uint8>(list.length);
148+
buffer.asTypedList(list.length).setRange(0, list.length, list);
149+
return buffer;
150+
}
151+
152+
Sendable<Mailbox> get asSendable => Sendable.wrap(
153+
Mailbox._fromSendable,
154+
_SendableMailbox(
155+
address: _mailbox.address,
156+
mutex: _mutex.asSendable,
157+
condVar: _condVar.asSendable));
158+
}

0 commit comments

Comments
 (0)