[vm/shared] Introduce ScopedThreadLocal to allow keeping state on

thread, outside of isolate, on isolate-group bound callback.

Fixes https://github.com/dart-lang/sdk/issues/61523
TEST=scoped_thread_local_test

Change-Id: I0c8280d2808d940a5fb0d628db191932400a5edb
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/451402
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Alexander Aprelev <aam@google.com>
This commit is contained in:
Alexander Aprelev
2025-11-13 09:50:34 -08:00
committed by Commit Queue
parent 438da65eb3
commit de77c6b766
42 changed files with 800 additions and 419 deletions
@@ -7,11 +7,12 @@ library from "org-dartlang-test:///main.dart" as main {
static method foo() → dynamic {}
}
And 20 platform libraries:
And 21 platform libraries:
- dart:_http
- dart:_builtin
- dart:vmservice_io
- dart:_compact_hash
- dart:_vm
- dart:async
- dart:cli
- dart:collection
@@ -14,11 +14,12 @@ library from "org-dartlang-test:///main.dart" as main {
static method foo() → dynamic {}
}
And 20 platform libraries:
And 21 platform libraries:
- dart:_http
- dart:_builtin
- dart:vmservice_io
- dart:_compact_hash
- dart:_vm
- dart:async
- dart:cli
- dart:collection
@@ -26,10 +26,11 @@ library from "org-dartlang-test:///main.dart" as main {
}
}
And 19 platform libraries:
And 20 platform libraries:
- dart:_builtin
- dart:_compact_hash
- dart:_internal
- dart:_vm
- dart:_vmservice
- dart:async
- dart:cli
@@ -8,11 +8,12 @@ library from "org-dartlang-test:///main.dart" as main {
return 42;
}
And 20 platform libraries:
And 21 platform libraries:
- dart:_http
- dart:_builtin
- dart:vmservice_io
- dart:_compact_hash
- dart:_vm
- dart:async
- dart:cli
- dart:collection
@@ -8,11 +8,12 @@ library from "org-dartlang-test:///main.dart" as main {
return 42;
}
And 20 platform libraries:
And 21 platform libraries:
- dart:_http
- dart:_builtin
- dart:vmservice_io
- dart:_compact_hash
- dart:_vm
- dart:async
- dart:cli
- dart:collection
@@ -16,11 +16,12 @@ library from "org-dartlang-test:///main.dart" as main {
}
}
And 20 platform libraries:
And 21 platform libraries:
- dart:_http
- dart:_builtin
- dart:vmservice_io
- dart:_compact_hash
- dart:_vm
- dart:async
- dart:cli
- dart:collection
@@ -16,11 +16,12 @@ library from "org-dartlang-test:///main.dart" as main {
}
}
And 20 platform libraries:
And 21 platform libraries:
- dart:_http
- dart:_builtin
- dart:vmservice_io
- dart:_compact_hash
- dart:_vm
- dart:async
- dart:cli
- dart:collection
@@ -6,11 +6,12 @@ library from "org-dartlang-test:///main.dart" as main {
}
}
And 20 platform libraries:
And 21 platform libraries:
- dart:_http
- dart:_builtin
- dart:vmservice_io
- dart:_compact_hash
- dart:_vm
- dart:async
- dart:cli
- dart:collection
@@ -14,11 +14,12 @@ library from "org-dartlang-test:///main.dart" as main {
}
}
And 20 platform libraries:
And 21 platform libraries:
- dart:_http
- dart:_builtin
- dart:vmservice_io
- dart:_compact_hash
- dart:_vm
- dart:async
- dart:cli
- dart:collection
@@ -14,11 +14,12 @@ library from "org-dartlang-test:///main.dart" as main {
}
}
And 20 platform libraries:
And 21 platform libraries:
- dart:_http
- dart:_builtin
- dart:vmservice_io
- dart:_compact_hash
- dart:_vm
- dart:async
- dart:cli
- dart:collection
@@ -27,11 +27,12 @@ constants {
#C1 = 0
}
And 20 platform libraries:
And 21 platform libraries:
- dart:_http
- dart:_builtin
- dart:vmservice_io
- dart:_compact_hash
- dart:_vm
- dart:async
- dart:cli
- dart:collection
@@ -29,11 +29,12 @@ constants {
#C1 = 0
}
And 20 platform libraries:
And 21 platform libraries:
- dart:_http
- dart:_builtin
- dart:vmservice_io
- dart:_compact_hash
- dart:_vm
- dart:async
- dart:cli
- dart:collection
@@ -27,11 +27,12 @@ constants {
#C1 = 0
}
And 20 platform libraries:
And 21 platform libraries:
- dart:_http
- dart:_builtin
- dart:vmservice_io
- dart:_compact_hash
- dart:_vm
- dart:async
- dart:cli
- dart:collection
@@ -30,6 +30,7 @@ class DartRunnerTarget extends VmTarget {
'dart:mirrors',
'dart:typed_data',
'dart:_vm',
'dart:nativewrappers',
'dart:io',
+1
View File
@@ -36,6 +36,7 @@ class FlutterTarget extends VmTarget {
'dart:mirrors',
'dart:typed_data',
'dart:_vm',
'dart:nativewrappers',
'dart:io',
@@ -30,6 +30,7 @@ class FlutterRunnerTarget extends VmTarget {
'dart:mirrors',
'dart:typed_data',
'dart:_vm',
'dart:nativewrappers',
'dart:io',
+1
View File
@@ -98,6 +98,7 @@ class VmTarget extends Target {
'dart:mirrors',
'dart:typed_data',
'dart:_vm',
'dart:vmservice_io',
'dart:_vmservice',
'dart:_builtin',
@@ -139,6 +139,16 @@ class DeeplyImmutableValidator {
}
}
if (node.name == 'ScopedThreadLocal') {
final uri = node.enclosingLibrary.importUri;
if (uri.isScheme('dart') && uri.path == '_vm') {
// ScopedThreadLocal has non-deeply-immutable initializer,
// but we allow it.
// TODO(dartbug.com/61962): remove this once the bug is fixed.
return;
}
}
// All instance fields should be non-late final and deeply immutable.
for (final field in node.fields) {
if (field.isStatic) {
@@ -10,6 +10,7 @@ LoadingUnit(id=1, parent=0,
dart:_builtin
dart:_vmservice
dart:vmservice_io
dart:_vm
dart:typed_data
dart:mirrors
dart:math
+1
View File
@@ -31,6 +31,7 @@ class Builtin {
kIOLibrary,
kHttpLibrary,
kCLILibrary,
kVMLibrary,
};
// Setup native resolver method built in library specified in 'id'.
+1
View File
@@ -337,6 +337,7 @@ class DartUtils {
static constexpr const char* kCLILibPatchURL = "dart:cli-patch";
static constexpr const char* kUriLibURL = "dart:uri";
static constexpr const char* kHttpScheme = "http:";
static constexpr const char* kVMLibURL = "dart:_vm";
static constexpr const char* kVMServiceLibURL = "dart:vmservice";
static void SetEnvironment(dart::SimpleHashMap* environment);
+78
View File
@@ -1278,6 +1278,84 @@ DEFINE_NATIVE_ENTRY(Isolate_sendOOB, 0, 2) {
return Object::null();
}
static void EnsureThreadLocalsTableExistsAndBigEnough(Thread* thread,
intptr_t index) {
GrowableObjectArray& locals =
GrowableObjectArray::Handle(thread->thread_locals());
if (locals.IsNull()) {
locals = GrowableObjectArray::New();
thread->set_thread_locals(locals);
}
if (index >= locals.Length()) {
locals.Grow(index + 1);
intptr_t old_length = locals.Length();
locals.SetLength(locals.Capacity());
for (intptr_t i = old_length; i < locals.Capacity(); i++) {
locals.SetAt(i, Object::sentinel());
}
}
}
DEFINE_NATIVE_ENTRY(ScopedThreadLocal_allocateId, 0, 0) {
auto isolate_group = thread->isolate_group();
isolate_group->increment_scoped_thread_locals_count();
intptr_t new_index = isolate_group->scoped_thread_locals_count() - 1;
EnsureThreadLocalsTableExistsAndBigEnough(thread, new_index);
GrowableObjectArray& locals =
GrowableObjectArray::Handle(thread->thread_locals());
locals.SetAt(new_index, Object::sentinel());
return Integer::New(new_index);
}
static void ValidateScopedThreadLocalId(Thread* thread, intptr_t id) {
if (id < 0 || id >= thread->isolate_group()->scoped_thread_locals_count()) {
const String& msg = String::Handle(String::New("Invalid local id."));
Exceptions::ThrowStateError(msg);
UNREACHABLE();
}
EnsureThreadLocalsTableExistsAndBigEnough(thread, id);
}
DEFINE_NATIVE_ENTRY(ScopedThreadLocal_hasValue, 0, 1) {
GET_NON_NULL_NATIVE_ARGUMENT(Integer, id_obj, arguments->NativeArgAt(0));
intptr_t id = id_obj.Value();
ValidateScopedThreadLocalId(thread, id);
GrowableObjectArray& locals =
GrowableObjectArray::Handle(thread->thread_locals());
return locals.At(id) == Object::sentinel().ptr() ? Bool::False().ptr()
: Bool::True().ptr();
}
DEFINE_NATIVE_ENTRY(ScopedThreadLocal_getValue, 0, 1) {
GET_NON_NULL_NATIVE_ARGUMENT(Integer, id_obj, arguments->NativeArgAt(0));
intptr_t id = id_obj.Value();
ValidateScopedThreadLocalId(thread, id);
GrowableObjectArray& locals =
GrowableObjectArray::Handle(thread->thread_locals());
return locals.At(id);
}
DEFINE_NATIVE_ENTRY(ScopedThreadLocal_setValue, 0, 2) {
GET_NON_NULL_NATIVE_ARGUMENT(Integer, id_obj, arguments->NativeArgAt(0));
intptr_t id = id_obj.Value();
ValidateScopedThreadLocalId(thread, id);
GET_NON_NULL_NATIVE_ARGUMENT(Instance, value, arguments->NativeArgAt(1));
GrowableObjectArray& locals =
GrowableObjectArray::Handle(thread->thread_locals());
locals.SetAt(id, value);
return Object::null();
}
DEFINE_NATIVE_ENTRY(ScopedThreadLocal_clearValue, 0, 1) {
GET_NON_NULL_NATIVE_ARGUMENT(Integer, id_obj, arguments->NativeArgAt(0));
intptr_t id = id_obj.Value();
ValidateScopedThreadLocalId(thread, id);
GrowableObjectArray& locals =
GrowableObjectArray::Handle(thread->thread_locals());
locals.SetAt(id, Object::sentinel());
return Object::null();
}
static void ExternalTypedDataFinalizer(void* isolate_callback_data,
void* peer) {
free(peer);
@@ -0,0 +1,115 @@
// 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 ScopedThreadLocal.
//
// VMOptions=--experimental-shared-data
import 'package:dart_internal/isolate_group.dart' show IsolateGroup;
import 'dart:_vm' show ScopedThreadLocal;
import "package:expect/expect.dart";
/// A collection used to identify cyclic lists during `toString` calls.
@pragma('vm:shared')
final toStringVisiting = ScopedThreadLocal<List<Object>>(() => <Object>[]);
/// Check if we are currently visiting [object] in a `toString` call.
bool isToStringVisiting(List<Object> toStringVisitingValue, Object object) {
for (int i = 0; i < toStringVisitingValue.length; i++) {
if (identical(object, toStringVisitingValue[i])) return true;
}
return false;
}
void _iterablePartsToStrings(Iterable<Object> iterable, List<String> parts) {
Iterator<Object> it = iterable.iterator;
// Initial run of elements, at least headCount, and then continue until
// passing at most lengthLimit characters.
while (it.moveNext()) {
if (it.current is Iterable<Object>) {
parts.add(iterableToShortString(it.current as Iterable<Object>));
} else {
parts.add("${it.current}");
}
}
}
String iterableToShortString(
Iterable<Object> iterable, [
String leftDelimiter = '(',
String rightDelimiter = ')',
]) {
return toStringVisiting.runInitialized((toStringVisitingValue) {
if (isToStringVisiting(toStringVisitingValue, iterable)) {
return "$leftDelimiter...$rightDelimiter";
}
List<String> parts = <String>[];
toStringVisitingValue.add(iterable);
try {
_iterablePartsToStrings(iterable, parts);
} finally {
assert(identical(toStringVisitingValue.last, iterable));
toStringVisitingValue.removeLast();
}
return (StringBuffer(leftDelimiter)
..writeAll(parts, ", ")
..write(rightDelimiter))
.toString();
});
}
@pragma('vm:shared')
final threadName = ScopedThreadLocal<String>();
main() {
Expect.isFalse(toStringVisiting.isBound);
Expect.throws(
() {
IsolateGroup.runSync(() {
var l = <Object>["foo", "bar"];
l.add(l);
l.add("baz");
final isBoundBefore = toStringVisiting.isBound;
final result = iterableToShortString(l);
final isBoundAfter = toStringVisiting.isBound;
throw "isBoundBefore: $isBoundBefore, $result, isBoundAfter: $isBoundAfter";
});
},
(e) =>
e.toString() ==
"isBoundBefore: false, (foo, bar, (...), baz), isBoundAfter: false",
);
{
Expect.isFalse(toStringVisiting.isBound);
var l = <Object>["foo", "bar"];
l.add(l);
l.add("baz");
Expect.equals("(foo, bar, (...), baz)", iterableToShortString(l));
Expect.isFalse(toStringVisiting.isBound);
}
Expect.equals(
"123",
IsolateGroup.runSync(() {
return threadName.runWith("123", (s) {
Expect.isTrue(threadName.isBound);
return s;
});
}),
);
Expect.throws(
() => IsolateGroup.runSync(() => threadName.value),
(e) => e is StateError,
);
Expect.throws(() => threadName.value, (e) => e is StateError);
Expect.equals(
"name:123",
threadName.runWith<String>("123", (String name) {
Expect.isTrue(threadName.isBound);
return "name:$name";
}),
);
Expect.isFalse(threadName.isBound);
}
+1
View File
@@ -251,6 +251,7 @@ dart/isolates/thread_pool_test: SkipByDesign # https://dartbug.com/37299 Test us
dart/reachability_test: SkipByDesign # Test takes too long on the simulator
dart/regress_41971_test: SkipByDesign # https://dartbug.com/37299 dart:ffi is not supported on simulator
dart/regress_b_216834909_test: SkipByDesign # No FFI on simulators
dart/scoped_thread_local_test: SkipByDesign # No FFI on simulators
dart/sdk_hash_test: SkipSlow # gen_kernel is slow to run on simarm
dart/sendport_api_test: SkipByDesign # No FFI on simulators
dart/shared_collect_all_garbage_test: SkipByDesign # No FFI on simulators
+1
View File
@@ -4,6 +4,7 @@
import("//build_overrides/build.gni")
import("../../build/executable_suffix.gni")
import("../../sdk/lib/_vm/vm_sources.gni")
import("../../sdk/lib/async/async_sources.gni")
import("../../sdk/lib/collection/collection_sources.gni")
import("../../sdk/lib/concurrent/concurrent_sources.gni")
+5
View File
@@ -141,6 +141,11 @@ void Bootstrap::SetupNativeResolver() {
library.set_native_entry_symbol_resolver(symbol_resolver);
library.set_ffi_native_resolver(ffi_native_resolver);
library = Library::VMLibrary();
ASSERT(!library.IsNull());
library.set_native_entry_resolver(resolver);
library.set_native_entry_symbol_resolver(symbol_resolver);
library = Library::IsolateLibrary();
ASSERT(!library.IsNull());
library.set_native_entry_resolver(resolver);
+5
View File
@@ -281,6 +281,11 @@ namespace dart {
V(Internal_allocateObjectInstructionsEnd, 0) \
V(InvocationMirror_unpackTypeArguments, 2) \
V(NoSuchMethodError_existingMethodSignature, 3) \
V(ScopedThreadLocal_allocateId, 0) \
V(ScopedThreadLocal_clearValue, 1) \
V(ScopedThreadLocal_getValue, 1) \
V(ScopedThreadLocal_hasValue, 1) \
V(ScopedThreadLocal_setValue, 2) \
V(Uri_isWindowsPlatform, 0) \
V(UserTag_new, 2) \
V(UserTag_label, 1) \
+13 -13
View File
@@ -28,7 +28,7 @@ namespace dart {
SuspendState_setErrorCallback, 0xd5e77404) \
V(AsyncLibrary, _SuspendState, _clone, SuspendState_clone, 0x751294d7) \
V(AsyncLibrary, _SuspendState, _resume, SuspendState_resume, 0x48d39768) \
V(ConvertLibrary, _Utf8Decoder, _scan, Utf8DecoderScan, 0x903cbc3e) \
V(ConvertLibrary, _Utf8Decoder, _scan, Utf8DecoderScan, 0x3948ffb0) \
V(CoreLibrary, ::, identical, ObjectIdentical, 0x03f96b55) \
V(CoreLibrary, Object, Object., ObjectConstructor, 0xab6d6cf2) \
V(CoreLibrary, _Array, [], ObjectArrayGetIndexed, 0x78d7e092) \
@@ -233,18 +233,18 @@ namespace dart {
0x56ff3b70) \
V(MathLibrary, ::, min, MathMin, 0x63eb7469) \
V(MathLibrary, ::, max, MathMax, 0xf9320c82) \
V(MathLibrary, ::, _doublePow, MathDoublePow, 0x424e2227) \
V(MathLibrary, ::, _intPow, MathIntPow, 0x9a0d648c) \
V(MathLibrary, ::, _sin, MathSin, 0x101882d8) \
V(MathLibrary, ::, _cos, MathCos, 0xf91585da) \
V(MathLibrary, ::, _tan, MathTan, 0xf720c4ea) \
V(MathLibrary, ::, _asin, MathAsin, 0xfe7986cb) \
V(MathLibrary, ::, _acos, MathAcos, 0x174c6974) \
V(MathLibrary, ::, _atan, MathAtan, 0x1ae3f717) \
V(MathLibrary, ::, _atan2, MathAtan2, 0x531004a9) \
V(MathLibrary, ::, _sqrt, MathSqrt, 0x1f167f7a) \
V(MathLibrary, ::, _exp, MathExp, 0x02565a46) \
V(MathLibrary, ::, _log, MathLog, 0x106c0978) \
V(MathLibrary, ::, _doublePow, MathDoublePow, 0x41e7435b) \
V(MathLibrary, ::, _intPow, MathIntPow, 0x831c7f0a) \
V(MathLibrary, ::, _sin, MathSin, 0x1e9ad4ea) \
V(MathLibrary, ::, _cos, MathCos, 0xf75f2165) \
V(MathLibrary, ::, _tan, MathTan, 0xfb847fa8) \
V(MathLibrary, ::, _asin, MathAsin, 0xf02d1b9e) \
V(MathLibrary, ::, _acos, MathAcos, 0xeda3de2d) \
V(MathLibrary, ::, _atan, MathAtan, 0xfc58e253) \
V(MathLibrary, ::, _atan2, MathAtan2, 0x751307ef) \
V(MathLibrary, ::, _sqrt, MathSqrt, 0x1cadc5cf) \
V(MathLibrary, ::, _exp, MathExp, 0x0c99a26a) \
V(MathLibrary, ::, _log, MathLog, 0xef163aae) \
V(NativeWrappersLibrary, ::, _getNativeField, GetNativeField, 0x8a67a22d) \
V(TypedDataLibrary, _Int8List, [], Int8ArrayGetIndexed, 0x23133682) \
V(TypedDataLibrary, _ExternalInt8Array, [], ExternalInt8ArrayGetIndexed, \
File diff suppressed because it is too large Load Diff
+7
View File
@@ -853,6 +853,11 @@ class IsolateGroup : public IntrusiveDListEntry<IsolateGroup> {
GrowableObjectArrayPtr tag_table() const { return tag_table_; }
void set_tag_table(const GrowableObjectArray& value);
intptr_t scoped_thread_locals_count() { return scoped_thread_locals_count_; }
intptr_t increment_scoped_thread_locals_count() {
return scoped_thread_locals_count_.fetch_add(1u, std::memory_order_relaxed);
}
private:
friend class Dart; // For `object_store_ = ` in Dart::Init
friend class Heap;
@@ -1013,6 +1018,8 @@ class IsolateGroup : public IntrusiveDListEntry<IsolateGroup> {
SafepointRwLock tag_table_lock_;
GrowableObjectArrayPtr tag_table_;
std::atomic<intptr_t> scoped_thread_locals_count_ = 0;
};
// When an isolate sends-and-exits this class represent things that it passed
+16 -3
View File
@@ -2532,6 +2532,14 @@ ErrorPtr Object::Init(IsolateGroup* isolate_group,
pending_classes.Add(cls);
RegisterClass(cls, Symbols::FinalizerEntry(), lib);
lib = Library::LookupLibrary(thread, Symbols::DartVM());
if (lib.IsNull()) {
lib = Library::NewLibraryHelper(Symbols::DartVM(), true);
lib.SetLoadRequested();
lib.Register(thread);
}
object_store->set_bootstrap_library(ObjectStore::kVM, lib);
// Finish the initialization by compiling the bootstrap scripts containing
// the base interfaces and the implementation of the internal classes.
const Error& error = Error::Handle(
@@ -15640,6 +15648,10 @@ LibraryPtr Library::InternalLibrary() {
return IsolateGroup::Current()->object_store()->_internal_library();
}
LibraryPtr Library::VMLibrary() {
return IsolateGroup::Current()->object_store()->_vm_library();
}
LibraryPtr Library::IsolateLibrary() {
return IsolateGroup::Current()->object_store()->isolate_library();
}
@@ -26631,13 +26643,14 @@ const char* ExternalTypedData::ToCString() const {
PointerPtr Pointer::New(uword native_address, Heap::Space space) {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
IsolateGroup* isolate_group = thread->isolate_group();
const auto& type_args = TypeArguments::Handle(
zone, IsolateGroup::Current()->object_store()->type_argument_never());
zone, isolate_group->object_store()->type_argument_never());
const Class& cls =
Class::Handle(IsolateGroup::Current()->class_table()->At(kPointerCid));
cls.EnsureIsAllocateFinalized(Thread::Current());
Class::Handle(isolate_group->class_table()->At(kPointerCid));
cls.EnsureIsAllocateFinalized(thread);
const auto& result = Pointer::Handle(zone, Object::Allocate<Pointer>(space));
result.SetTypeArguments(type_args);
+1
View File
@@ -5439,6 +5439,7 @@ class Library : public Object {
static LibraryPtr DeveloperLibrary();
static LibraryPtr FfiLibrary();
static LibraryPtr InternalLibrary();
static LibraryPtr VMLibrary();
static LibraryPtr IsolateLibrary();
static LibraryPtr MathLibrary();
#if !defined(DART_PRECOMPILED_RUNTIME)
+2
View File
@@ -29,6 +29,7 @@ class ObjectPointerVisitor;
M(Math, math) \
M(Mirrors, mirrors) \
M(TypedData, typed_data) \
M(VM, _vm) \
M(VMService, _vmservice) \
M(Concurrent, concurrent)
@@ -162,6 +163,7 @@ class ObjectPointerVisitor;
RW(Library, native_wrappers_library) \
RW(Library, root_library) \
RW(Library, typed_data_library) \
RW(Library, _vm_library) \
RW(Library, _vmservice_library) \
RW(Library, native_assets_library) \
RW(Array, native_assets_map) \
+1
View File
@@ -74,6 +74,7 @@ class ObjectPointerVisitor;
V(DartScheme, "dart:") \
V(DartSchemePrivate, "dart:_") \
V(DartTypedData, "dart:typed_data") \
V(DartVM, "dart:_vm") \
V(DartVMProduct, "dart.vm.product") \
V(DartVMService, "dart:_vmservice") \
V(DebugProcedureName, ":Eval") \
+8
View File
@@ -96,6 +96,7 @@ Thread::Thread(bool is_vm_isolate)
thread_lock_(),
reusable_handles_(),
sticky_error_(Error::null()),
thread_locals_(GrowableObjectArray::null()),
REUSABLE_HANDLE_LIST(REUSABLE_HANDLE_INITIALIZERS)
REUSABLE_HANDLE_LIST(REUSABLE_HANDLE_SCOPE_INIT)
#if !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)
@@ -267,6 +268,10 @@ void Thread::set_default_tag(const UserTag& tag) {
default_tag_ = tag.ptr();
}
void Thread::set_thread_locals(const GrowableObjectArray& thread_locals) {
thread_locals_ = thread_locals.ptr();
}
ErrorPtr Thread::StealStickyError() {
NoSafepointScope no_safepoint;
ErrorPtr return_value = sticky_error_;
@@ -357,6 +362,7 @@ void Thread::AssertEmptyThreadInvariants() {
ASSERT(default_tag_ == UserTag::null());
ASSERT(current_tag_ == UserTag::null());
ASSERT(thread_locals_ == GrowableObjectArray::null());
// Avoid running these asserts for `vm-isolate`.
if (active_stacktrace_.untag() != 0) {
@@ -768,6 +774,7 @@ void Thread::FreeActiveThread(Thread* thread,
thread->ResetStateLocked();
thread->current_tag_ = UserTag::null();
thread->default_tag_ = UserTag::null();
thread->thread_locals_ = GrowableObjectArray::null();
thread->AssertEmptyThreadInvariants();
thread_registry->ReturnThreadLocked(thread);
@@ -1151,6 +1158,7 @@ void Thread::VisitObjectPointers(ObjectPointerVisitor* visitor,
visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&current_tag_));
visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&default_tag_));
visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&thread_locals_));
}
class RestoreWriteBarrierInvariantVisitor : public ObjectPointerVisitor {
+5
View File
@@ -1383,6 +1383,9 @@ class Thread : public ThreadState, public IntrusiveDListEntry<Thread> {
static void VisitMutators(MutatorThreadVisitor* visitor);
GrowableObjectArrayPtr thread_locals() const { return thread_locals_; }
void set_thread_locals(const GrowableObjectArray& thread_locals);
private:
template <class T>
T* AllocateReusableHandle();
@@ -1576,6 +1579,8 @@ class Thread : public ThreadState, public IntrusiveDListEntry<Thread> {
return shared_field_table_values_;
}
GrowableObjectArrayPtr thread_locals_ = nullptr;
// Reusable handles support.
#define REUSABLE_HANDLE_FIELDS(object) object* object##_handle_;
REUSABLE_HANDLE_LIST(REUSABLE_HANDLE_FIELDS)
+1
View File
@@ -229,6 +229,7 @@ _full_sdk_libraries = [
"mirrors",
"svg",
"typed_data",
"_vm",
"_wasm",
"web_audio",
"web_gl",
@@ -193,6 +193,11 @@ const Map<String, LibraryInfo> libraries = const {
maturity: Maturity.WEB_STABLE,
platforms: DART2JS_PLATFORM,
),
'_vm': const LibraryInfo(
'_vm/_vm.dart',
categories: 'Server',
platforms: VM_PLATFORM,
),
'vmservice_io': const LibraryInfo(
'_internal/vm/bin/vmservice_io.dart',
categories: '',
+91
View File
@@ -0,0 +1,91 @@
// 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.
library dart._vm;
import "dart:_internal" show unsafeCast;
@pragma("vm:deeply-immutable")
@pragma('vm:entry-point')
final class ScopedThreadLocal<T> {
/// Creates scoped thread local value with the given [initializer] function.
///
/// [initializer] must be trivially shareable.
ScopedThreadLocal([this._initializer]) : _id = _allocateId();
/// Execute [f] binding this [ScopedThreadLocal] to the given
/// [value] for the duration of the execution.
R runWith<R>(T value, R Function(T) f) {
bool hadValue = _hasValue(_id);
Object? previous_value = hadValue ? _getValue(_id) : null;
_setValue(_id, value);
R result = f(value);
if (hadValue) {
_setValue(_id, previous_value!);
} else {
_clearValue(_id);
}
return result;
}
/// Execute [f] initializing this [ScopedThreadLocal] using default initializer if needed.
/// Throws [StateError] if this [ScopedThreadLocal] does not have an initializer.
R runInitialized<R>(R Function(T) f) {
bool hadValue = _hasValue(_id);
Object? previous_value = hadValue ? _getValue(_id) : null;
late T v;
if (!isBound) {
if (_initializer == null) {
throw StateError(
"No initializer was provided for this ScopedThreadLocal.",
);
}
v = _initializer!();
_setValue(_id, v);
} else {
v = unsafeCast<T>(_getValue(_id));
}
R result = f(v);
if (hadValue) {
_setValue(_id, previous_value!);
} else {
_clearValue(_id);
}
return result;
}
/// Returns the value specified by the closest enclosing invocation of
/// [runWith] or [runInititalized] or throws [StateError] if this
/// [ScopedThreadLocal] is not bound to a value.
T get value {
if (!_hasValue(_id)) {
throw StateError(
"Attempt to access value that was not bound. "
"Use runInititalized or runWith.",
);
}
return unsafeCast<T>(_getValue(_id));
}
/// Returns `true` if this [ScopedThreadLocal] is bound to a value.
bool get isBound => _hasValue(_id);
@pragma("vm:external-name", "ScopedThreadLocal_allocateId")
external static int _allocateId();
@pragma("vm:external-name", "ScopedThreadLocal_hasValue")
external static bool _hasValue(int id);
@pragma("vm:external-name", "ScopedThreadLocal_getValue")
external static Object? _getValue(int id);
@pragma("vm:external-name", "ScopedThreadLocal_setValue")
external static void _setValue(int id, Object? value);
@pragma("vm:external-name", "ScopedThreadLocal_clearValue")
external static void _clearValue(int id);
final int _id;
final T Function()? _initializer;
}
+6
View File
@@ -0,0 +1,6 @@
# Copyright (c) 2017, 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.
# This file contains all sources for the dart:_vm library.
vm_sdk_sources = [ "_vm.dart" ]
+3
View File
@@ -104,6 +104,9 @@
"uri": "typed_data/typed_data.dart",
"patches": "_internal/vm/lib/typed_data_patch.dart"
},
"_vm": {
"uri": "_vm/_vm.dart"
},
"_vmservice": {
"uri": "vmservice/vmservice.dart"
},
+3
View File
@@ -104,6 +104,9 @@ vm_common:
uri: "typed_data/typed_data.dart"
patches: "_internal/vm/lib/typed_data_patch.dart"
_vm:
uri: "_vm/_vm.dart"
_vmservice:
uri: "vmservice/vmservice.dart"