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

Commit bfb5810

Browse files
authored
Fix incorrect handling of error handling in case an isolate initialization fails (#31207)
For isolates spawned by the application via `Isolate.spawn()`ed, the VM will create a "lightweight" isolate and invoke the `initialize_isolate` embedder callback to initialize it. The embedder-provided callback will be invoked with the active isolate and is expected to return with that active isolate - irrespective of whether it succeeded to initialize or not. => The unsuccessful path was using `Dart_ExitIsolate()` - which is incorrect. This PR fixes that by not exiting the isolate. As a side-effect of the fix, we also do less `Dart_EnterIsolate()`/`Dart_ExitIsolate()` calls in initialization (which makes it faster) and handle failure to spawn the root isolate. Furthermore this PR removes some dead code and replaces it with `FML_DCHECK()`s instead. The PR adds a test that will set the root library to null which will make the engine fail initializing of the isolate and therefore trigger this error handling path. Fixes flutter/flutter#90478
1 parent f96c187 commit bfb5810

File tree

4 files changed

+123
-22
lines changed

4 files changed

+123
-22
lines changed

runtime/dart_isolate.cc

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <cstdlib>
88
#include <tuple>
99

10+
#include "flutter/fml/logging.h"
1011
#include "flutter/fml/posix_wrappers.h"
1112
#include "flutter/fml/trace_event.h"
1213
#include "flutter/lib/io/dart_io.h"
@@ -314,25 +315,12 @@ bool DartIsolate::Initialize(Dart_Isolate dart_isolate) {
314315
return false;
315316
}
316317

317-
if (dart_isolate == nullptr) {
318-
return false;
319-
}
320-
321-
if (Dart_CurrentIsolate() != dart_isolate) {
322-
return false;
323-
}
318+
FML_DCHECK(dart_isolate != nullptr);
319+
FML_DCHECK(dart_isolate == Dart_CurrentIsolate());
324320

325321
// After this point, isolate scopes can be safely used.
326322
SetIsolate(dart_isolate);
327323

328-
// We are entering a new scope (for the first time since initialization) and
329-
// we want to restore the current scope to null when we exit out of this
330-
// method. This balances the implicit Dart_EnterIsolate call made by
331-
// Dart_CreateIsolateGroup (which calls the Initialize).
332-
Dart_ExitIsolate();
333-
334-
tonic::DartIsolateScope scope(isolate());
335-
336324
// For the root isolate set the "AppStartUp" as soon as the root isolate
337325
// has been initialized. This is to ensure that all the timeline events
338326
// that have the set user-tag will be listed user AppStartUp.
@@ -997,7 +985,6 @@ bool DartIsolate::DartIsolateInitializeCallback(void** child_callback_data,
997985
// only reference returned to the caller is weak.
998986
*child_callback_data = embedder_isolate.release();
999987

1000-
Dart_EnterIsolate(isolate);
1001988
return true;
1002989
}
1003990

@@ -1017,15 +1004,23 @@ Dart_Isolate DartIsolate::CreateDartIsolateGroup(
10171004
return nullptr;
10181005
}
10191006

1020-
// Ownership of the isolate data objects has been transferred to the Dart VM.
1021-
std::shared_ptr<DartIsolate> embedder_isolate(*isolate_data);
1022-
isolate_group_data.release();
1023-
isolate_data.release();
1007+
bool success = false;
1008+
{
1009+
// Ownership of the isolate data objects has been transferred to the Dart
1010+
// VM.
1011+
std::shared_ptr<DartIsolate> embedder_isolate(*isolate_data);
1012+
isolate_group_data.release();
1013+
isolate_data.release();
10241014

1025-
if (!InitializeIsolate(std::move(embedder_isolate), isolate, error)) {
1015+
success = InitializeIsolate(std::move(embedder_isolate), isolate, error);
1016+
}
1017+
if (!success) {
1018+
Dart_ShutdownIsolate();
10261019
return nullptr;
10271020
}
10281021

1022+
// Balances the implicit [Dart_EnterIsolate] by [make_isolate] above.
1023+
Dart_ExitIsolate();
10291024
return isolate;
10301025
}
10311026

@@ -1070,6 +1065,14 @@ void DartIsolate::DartIsolateShutdownCallback(
10701065
std::shared_ptr<DartIsolateGroupData>* isolate_group_data,
10711066
std::shared_ptr<DartIsolate>* isolate_data) {
10721067
TRACE_EVENT0("flutter", "DartIsolate::DartIsolateShutdownCallback");
1068+
1069+
// If the isolate initialization failed there will be nothing to do.
1070+
// This can happen e.g. during a [DartIsolateInitializeCallback] invocation
1071+
// that fails to initialize the VM-created isolate.
1072+
if (isolate_data == nullptr) {
1073+
return;
1074+
}
1075+
10731076
isolate_data->get()->OnShutdownCallback();
10741077
}
10751078

runtime/dart_isolate.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,16 @@ class DartIsolate : public UIDartState {
421421
bool is_root_isolate,
422422
const UIDartState::Context& context);
423423

424-
[[nodiscard]] bool Initialize(Dart_Isolate isolate);
424+
//----------------------------------------------------------------------------
425+
/// @brief Initializes the given (current) isolate.
426+
///
427+
/// @param[in] dart_isolate The current isolate that is to be initialized.
428+
///
429+
/// @return Whether the initialization succeeded. Irrespective of whether
430+
/// the initialization suceeded, the current isolate will still be
431+
/// active.
432+
///
433+
[[nodiscard]] bool Initialize(Dart_Isolate dart_isolate);
425434

426435
void SetMessageHandlingTaskRunner(fml::RefPtr<fml::TaskRunner> runner);
427436

runtime/dart_isolate_unittests.cc

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "flutter/testing/dart_isolate_runner.h"
1414
#include "flutter/testing/fixture_test.h"
1515
#include "flutter/testing/testing.h"
16+
#include "third_party/dart/runtime/include/dart_api.h"
1617
#include "third_party/tonic/converter/dart_converter.h"
1718
#include "third_party/tonic/scopes/dart_isolate_scope.h"
1819

@@ -258,6 +259,44 @@ TEST_F(DartSecondaryIsolateTest, CanLaunchSecondaryIsolates) {
258259
// root isolate will be auto-shutdown
259260
}
260261

262+
/// Tests error handling path of `Isolate.spawn()` in the engine.
263+
class IsolateStartupFailureTest : public FixtureTest {
264+
public:
265+
IsolateStartupFailureTest() : latch_(1) {}
266+
void NotifyDone() { latch_.CountDown(); }
267+
void WaitForDone() { latch_.Wait(); }
268+
269+
private:
270+
fml::CountDownLatch latch_;
271+
FML_DISALLOW_COPY_AND_ASSIGN(IsolateStartupFailureTest);
272+
};
273+
274+
TEST_F(IsolateStartupFailureTest,
275+
HandlesIsolateInitializationFailureCorrectly) {
276+
AddNativeCallback("MakeNextIsolateSpawnFail",
277+
CREATE_NATIVE_ENTRY(([](Dart_NativeArguments args) {
278+
Dart_SetRootLibrary(Dart_Null());
279+
})));
280+
AddNativeCallback("NotifyNative",
281+
CREATE_NATIVE_ENTRY(
282+
([this](Dart_NativeArguments args) { NotifyDone(); })));
283+
auto settings = CreateSettingsForFixture();
284+
auto vm_ref = DartVMRef::Create(settings);
285+
auto thread = CreateNewThread();
286+
TaskRunners task_runners(GetCurrentTestName(), //
287+
thread, //
288+
thread, //
289+
thread, //
290+
thread //
291+
);
292+
auto isolate = RunDartCodeInIsolate(vm_ref, settings, task_runners,
293+
"testIsolateStartupFailure", {},
294+
GetDefaultKernelFilePath());
295+
ASSERT_TRUE(isolate);
296+
ASSERT_EQ(isolate->get()->GetPhase(), DartIsolate::Phase::Running);
297+
WaitForDone();
298+
}
299+
261300
TEST_F(DartIsolateTest, CanReceiveArguments) {
262301
AddNativeCallback("NotifyNative",
263302
CREATE_NATIVE_ENTRY(([this](Dart_NativeArguments args) {

runtime/fixtures/runtime_test.dart

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,56 @@ void testCanLaunchSecondaryIsolate() {
6464
Isolate.spawn(secondaryIsolateMain, 'Hello from root isolate.', onExit: onExit.sendPort);
6565
}
6666

67+
68+
@pragma('vm:entry-point')
69+
void testIsolateStartupFailure() async {
70+
Future mainTest(dynamic _) async {
71+
Future testSuccessfullIsolateLaunch() async {
72+
final onMessage = ReceivePort();
73+
final onExit = ReceivePort();
74+
75+
final messages = StreamIterator<dynamic>(onMessage);
76+
final exits = StreamIterator<dynamic>(onExit);
77+
78+
await Isolate.spawn((SendPort port) => port.send('good'),
79+
onMessage.sendPort, onExit: onExit.sendPort);
80+
if (!await messages.moveNext()) {
81+
throw 'Failed to receive message';
82+
}
83+
if (messages.current != 'good') {
84+
throw 'Failed to receive correct message';
85+
}
86+
if (!await exits.moveNext()) {
87+
throw 'Failed to receive onExit';
88+
}
89+
messages.cancel();
90+
exits.cancel();
91+
}
92+
93+
Future testUnsuccessfullIsolateLaunch() async {
94+
IsolateSpawnException? error;
95+
try {
96+
await Isolate.spawn((_) {}, null);
97+
} on IsolateSpawnException catch (e) {
98+
error = e;
99+
}
100+
if (error == null) {
101+
throw 'Expected isolate spawn to fail.';
102+
}
103+
}
104+
105+
await testSuccessfullIsolateLaunch();
106+
makeNextIsolateSpawnFail();
107+
await testUnsuccessfullIsolateLaunch();
108+
notifyNative();
109+
}
110+
111+
// The root isolate will not run an eventloop, so we have to run the actual
112+
// test in an isolate.
113+
Isolate.spawn(mainTest, null);
114+
}
115+
void makeNextIsolateSpawnFail() native 'MakeNextIsolateSpawnFail';
116+
67117
@pragma('vm:entry-point')
68118
void testCanReceiveArguments(List<String> args) {
69119
notifyResult(args.length == 1 && args[0] == 'arg1');

0 commit comments

Comments
 (0)