[vm] Preserve ThreadLocal value if dart::Thread is reclaimed

dart::Thread object representing Isolate's mutator can be reclaimed when
thread is suspended, so we need to preserve thread_locals on Isolate
itself.

TEST=vm/dart/thread_local_test

Fixes https://github.com/dart-lang/sdk/issues/63408

Change-Id: I7502b9bc67a07fb2e82479d3052740516a6a6964
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/504921
Commit-Queue: Slava Egorov <vegorov@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
This commit is contained in:
Slava Egorov
2026-05-20 06:21:29 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent cbb94bc140
commit bc16ca4d51
6 changed files with 41 additions and 6 deletions
@@ -0,0 +1,32 @@
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// Tests ThreadLocal.
//
// VMOptions=--experimental-shared-data
import 'dart:isolate';
import 'dart:_vm' show ThreadLocal;
import 'package:expect/expect.dart';
import 'package:expect/async_helper.dart';
@pragma('vm:shared')
final ThreadLocal<String> threadLocal = ThreadLocal<String>();
void main() async {
asyncStart();
// Make sure that threadLocal retains its value even if underlying
// dart::Thread gets reclaimed and recreated.
final results = await List.generate(
64,
(_) => Isolate.run(() async {
threadLocal.value = "ok";
await Future.delayed(const Duration(milliseconds: 10));
return threadLocal.hasValue ? threadLocal.value : "fail";
}),
).wait;
Expect.listEquals(['ok'], results.toSet().toList());
asyncEnd();
}
-1
View File
@@ -7546,7 +7546,6 @@ class ProgramDeserializationRoots : public DeserializationRoots {
if (Snapshot::IncludesCode(d->kind())) {
d->thread()->InitVMConstants();
d->thread()->set_thread_locals(Array::empty_array());
}
auto object_store = isolate_group->object_store();
+2 -1
View File
@@ -791,7 +791,6 @@ char* Dart::InitializeIsolateGroup(Thread* T,
ICData::Init();
StubCode::Init();
T->InitVMConstants();
T->set_thread_locals(Array::empty_array());
Symbols::Init(IG);
Object::FinishInit(IG);
Api::InitHandles();
@@ -857,6 +856,8 @@ ErrorPtr Dart::InitializeIsolate(Thread* T,
I->field_table()->MarkReadyToUse();
}
T->set_thread_locals(Object::empty_array());
const auto& error =
Error::Handle(Z, I->isolate_object_store()->PreallocateObjects());
if (!error.IsNull()) {
+1
View File
@@ -225,6 +225,7 @@ class ObjectPointerVisitor;
#define ISOLATE_OBJECT_STORE_FIELD_LIST(R_, RW) \
R_(Array, dart_args_1) \
R_(Array, dart_args_2) \
RW(Array, thread_locals) \
R_(GrowableObjectArray, resume_capabilities) \
R_(GrowableObjectArray, exit_listeners) \
R_(GrowableObjectArray, error_listeners)
+5 -4
View File
@@ -279,6 +279,9 @@ void Thread::set_default_tag(const UserTag& tag) {
}
void Thread::set_thread_locals(const Array& thread_locals) {
if (isolate_ != nullptr) {
isolate_->isolate_object_store()->set_thread_locals(thread_locals);
}
thread_locals_ = thread_locals.ptr();
}
@@ -436,10 +439,6 @@ void Thread::EnterIsolate(Isolate* isolate) {
/*bypass_safepoint=*/false);
thread->SetupMutatorState();
thread->SetupDartMutatorState(isolate);
if (Array::empty_array().ptr() != nullptr) {
thread->set_thread_locals(Array::empty_array());
}
}
isolate->scheduled_mutator_thread_ = thread;
@@ -1643,6 +1642,7 @@ void Thread::ResetMutatorState() {
void Thread::SetupDartMutatorState(Isolate* isolate) {
field_table_values_ = isolate->field_table_->table();
thread_locals_ = isolate->isolate_object_store()->thread_locals();
SetupDartMutatorStateDependingOnSnapshot(isolate->group());
}
@@ -1682,6 +1682,7 @@ void Thread::ResetDartMutatorState() {
field_table_values_ = nullptr;
shared_field_table_values_ = nullptr;
thread_locals_ = Array::null();
ONLY_IN_PRECOMPILED(global_object_pool_ = ObjectPool::null());
ONLY_IN_PRECOMPILED(dispatch_table_array_ = nullptr);
}
+1
View File
@@ -178,6 +178,7 @@ void ThreadRegistry::ReturnToFreelistLocked(Thread* thread) {
ASSERT(thread->isolate_group_ == nullptr);
ASSERT(thread->field_table_values_ == nullptr);
ASSERT(thread->shared_field_table_values_ == nullptr);
ASSERT(thread->thread_locals_ == Array::null());
ASSERT(threads_lock()->IsOwnedByCurrentThread());
// Add thread to the free list.
thread->next_ = free_list_;