-
Notifications
You must be signed in to change notification settings - Fork 6k
Experimental platform isolates API #48551
Changes from all commits
765300a
642de7d
e193318
eb2d782
e4b3c24
9894b98
a33ff74
97ca3fd
d88fdfd
527eb03
a441ddb
fd31dbc
c08bd8d
675ba56
d816957
3d2d5d8
81cfe52
27b4db3
430d342
dfffe7a
c279bfc
6e384c3
3e31187
22af0cc
dfdf2c7
4d726f0
56df527
6e48192
b196c55
c7af637
679abcd
b084018
c982a10
75d4dde
4d2acfd
167f015
699175c
a4f4a61
b9a51f9
51bad14
e823f32
4a343cb
f50f791
185e248
a45ca33
8bc9944
998aa5e
99dbdd8
400da57
cb9b845
21f5141
3b5a2c4
c98528f
b9a87d9
d53522d
6de7797
5dd4f9b
b493f1d
2094637
87c2060
9ee715c
9d0ce6d
45cd307
33f483b
40d06ef
de58637
94c25cd
de5d6b5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,4 +10,5 @@ | |
| Spawn; | ||
| LoadLibraryFromKernel; | ||
| LookupEntryPoint; | ||
| ForceShutdownIsolate; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,3 +9,4 @@ _kInternalFlutterGpu* | |
| _Spawn | ||
| _LoadLibraryFromKernel | ||
| _LookupEntryPoint | ||
| _ForceShutdownIsolate | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,189 @@ | ||
| // Copyright 2013 The Flutter Authors. All rights reserved. | ||
| // Use of this source code is governed by a BSD-style license that can be | ||
| // found in the LICENSE file. | ||
| part of dart.ui; | ||
|
|
||
| /// Runs [computation] on the platform thread and returns the result. | ||
| /// | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the platform doesn't support a platform thread (or it wasn't implemented) - e.g. like jason mentions linux desktop embedder - this function will throw. |
||
| /// This may run the computation on a separate isolate. That isolate will be | ||
| /// reused for subsequent [runOnPlatformThread] calls. This means that global | ||
| /// state is maintained in that isolate between calls. | ||
| /// | ||
| /// The [computation] and any state it captures may be sent to that isolate. | ||
| /// See [SendPort.send] for information about what types can be sent. | ||
| /// | ||
| /// If [computation] is asynchronous (returns a `Future<R>`) then | ||
| /// that future is awaited in the new isolate, completing the entire | ||
| /// asynchronous computation, before returning the result. | ||
| /// | ||
| /// If [computation] throws, the `Future` returned by this function completes | ||
| /// with that error. | ||
| /// | ||
| /// The [computation] function and its result (or error) must be | ||
| /// sendable between isolates. Objects that cannot be sent include open | ||
| /// files and sockets (see [SendPort.send] for details). | ||
| /// | ||
| /// This method can only be invoked from the main isolate. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we should mark this temporarily as
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| /// | ||
| /// This API is currently experimental. | ||
| Future<R> runOnPlatformThread<R>(FutureOr<R> Function() computation) { | ||
| if (isRunningOnPlatformThread) { | ||
| return Future<R>(computation); | ||
| } | ||
| final SendPort? sendPort = _platformRunnerSendPort; | ||
| if (sendPort != null) { | ||
| return _sendComputation(sendPort, computation); | ||
| } else { | ||
| return (_platformRunnerSendPortFuture ??= _spawnPlatformIsolate()) | ||
| .then((SendPort port) => _sendComputation(port, computation)); | ||
| } | ||
| } | ||
|
|
||
| SendPort? _platformRunnerSendPort; | ||
| Future<SendPort>? _platformRunnerSendPortFuture; | ||
| final Map<int, Completer<Object?>> _pending = <int, Completer<Object?>>{}; | ||
| int _nextId = 0; | ||
|
|
||
| Future<SendPort> _spawnPlatformIsolate() { | ||
| final Completer<SendPort> sendPortCompleter = Completer<SendPort>(); | ||
| final RawReceivePort receiver = RawReceivePort()..keepIsolateAlive = false; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When we later allow calling this from a helper isolates, we may want to // When scheduling a computation
if (_pending.isEmpty) {
receiver.keepIsolateAlive = true;
}
_pending[nextId++] = ...
// When getting result
... = _pending.removeAt(id);
if (_pending.isEmpty) receiver.keepIsolateAlive = false;Consider leaving a comment (the |
||
| receiver.handler = (Object? message) { | ||
| if (message == null) { | ||
| // This is the platform isolate's onExit handler. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we actually registering an exit handler (don't see where)? Either we should throw an error if we think this cannot happen or we should just complete all pending RPCs with an error - but not silently ignore it.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Jason's comment mentions how to make a private test-only API, so I can now implement and test this logic. |
||
| // This shouldn't really happen, since Isolate.exit is disabled, the | ||
| // pause and terminate capabilities aren't provided to the parent | ||
| // isolate, and errors are fatal is false. But if the isolate does | ||
| // shutdown unexpectedly, clear the singleton so we can create another. | ||
| for (final Completer<Object?> completer in _pending.values) { | ||
| completer.completeError(RemoteError( | ||
| 'PlatformIsolate shutdown unexpectedly', | ||
| StackTrace.empty.toString())); | ||
| } | ||
| _pending.clear(); | ||
| _platformRunnerSendPort = null; | ||
| _platformRunnerSendPortFuture = null; | ||
| } else if (message is _PlatformIsolateReadyMessage) { | ||
| _platformRunnerSendPort = message.computationPort; | ||
| sendPortCompleter.complete(message.computationPort); | ||
| } else if (message is _ComputationResult) { | ||
| final Completer<Object?> resultCompleter = _pending.remove(message.id)!; | ||
| final Object? remoteStack = message.remoteStack; | ||
| final Object? remoteError = message.remoteError; | ||
| if (remoteStack != null) { | ||
| if (remoteStack is StackTrace) { | ||
| // Typed error. | ||
| resultCompleter.completeError(remoteError!, remoteStack); | ||
| } else { | ||
| // onError handler message, uncaught async error. | ||
| // Both values are strings, so calling `toString` is efficient. | ||
| final RemoteError error = | ||
| RemoteError(remoteError!.toString(), remoteStack.toString()); | ||
| resultCompleter.completeError(error, error.stackTrace); | ||
| } | ||
| } else { | ||
| resultCompleter.complete(message.result); | ||
| } | ||
| } else { | ||
| // We encountered an error while starting the new isolate. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If that's true, shouldn't it be sendPortCompleter.completeError(...);maybe make it if (!sendPortCompleter.isCompleted) {
sendPortCompleter.completeError(...);
return;
}
throw ...("unexpected message: $message");
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| if (!sendPortCompleter.isCompleted) { | ||
| sendPortCompleter.completeError( | ||
| IsolateSpawnException('Unable to spawn isolate: $message')); | ||
| return; | ||
| } | ||
| // This shouldn't happen. | ||
| throw IsolateSpawnException( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not really related to spawning, is it? Maybe
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed, per your other comment |
||
| "Internal error: unexpected message: '$message'"); | ||
| } | ||
| }; | ||
| final Isolate parentIsolate = Isolate.current; | ||
| final SendPort sendPort = receiver.sendPort; | ||
| try { | ||
| _nativeSpawn(() => _platformIsolateMain(parentIsolate, sendPort)); | ||
| } on Object { | ||
| receiver.close(); | ||
| rethrow; | ||
| } | ||
| return sendPortCompleter.future; | ||
| } | ||
|
|
||
| Future<R> _sendComputation<R>( | ||
| SendPort port, FutureOr<R> Function() computation) { | ||
| final int id = ++_nextId; | ||
| final Completer<R> resultCompleter = Completer<R>(); | ||
| _pending[id] = resultCompleter; | ||
| port.send(_ComputationRequest(id, computation)); | ||
| return resultCompleter.future; | ||
| } | ||
|
|
||
| void _safeSend(SendPort sendPort, int id, Object? result, Object? error, | ||
| Object? stackTrace) { | ||
| try { | ||
| sendPort.send(_ComputationResult(id, result, error, stackTrace)); | ||
| } catch (sendError, sendStack) { | ||
| sendPort.send(_ComputationResult(id, null, sendError, sendStack)); | ||
| } | ||
| } | ||
|
|
||
| void _platformIsolateMain(Isolate parentIsolate, SendPort sendPort) { | ||
| final RawReceivePort computationPort = RawReceivePort(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be nice to avoid repeated port creations (it's expensive, has to go to runtime, ...) - see comment above.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| computationPort.handler = (_ComputationRequest? message) { | ||
| if (message == null) { | ||
| // The parent isolate has shutdown. Allow this isolate to shutdown. | ||
| computationPort.keepIsolateAlive = false; | ||
| return; | ||
| } | ||
|
|
||
| late final FutureOr<Object?> potentiallyAsyncResult; | ||
| try { | ||
| potentiallyAsyncResult = message.computation(); | ||
| } catch (e, s) { | ||
| _safeSend(sendPort, message.id, null, e, s); | ||
| return; | ||
| } | ||
|
|
||
| if (potentiallyAsyncResult is Future<Object?>) { | ||
| potentiallyAsyncResult.then((Object? result) { | ||
| _safeSend(sendPort, message.id, result, null, null); | ||
| }, onError: (Object? e, Object? s) { | ||
| _safeSend(sendPort, message.id, null, e, s ?? StackTrace.empty); | ||
| }); | ||
| } else { | ||
| _safeSend(sendPort, message.id, potentiallyAsyncResult, null, null); | ||
| } | ||
| }; | ||
| Isolate.current.addOnExitListener(sendPort); | ||
| parentIsolate.addOnExitListener(computationPort.sendPort); | ||
| sendPort.send(_PlatformIsolateReadyMessage(computationPort.sendPort)); | ||
| } | ||
|
|
||
| @Native<Void Function(Handle)>(symbol: 'PlatformIsolateNativeApi::Spawn') | ||
| external void _nativeSpawn(Function entryPoint); | ||
|
|
||
| /// Whether the current isolate is running on the platform thread. | ||
| final bool isRunningOnPlatformThread = _isRunningOnPlatformThread(); | ||
|
|
||
| @Native<Bool Function()>( | ||
| symbol: 'PlatformIsolateNativeApi::IsRunningOnPlatformThread', isLeaf: true) | ||
| external bool _isRunningOnPlatformThread(); | ||
|
|
||
| class _PlatformIsolateReadyMessage { | ||
| _PlatformIsolateReadyMessage(this.computationPort); | ||
|
|
||
| final SendPort computationPort; | ||
| } | ||
|
|
||
| class _ComputationRequest { | ||
| _ComputationRequest(this.id, this.computation); | ||
|
|
||
| final int id; | ||
| final FutureOr<Object?> Function() computation; | ||
| } | ||
|
|
||
| class _ComputationResult { | ||
| _ComputationResult(this.id, this.result, this.remoteError, this.remoteStack); | ||
|
|
||
| final int id; | ||
| final Object? result; | ||
| final Object? remoteError; | ||
| final Object? remoteStack; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2024
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Flutter uses 2013 throughout its codebase
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not what go/copyright (external version https://opensource.google/documentation/reference/copyright) instructs us to do though. It should be the year when the file got first published.
Other coding styles (like Chromium1) seem to adhere to the go/copyright.
Footnotes
https://chromium.googlesource.com/chromium/src/+/HEAD/styleguide/c++/c++.md#file-headers ↩