Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 24 additions & 21 deletions runtime/dart_isolate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <cstdlib>
#include <tuple>

#include "flutter/fml/logging.h"
#include "flutter/fml/posix_wrappers.h"
#include "flutter/fml/trace_event.h"
#include "flutter/lib/io/dart_io.h"
Expand Down Expand Up @@ -314,25 +315,12 @@ bool DartIsolate::Initialize(Dart_Isolate dart_isolate) {
return false;
}

if (dart_isolate == nullptr) {
return false;
}

if (Dart_CurrentIsolate() != dart_isolate) {
return false;
}
FML_DCHECK(dart_isolate != nullptr);
FML_DCHECK(dart_isolate == Dart_CurrentIsolate());

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

// We are entering a new scope (for the first time since initialization) and
// we want to restore the current scope to null when we exit out of this
// method. This balances the implicit Dart_EnterIsolate call made by
// Dart_CreateIsolateGroup (which calls the Initialize).
Dart_ExitIsolate();

tonic::DartIsolateScope scope(isolate());

// For the root isolate set the "AppStartUp" as soon as the root isolate
// has been initialized. This is to ensure that all the timeline events
// that have the set user-tag will be listed user AppStartUp.
Copy link
Member

@aam aam Feb 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the code below do you have some sense as to which method might end up returning false?
None of the Dart_SetLibraryTagHandler, Dart_SetDeferredLoadHandler, UpdateThreadPoolNames can seem to do that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may not be this method that returns false. As mentioned in CL description we know (from crash stack trace) it's DartIsolate::InitializeIsolate() which returns false - which looks like this:

bool DartIsolate::InitializeIsolate(...) {
  ...
  if (!embedder_isolate->Initialize(isolate)) { ... }

  if (!embedder_isolate->LoadLibraries()) {
    ... 
    return false;
  }
  ...
  return true;
}

My suspicion is that LoadLibraries() - which does all kinds of stuff, including calling into Dart code when setting up dart:io ... - fails.

From flutter/flutter#90478 (comment):

crash log:
...
I/scudo   (28282): Scudo OOM: The process has Exhausted 256M for size class 262160.

it indicates that this app is running in low-memory situations. So one option I can see is that when we initialize the libraries we get into a OOM situation.

Just a suspicion - really hard to verify.

Though the bug itself is quite obvious when reading the code and I've verified that it fixes the error handling path.

Expand Down Expand Up @@ -997,7 +985,6 @@ bool DartIsolate::DartIsolateInitializeCallback(void** child_callback_data,
// only reference returned to the caller is weak.
*child_callback_data = embedder_isolate.release();

Dart_EnterIsolate(isolate);
return true;
}

Expand All @@ -1017,15 +1004,23 @@ Dart_Isolate DartIsolate::CreateDartIsolateGroup(
return nullptr;
}

// Ownership of the isolate data objects has been transferred to the Dart VM.
std::shared_ptr<DartIsolate> embedder_isolate(*isolate_data);
isolate_group_data.release();
isolate_data.release();
bool success = false;
{
// Ownership of the isolate data objects has been transferred to the Dart
// VM.
std::shared_ptr<DartIsolate> embedder_isolate(*isolate_data);
isolate_group_data.release();
isolate_data.release();

if (!InitializeIsolate(std::move(embedder_isolate), isolate, error)) {
success = InitializeIsolate(std::move(embedder_isolate), isolate, error);
}
if (!success) {
Dart_ShutdownIsolate();
return nullptr;
}

// Balances the implicit [Dart_EnterIsolate] by [make_isolate] above.
Dart_ExitIsolate();
return isolate;
}

Expand Down Expand Up @@ -1070,6 +1065,14 @@ void DartIsolate::DartIsolateShutdownCallback(
std::shared_ptr<DartIsolateGroupData>* isolate_group_data,
std::shared_ptr<DartIsolate>* isolate_data) {
TRACE_EVENT0("flutter", "DartIsolate::DartIsolateShutdownCallback");

// If the isolate initialization failed there will be nothing to do.
// This can happen e.g. during a [DartIsolateInitializeCallback] invocation
// that fails to initialize the VM-created isolate.
if (isolate_data == nullptr) {
return;
}

isolate_data->get()->OnShutdownCallback();
}

Expand Down
11 changes: 10 additions & 1 deletion runtime/dart_isolate.h
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,16 @@ class DartIsolate : public UIDartState {
bool is_root_isolate,
const UIDartState::Context& context);

