Skip to content

Commit 42ac762

Browse files
mkustermanncommit-bot@chromium.org
authored andcommitted
[vm/concurrency] Fix inconsistency in Dart's spawn implementation
When Dart invokes Isolate.spawn the Dart VM calls out to the embedder to create an isolate (group) or initialize it (in case of lightweight isolates). * If the embedder doesn't implement this functionality or fails to perform the creation/initialization, it will signal this failure via the return value it gives to the VM in the call back implementation (i.e. Dart_InitializeParams.create_group and Dart_InitializeParams.initialize_isolate}). * If the embeder sucessfully completed it's work, the VM owns the isolate after the embedder call returns. The VM is then responsible for running it. It is an undocumented invariant atm that the embedder is responsible to make the isolate runnable if it signals the VM that isolate was sucessfully created/initialized. => If it does not do that, we have effectively an isolate leak. => Though right now seemingly all of our embedders to that correctly. To avoid this unintentional isolate leak we make the isolate runnable ourselves if embedder callback was successful but did not make it runnable itself. => This also avoids unnecessary ceremony code in the embedders that look like this: Dart_ExitIsolate(); Dart_IsolateMakeRunnable(child); Dart_EnterIsolate(child); The implementation of `Dart_IsolateMakeRunnable()` had an untested and unused code path that caused running the isolate (on a thread pool). This is not documented and can lead to bugs because often the embedders enter the isolate right after making it runnable (e.g. above code sequence) - which would be racing with the spawned message handler thread. => Furtunately our embedders are well behaved and make the isolate runnable during the callback. => As a safeguard we'll make `Dart_IsolateMakeRunnable()` return an error if it's called outside the callback (which we can detect by the presense of a spawn state) Issue #44088 Issue #36097 TEST=Changes internal details of implementation which is already covered by tests. Change-Id: Ieef316cc0807d99f2fcb1fbd56bbc9c41e904ba8 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/170882 Commit-Queue: Martin Kustermann <[email protected]> Reviewed-by: Alexander Aprelev <[email protected]> Reviewed-by: Ryan Macnak <[email protected]>
1 parent 813b0d0 commit 42ac762

File tree

5 files changed

+41
-34
lines changed

5 files changed

+41
-34
lines changed

runtime/bin/main.cc

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -245,12 +245,8 @@ static bool OnIsolateInitialize(void** child_callback_data, char** error) {
245245
if (Dart_IsError(result)) goto failed;
246246
}
247247

248-
// Make the isolate runnable so that it is ready to handle messages.
249248
Dart_ExitScope();
250-
Dart_ExitIsolate();
251-
*error = Dart_IsolateMakeRunnable(isolate);
252-
Dart_EnterIsolate(isolate);
253-
return *error == nullptr;
249+
return true;
254250

255251
failed:
256252
*error = Utils::StrDup(Dart_GetError(result));

runtime/lib/isolate.cc

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,13 @@ class SpawnIsolateTask : public ThreadPool::Task {
352352
return;
353353
}
354354

355+
if (isolate->object_store()->root_library() == Library::null()) {
356+
Dart_ShutdownIsolate();
357+
FailedSpawn(
358+
"The embedder has to ensure there is a root library (e.g. by calling "
359+
"Dart_LoadScriptFromKernel).");
360+
}
361+
355362
Run(isolate);
356363
}
357364

