Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 9cbca80

Browse files
authored
Experimental platform isolates API (#48551)
This is a prototype of the [PlatformIsolate API](flutter/flutter#136314). **UPDATE (Jan 25):** The PR is ready for review. PTAL. The `PlatformIsolate` creation flow is: 1. `PlatformIsolate.spawn` running on parent isolate (platform_isolate.dart) a. Create `isolateReadyPort` b. `PlatformIsolateNativeApi::Spawn` (platform_isolate.cc) c. `DartIsolate::CreatePlatformIsolate` (dart_isolate.cc) d. Isolate created. Entry point invocation task dispatched to platform thread e. `PlatformIsolate.spawn` returns a `Future<Isolate>` 2. On the platform thread, `_platformIsolateMain` is invoked in the platform isolate a. Create `entryPointPort` b. Send `Isolate.current` metadata and `entryPointPort` back to the parent isolate via `isolateReadyPort` 3. Back in the parent isolate, `isolateReadyPort.handler` is invoked a. Send the user's `entryPoint` and `message` to the platform isolate via `entryPointPort` b. Use received isolate metadata to create a new `Isolate` representing the platform isolate and complete the `Future<Isolate>` 4. In the platform isolate, `entryPointPort.handler` is invoked a. Run the user's `entryPoint(message)` The engine shutdown flow is handled by `PlatformIsolateManager`, which maintains a set of running platform isolates.
1 parent c52b303 commit 9cbca80

36 files changed

+1775
-16
lines changed

ci/licenses_golden/excluded_files

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@
245245
../../../flutter/runtime/dart_vm_unittests.cc
246246
../../../flutter/runtime/fixtures
247247
../../../flutter/runtime/no_dart_plugin_registrant_unittests.cc
248+
../../../flutter/runtime/platform_isolate_manager_unittests.cc
248249
../../../flutter/runtime/type_conversions_unittests.cc
249250
../../../flutter/shell/common/animator_unittests.cc
250251
../../../flutter/shell/common/base64_unittests.cc

ci/licenses_golden/licenses_flutter

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35635,6 +35635,7 @@ ORIGIN: ../../../flutter/lib/ui/painting/single_frame_codec.h + ../../../flutter
3563535635
ORIGIN: ../../../flutter/lib/ui/painting/vertices.cc + ../../../flutter/LICENSE
3563635636
ORIGIN: ../../../flutter/lib/ui/painting/vertices.h + ../../../flutter/LICENSE
3563735637
ORIGIN: ../../../flutter/lib/ui/platform_dispatcher.dart + ../../../flutter/LICENSE
35638+
ORIGIN: ../../../flutter/lib/ui/platform_isolate.dart + ../../../flutter/LICENSE
3563835639
ORIGIN: ../../../flutter/lib/ui/plugins.dart + ../../../flutter/LICENSE
3563935640
ORIGIN: ../../../flutter/lib/ui/plugins/callback_cache.cc + ../../../flutter/LICENSE
3564035641
ORIGIN: ../../../flutter/lib/ui/plugins/callback_cache.h + ../../../flutter/LICENSE
@@ -35675,6 +35676,8 @@ ORIGIN: ../../../flutter/lib/ui/window/key_data_packet.cc + ../../../flutter/LIC
3567535676
ORIGIN: ../../../flutter/lib/ui/window/key_data_packet.h + ../../../flutter/LICENSE
3567635677
ORIGIN: ../../../flutter/lib/ui/window/platform_configuration.cc + ../../../flutter/LICENSE
3567735678
ORIGIN: ../../../flutter/lib/ui/window/platform_configuration.h + ../../../flutter/LICENSE
35679+
ORIGIN: ../../../flutter/lib/ui/window/platform_isolate.cc + ../../../flutter/LICENSE
35680+
ORIGIN: ../../../flutter/lib/ui/window/platform_isolate.h + ../../../flutter/LICENSE
3567835681
ORIGIN: ../../../flutter/lib/ui/window/platform_message.cc + ../../../flutter/LICENSE
3567935682
ORIGIN: ../../../flutter/lib/ui/window/platform_message.h + ../../../flutter/LICENSE
3568035683
ORIGIN: ../../../flutter/lib/ui/window/platform_message_response.cc + ../../../flutter/LICENSE
@@ -35716,6 +35719,7 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/painting.dart + ../../../flutter/LICENSE
3571635719
ORIGIN: ../../../flutter/lib/web_ui/lib/path.dart + ../../../flutter/LICENSE
3571735720
ORIGIN: ../../../flutter/lib/web_ui/lib/path_metrics.dart + ../../../flutter/LICENSE
3571835721
ORIGIN: ../../../flutter/lib/web_ui/lib/platform_dispatcher.dart + ../../../flutter/LICENSE
35722+
ORIGIN: ../../../flutter/lib/web_ui/lib/platform_isolate.dart + ../../../flutter/LICENSE
3571935723
ORIGIN: ../../../flutter/lib/web_ui/lib/pointer.dart + ../../../flutter/LICENSE
3572035724
ORIGIN: ../../../flutter/lib/web_ui/lib/semantics.dart + ../../../flutter/LICENSE
3572135725
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine.dart + ../../../flutter/LICENSE
@@ -36015,6 +36019,8 @@ ORIGIN: ../../../flutter/runtime/isolate_configuration.cc + ../../../flutter/LIC
3601536019
ORIGIN: ../../../flutter/runtime/isolate_configuration.h + ../../../flutter/LICENSE
3601636020
ORIGIN: ../../../flutter/runtime/platform_data.cc + ../../../flutter/LICENSE
3601736021
ORIGIN: ../../../flutter/runtime/platform_data.h + ../../../flutter/LICENSE
36022+
ORIGIN: ../../../flutter/runtime/platform_isolate_manager.cc + ../../../flutter/LICENSE
36023+
ORIGIN: ../../../flutter/runtime/platform_isolate_manager.h + ../../../flutter/LICENSE
3601836024
ORIGIN: ../../../flutter/runtime/ptrace_check.cc + ../../../flutter/LICENSE
3601936025
ORIGIN: ../../../flutter/runtime/ptrace_check.h + ../../../flutter/LICENSE
3602036026
ORIGIN: ../../../flutter/runtime/runtime_controller.cc + ../../../flutter/LICENSE
@@ -38475,6 +38481,7 @@ FILE: ../../../flutter/lib/ui/painting/single_frame_codec.h
3847538481
FILE: ../../../flutter/lib/ui/painting/vertices.cc
3847638482
FILE: ../../../flutter/lib/ui/painting/vertices.h
3847738483
FILE: ../../../flutter/lib/ui/platform_dispatcher.dart
38484+
FILE: ../../../flutter/lib/ui/platform_isolate.dart
3847838485
FILE: ../../../flutter/lib/ui/plugins.dart
3847938486
FILE: ../../../flutter/lib/ui/plugins/callback_cache.cc
3848038487
FILE: ../../../flutter/lib/ui/plugins/callback_cache.h
@@ -38515,6 +38522,8 @@ FILE: ../../../flutter/lib/ui/window/key_data_packet.cc
3851538522
FILE: ../../../flutter/lib/ui/window/key_data_packet.h
3851638523
FILE: ../../../flutter/lib/ui/window/platform_configuration.cc
3851738524
FILE: ../../../flutter/lib/ui/window/platform_configuration.h
38525+
FILE: ../../../flutter/lib/ui/window/platform_isolate.cc
38526+
FILE: ../../../flutter/lib/ui/window/platform_isolate.h
3851838527
FILE: ../../../flutter/lib/ui/window/platform_message.cc
3851938528
FILE: ../../../flutter/lib/ui/window/platform_message.h
3852038529
FILE: ../../../flutter/lib/ui/window/platform_message_response.cc
@@ -38557,6 +38566,7 @@ FILE: ../../../flutter/lib/web_ui/lib/painting.dart
3855738566
FILE: ../../../flutter/lib/web_ui/lib/path.dart
3855838567
FILE: ../../../flutter/lib/web_ui/lib/path_metrics.dart
3855938568
FILE: ../../../flutter/lib/web_ui/lib/platform_dispatcher.dart
38569+
FILE: ../../../flutter/lib/web_ui/lib/platform_isolate.dart
3856038570
FILE: ../../../flutter/lib/web_ui/lib/pointer.dart
3856138571
FILE: ../../../flutter/lib/web_ui/lib/semantics.dart
3856238572
FILE: ../../../flutter/lib/web_ui/lib/src/engine.dart
@@ -38856,6 +38866,8 @@ FILE: ../../../flutter/runtime/isolate_configuration.cc
3885638866
FILE: ../../../flutter/runtime/isolate_configuration.h
3885738867
FILE: ../../../flutter/runtime/platform_data.cc
3885838868
FILE: ../../../flutter/runtime/platform_data.h
38869+
FILE: ../../../flutter/runtime/platform_isolate_manager.cc
38870+
FILE: ../../../flutter/runtime/platform_isolate_manager.h
3885938871
FILE: ../../../flutter/runtime/ptrace_check.cc
3886038872
FILE: ../../../flutter/runtime/ptrace_check.h
3886138873
FILE: ../../../flutter/runtime/runtime_controller.cc

common/exported_test_symbols.sym

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@
1010
Spawn;
1111
LoadLibraryFromKernel;
1212
LookupEntryPoint;
13+
ForceShutdownIsolate;
1314
};

common/exported_test_symbols_mac.sym

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ _kInternalFlutterGpu*
99
_Spawn
1010
_LoadLibraryFromKernel
1111
_LookupEntryPoint
12+
_ForceShutdownIsolate

lib/ui/BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ source_set("ui") {
138138
"window/key_data_packet.h",
139139
"window/platform_configuration.cc",
140140
"window/platform_configuration.h",
141+
"window/platform_isolate.cc",
142+
"window/platform_isolate.h",
141143
"window/platform_message.cc",
142144
"window/platform_message.h",
143145
"window/platform_message_response.cc",

lib/ui/dart_ui.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include "flutter/lib/ui/text/paragraph.h"
3939
#include "flutter/lib/ui/text/paragraph_builder.h"
4040
#include "flutter/lib/ui/window/platform_configuration.h"
41+
#include "flutter/lib/ui/window/platform_isolate.h"
4142
#include "third_party/tonic/converter/dart_converter.h"
4243
#include "third_party/tonic/dart_args.h"
4344
#include "third_party/tonic/logging/dart_error.h"
@@ -113,6 +114,8 @@ typedef CanvasPath Path;
113114
V(PlatformConfigurationNativeApi::SendPortPlatformMessage) \
114115
V(PlatformConfigurationNativeApi::SendChannelUpdate) \
115116
V(PlatformConfigurationNativeApi::GetScaledFontSize) \
117+
V(PlatformIsolateNativeApi::IsRunningOnPlatformThread) \
118+
V(PlatformIsolateNativeApi::Spawn) \
116119
V(DartRuntimeHooks::Logger_PrintDebugString) \
117120
V(DartRuntimeHooks::Logger_PrintString) \
118121
V(DartRuntimeHooks::ScheduleMicrotask) \

lib/ui/experiments/ui.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,13 @@ import 'dart:convert';
1717
import 'dart:developer' as developer;
1818
import 'dart:ffi';
1919
import 'dart:io';
20-
import 'dart:isolate' show SendPort;
20+
import 'dart:isolate'
21+
show
22+
Isolate,
23+
IsolateSpawnException,
24+
RawReceivePort,
25+
RemoteError,
26+
SendPort;
2127
import 'dart:math' as math;
2228
import 'dart:nativewrappers';
2329
import 'dart:typed_data';
@@ -35,6 +41,7 @@ part '../math.dart';
3541
part '../natives.dart';
3642
part '../painting.dart';
3743
part '../platform_dispatcher.dart';
44+
part '../platform_isolate.dart';
3845
part '../plugins.dart';
3946
part '../pointer.dart';
4047
part '../semantics.dart';

lib/ui/platform_isolate.dart

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
part of dart.ui;
5+
6+
/// Runs [computation] on the platform thread and returns the result.
7+
///
8+
/// This may run the computation on a separate isolate. That isolate will be
9+
/// reused for subsequent [runOnPlatformThread] calls. This means that global
10+
/// state is maintained in that isolate between calls.
11+
///
12+
/// The [computation] and any state it captures may be sent to that isolate.
13+
/// See [SendPort.send] for information about what types can be sent.
14+
///
15+
/// If [computation] is asynchronous (returns a `Future<R>`) then
16+
/// that future is awaited in the new isolate, completing the entire
17+
/// asynchronous computation, before returning the result.
18+
///
19+
/// If [computation] throws, the `Future` returned by this function completes
20+
/// with that error.
21+
///
22+
/// The [computation] function and its result (or error) must be
23+
/// sendable between isolates. Objects that cannot be sent include open
24+
/// files and sockets (see [SendPort.send] for details).
25+
///
26+
/// This method can only be invoked from the main isolate.
27+
///
28+
/// This API is currently experimental.
29+
Future<R> runOnPlatformThread<R>(FutureOr<R> Function() computation) {
30+
if (isRunningOnPlatformThread) {
31+
return Future<R>(computation);
32+
}
33+
final SendPort? sendPort = _platformRunnerSendPort;
34+
if (sendPort != null) {
35+
return _sendComputation(sendPort, computation);
36+
} else {
37+
return (_platformRunnerSendPortFuture ??= _spawnPlatformIsolate())
38+
.then((SendPort port) => _sendComputation(port, computation));
39+
}
40+
}
41+
42+
SendPort? _platformRunnerSendPort;
43+
Future<SendPort>? _platformRunnerSendPortFuture;
44+
final Map<int, Completer<Object?>> _pending = <int, Completer<Object?>>{};
45+
int _nextId = 0;
46+
47+
Future<SendPort> _spawnPlatformIsolate() {
48+
final Completer<SendPort> sendPortCompleter = Completer<SendPort>();
49+
final RawReceivePort receiver = RawReceivePort()..keepIsolateAlive = false;
50+
receiver.handler = (Object? message) {
51+
if (message == null) {
52+
// This is the platform isolate's onExit handler.
53+
// This shouldn't really happen, since Isolate.exit is disabled, the
54+
// pause and terminate capabilities aren't provided to the parent
55+
// isolate, and errors are fatal is false. But if the isolate does
56+
// shutdown unexpectedly, clear the singleton so we can create another.
57+
for (final Completer<Object?> completer in _pending.values) {
58+
completer.completeError(RemoteError(
59+
'PlatformIsolate shutdown unexpectedly',
60+
StackTrace.empty.toString()));
61+
}
62+
_pending.clear();
63+
_platformRunnerSendPort = null;
64+
_platformRunnerSendPortFuture = null;
65+
} else if (message is _PlatformIsolateReadyMessage) {
66+
_platformRunnerSendPort = message.computationPort;
67+
sendPortCompleter.complete(message.computationPort);
68+
} else if (message is _ComputationResult) {
69+
final Completer<Object?> resultCompleter = _pending.remove(message.id)!;
70+
final Object? remoteStack = message.remoteStack;
71+
final Object? remoteError = message.remoteError;
72+
if (remoteStack != null) {
73+
if (remoteStack is StackTrace) {
74+
// Typed error.
75+
resultCompleter.completeError(remoteError!, remoteStack);
76+
} else {
77+
// onError handler message, uncaught async error.
78+
// Both values are strings, so calling `toString` is efficient.
79+
final RemoteError error =
80+
RemoteError(remoteError!.toString(), remoteStack.toString());
81+
resultCompleter.completeError(error, error.stackTrace);
82+
}
83+
} else {
84+
resultCompleter.complete(message.result);
85+
}
86+
} else {
87+
// We encountered an error while starting the new isolate.
88+
if (!sendPortCompleter.isCompleted) {
89+
sendPortCompleter.completeError(
90+
IsolateSpawnException('Unable to spawn isolate: $message'));
91+
return;
92+
}
93+
// This shouldn't happen.
94+
throw IsolateSpawnException(
95+
"Internal error: unexpected message: '$message'");
96+
}
97+
};
98+
final Isolate parentIsolate = Isolate.current;
99+
final SendPort sendPort = receiver.sendPort;
100+
try {
101+
_nativeSpawn(() => _platformIsolateMain(parentIsolate, sendPort));
102+
} on Object {
103+
receiver.close();
104+
rethrow;
105+
}
106+
return sendPortCompleter.future;
107+
}
108+
109+
Future<R> _sendComputation<R>(
110+
SendPort port, FutureOr<R> Function() computation) {
111+
final int id = ++_nextId;
112+
final Completer<R> resultCompleter = Completer<R>();
113+
_pending[id] = resultCompleter;
114+
port.send(_ComputationRequest(id, computation));
115+
return resultCompleter.future;
116+
}
117+
118+
void _safeSend(SendPort sendPort, int id, Object? result, Object? error,
119+
Object? stackTrace) {
120+
try {
121+
sendPort.send(_ComputationResult(id, result, error, stackTrace));
122+
} catch (sendError, sendStack) {
123+
sendPort.send(_ComputationResult(id, null, sendError, sendStack));
124+
}
125+
}
126+
127+
void _platformIsolateMain(Isolate parentIsolate, SendPort sendPort) {
128+
final RawReceivePort computationPort = RawReceivePort();
129+
computationPort.handler = (_ComputationRequest? message) {
130+
if (message == null) {
131+
// The parent isolate has shutdown. Allow this isolate to shutdown.
132+
computationPort.keepIsolateAlive = false;
133+
return;
134+
}
135+
136+
late final FutureOr<Object?> potentiallyAsyncResult;
137+
try {
138+
potentiallyAsyncResult = message.computation();
139+
} catch (e, s) {
140+
_safeSend(sendPort, message.id, null, e, s);
141+
return;
142+
}
143+
144+
if (potentiallyAsyncResult is Future<Object?>) {
145+
potentiallyAsyncResult.then((Object? result) {
146+
_safeSend(sendPort, message.id, result, null, null);
147+
}, onError: (Object? e, Object? s) {
148+
_safeSend(sendPort, message.id, null, e, s ?? StackTrace.empty);
149+
});
150+
} else {
151+
_safeSend(sendPort, message.id, potentiallyAsyncResult, null, null);
152+
}
153+
};
154+
Isolate.current.addOnExitListener(sendPort);
155+
parentIsolate.addOnExitListener(computationPort.sendPort);
156+
sendPort.send(_PlatformIsolateReadyMessage(computationPort.sendPort));
157+
}
158+
159+
@Native<Void Function(Handle)>(symbol: 'PlatformIsolateNativeApi::Spawn')
160+
external void _nativeSpawn(Function entryPoint);
161+
162+
/// Whether the current isolate is running on the platform thread.
163+
final bool isRunningOnPlatformThread = _isRunningOnPlatformThread();
164+
165+
@Native<Bool Function()>(
166+
symbol: 'PlatformIsolateNativeApi::IsRunningOnPlatformThread', isLeaf: true)
167+
external bool _isRunningOnPlatformThread();
168+
169+
class _PlatformIsolateReadyMessage {
170+
_PlatformIsolateReadyMessage(this.computationPort);
171+
172+
final SendPort computationPort;
173+
}
174+
175+
class _ComputationRequest {
176+
_ComputationRequest(this.id, this.computation);
177+
178+
final int id;
179+
final FutureOr<Object?> Function() computation;
180+
}
181+
182+
class _ComputationResult {
183+
_ComputationResult(this.id, this.result, this.remoteError, this.remoteStack);
184+
185+
final int id;
186+
final Object? result;
187+
final Object? remoteError;
188+
final Object? remoteStack;
189+
}

lib/ui/ui.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,13 @@ import 'dart:convert';
1717
import 'dart:developer' as developer;
1818
import 'dart:ffi';
1919
import 'dart:io';
20-
import 'dart:isolate' show SendPort;
20+
import 'dart:isolate'
21+
show
22+
Isolate,
23+
IsolateSpawnException,
24+
RawReceivePort,
25+
RemoteError,
26+
SendPort;
2127
import 'dart:math' as math;
2228
import 'dart:nativewrappers';
2329
import 'dart:typed_data';
@@ -35,6 +41,7 @@ part 'math.dart';
3541
part 'natives.dart';
3642
part 'painting.dart';
3743
part 'platform_dispatcher.dart';
44+
part 'platform_isolate.dart';
3845
part 'plugins.dart';
3946
part 'pointer.dart';
4047
part 'semantics.dart';

lib/ui/ui_dart_state.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,4 +262,10 @@ int64_t UIDartState::GetRootIsolateToken() const {
262262
return IsRootIsolate() ? reinterpret_cast<int64_t>(this) : 0;
263263
}
264264

265+
Dart_Isolate UIDartState::CreatePlatformIsolate(Dart_Handle entry_point,
266+
char** error) {
267+
FML_UNREACHABLE();
268+
return nullptr;
269+
}
270+
265271
} // namespace flutter

lib/ui/ui_dart_state.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,9 @@ class UIDartState : public tonic::DartState {
171171
/// The expected type for runtime stage shaders.
172172
impeller::RuntimeStageBackend GetRuntimeStageBackend() const;
173173

174+
virtual Dart_Isolate CreatePlatformIsolate(Dart_Handle entry_point,
175+
char** error);
176+
174177
protected:
175178
UIDartState(TaskObserverAdd add_callback,
176179
TaskObserverRemove remove_callback,

lib/ui/window/platform_configuration.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ namespace flutter {
2525
class FontCollection;
2626
class PlatformMessage;
2727
class PlatformMessageHandler;
28+
class PlatformIsolateManager;
2829
class Scene;
2930

3031
//--------------------------------------------------------------------------
@@ -247,6 +248,9 @@ class PlatformConfigurationClient {
247248
virtual double GetScaledFontSize(double unscaled_font_size,
248249
int configuration_id) const = 0;
249250

251+
virtual std::shared_ptr<PlatformIsolateManager>
252+
GetPlatformIsolateManager() = 0;
253+
250254
protected:
251255
virtual ~PlatformConfigurationClient();
252256
};

0 commit comments

Comments
 (0)