[[nodiscard]] bool Initialize(Dart_Isolate isolate);
//----------------------------------------------------------------------------
/// @brief Initializes the given (current) isolate.
///
/// @param[in] dart_isolate The current isolate that is to be initialized.
///
/// @return Whether the initialization succeeded. Irrespective of whether
/// the initialization suceeded, the current isolate will still be
/// active.
///
[[nodiscard]] bool Initialize(Dart_Isolate dart_isolate);

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

Expand Down
39 changes: 39 additions & 0 deletions runtime/dart_isolate_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "flutter/testing/dart_isolate_runner.h"
#include "flutter/testing/fixture_test.h"
#include "flutter/testing/testing.h"
#include "third_party/dart/runtime/include/dart_api.h"
#include "third_party/tonic/converter/dart_converter.h"
#include "third_party/tonic/scopes/dart_isolate_scope.h"

Expand Down Expand Up @@ -258,6 +259,44 @@ TEST_F(DartSecondaryIsolateTest, CanLaunchSecondaryIsolates) {
// root isolate will be auto-shutdown
}

/// Tests error handling path of `Isolate.spawn()` in the engine.
class IsolateStartupFailureTest : public FixtureTest {
public:
IsolateStartupFailureTest() : latch_(1) {}
void NotifyDone() { latch_.CountDown(); }
void WaitForDone() { latch_.Wait(); }

private:
fml::CountDownLatch latch_;
FML_DISALLOW_COPY_AND_ASSIGN(IsolateStartupFailureTest);
};

TEST_F(IsolateStartupFailureTest,
HandlesIsolateInitializationFailureCorrectly) {
AddNativeCallback("MakeNextIsolateSpawnFail",
CREATE_NATIVE_ENTRY(([](Dart_NativeArguments args) {
Dart_SetRootLibrary(Dart_Null());
})));
AddNativeCallback("NotifyNative",
CREATE_NATIVE_ENTRY(
([this](Dart_NativeArguments args) { NotifyDone(); })));
auto settings = CreateSettingsForFixture();
auto vm_ref = DartVMRef::Create(settings);
auto thread = CreateNewThread();
TaskRunners task_runners(GetCurrentTestName(), //
thread, //
thread, //
thread, //
thread //
);
auto isolate = RunDartCodeInIsolate(vm_ref, settings, task_runners,
"testIsolateStartupFailure", {},
GetDefaultKernelFilePath());
ASSERT_TRUE(isolate);
ASSERT_EQ(isolate->get()->GetPhase(), DartIsolate::Phase::Running);
WaitForDone();
}

TEST_F(DartIsolateTest, CanReceiveArguments) {
AddNativeCallback("NotifyNative",
CREATE_NATIVE_ENTRY(([this](Dart_NativeArguments args) {
Expand Down
50 changes: 50 additions & 0 deletions runtime/fixtures/runtime_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,56 @@ void testCanLaunchSecondaryIsolate() {
Isolate.spawn(secondaryIsolateMain, 'Hello from root isolate.', onExit: onExit.sendPort);
}


@pragma('vm:entry-point')
void testIsolateStartupFailure() async {
Future mainTest(dynamic _) async {
Future testSuccessfullIsolateLaunch() async {
final onMessage = ReceivePort();
final onExit = ReceivePort();

final messages = StreamIterator<dynamic>(onMessage);
final exits = StreamIterator<dynamic>(onExit);

await Isolate.spawn((SendPort port) => port.send('good'),
onMessage.sendPort, onExit: onExit.sendPort);
if (!await messages.moveNext()) {
throw 'Failed to receive message';
}
if (messages.current != 'good') {
throw 'Failed to receive correct message';
}
if (!await exits.moveNext()) {
throw 'Failed to receive onExit';
}
messages.cancel();
exits.cancel();
}

Future testUnsuccessfullIsolateLaunch() async {
IsolateSpawnException? error;
try {
await Isolate.spawn((_) {}, null);
} on IsolateSpawnException catch (e) {
error = e;
}
if (error == null) {
throw 'Expected isolate spawn to fail.';
}
}

await testSuccessfullIsolateLaunch();
makeNextIsolateSpawnFail();
await testUnsuccessfullIsolateLaunch();
notifyNative();
}

// The root isolate will not run an eventloop, so we have to run the actual
// test in an isolate.
Isolate.spawn(mainTest, null);
}
void makeNextIsolateSpawnFail() native 'MakeNextIsolateSpawnFail';

@pragma('vm:entry-point')
void testCanReceiveArguments(List<String> args) {
notifyResult(args.length == 1 && args[0] == 'arg1');
Expand Down