@@ -401,15 +408,19 @@ class SpawnIsolateTask : public ThreadPool::Task {
401408
state_->set_isolate(child);
402409

403410
MutexLocker ml(child->mutex());
404-
child->set_origin_id(state_->origin_id());
405-
child->set_spawn_state(std::move(state_));
406411

407-
// If the isolate is not marked as runnable, then the embedder might do so
408-
// later on and the launch of the isolate will happen inside
409-
// `Dart_IsolateMakeRunnable`.
410-
if (child->is_runnable()) {
411-
child->Run();
412+
// We called out to the embedder to create/initialize a new isolate. The
413+
// embedder callback sucessfully did so. It is now our responsibility to
414+
// run the isolate.
415+
// If the isolate was not marked as runnable, we'll do so here and run it
416+
if (!child->is_runnable()) {
417+
child->MakeRunnableLocked();
412418
}
419+
ASSERT(child->is_runnable());
420+
421+
child->set_origin_id(state_->origin_id());
422+
child->set_spawn_state(std::move(state_));
423+
child->Run();
413424
}
414425

415426
void FailedSpawn(const char* error) {

runtime/vm/dart_api_impl.cc

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2001,18 +2001,11 @@ DART_EXPORT char* Dart_IsolateMakeRunnable(Dart_Isolate isolate) {
20012001
FATAL1("%s expects argument 'isolate' to be non-null.", CURRENT_FUNC);
20022002
}
20032003
// TODO(16615): Validate isolate parameter.
2004-
Isolate* iso = reinterpret_cast<Isolate*>(isolate);
2005-
const char* error;
2006-
if (iso->object_store()->root_library() == Library::null()) {
2007-
// The embedder should have called Dart_LoadScriptFromKernel by now.
2008-
error = "Missing root library";
2009-
} else {
2010-
error = iso->MakeRunnable();
2011-
}
2012-
if (error != NULL) {
2004+
const char* error = reinterpret_cast<Isolate*>(isolate)->MakeRunnable();
2005+
if (error != nullptr) {
20132006
return Utils::StrDup(error);
20142007
}
2015-
return NULL;
2008+
return nullptr;
20162009
}
20172010

20182011
// --- Messages and Ports ---

runtime/vm/isolate.cc

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2055,9 +2055,26 @@ const char* Isolate::MakeRunnable() {
20552055
if (is_runnable() == true) {
20562056
return "Isolate is already runnable";
20572057
}
2058+
if (spawn_state() != nullptr) {
2059+
return "The embedder has to make the isolate runnable during isolate "
2060+
"creation / initialization callback.";
2061+
}
2062+
if (object_store()->root_library() == Library::null()) {
2063+
return "The embedder has to ensure there is a root library (e.g. by "
2064+
"calling Dart_LoadScriptFromKernel ).";
2065+
}
2066+
MakeRunnableLocked();
2067+
return nullptr;
2068+
}
2069+
2070+
void Isolate::MakeRunnableLocked() {
2071+
ASSERT(mutex_.IsOwnedByCurrentThread());
2072+
ASSERT(!is_runnable());
2073+
ASSERT(spawn_state() == nullptr);
2074+
ASSERT(object_store()->root_library() != Library::null());
2075+
20582076
// Set the isolate as runnable and if we are being spawned schedule
20592077
// isolate on thread pool for execution.
2060-
ASSERT(object_store()->root_library() != Library::null());
20612078
set_is_runnable(true);
20622079
#ifndef PRODUCT
20632080
if (!Isolate::IsSystemIsolate(this)) {
@@ -2066,16 +2083,6 @@ const char* Isolate::MakeRunnable() {
20662083
}
20672084
}
20682085
#endif // !PRODUCT
2069-
IsolateSpawnState* state = spawn_state();
2070-
if (state != nullptr) {
2071-
// If the embedder does not make the isolate runnable during the
2072-
// `create_isolate_group`/`initialize_isolate` embedder callbacks but rather
2073-
// some time in the future, we'll hit this case.
2074-
// WARNING: This is currently untested - we might consider changing our APIs
2075-
// to disallow two different flows.
2076-
ASSERT(this == state->isolate());
2077-
Run();
2078-
}
20792086
#if defined(SUPPORT_TIMELINE)
20802087
TimelineStream* stream = Timeline::GetIsolateStream();
20812088
ASSERT(stream != nullptr);
@@ -2092,7 +2099,6 @@ const char* Isolate::MakeRunnable() {
20922099
}
20932100
GetRunnableLatencyMetric()->set_value(UptimeMicros());
20942101
#endif // !PRODUCT
2095-
return nullptr;
20962102
}
20972103

20982104
bool Isolate::VerifyPauseCapability(const Object& capability) const {

runtime/vm/isolate.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -923,6 +923,7 @@ class Isolate : public BaseIsolate, public IntrusiveDListEntry<Isolate> {
923923
void ScheduleInterrupts(uword interrupt_bits);
924924

925925
const char* MakeRunnable();
926+
void MakeRunnableLocked();
926927
void Run();
927928

928929
MessageHandler* message_handler() const { return message_handler_; }

0 commit comments

Comments
 (0)