blob: 71b638485af3bfe42122ea85076aaab7cfadc54f [file] [log] [blame] [edit]
// 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 ThreadLocal<T> {
/// Creates dart thread local variable.
ThreadLocal() : _id = _allocateId();
/// Returns the value of this thread-local variable or throws [StateError]
/// if it has no value.
T get value {
if (!_hasValue(_id)) {
throw StateError(
"Attempt to access variable that was not assigned a value.",
);
}
return unsafeCast<T>(_getValue(_id));
}
/// Sets the value of this variable. Overwrites old value if it was previously
/// set.
set value(T newValue) {
_setValue(_id, newValue);
}
/// Returns `true` if some value was assigned to this variable.
bool get hasValue => _hasValue(_id);
// Clears this variable of its assigned value.
void clearValue() {
_clearValue(_id);
}
@pragma("vm:external-name", "ThreadLocal_allocateId")
external static int _allocateId();
@pragma("vm:recognized", "graph-intrinsic")
@pragma("vm:external-name", "ThreadLocal_hasValue")
external static bool _hasValue(int id);
@pragma("vm:recognized", "graph-intrinsic")
@pragma("vm:external-name", "ThreadLocal_getValue")
external static Object? _getValue(int id);
@pragma("vm:external-name", "ThreadLocal_setValue")
external static void _setValue(int id, Object? value);
@pragma("vm:external-name", "ThreadLocal_clearValue")
external static void _clearValue(int id);
final int _id;
}
@pragma("vm:deeply-immutable")
@pragma('vm:entry-point')
final class ScopedThreadLocal<T> {
/// Creates scoped thread-local variable with given [initializer] function.
///
/// [initializer] must be trivially shareable.
ScopedThreadLocal([this._initializer]);
/// Execute [f] binding this [ScopedThreadLocal] to the given
/// [value] for the duration of the execution.
R runWith<R>(T newValue, R Function(T) f) {
bool hadValue = variable.hasValue;
T? previousValue = hadValue ? variable.value : null;
variable.value = newValue;
R result = f(newValue);
if (hadValue) {
variable.value = previousValue as T;
} else {
variable.clearValue();
}
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 = variable.hasValue;
T? previousValue = hadValue ? variable.value : null;
if (!variable.hasValue) {
if (_initializer == null) {
throw StateError(
"No initializer was provided for this ScopedThreadLocal.",
);
}
variable.value = _initializer!();
}
R result = f(variable.value);
if (hadValue) {
variable.value = previousValue as T;
} else {
variable.clearValue();
}
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 => variable.value;
/// Returns `true` if this [ScopedThreadLocal] is bound to a value.
bool get isBound => variable.hasValue;
final T Function()? _initializer;
final variable = ThreadLocal<T>();
}
@pragma("vm:deeply-immutable")
@pragma('vm:entry-point')
final class FinalThreadLocal<T> {
/// Creates thread local value with the given [initializer] function.
///
/// The value can be assigned only once, remains assigned for the duration
/// of this dart thread lifetime.
///
/// [initializer] must be trivially shareable.
FinalThreadLocal(this._initializer);
/// Returns the value bound to [FinalThreadLocal].
T get value {
if (!variable.hasValue) {
final v = _initializer();
variable.value = v;
return v;
}
return unsafeCast<T>(ThreadLocal._getValue(variable._id));
}
set value(_) {
throw StateError("Final value can not be updated");
}
final T Function() _initializer;
final variable = ThreadLocal<T>();
}