8286666: JEP 429: Implementation of Scoped Values (Incubator)

Reviewed-by: psandoz, dlong, alanb, mcimadamore
This commit is contained in:
Andrew Haley 2022-12-07 10:14:06 +00:00 committed by Alan Bateman
parent ccc69af966
commit 221e1a4260
61 changed files with 2889 additions and 230 deletions

View File

@ -194,8 +194,6 @@ JVM_RegisterLambdaProxyClassForArchiving
JVM_RegisterSignal
JVM_ReleaseUTF
JVM_ReportFinalizationComplete
JVM_ExtentLocalCache
JVM_SetExtentLocalCache
JVM_SetArrayElement
JVM_SetClassSigners
JVM_SetNativeThreadName
@ -225,4 +223,10 @@ JVM_VirtualThreadMountEnd
JVM_VirtualThreadUnmountBegin
JVM_VirtualThreadUnmountEnd
JVM_VirtualThreadHideFrames
# Scoped values
JVM_EnsureMaterializedForStackWalk_func
JVM_FindScopedValueBindings
JVM_ScopedValueCache
JVM_SetScopedValueCache
#

View File

@ -3632,6 +3632,11 @@ encode %{
ciEnv::current()->record_failure("CodeCache is full");
return;
}
} else if (_method->intrinsic_id() == vmIntrinsicID::_ensureMaterializedForStackWalk) {
// The NOP here is purely to ensure that eliding a call to
// JVM_EnsureMaterializedForStackWalk doesn't change the code size.
__ nop();
__ block_comment("call JVM_EnsureMaterializedForStackWalk (elided)");
} else {
int method_index = resolved_method_index(cbuf);
RelocationHolder rspec = _optimized_virtual ? opt_virtual_call_Relocation::spec(method_index)

View File

@ -2164,13 +2164,19 @@ encode %{
// determine who we intended to call.
MacroAssembler _masm(&cbuf);
cbuf.set_insts_mark();
$$$emit8$primary;
if (!_method) {
$$$emit8$primary;
emit_d32_reloc(cbuf, (int) ($meth$$method - ((intptr_t) cbuf.insts_end()) - 4),
runtime_call_Relocation::spec(),
RELOC_DISP32);
} else if (_method->intrinsic_id() == vmIntrinsicID::_ensureMaterializedForStackWalk) {
// The NOP here is purely to ensure that eliding a call to
// JVM_EnsureMaterializedForStackWalk doesn't change the code size.
__ addr_nop_5();
__ block_comment("call JVM_EnsureMaterializedForStackWalk (elided)");
} else {
$$$emit8$primary;
int method_index = resolved_method_index(cbuf);
RelocationHolder rspec = _optimized_virtual ? opt_virtual_call_Relocation::spec(method_index)
: static_call_Relocation::spec(method_index);

View File

@ -154,7 +154,7 @@ bool Compiler::is_intrinsic_supported(const methodHandle& method) {
case vmIntrinsics::_getModifiers:
case vmIntrinsics::_currentCarrierThread:
case vmIntrinsics::_currentThread:
case vmIntrinsics::_extentLocalCache:
case vmIntrinsics::_scopedValueCache:
case vmIntrinsics::_dabs:
case vmIntrinsics::_dsqrt:
case vmIntrinsics::_dsqrt_strict:

View File

@ -1428,8 +1428,8 @@ void LIRGenerator::do_getObjectSize(Intrinsic* x) {
__ branch_destination(L_done->label());
}
void LIRGenerator::do_extentLocalCache(Intrinsic* x) {
do_JavaThreadField(x, JavaThread::extentLocalCache_offset());
void LIRGenerator::do_scopedValueCache(Intrinsic* x) {
do_JavaThreadField(x, JavaThread::scopedValueCache_offset());
}
// Example: Thread.currentCarrierThread()
@ -2948,7 +2948,7 @@ void LIRGenerator::do_Intrinsic(Intrinsic* x) {
case vmIntrinsics::_getObjectSize: do_getObjectSize(x); break;
case vmIntrinsics::_currentCarrierThread: do_currentCarrierThread(x); break;
case vmIntrinsics::_currentThread: do_vthread(x); break;
case vmIntrinsics::_extentLocalCache: do_extentLocalCache(x); break;
case vmIntrinsics::_scopedValueCache: do_scopedValueCache(x); break;
case vmIntrinsics::_dlog: // fall through
case vmIntrinsics::_dlog10: // fall through

View File

@ -257,7 +257,7 @@ class LIRGenerator: public InstructionVisitor, public BlockClosure {
void do_getClass(Intrinsic* x);
void do_getObjectSize(Intrinsic* x);
void do_currentCarrierThread(Intrinsic* x);
void do_extentLocalCache(Intrinsic* x);
void do_scopedValueCache(Intrinsic* x);
void do_vthread(Intrinsic* x);
void do_JavaThreadField(Intrinsic* x, ByteSize offset);
void do_FmaIntrinsic(Intrinsic* x);

View File

@ -1683,7 +1683,7 @@ int java_lang_Thread::_interrupted_offset;
int java_lang_Thread::_tid_offset;
int java_lang_Thread::_continuation_offset;
int java_lang_Thread::_park_blocker_offset;
int java_lang_Thread::_extentLocalBindings_offset;
int java_lang_Thread::_scopedValueBindings_offset;
JFR_ONLY(int java_lang_Thread::_jfr_epoch_offset;)
#define THREAD_FIELDS_DO(macro) \
@ -1696,7 +1696,7 @@ JFR_ONLY(int java_lang_Thread::_jfr_epoch_offset;)
macro(_tid_offset, k, "tid", long_signature, false); \
macro(_park_blocker_offset, k, "parkBlocker", object_signature, false); \
macro(_continuation_offset, k, "cont", continuation_signature, false); \
macro(_extentLocalBindings_offset, k, "extentLocalBindings", object_signature, false);
macro(_scopedValueBindings_offset, k, "scopedValueBindings", object_signature, false);
void java_lang_Thread::compute_offsets() {
assert(_holder_offset == 0, "offsets should be initialized only once");
@ -1729,8 +1729,9 @@ void java_lang_Thread::set_jvmti_thread_state(oop java_thread, JvmtiThreadState*
java_thread->address_field_put(_jvmti_thread_state_offset, (address)state);
}
void java_lang_Thread::clear_extentLocalBindings(oop java_thread) {
java_thread->obj_field_put(_extentLocalBindings_offset, NULL);
void java_lang_Thread::clear_scopedValueBindings(oop java_thread) {
assert(java_thread != NULL, "need a java_lang_Thread pointer here");
java_thread->obj_field_put(_scopedValueBindings_offset, NULL);
}
oop java_lang_Thread::holder(oop java_thread) {

View File

@ -352,7 +352,7 @@ class java_lang_Thread : AllStatic {
static int _tid_offset;
static int _continuation_offset;
static int _park_blocker_offset;
static int _extentLocalBindings_offset;
static int _scopedValueBindings_offset;
JFR_ONLY(static int _jfr_epoch_offset;)
static void compute_offsets();
@ -398,8 +398,8 @@ class java_lang_Thread : AllStatic {
static JvmtiThreadState* jvmti_thread_state(oop java_thread);
static void set_jvmti_thread_state(oop java_thread, JvmtiThreadState* state);
// Clear all extent local bindings on error
static void clear_extentLocalBindings(oop java_thread);
// Clear all scoped value bindings on error
static void clear_scopedValueBindings(oop java_thread);
// Blocker object responsible for thread parking
static oop park_blocker(oop java_thread);

View File

@ -77,7 +77,7 @@ bool vmIntrinsics::preserves_state(vmIntrinsics::ID id) {
case vmIntrinsics::_isInstance:
case vmIntrinsics::_currentCarrierThread:
case vmIntrinsics::_currentThread:
case vmIntrinsics::_extentLocalCache:
case vmIntrinsics::_scopedValueCache:
case vmIntrinsics::_dabs:
case vmIntrinsics::_fabs:
case vmIntrinsics::_iabs:
@ -127,8 +127,8 @@ bool vmIntrinsics::can_trap(vmIntrinsics::ID id) {
case vmIntrinsics::_currentCarrierThread:
case vmIntrinsics::_currentThread:
case vmIntrinsics::_setCurrentThread:
case vmIntrinsics::_extentLocalCache:
case vmIntrinsics::_setExtentLocalCache:
case vmIntrinsics::_scopedValueCache:
case vmIntrinsics::_setScopedValueCache:
case vmIntrinsics::_dabs:
case vmIntrinsics::_fabs:
case vmIntrinsics::_iabs:
@ -265,8 +265,8 @@ bool vmIntrinsics::disabled_by_jvm_flags(vmIntrinsics::ID id) {
if (!InlineThreadNatives) return true;
break;
case vmIntrinsics::_setCurrentThread:
case vmIntrinsics::_extentLocalCache:
case vmIntrinsics::_setExtentLocalCache:
case vmIntrinsics::_scopedValueCache:
case vmIntrinsics::_setScopedValueCache:
case vmIntrinsics::_floatToRawIntBits:
case vmIntrinsics::_intBitsToFloat:
case vmIntrinsics::_doubleToRawLongBits:

View File

@ -270,7 +270,7 @@ class methodHandle;
do_intrinsic(_identityHashCode, java_lang_System, identityHashCode_name, object_int_signature, F_SN) \
do_name( identityHashCode_name, "identityHashCode") \
do_intrinsic(_currentTimeMillis, java_lang_System, currentTimeMillis_name, void_long_signature, F_SN) \
\
\
do_name( currentTimeMillis_name, "currentTimeMillis") \
do_intrinsic(_nanoTime, java_lang_System, nanoTime_name, void_long_signature, F_SN) \
do_name( nanoTime_name, "nanoTime") \
@ -286,12 +286,15 @@ class methodHandle;
do_intrinsic(_currentThread, java_lang_Thread, currentThread_name, currentThread_signature, F_SN) \
do_name( currentThread_name, "currentThread") \
do_signature(currentThread_signature, "()Ljava/lang/Thread;") \
do_intrinsic(_extentLocalCache, java_lang_Thread, extentLocalCache_name, extentLocalCache_signature, F_SN) \
do_name( extentLocalCache_name, "extentLocalCache") \
do_signature(extentLocalCache_signature, "()[Ljava/lang/Object;") \
do_intrinsic(_setExtentLocalCache, java_lang_Thread, setExtentLocalCache_name, setExtentLocalCache_signature, F_SN) \
do_name( setExtentLocalCache_name, "setExtentLocalCache") \
do_signature(setExtentLocalCache_signature, "([Ljava/lang/Object;)V") \
do_intrinsic(_scopedValueCache, java_lang_Thread, scopedValueCache_name, scopedValueCache_signature, F_SN) \
do_name( scopedValueCache_name, "scopedValueCache") \
do_signature(scopedValueCache_signature, "()[Ljava/lang/Object;") \
do_intrinsic(_setScopedValueCache, java_lang_Thread, setScopedValueCache_name, setScopedValueCache_signature, F_SN) \
do_name( setScopedValueCache_name, "setScopedValueCache") \
do_signature(setScopedValueCache_signature, "([Ljava/lang/Object;)V") \
do_intrinsic(_findScopedValueBindings, java_lang_Thread, findScopedValueBindings_name, void_object_signature, F_SN) \
do_name( findScopedValueBindings_name, "findScopedValueBindings") \
\
do_intrinsic(_setCurrentThread, java_lang_Thread, setCurrentThread_name, thread_void_signature, F_RN) \
do_name( setCurrentThread_name, "setCurrentThread") \
\
@ -331,6 +334,9 @@ class methodHandle;
do_name( onSpinWait_name, "onSpinWait") \
do_alias( onSpinWait_signature, void_method_signature) \
\
do_intrinsic(_ensureMaterializedForStackWalk, java_lang_Thread, ensureMaterializedForStackWalk_name, object_void_signature, F_SN) \
do_name( ensureMaterializedForStackWalk_name, "ensureMaterializedForStackWalk") \
\
do_intrinsic(_copyOf, java_util_Arrays, copyOf_name, copyOf_signature, F_S) \
do_name( copyOf_name, "copyOf") \
do_signature(copyOf_signature, "([Ljava/lang/Object;ILjava/lang/Class;)[Ljava/lang/Object;") \

View File

@ -156,6 +156,8 @@
template(jdk_internal_loader_BuiltinClassLoader, "jdk/internal/loader/BuiltinClassLoader") \
template(jdk_internal_loader_ClassLoaders_AppClassLoader, "jdk/internal/loader/ClassLoaders$AppClassLoader") \
template(jdk_internal_loader_ClassLoaders_PlatformClassLoader, "jdk/internal/loader/ClassLoaders$PlatformClassLoader") \
template(jdk_incubator_concurrent_ScopedValue, "jdk/incubator/concurrent/ScopedValue") \
template(jdk_incubator_concurrent_ScopedValue_Carrier, "jdk/incubator/concurrent/ScopedValue$Carrier") \
\
/* Java runtime version access */ \
template(java_lang_VersionProps, "java/lang/VersionProps") \
@ -396,6 +398,7 @@
template(group_name, "group") \
template(daemon_name, "daemon") \
template(run_method_name, "run") \
template(runWith_method_name, "runWith") \
template(interrupt_method_name, "interrupt") \
template(exit_method_name, "exit") \
template(remove_method_name, "remove") \
@ -601,6 +604,7 @@
template(string_array_string_array_void_signature, "([Ljava/lang/String;[Ljava/lang/String;)V") \
template(thread_throwable_void_signature, "(Ljava/lang/Thread;Ljava/lang/Throwable;)V") \
template(thread_void_signature, "(Ljava/lang/Thread;)V") \
template(runnable_void_signature, "(Ljava/lang/Runnable;)V") \
template(threadgroup_runnable_void_signature, "(Ljava/lang/ThreadGroup;Ljava/lang/Runnable;)V") \
template(threadgroup_string_void_signature, "(Ljava/lang/ThreadGroup;Ljava/lang/String;)V") \
template(void_threadgroup_array_signature, "()[Ljava/lang/ThreadGroup;") \

View File

@ -310,10 +310,13 @@ JNIEXPORT jobjectArray JNICALL
JVM_DumpThreads(JNIEnv *env, jclass threadClass, jobjectArray threads);
JNIEXPORT jobject JNICALL
JVM_ExtentLocalCache(JNIEnv *env, jclass threadClass);
JVM_ScopedValueCache(JNIEnv *env, jclass threadClass);
JNIEXPORT void JNICALL
JVM_SetExtentLocalCache(JNIEnv *env, jclass threadClass, jobject theCache);
JVM_SetScopedValueCache(JNIEnv *env, jclass threadClass, jobject theCache);
JNIEXPORT jobject JNICALL
JVM_FindScopedValueBindings(JNIEnv *env, jclass threadClass);
JNIEXPORT jlong JNICALL
JVM_GetNextThreadIdOffset(JNIEnv *env, jclass threadClass);
@ -742,6 +745,8 @@ JVM_GetInheritedAccessControlContext(JNIEnv *env, jclass cls);
#define JVM_EnsureMaterializedForStackWalk(env, value) \
do {} while(0) // Nothing to do. The fact that the value escaped
// through a native method is enough.
JNIEXPORT void JNICALL
JVM_EnsureMaterializedForStackWalk_func(JNIEnv* env, jobject vthread, jobject value);
JNIEXPORT jobject JNICALL
JVM_GetStackAccessControlContext(JNIEnv *env, jclass cls);

View File

@ -372,6 +372,9 @@ JRT_ENTRY(void, InterpreterRuntime::throw_StackOverflowError(JavaThread* current
CHECK);
// Increment counter for hs_err file reporting
Atomic::inc(&Exceptions::_stack_overflow_errors);
// Remove the ScopedValue bindings in case we got a StackOverflowError
// while we were trying to manipulate ScopedValue bindings.
current->clear_scopedValueBindings();
THROW_HANDLE(exception);
JRT_END
@ -383,6 +386,9 @@ JRT_ENTRY(void, InterpreterRuntime::throw_delayed_StackOverflowError(JavaThread*
Universe::delayed_stack_overflow_error_message());
// Increment counter for hs_err file reporting
Atomic::inc(&Exceptions::_stack_overflow_errors);
// Remove the ScopedValue bindings in case we got a StackOverflowError
// while we were trying to manipulate ScopedValue bindings.
current->clear_scopedValueBindings();
THROW_HANDLE(exception);
JRT_END

View File

@ -1609,12 +1609,12 @@ C2V_VMENTRY(void, materializeVirtualObjects, (JNIEnv* env, jobject, jobject _hs_
for (int frame_index = 0; frame_index < virtualFrames->length(); frame_index++) {
compiledVFrame* cvf = virtualFrames->at(frame_index);
GrowableArray<ScopeValue*>* extentLocals = cvf->scope()->locals();
GrowableArray<ScopeValue*>* scopedValues = cvf->scope()->locals();
StackValueCollection* locals = cvf->locals();
if (locals != NULL) {
for (int i2 = 0; i2 < locals->size(); i2++) {
StackValue* var = locals->at(i2);
if (var->type() == T_OBJECT && extentLocals->at(i2)->is_object()) {
if (var->type() == T_OBJECT && scopedValues->at(i2)->is_object()) {
jvalue val;
val.l = cast_from_oop<jobject>(locals->at(i2)->get_obj()());
cvf->update_local(T_OBJECT, i2, val);

View File

@ -174,7 +174,7 @@
\
nonstatic_field(JavaThread, _threadObj, OopHandle) \
nonstatic_field(JavaThread, _vthread, OopHandle) \
nonstatic_field(JavaThread, _extentLocalCache, OopHandle) \
nonstatic_field(JavaThread, _scopedValueCache, OopHandle) \
nonstatic_field(JavaThread, _anchor, JavaFrameAnchor) \
nonstatic_field(JavaThread, _vm_result, oop) \
nonstatic_field(JavaThread, _stack_overflow_state._stack_overflow_limit, address) \

View File

@ -70,7 +70,6 @@ public:
inline oop xchg(oop new_value);
// Used only for removing handle.
oop* ptr_raw() const { return _obj; }
};

View File

@ -680,8 +680,8 @@ bool C2Compiler::is_intrinsic_supported(const methodHandle& method, bool is_virt
case vmIntrinsics::_currentCarrierThread:
case vmIntrinsics::_currentThread:
case vmIntrinsics::_setCurrentThread:
case vmIntrinsics::_extentLocalCache:
case vmIntrinsics::_setExtentLocalCache:
case vmIntrinsics::_scopedValueCache:
case vmIntrinsics::_setScopedValueCache:
#ifdef JFR_HAVE_INTRINSICS
case vmIntrinsics::_counterTime:
case vmIntrinsics::_getEventWriter:

View File

@ -472,8 +472,8 @@ bool LibraryCallKit::try_to_inline(int predicate) {
case vmIntrinsics::_currentThread: return inline_native_currentThread();
case vmIntrinsics::_setCurrentThread: return inline_native_setCurrentThread();
case vmIntrinsics::_extentLocalCache: return inline_native_extentLocalCache();
case vmIntrinsics::_setExtentLocalCache: return inline_native_setExtentLocalCache();
case vmIntrinsics::_scopedValueCache: return inline_native_scopedValueCache();
case vmIntrinsics::_setScopedValueCache: return inline_native_setScopedValueCache();
#ifdef JFR_HAVE_INTRINSICS
case vmIntrinsics::_counterTime: return inline_native_time_funcs(CAST_FROM_FN_PTR(address, JfrTime::time_function()), "counterTime");
@ -3357,38 +3357,42 @@ bool LibraryCallKit::inline_native_setCurrentThread() {
return true;
}
Node* LibraryCallKit::extentLocalCache_helper() {
Node* LibraryCallKit::scopedValueCache_helper() {
ciKlass *objects_klass = ciObjArrayKlass::make(env()->Object_klass());
const TypeOopPtr *etype = TypeOopPtr::make_from_klass(env()->Object_klass());
bool xk = etype->klass_is_exact();
Node* thread = _gvn.transform(new ThreadLocalNode());
Node* p = basic_plus_adr(top()/*!oop*/, thread, in_bytes(JavaThread::extentLocalCache_offset()));
return _gvn.transform(LoadNode::make(_gvn, NULL, immutable_memory(), p, p->bottom_type()->is_ptr(),
TypeRawPtr::NOTNULL, T_ADDRESS, MemNode::unordered));
Node* p = basic_plus_adr(top()/*!oop*/, thread, in_bytes(JavaThread::scopedValueCache_offset()));
// We cannot use immutable_memory() because we might flip onto a
// different carrier thread, at which point we'll need to use that
// carrier thread's cache.
// return _gvn.transform(LoadNode::make(_gvn, NULL, immutable_memory(), p, p->bottom_type()->is_ptr(),
// TypeRawPtr::NOTNULL, T_ADDRESS, MemNode::unordered));
return make_load(NULL, p, p->bottom_type()->is_ptr(), T_ADDRESS, MemNode::unordered);
}
//------------------------inline_native_extentLocalCache------------------
bool LibraryCallKit::inline_native_extentLocalCache() {
//------------------------inline_native_scopedValueCache------------------
bool LibraryCallKit::inline_native_scopedValueCache() {
ciKlass *objects_klass = ciObjArrayKlass::make(env()->Object_klass());
const TypeOopPtr *etype = TypeOopPtr::make_from_klass(env()->Object_klass());
const TypeAry* arr0 = TypeAry::make(etype, TypeInt::POS);
// Because we create the extentLocal cache lazily we have to make the
// Because we create the scopedValue cache lazily we have to make the
// type of the result BotPTR.
bool xk = etype->klass_is_exact();
const Type* objects_type = TypeAryPtr::make(TypePtr::BotPTR, arr0, objects_klass, xk, 0);
Node* cache_obj_handle = extentLocalCache_helper();
Node* cache_obj_handle = scopedValueCache_helper();
set_result(access_load(cache_obj_handle, objects_type, T_OBJECT, IN_NATIVE));
return true;
}
//------------------------inline_native_setExtentLocalCache------------------
bool LibraryCallKit::inline_native_setExtentLocalCache() {
//------------------------inline_native_setScopedValueCache------------------
bool LibraryCallKit::inline_native_setScopedValueCache() {
Node* arr = argument(0);
Node* cache_obj_handle = extentLocalCache_helper();
Node* cache_obj_handle = scopedValueCache_helper();
const TypePtr *adr_type = _gvn.type(cache_obj_handle)->isa_ptr();
store_to_memory(control(), cache_obj_handle, arr, T_OBJECT, adr_type,

View File

@ -239,9 +239,9 @@ class LibraryCallKit : public GraphKit {
bool inline_native_currentThread();
bool inline_native_setCurrentThread();
bool inline_native_extentLocalCache();
Node* extentLocalCache_helper();
bool inline_native_setExtentLocalCache();
bool inline_native_scopedValueCache();
Node* scopedValueCache_helper();
bool inline_native_setScopedValueCache();
bool inline_native_time_funcs(address method, const char* funcName);
#ifdef JFR_HAVE_INTRINSICS

View File

@ -865,7 +865,7 @@ bool LoadNode::is_immutable_value(Node* adr) {
in_bytes(JavaThread::osthread_offset()),
in_bytes(JavaThread::threadObj_offset()),
in_bytes(JavaThread::vthread_offset()),
in_bytes(JavaThread::extentLocalCache_offset()),
in_bytes(JavaThread::scopedValueCache_offset()),
};
for (size_t i = 0; i < sizeof offsets / sizeof offsets[0]; i++) {

View File

@ -1363,6 +1363,54 @@ JVM_ENTRY(jobject, JVM_GetStackAccessControlContext(JNIEnv *env, jclass cls))
return JNIHandles::make_local(THREAD, result);
JVM_END
class ScopedValueBindingsResolver {
public:
InstanceKlass* Carrier_klass;
ScopedValueBindingsResolver(JavaThread* THREAD) {
Klass *k = SystemDictionary::resolve_or_fail(vmSymbols::jdk_incubator_concurrent_ScopedValue_Carrier(), true, THREAD);
Carrier_klass = InstanceKlass::cast(k);
}
};
JVM_ENTRY(jobject, JVM_FindScopedValueBindings(JNIEnv *env, jclass cls))
ResourceMark rm(THREAD);
GrowableArray<Handle>* local_array = new GrowableArray<Handle>(12);
JvmtiVMObjectAllocEventCollector oam;
static ScopedValueBindingsResolver resolver(THREAD);
// Iterate through Java frames
vframeStream vfst(thread);
for(; !vfst.at_end(); vfst.next()) {
int loc = -1;
// get method of frame
Method* method = vfst.method();
Symbol *name = method->name();
InstanceKlass* holder = method->method_holder();
if (name == vmSymbols::runWith_method_name()) {
if ((holder == resolver.Carrier_klass
|| holder == vmClasses::VirtualThread_klass()
|| holder == vmClasses::Thread_klass())) {
loc = 1;
}
}
if (loc != -1) {
javaVFrame *frame = vfst.asJavaVFrame();
StackValueCollection* locals = frame->locals();
StackValue* head_sv = locals->at(loc); // jdk/incubator/concurrent/ScopedValue$Snapshot
Handle result = head_sv->get_obj();
assert(!head_sv->obj_is_scalar_replaced(), "found scalar-replaced object");
if (result() != NULL) {
return JNIHandles::make_local(THREAD, result());
}
}
}
return NULL;
JVM_END
JVM_ENTRY(jboolean, JVM_IsArrayClass(JNIEnv *env, jclass cls))
Klass* k = java_lang_Class::as_Klass(JNIHandles::resolve_non_null(cls));
@ -3114,22 +3162,15 @@ JVM_ENTRY(void, JVM_SetNativeThreadName(JNIEnv* env, jobject jthread, jstring na
}
JVM_END
JVM_ENTRY(jobject, JVM_ExtentLocalCache(JNIEnv* env, jclass threadClass))
oop theCache = thread->extentLocalCache();
if (theCache) {
arrayOop objs = arrayOop(theCache);
assert(objs->length() == ExtentLocalCacheSize * 2, "wrong length");
}
JVM_ENTRY(jobject, JVM_ScopedValueCache(JNIEnv* env, jclass threadClass))
oop theCache = thread->scopedValueCache();
return JNIHandles::make_local(THREAD, theCache);
JVM_END
JVM_ENTRY(void, JVM_SetExtentLocalCache(JNIEnv* env, jclass threadClass,
JVM_ENTRY(void, JVM_SetScopedValueCache(JNIEnv* env, jclass threadClass,
jobject theCache))
arrayOop objs = arrayOop(JNIHandles::resolve(theCache));
if (objs != NULL) {
assert(objs->length() == ExtentLocalCacheSize * 2, "wrong length");
}
thread->set_extentLocalCache(objs);
thread->set_scopedValueCache(objs);
JVM_END
// java.lang.SecurityManager ///////////////////////////////////////////////////////////////////////
@ -4019,3 +4060,12 @@ JVM_ENTRY(jint, JVM_GetClassFileVersion(JNIEnv* env, jclass current))
InstanceKlass* ik = InstanceKlass::cast(c);
return (ik->minor_version() << 16) | ik->major_version();
JVM_END
/*
* Ensure that code doing a stackwalk and using javaVFrame::locals() to
* get the value will see a materialized value and not a scalar-replaced
* null value.
*/
JVM_ENTRY(void, JVM_EnsureMaterializedForStackWalk_func(JNIEnv* env, jobject vthread, jobject value))
JVM_EnsureMaterializedForStackWalk(env, value);
JVM_END

View File

@ -441,11 +441,8 @@ Deoptimization::UnrollBlock* Deoptimization::fetch_unroll_info_helper(JavaThread
vframeArray* array = create_vframeArray(current, deoptee, &map, chunk, realloc_failures);
#if COMPILER2_OR_JVMCI
if (realloc_failures) {
// FIXME: This very crudely destroys all ExtentLocal bindings. This
// is better than a bound value escaping, but far from ideal.
oop java_thread = current->threadObj();
current->set_extentLocalCache(NULL);
java_lang_Thread::clear_extentLocalBindings(java_thread);
// This destroys all ScopedValue bindings.
current->clear_scopedValueBindings();
pop_frames_failed_reallocs(current, array);
}
#endif

View File

@ -92,18 +92,6 @@ JVMFlag::Error VMPageSizeConstraintFunc(uintx value, bool verbose) {
return JVMFlag::SUCCESS;
}
JVMFlag::Error ExtentLocalCacheSizeConstraintFunc(intx value, bool verbose) {
if (!is_power_of_2(value)) {
JVMFlag::printError(verbose,
"ExtentLocalCacheSize (" INTX_FORMAT ") must be "
"power of 2\n",
value);
return JVMFlag::VIOLATES_CONSTRAINT;
}
return JVMFlag::SUCCESS;
}
JVMFlag::Error NUMAInterleaveGranularityConstraintFunc(size_t value, bool verbose) {
size_t min = os::vm_allocation_granularity();
size_t max = NOT_LP64(2*G) LP64_ONLY(8192*G);

View File

@ -37,7 +37,6 @@
f(int, ObjectAlignmentInBytesConstraintFunc) \
f(intx, ContendedPaddingWidthConstraintFunc) \
f(intx, PerfDataSamplingIntervalFunc) \
f(intx, ExtentLocalCacheSizeConstraintFunc) \
f(uintx, VMPageSizeConstraintFunc) \
f(size_t, NUMAInterleaveGranularityConstraintFunc)

View File

@ -1947,11 +1947,6 @@ const int ObjectAlignmentInBytes = 8;
develop(bool, UseContinuationFastPath, true, \
"Use fast-path frame walking in continuations") \
\
product(intx, ExtentLocalCacheSize, 16, \
"Size of the cache for scoped values") \
range(0, max_intx) \
constraint(ExtentLocalCacheSizeConstraintFunc, AtParse) \
\
develop(int, VerifyMetaspaceInterval, DEBUG_ONLY(500) NOT_DEBUG(0), \
"Run periodic metaspace verifications (0 - none, " \
"1 - always, >1 every nth interval)") \

View File

@ -157,7 +157,7 @@ void JavaThread::set_threadOopHandles(oop p) {
_threadObj = OopHandle(_thread_oop_storage, p);
_vthread = OopHandle(_thread_oop_storage, p);
_jvmti_vthread = OopHandle(_thread_oop_storage, NULL);
_extentLocalCache = OopHandle(_thread_oop_storage, NULL);
_scopedValueCache = OopHandle(_thread_oop_storage, NULL);
}
oop JavaThread::threadObj() const {
@ -186,13 +186,26 @@ void JavaThread::set_jvmti_vthread(oop p) {
_jvmti_vthread.replace(p);
}
oop JavaThread::extentLocalCache() const {
return _extentLocalCache.resolve();
oop JavaThread::scopedValueCache() const {
return _scopedValueCache.resolve();
}
void JavaThread::set_extentLocalCache(oop p) {
assert(_thread_oop_storage != NULL, "not yet initialized");
_extentLocalCache.replace(p);
void JavaThread::set_scopedValueCache(oop p) {
if (_scopedValueCache.ptr_raw() != NULL) { // i.e. if the OopHandle has been allocated
_scopedValueCache.replace(p);
} else {
assert(p == NULL, "not yet initialized");
}
}
void JavaThread::clear_scopedValueBindings() {
set_scopedValueCache(NULL);
oop vthread_oop = vthread();
// vthread may be null here if we get a VM error during startup,
// before the java.lang.Thread instance has been created.
if (vthread_oop != NULL) {
java_lang_Thread::clear_scopedValueBindings(vthread_oop);
}
}
void JavaThread::allocate_threadObj(Handle thread_group, const char* thread_name,
@ -1040,11 +1053,7 @@ void JavaThread::handle_async_exception(oop java_throwable) {
// We cannot call Exceptions::_throw(...) here because we cannot block
set_pending_exception(java_throwable, __FILE__, __LINE__);
// Clear any extent-local bindings
set_extentLocalCache(NULL);
oop threadOop = threadObj();
assert(threadOop != NULL, "must be");
java_lang_Thread::clear_extentLocalBindings(threadOop);
clear_scopedValueBindings();
LogTarget(Info, exceptions) lt;
if (lt.is_enabled()) {
@ -2097,7 +2106,7 @@ void JavaThread::add_oop_handles_for_release() {
new_head->add(_threadObj);
new_head->add(_vthread);
new_head->add(_jvmti_vthread);
new_head->add(_extentLocalCache);
new_head->add(_scopedValueCache);
_oop_handle_list = new_head;
Service_lock->notify_all();
}

View File

@ -92,7 +92,7 @@ class JavaThread: public Thread {
OopHandle _threadObj; // The Java level thread object
OopHandle _vthread; // the value returned by Thread.currentThread(): the virtual thread, if mounted, otherwise _threadObj
OopHandle _jvmti_vthread;
OopHandle _extentLocalCache;
OopHandle _scopedValueCache;
static OopStorage* _thread_oop_storage;
@ -520,8 +520,9 @@ private:
void set_threadOopHandles(oop p);
oop vthread() const;
void set_vthread(oop p);
oop extentLocalCache() const;
void set_extentLocalCache(oop p);
oop scopedValueCache() const;
void set_scopedValueCache(oop p);
void clear_scopedValueBindings();
oop jvmti_vthread() const;
void set_jvmti_vthread(oop p);
@ -744,7 +745,7 @@ private:
void clr_do_not_unlock(void) { _do_not_unlock_if_synchronized = false; }
bool do_not_unlock(void) { return _do_not_unlock_if_synchronized; }
static ByteSize extentLocalCache_offset() { return byte_offset_of(JavaThread, _extentLocalCache); }
static ByteSize scopedValueCache_offset() { return byte_offset_of(JavaThread, _scopedValueCache); }
// For assembly stub generation
static ByteSize threadObj_offset() { return byte_offset_of(JavaThread, _threadObj); }

View File

@ -884,9 +884,10 @@ void SharedRuntime::throw_StackOverflowError_common(JavaThread* current, bool de
if (StackTraceInThrowable) {
java_lang_Throwable::fill_in_stack_trace(exception);
}
// Remove the ExtentLocal cache in case we got a StackOverflowError
// while we were trying to remove ExtentLocal bindings.
current->set_extentLocalCache(NULL);
// Remove the ScopedValue bindings in case we got a
// StackOverflowError while we were trying to remove ScopedValue
// bindings.
current->clear_scopedValueBindings();
// Increment counter for hs_err file reporting
Atomic::inc(&Exceptions::_stack_overflow_errors);
throw_and_post_jvmti_exception(current, exception);

View File

@ -145,12 +145,12 @@ void compiledVFrame::update_deferred_value(BasicType type, int index, jvalue val
// original update is kept.
void compiledVFrame::create_deferred_updates_after_object_deoptimization() {
// locals
GrowableArray<ScopeValue*>* extentLocals = scope()->locals();
GrowableArray<ScopeValue*>* scopedValues = scope()->locals();
StackValueCollection* lcls = locals();
if (lcls != NULL) {
for (int i2 = 0; i2 < lcls->size(); i2++) {
StackValue* var = lcls->at(i2);
if (var->type() == T_OBJECT && extentLocals->at(i2)->is_object()) {
if (var->type() == T_OBJECT && scopedValues->at(i2)->is_object()) {
jvalue val;
val.l = cast_from_oop<jobject>(lcls->at(i2)->get_obj()());
update_local(T_OBJECT, i2, val);

View File

@ -710,7 +710,7 @@
nonstatic_field(JavaThread, _threadObj, OopHandle) \
nonstatic_field(JavaThread, _vthread, OopHandle) \
nonstatic_field(JavaThread, _jvmti_vthread, OopHandle) \
nonstatic_field(JavaThread, _extentLocalCache, OopHandle) \
nonstatic_field(JavaThread, _scopedValueCache, OopHandle) \
nonstatic_field(JavaThread, _anchor, JavaFrameAnchor) \
nonstatic_field(JavaThread, _vm_result, oop) \
nonstatic_field(JavaThread, _vm_result_2, Metadata*) \

View File

@ -160,8 +160,14 @@ void Exceptions::_throw(JavaThread* thread, const char* file, int line, Handle h
return;
}
if (h_exception->is_a(vmClasses::OutOfMemoryError_klass())) {
count_out_of_memory_exceptions(h_exception);
if (h_exception->is_a(vmClasses::VirtualMachineError_klass())) {
// Remove the ScopedValue bindings in case we got a virtual machine
// Error while we were trying to manipulate ScopedValue bindings.
thread->clear_scopedValueBindings();
if (h_exception->is_a(vmClasses::OutOfMemoryError_klass())) {
count_out_of_memory_exceptions(h_exception);
}
}
if (h_exception->is_a(vmClasses::LinkageError_klass())) {

View File

@ -86,6 +86,7 @@ import jdk.internal.vm.Continuation;
import jdk.internal.vm.ContinuationScope;
import jdk.internal.vm.StackableScope;
import jdk.internal.vm.ThreadContainer;
import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.IntrinsicCandidate;
import jdk.internal.vm.annotation.Stable;
import jdk.internal.vm.annotation.ChangesCurrentThread;
@ -2578,20 +2579,29 @@ public final class System {
return ((ThreadLocal<?>)local).isCarrierThreadLocalPresent();
}
public Object[] extentLocalCache() {
return Thread.extentLocalCache();
public Object[] scopedValueCache() {
return Thread.scopedValueCache();
}
public void setExtentLocalCache(Object[] cache) {
Thread.setExtentLocalCache(cache);
public void setScopedValueCache(Object[] cache) {
Thread.setScopedValueCache(cache);
}
public Object extentLocalBindings() {
return Thread.extentLocalBindings();
public Object scopedValueBindings() {
return Thread.scopedValueBindings();
}
public void setExtentLocalBindings(Object bindings) {
Thread.setExtentLocalBindings(bindings);
public Object findScopedValueBindings() {
return Thread.findScopedValueBindings();
}
public void setScopedValueBindings(Object bindings) {
Thread.setScopedValueBindings(bindings);
}
@ForceInline
public void ensureMaterializedForStackWalk(Object value) {
Thread.ensureMaterializedForStackWalk(value);
}
public Continuation getContinuation(Thread thread) {

View File

@ -25,6 +25,7 @@
package java.lang;
import java.lang.ref.Reference;
import java.lang.reflect.Field;
import java.security.AccessController;
import java.security.AccessControlContext;
@ -49,9 +50,11 @@ import jdk.internal.misc.VM;
import jdk.internal.reflect.CallerSensitive;
import jdk.internal.reflect.Reflection;
import jdk.internal.vm.Continuation;
import jdk.internal.vm.ExtentLocalContainer;
import jdk.internal.vm.ScopedValueContainer;
import jdk.internal.vm.StackableScope;
import jdk.internal.vm.ThreadContainer;
import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.Hidden;
import jdk.internal.vm.annotation.IntrinsicCandidate;
import sun.nio.ch.Interruptible;
import sun.security.util.SecurityConstants;
@ -279,34 +282,44 @@ public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap inheritableThreadLocals;
/*
* Extent locals binding are maintained by the ExtentLocal class.
* Scoped value bindings are maintained by the ScopedValue class.
*/
private Object extentLocalBindings;
private Object scopedValueBindings;
static Object extentLocalBindings() {
return currentThread().extentLocalBindings;
// Special value to indicate this is a newly-created Thread
// Note that his must match the declaration in ScopedValue.
private static final Object NEW_THREAD_BINDINGS = Thread.class;
static Object scopedValueBindings() {
return currentThread().scopedValueBindings;
}
static void setExtentLocalBindings(Object bindings) {
currentThread().extentLocalBindings = bindings;
static void setScopedValueBindings(Object bindings) {
currentThread().scopedValueBindings = bindings;
}
/**
* Inherit the extent-local bindings from the given container.
* Search the stack for the most recent scoped-value bindings.
*/
@IntrinsicCandidate
static native Object findScopedValueBindings();
/**
* Inherit the scoped-value bindings from the given container.
* Invoked when starting a thread.
*/
void inheritExtentLocalBindings(ThreadContainer container) {
ExtentLocalContainer.BindingsSnapshot snapshot;
void inheritScopedValueBindings(ThreadContainer container) {
ScopedValueContainer.BindingsSnapshot snapshot;
if (container.owner() != null
&& (snapshot = container.extentLocalBindings()) != null) {
&& (snapshot = container.scopedValueBindings()) != null) {
// bindings established for running/calling an operation
Object bindings = snapshot.extentLocalBindings();
if (currentThread().extentLocalBindings != bindings) {
StructureViolationExceptions.throwException("Extent local bindings have changed");
Object bindings = snapshot.scopedValueBindings();
if (currentThread().scopedValueBindings != bindings) {
StructureViolationExceptions.throwException("Scoped value bindings have changed");
}
this.extentLocalBindings = bindings;
this.scopedValueBindings = bindings;
}
}
@ -393,13 +406,16 @@ public class Thread implements Runnable {
@IntrinsicCandidate
native void setCurrentThread(Thread thread);
// ExtentLocal support:
// ScopedValue support:
@IntrinsicCandidate
static native Object[] extentLocalCache();
static native Object[] scopedValueCache();
@IntrinsicCandidate
static native void setExtentLocalCache(Object[] cache);
static native void setScopedValueCache(Object[] cache);
@IntrinsicCandidate
static native void ensureMaterializedForStackWalk(Object o);
/**
* A hint to the scheduler that the current thread is willing to yield
@ -728,6 +744,10 @@ public class Thread implements Runnable {
this.contextClassLoader = ClassLoader.getSystemClassLoader();
}
}
// Special value to indicate this is a newly-created Thread
// Note that his must match the declaration in ScopedValue.
this.scopedValueBindings = NEW_THREAD_BINDINGS;
}
/**
@ -767,6 +787,9 @@ public class Thread implements Runnable {
this.contextClassLoader = ClassLoader.getSystemClassLoader();
}
// Special value to indicate this is a newly-created Thread
this.scopedValueBindings = NEW_THREAD_BINDINGS;
// create a FieldHolder object, needed when bound to an OS thread
if (bound) {
ThreadGroup g = Constants.VTHREAD_GROUP;
@ -1564,8 +1587,8 @@ public class Thread implements Runnable {
boolean started = false;
container.onStart(this); // may throw
try {
// extent locals may be inherited
inheritExtentLocalBindings(container);
// scoped values may be inherited
inheritScopedValueBindings(container);
start0();
started = true;
@ -1596,10 +1619,24 @@ public class Thread implements Runnable {
public void run() {
Runnable task = holder.task;
if (task != null) {
task.run();
Object bindings = scopedValueBindings();
runWith(bindings, task);
}
}
/**
* The VM recognizes this method as special, so any changes to the
* name or signature require corresponding changes in
* JVM_FindScopedValueBindings().
*/
@Hidden
@ForceInline
private void runWith(Object bindings, Runnable op) {
ensureMaterializedForStackWalk(bindings);
op.run();
Reference.reachabilityFence(bindings);
}
/**
* Null out reference after Thread termination.
*/

View File

@ -24,6 +24,7 @@
*/
package java.lang;
import java.lang.ref.Reference;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Locale;
@ -53,6 +54,8 @@ import jdk.internal.vm.StackableScope;
import jdk.internal.vm.ThreadContainer;
import jdk.internal.vm.ThreadContainers;
import jdk.internal.vm.annotation.ChangesCurrentThread;
import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.Hidden;
import jdk.internal.vm.annotation.JvmtiMountTransition;
import sun.nio.ch.Interruptible;
import sun.security.action.GetPropertyAction;
@ -283,13 +286,13 @@ final class VirtualThread extends BaseVirtualThread {
event.commit();
}
Object bindings = scopedValueBindings();
try {
task.run();
runWith(bindings, task);
} catch (Throwable exc) {
dispatchUncaughtException(exc);
} finally {
try {
// pop any remaining scopes from the stack, this may block
StackableScope.popAll();
@ -311,6 +314,14 @@ final class VirtualThread extends BaseVirtualThread {
}
}
@Hidden
@ForceInline
private void runWith(Object bindings, Runnable op) {
ensureMaterializedForStackWalk(bindings);
op.run();
Reference.reachabilityFence(bindings);
}
/**
* Mounts this virtual thread onto the current platform thread. On
* return, the current thread is the virtual thread.
@ -488,8 +499,8 @@ final class VirtualThread extends BaseVirtualThread {
boolean started = false;
container.onStart(this); // may throw
try {
// extent locals may be inherited
inheritExtentLocalBindings(container);
// scoped values may be inherited
inheritScopedValueBindings(container);
// submit task to run thread
submitRunContinuation();

View File

@ -401,7 +401,7 @@ public final class ThreadLocalRandom extends Random {
= new AtomicLong(RandomSupport.mixMurmur64(System.currentTimeMillis()) ^
RandomSupport.mixMurmur64(System.nanoTime()));
// used by ExtentLocal
// used by ScopedValue
private static class Access {
static {
SharedSecrets.setJavaUtilConcurrentTLRAccess(

View File

@ -476,24 +476,28 @@ public interface JavaLangAccess {
boolean isCarrierThreadLocalPresent(CarrierThreadLocal<?> local);
/**
* Returns the current thread's extent locals cache
* Returns the current thread's scoped values cache
*/
Object[] extentLocalCache();
Object[] scopedValueCache();
/**
* Sets the current thread's extent locals cache
* Sets the current thread's scoped values cache
*/
void setExtentLocalCache(Object[] cache);
void setScopedValueCache(Object[] cache);
/**
* Return the current thread's extent local bindings.
* Return the current thread's scoped value bindings.
*/
Object extentLocalBindings();
Object scopedValueBindings();
/**
* Set the current thread's extent local bindings.
* Set the current thread's scoped value bindings.
*/
void setExtentLocalBindings(Object bindings);
void setScopedValueBindings(Object bindings);
Object findScopedValueBindings();
void ensureMaterializedForStackWalk(Object value);
/**
* Returns the innermost mounted continuation

View File

@ -35,7 +35,7 @@ import java.util.concurrent.locks.LockSupport;
import java.util.stream.Stream;
import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.vm.ExtentLocalContainer;
import jdk.internal.vm.ScopedValueContainer;
import jdk.internal.vm.ThreadContainer;
import jdk.internal.vm.ThreadContainers;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
@ -99,7 +99,7 @@ public class ThreadFlock implements AutoCloseable {
private volatile int threadCount;
private final String name;
private final ExtentLocalContainer.BindingsSnapshot extentLocalBindings;
private final ScopedValueContainer.BindingsSnapshot scopedValueBindings;
private final ThreadContainerImpl container; // encapsulate for now
// state
@ -111,7 +111,7 @@ public class ThreadFlock implements AutoCloseable {
ThreadFlock(String name) {
this.name = name;
this.extentLocalBindings = ExtentLocalContainer.captureBindings();
this.scopedValueBindings = ScopedValueContainer.captureBindings();
this.container = new ThreadContainerImpl(this);
}
@ -119,8 +119,8 @@ public class ThreadFlock implements AutoCloseable {
return threadCount;
}
private ExtentLocalContainer.BindingsSnapshot extentLocalBindings() {
return extentLocalBindings;
private ScopedValueContainer.BindingsSnapshot scopedValueBindings() {
return scopedValueBindings;
}
private void incrementThreadCount() {
@ -210,7 +210,7 @@ public class ThreadFlock implements AutoCloseable {
* Opens a new thread flock. The flock is owned by the current thread. It can be
* named to aid debugging.
*
* <p> This method captures the current thread's {@linkplain ExtentLocal extent-local}
* <p> This method captures the current thread's {@linkplain ScopedValue scoped value}
* bindings for inheritance by threads created in the flock.
*
* <p> For the purposes of containment, monitoring, and debugging, the parent
@ -250,7 +250,7 @@ public class ThreadFlock implements AutoCloseable {
/**
* Starts the given unstarted thread in this flock.
*
* <p> The thread is started with the extent-local bindings that were captured
* <p> The thread is started with the scoped value bindings that were captured
* when opening the flock. The bindings must match the current thread's bindings.
*
* <p> This method may only be invoked by the flock owner or threads {@linkplain
@ -263,7 +263,7 @@ public class ThreadFlock implements AutoCloseable {
* @throws WrongThreadException if the current thread is not the owner or a thread
* contained in the flock
* @throws jdk.incubator.concurrent.StructureViolationException if the current
* extent-local bindings are not the same as when the flock was created
* scoped value bindings are not the same as when the flock was created
*/
public Thread start(Thread thread) {
ensureOwnerOrContainsThread();
@ -398,12 +398,11 @@ public class ThreadFlock implements AutoCloseable {
* <p> A ThreadFlock is intended to be used in a <em>structured manner</em>. If
* this method is called to close a flock before nested flocks are closed then it
* closes the nested flocks (in the reverse order that they were created in),
* closes this flock, and then throws {@link
* jdk.incubator.concurrent.StructureViolationException}.
* Similarly, if called to close a flock that <em>encloses</em> {@linkplain
* jdk.incubator.concurrent.ExtentLocal.Carrier#run(Runnable) operations} with
* extent-local bindings then it also throws {@code StructureViolationException}
* after closing the flock.
* closes this flock, and then throws {@code StructureViolationException}.
* Similarly, if this method is called to close a thread flock while executing with
* scoped value bindings, and the thread flock was created before the scoped values
* were bound, then {@code StructureViolationException} is thrown after closing the
* thread flock.
*
* @throws WrongThreadException if invoked by a thread that is not the owner
* @throws jdk.incubator.concurrent.StructureViolationException if a structure
@ -585,8 +584,8 @@ public class ThreadFlock implements AutoCloseable {
return flock.toString();
}
@Override
public ExtentLocalContainer.BindingsSnapshot extentLocalBindings() {
return flock.extentLocalBindings();
public ScopedValueContainer.BindingsSnapshot scopedValueBindings() {
return flock.scopedValueBindings();
}
}
}

View File

@ -46,7 +46,7 @@ import jdk.internal.access.SharedSecrets;
*/
public class Continuation {
private static final Unsafe U = Unsafe.getUnsafe();
private static final boolean PRESERVE_EXTENT_LOCAL_CACHE;
private static final boolean PRESERVE_SCOPED_VALUE_CACHE;
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
static {
ContinuationSupport.ensureSupported();
@ -54,8 +54,8 @@ public class Continuation {
StackChunk.init(); // ensure StackChunk class is initialized
String value = GetPropertyAction.privilegedGetProperty("jdk.preserveExtentLocalCache");
PRESERVE_EXTENT_LOCAL_CACHE = (value == null) || Boolean.parseBoolean(value);
String value = GetPropertyAction.privilegedGetProperty("jdk.preserveScopedValueCache");
PRESERVE_SCOPED_VALUE_CACHE = (value == null) || Boolean.parseBoolean(value);
}
private static final VarHandle MOUNTED;
@ -129,7 +129,7 @@ public class Continuation {
private Object yieldInfo;
private boolean preempted;
private Object[] extentLocalCache;
private Object[] scopedValueCache;
/**
* Constructs a continuation
@ -238,7 +238,7 @@ public class Continuation {
public final void run() {
while (true) {
mount();
JLA.setExtentLocalCache(extentLocalCache);
JLA.setScopedValueCache(scopedValueCache);
if (done)
throw new IllegalStateException("Continuation terminated");
@ -270,12 +270,12 @@ public class Continuation {
postYieldCleanup();
unmount();
if (PRESERVE_EXTENT_LOCAL_CACHE) {
extentLocalCache = JLA.extentLocalCache();
if (PRESERVE_SCOPED_VALUE_CACHE) {
scopedValueCache = JLA.scopedValueCache();
} else {
extentLocalCache = null;
scopedValueCache = null;
}
JLA.setExtentLocalCache(null);
JLA.setScopedValueCache(null);
} catch (Throwable e) { e.printStackTrace(); System.exit(1); }
}
// we're now in the parent continuation

View File

@ -33,26 +33,26 @@ import jdk.internal.vm.annotation.DontInline;
import jdk.internal.vm.annotation.ReservedStackAccess;
/**
* A StackableScope to represent extent-local bindings.
* A StackableScope to represent scoped-value bindings.
*
* This class defines static methods to run an operation with a ExtentLocalContainer
* on the scope stack. It also defines a method to get the latest ExtentLocalContainer
* and a method to return a snapshot of the extent local bindings.
* This class defines static methods to run an operation with a ScopedValueContainer
* on the scope stack. It also defines a method to get the latest ScopedValueContainer
* and a method to return a snapshot of the scoped value bindings.
*/
public class ExtentLocalContainer extends StackableScope {
public class ScopedValueContainer extends StackableScope {
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
static {
Unsafe.getUnsafe().ensureClassInitialized(StructureViolationExceptions.class);
}
private ExtentLocalContainer() {
private ScopedValueContainer() {
}
/**
* Returns the "latest" ExtentLocalContainer for the current Thread. This may be on
* the current thread's scope task or ma require walking up the tree to find it.
* Returns the "latest" ScopedValueContainer for the current Thread. This may be on
* the current thread's scope task or may require walking up the tree to find it.
*/
public static <T extends ExtentLocalContainer> T latest(Class<T> containerClass) {
public static <T extends ScopedValueContainer> T latest(Class<T> containerClass) {
StackableScope scope = head();
if (scope == null) {
scope = JLA.threadContainer(Thread.currentThread());
@ -69,37 +69,37 @@ public class ExtentLocalContainer extends StackableScope {
}
/**
* Returns the "latest" ExtentLocalContainer for the current Thread. This
* Returns the "latest" ScopedValueContainer for the current Thread. This
* may be on the current thread's scope task or may require walking up the
* tree to find it.
*/
public static ExtentLocalContainer latest() {
return latest(ExtentLocalContainer.class);
public static ScopedValueContainer latest() {
return latest(ScopedValueContainer.class);
}
/**
* A snapshot of the extent local bindings. The snapshot includes the bindings
* established for the current thread and extent local container.
* A snapshot of the scoped value bindings. The snapshot includes the bindings
* established for the current thread and scoped value container.
*/
public record BindingsSnapshot(Object extentLocalBindings,
ExtentLocalContainer container) { }
public record BindingsSnapshot(Object scopedValueBindings,
ScopedValueContainer container) { }
/**
* Returns the extent local bindings for the current thread.
* Returns the scoped value bindings for the current thread.
*/
public static BindingsSnapshot captureBindings() {
return new BindingsSnapshot(JLA.extentLocalBindings(), latest());
return new BindingsSnapshot(JLA.scopedValueBindings(), latest());
}
/**
* For use by ExtentLocal to run an operation in a structured context.
* For use by ScopedValue to run an operation in a structured context.
*/
public static void run(Runnable op) {
if (head() == null) {
// no need to push scope when stack is empty
runWithoutScope(op);
} else {
new ExtentLocalContainer().doRun(op);
new ScopedValueContainer().doRun(op);
}
}
@ -141,14 +141,14 @@ public class ExtentLocalContainer extends StackableScope {
}
/**
* For use by ExtentLocal to call a value returning operation in a structured context.
* For use by ScopedValue to call a value returning operation in a structured context.
*/
public static <V> V call(Callable<V> op) throws Exception {
if (head() == null) {
// no need to push scope when stack is empty
return callWithoutScope(op);
} else {
return new ExtentLocalContainer().doCall(op);
return new ScopedValueContainer().doCall(op);
}
}
@ -199,7 +199,6 @@ public class ExtentLocalContainer extends StackableScope {
* Throws {@code ex} if not null. StructureViolationException is thrown or added
* as a suppressed exception when {@code atTop} is false.
*/
@DontInline @ReservedStackAccess
private static void throwIfFailed(Throwable ex, boolean atTop) {
if (ex != null || !atTop) {
if (!atTop) {

View File

@ -89,9 +89,9 @@ public abstract class ThreadContainer extends StackableScope {
}
/**
* The extent locals captured when the thread container was created.
* The scoped values captured when the thread container was created.
*/
public ExtentLocalContainer.BindingsSnapshot extentLocalBindings() {
public ScopedValueContainer.BindingsSnapshot scopedValueBindings() {
return null;
}
}

View File

@ -167,6 +167,7 @@ module java.base {
jdk.jlink,
jdk.jfr,
jdk.net,
jdk.incubator.concurrent,
jdk.sctp,
jdk.crypto.cryptoki;
exports jdk.internal.foreign to
@ -247,12 +248,14 @@ module java.base {
jdk.unsupported;
exports jdk.internal.vm to
java.management,
jdk.incubator.concurrent,
jdk.internal.jvmstat,
jdk.management,
jdk.management.agent;
exports jdk.internal.vm.annotation to
java.instrument,
jdk.internal.vm.ci,
jdk.incubator.concurrent,
jdk.incubator.vector,
jdk.jfr,
jdk.unsupported;
@ -307,7 +310,8 @@ module java.base {
exports sun.security.action to
java.desktop,
java.security.jgss,
jdk.crypto.ec;
jdk.crypto.ec,
jdk.incubator.concurrent;
exports sun.security.internal.interfaces to
jdk.crypto.cryptoki;
exports sun.security.internal.spec to

View File

@ -50,9 +50,12 @@ static JNINativeMethod methods[] = {
{"dumpThreads", "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
{"getStackTrace0", "()" OBJ, (void *)&JVM_GetStackTrace},
{"setNativeName", "(" STR ")V", (void *)&JVM_SetNativeThreadName},
{"extentLocalCache", "()[" OBJ, (void *)&JVM_ExtentLocalCache},
{"setExtentLocalCache", "([" OBJ ")V",(void *)&JVM_SetExtentLocalCache},
{"getNextThreadIdOffset", "()J", (void *)&JVM_GetNextThreadIdOffset}
{"scopedValueCache", "()[" OBJ, (void *)&JVM_ScopedValueCache},
{"setScopedValueCache", "([" OBJ ")V",(void *)&JVM_SetScopedValueCache},
{"getNextThreadIdOffset", "()J", (void *)&JVM_GetNextThreadIdOffset},
{"findScopedValueBindings", "()" OBJ, (void *)&JVM_FindScopedValueBindings},
{"ensureMaterializedForStackWalk",
"(" OBJ ")V", (void*)&JVM_EnsureMaterializedForStackWalk_func},
};
#undef THD

View File

@ -0,0 +1,838 @@
/*
* Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2020, 2022, Red Hat Inc.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.incubator.concurrent;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.lang.ref.Reference;
import java.util.concurrent.Callable;
import java.util.function.Supplier;
import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.JavaUtilConcurrentTLRAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.Hidden;
import jdk.internal.vm.annotation.Stable;
import jdk.internal.vm.ScopedValueContainer;
import sun.security.action.GetPropertyAction;
/**
* A value that is set once and is then available for reading for a bounded period of
* execution by a thread. A {@code ScopedValue} allows for safely and efficiently sharing
* data for a bounded period of execution without passing the data as method arguments.
*
* <p> {@code ScopedValue} defines the {@link #where(ScopedValue, Object, Runnable)}
* method to set the value of a {@code ScopedValue} for the bouned period of execution by
* a thread of the runnable's {@link Runnable#run() run} method. The unfolding execution of
* the methods executed by {@code run} defines a <b><em>dynamic scope</em></b>. The scoped
* value is {@linkplain #isBound() bound} while executing in the dynamic scope, it reverts
* to being <em>unbound</em> when the {@code run} method completes (normally or with an
* exception). Code executing in the dynamic scope uses the {@code ScopedValue} {@link
* #get() get} method to read its value.
*
* <p> Like a {@linkplain ThreadLocal thread-local variable}, a scoped value has multiple
* incarnations, one per thread. The particular incarnation that is used depends on which
* thread calls its methods.
*
* <p> Consider the following example with a scoped value {@code USERNAME} that is
* <em>bound</em> to the value "{@code duke}" for the execution, by a thread, of a run
* method that invokes {@code doSomething()}.
* {@snippet lang=java :
* // @link substring="newInstance" target="#newInstance" :
* private static final ScopedValue<String> USERNAME = ScopedValue.newInstance();
*
* ScopedValue.where(USERNAME, "duke", () -> doSomething());
* }
* Code executed directly or indirectly by {@code doSomething()} that invokes {@code
* USERNAME.get()} will read the value "{@code duke}". The scoped value is bound while
* executing {@code doSomething()} and becomes unbound when {@code doSomething()}
* completes (normally or with an exception). If one thread were to call {@code
* doSomething()} with {@code USERNAME} bound to "{@code duke1}", and another thread
* were to call the method with {@code USERNAME} bound to "{@code duke2}", then
* {@code USERNAME.get()} would read the value "{@code duke1}" or "{@code duke2}",
* depending on which thread is executing.
*
* <p> In addition to the {@code where} method that executes a {@code run} method, {@code
* ScopedValue} defines the {@link #where(ScopedValue, Object, Callable)} method to execute
* a method that returns a result. It also defines the {@link #where(ScopedValue, Object)}
* method for cases where it is useful to accumulate mappings of {@code ScopedValue} to
* value.
*
* <p> A {@code ScopedValue} will typically be declared in a {@code final} and {@code
* static} field. The accessibility of the field will determine which components can
* bind or read its value.
*
* <p> Unless otherwise specified, passing a {@code null} argument to a method in this
* class will cause a {@link NullPointerException} to be thrown.
*
* <h2><a id="rebind">Rebinding</a></h2>
*
* The {@code ScopedValue} API allows a new binding to be established for <em>nested
* dynamic scopes</em>. This is known as <em>rebinding</em>. A {@code ScopedValue} that
* is bound to some value may be bound to a new value for the bounded execution of some
* method. The unfolding execution of code executed by that method defines the nested
* dynamic scope. When the method completes (normally or with an exception), the value of
* the {@code ScopedValue} reverts to its previous value.
*
* <p> In the above example, suppose that code executed by {@code doSomething()} binds
* {@code USERNAME} to a new value with:
* {@snippet lang=java :
* ScopedValue.where(USERNAME, "duchess", () -> doMore());
* }
* Code executed directly or indirectly by {@code doMore()} that invokes {@code
* USERNAME.get()} will read the value "{@code duchess}". When {@code doMore()} completes
* (normally or with an exception), the value of {@code USERNAME} reverts to
* "{@code duke}".
*
* <h2><a id="inheritance">Inheritance</a></h2>
*
* {@code ScopedValue} supports sharing data across threads. This sharing is limited to
* structured cases where child threads are started and terminate within the bounded
* period of execution by a parent thread. More specifically, when using a {@link
* StructuredTaskScope}, scoped value bindings are <em>captured</em> when creating a
* {@code StructuredTaskScope} and inherited by all threads started in that scope with
* the {@link StructuredTaskScope#fork(Callable) fork} method.
*
* <p> In the following example, the {@code ScopedValue} {@code USERNAME} is bound to the
* value "{@code duke}" for the execution of a runnable operation. The code in the {@code
* run} method creates a {@code StructuredTaskScope} and forks three child threads. Code
* executed directly or indirectly by these threads running {@code childTask1()},
* {@code childTask2()}, and {@code childTask3()} will read the value "{@code duke}".
*
* {@snippet lang=java :
* private static final ScopedValue<String> USERNAME = ScopedValue.newInstance();
* ScopedValue.where(USERNAME, "duke", () -> {
* try (var scope = new StructuredTaskScope<String>()) {
*
* scope.fork(() -> childTask1());
* scope.fork(() -> childTask2());
* scope.fork(() -> childTask3());
*
* ...
* }
* });
* }
*
* @implNote
* Scoped values are designed to be used in fairly small
* numbers. {@link #get} initially performs a search through enclosing
* scopes to find a scoped value's innermost binding. It
* then caches the result of the search in a small thread-local
* cache. Subsequent invocations of {@link #get} for that scoped value
* will almost always be very fast. However, if a program has many
* scoped values that it uses cyclically, the cache hit rate
* will be low and performance will be poor. This design allows
* scoped-value inheritance by {@link StructuredTaskScope} threads to
* be very fast: in essence, no more than copying a pointer, and
* leaving a scoped-value binding also requires little more than
* updating a pointer.
*
* <p>Because the scoped-value per-thread cache is small, clients
* should minimize the number of bound scoped values in use. For
* example, if it is necessary to pass a number of values in this way,
* it makes sense to create a record class to hold those values, and
* then bind a single {@code ScopedValue} to an instance of that record.
*
* <p>For this incubator release, the reference implementation
* provides some system properties to tune the performance of scoped
* values.
*
* <p>The system property {@code jdk.incubator.concurrent.ScopedValue.cacheSize}
* controls the size of the (per-thread) scoped-value cache. This cache is crucial
* for the performance of scoped values. If it is too small,
* the runtime library will repeatedly need to scan for each
* {@link #get}. If it is too large, memory will be unnecessarily
* consumed. The default scoped-value cache size is 16 entries. It may
* be varied from 2 to 16 entries in size. {@code ScopedValue.cacheSize}
* must be an integer power of 2.
*
* <p>For example, you could use {@code -Djdk.incubator.concurrent.ScopedValue.cacheSize=8}.
*
* <p>The other system property is {@code jdk.preserveScopedValueCache}.
* This property determines whether the per-thread scoped-value
* cache is preserved when a virtual thread is blocked. By default
* this property is set to {@code true}, meaning that every virtual
* thread preserves its scoped-value cache when blocked. Like {@code
* ScopedValue.cacheSize}, this is a space versus speed trade-off: in
* situations where many virtual threads are blocked most of the time,
* setting this property to {@code false} might result in a useful
* memory saving, but each virtual thread's scoped-value cache would
* have to be regenerated after a blocking operation.
*
* @param <T> the type of the object bound to this {@code ScopedValue}
* @since 20
*/
public final class ScopedValue<T> {
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
private final @Stable int hash;
@Override
public int hashCode() { return hash; }
/**
* An immutable map from {@code ScopedValue} to values.
*
* <p> Unless otherwise specified, passing a {@code null} argument to a constructor
* or method in this class will cause a {@link NullPointerException} to be thrown.
*/
static final class Snapshot {
final Snapshot prev;
final Carrier bindings;
final int bitmask;
private static final Object NIL = new Object();
static final Snapshot EMPTY_SNAPSHOT = new Snapshot();
Snapshot(Carrier bindings, Snapshot prev) {
this.prev = prev;
this.bindings = bindings;
this.bitmask = bindings.bitmask | prev.bitmask;
}
protected Snapshot() {
this.prev = null;
this.bindings = null;
this.bitmask = 0;
}
Object find(ScopedValue<?> key) {
int bits = key.bitmask();
for (Snapshot snapshot = this;
containsAll(snapshot.bitmask, bits);
snapshot = snapshot.prev) {
for (Carrier carrier = snapshot.bindings;
carrier != null && containsAll(carrier.bitmask, bits);
carrier = carrier.prev) {
if (carrier.getKey() == key) {
Object value = carrier.get();
return value;
}
}
}
return NIL;
}
}
/**
* A mapping of scoped values, as <em>keys</em>, to values.
*
* <p> A {@code Carrier} is used to accumlate mappings so that an operation (a
* {@link Runnable} or {@link Callable}) can be executed with all scoped values in the
* mapping bound to values. The following example runs an operation with {@code k1}
* bound (or rebound) to {@code v1}, and {@code k2} bound (or rebound) to {@code v2}.
* {@snippet lang=java :
* // @link substring="where" target="#where(ScopedValue, Object)" :
* ScopedValue.where(k1, v1).where(k2, v2).run(() -> ... );
* }
*
* <p> A {@code Carrier} is immutable and thread-safe. The {@link
* #where(ScopedValue, Object) where} method returns a new {@code Carrier} object,
* it does not mutate an existing mapping.
*
* <p> Unless otherwise specified, passing a {@code null} argument to a method in
* this class will cause a {@link NullPointerException} to be thrown.
*
* @since 20
*/
public static final class Carrier {
// Bit masks: a 1 in postion n indicates that this set of bound values
// hits that slot in the cache.
final int bitmask;
final ScopedValue<?> key;
final Object value;
final Carrier prev;
Carrier(ScopedValue<?> key, Object value, Carrier prev) {
this.key = key;
this.value = value;
this.prev = prev;
int bits = key.bitmask();
if (prev != null) {
bits |= prev.bitmask;
}
this.bitmask = bits;
}
/**
* Add a binding to this map, returning a new Carrier instance.
*/
private static final <T> Carrier where(ScopedValue<T> key, T value,
Carrier prev) {
return new Carrier(key, value, prev);
}
/**
* Returns a new {@code Carrier} with the mappings from this carrier plus a
* new mapping from {@code key} to {@code value}. If this carrier already has a
* mapping for the scoped value {@code key} then it will map to the new
* {@code value}. The current carrier is immutable, so it is not changed by this
* method.
*
* @param key the {@code ScopedValue} key
* @param value the value, can be {@code null}
* @param <T> the type of the value
* @return a new {@code Carrier} with the mappings from this carrier plus the new mapping
*/
public <T> Carrier where(ScopedValue<T> key, T value) {
return where(key, value, this);
}
/*
* Return a new set consisting of a single binding.
*/
static <T> Carrier of(ScopedValue<T> key, T value) {
return where(key, value, null);
}
final Object get() {
return value;
}
final ScopedValue<?> getKey() {
return key;
}
/**
* Returns the value of a {@link ScopedValue} in this mapping.
*
* @param key the {@code ScopedValue} key
* @param <T> the type of the value
* @return the value
* @throws NoSuchElementException if the key is not present in this mapping
*/
@SuppressWarnings("unchecked")
public <T> T get(ScopedValue<T> key) {
var bits = key.bitmask();
for (Carrier carrier = this;
carrier != null && containsAll(carrier.bitmask, bits);
carrier = carrier.prev) {
if (carrier.getKey() == key) {
Object value = carrier.get();
return (T)value;
}
}
throw new NoSuchElementException();
}
/**
* Calls a value-returning operation with each scoped value in this mapping bound
* to its value in the current thread.
* When the operation completes (normally or with an exception), each scoped value
* in the mapping will revert to being unbound, or revert to its previous value
* when previously bound, in the current thread.
*
* <p> Scoped values are intended to be used in a <em>structured manner</em>.
* If {@code op} creates a {@link StructuredTaskScope} but does not {@linkplain
* StructuredTaskScope#close() close} it, then exiting {@code op} causes the
* underlying construct of each {@code StructuredTaskScope} created in the
* dynamic scope to be closed. This may require blocking until all child threads
* have completed their sub-tasks. The closing is done in the reverse order that
* they were created. Once closed, {@link StructureViolationException} is thrown.
*
* @param op the operation to run
* @param <R> the type of the result of the operation
* @return the result
* @throws Exception if {@code op} completes with an exception
* @see ScopedValue#where(ScopedValue, Object, Callable)
*/
public <R> R call(Callable<? extends R> op) throws Exception {
Objects.requireNonNull(op);
Cache.invalidate(bitmask);
var prevSnapshot = scopedValueBindings();
var newSnapshot = new Snapshot(this, prevSnapshot);
return runWith(newSnapshot, op);
}
/**
* Execute the action with a set of ScopedValue bindings.
*
* The VM recognizes this method as special, so any changes to the
* name or signature require corresponding changes in
* JVM_FindScopedValueBindings().
*/
@Hidden
@ForceInline
private <R> R runWith(Snapshot newSnapshot, Callable<R> op) throws Exception {
try {
JLA.setScopedValueBindings(newSnapshot);
JLA.ensureMaterializedForStackWalk(newSnapshot);
return ScopedValueContainer.call(op);
} finally {
Reference.reachabilityFence(newSnapshot);
JLA.setScopedValueBindings(newSnapshot.prev);
Cache.invalidate(bitmask);
}
}
/**
* Runs an operation with each scoped value in this mapping bound to its value
* in the current thread.
* When the operation completes (normally or with an exception), each scoped value
* in the mapping will revert to being unbound, or revert to its previous value
* when previously bound, in the current thread.
*
* <p> Scoped values are intended to be used in a <em>structured manner</em>.
* If {@code op} creates a {@link StructuredTaskScope} but does not {@linkplain
* StructuredTaskScope#close() close} it, then exiting {@code op} causes the
* underlying construct of each {@code StructuredTaskScope} created in the
* dynamic scope to be closed. This may require blocking until all child threads
* have completed their sub-tasks. The closing is done in the reverse order that
* they were created. Once closed, {@link StructureViolationException} is thrown.
*
* @param op the operation to run
* @see ScopedValue#where(ScopedValue, Object, Runnable)
*/
public void run(Runnable op) {
Objects.requireNonNull(op);
Cache.invalidate(bitmask);
var prevSnapshot = scopedValueBindings();
var newSnapshot = new Snapshot(this, prevSnapshot);
runWith(newSnapshot, op);
}
/**
* Execute the action with a set of {@code ScopedValue} bindings.
*
* The VM recognizes this method as special, so any changes to the
* name or signature require corresponding changes in
* JVM_FindScopedValueBindings().
*/
@Hidden
@ForceInline
private void runWith(Snapshot newSnapshot, Runnable op) {
try {
JLA.setScopedValueBindings(newSnapshot);
JLA.ensureMaterializedForStackWalk(newSnapshot);
ScopedValueContainer.run(op);
} finally {
Reference.reachabilityFence(newSnapshot);
JLA.setScopedValueBindings(newSnapshot.prev);
Cache.invalidate(bitmask);
}
}
}
/**
* Creates a new {@code Carrier} with a single mapping of a {@code ScopedValue}
* <em>key</em> to a value. The {@code Carrier} can be used to accumlate mappings so
* that an operation can be executed with all scoped values in the mapping bound to
* values. The following example runs an operation with {@code k1} bound (or rebound)
* to {@code v1}, and {@code k2} bound (or rebound) to {@code v2}.
* {@snippet lang=java :
* // @link substring="run" target="Carrier#run(Runnable)" :
* ScopedValue.where(k1, v1).where(k2, v2).run(() -> ... );
* }
*
* @param key the {@code ScopedValue} key
* @param value the value, can be {@code null}
* @param <T> the type of the value
* @return a new {@code Carrier} with a single mapping
*/
public static <T> Carrier where(ScopedValue<T> key, T value) {
return Carrier.of(key, value);
}
/**
* Calls a value-returning operation with a {@code ScopedValue} bound to a value
* in the current thread. When the operation completes (normally or with an
* exception), the {@code ScopedValue} will revert to being unbound, or revert to
* its previous value when previously bound, in the current thread.
*
* <p> Scoped values are intended to be used in a <em>structured manner</em>.
* If {@code op} creates a {@link StructuredTaskScope} but does not {@linkplain
* StructuredTaskScope#close() close} it, then exiting {@code op} causes the
* underlying construct of each {@code StructuredTaskScope} created in the
* dynamic scope to be closed. This may require blocking until all child threads
* have completed their sub-tasks. The closing is done in the reverse order that
* they were created. Once closed, {@link StructureViolationException} is thrown.
*
* @implNote
* This method is implemented to be equivalent to:
* {@snippet lang=java :
* // @link substring="call" target="Carrier#call(Callable)" :
* ScopedValue.where(key, value).call(op);
* }
*
* @param key the {@code ScopedValue} key
* @param value the value, can be {@code null}
* @param <T> the type of the value
* @param <R> the result type
* @param op the operation to call
* @return the result
* @throws Exception if the operation completes with an exception
*/
public static <T, R> R where(ScopedValue<T> key,
T value,
Callable<? extends R> op) throws Exception {
return where(key, value).call(op);
}
/**
* Run an operation with a {@code ScopedValue} bound to a value in the current
* thread. When the operation completes (normally or with an exception), the
* {@code ScopedValue} will revert to being unbound, or revert to its previous value
* when previously bound, in the current thread.
*
* <p> Scoped values are intended to be used in a <em>structured manner</em>.
* If {@code op} creates a {@link StructuredTaskScope} but does not {@linkplain
* StructuredTaskScope#close() close} it, then exiting {@code op} causes the
* underlying construct of each {@code StructuredTaskScope} created in the
* dynamic scope to be closed. This may require blocking until all child threads
* have completed their sub-tasks. The closing is done in the reverse order that
* they were created. Once closed, {@link StructureViolationException} is thrown.
*
* @implNote
* This method is implemented to be equivalent to:
* {@snippet lang=java :
* // @link substring="run" target="Carrier#run(Runnable)" :
* ScopedValue.where(key, value).run(op);
* }
*
* @param key the {@code ScopedValue} key
* @param value the value, can be {@code null}
* @param <T> the type of the value
* @param op the operation to call
*/
public static <T> void where(ScopedValue<T> key, T value, Runnable op) {
where(key, value).run(op);
}
private ScopedValue() {
this.hash = generateKey();
}
/**
* Creates a scoped value that is initially unbound for all threads.
*
* @param <T> the type of the value
* @return a new {@code ScopedValue}
*/
public static <T> ScopedValue<T> newInstance() {
return new ScopedValue<T>();
}
/**
* {@return the value of the scoped value if bound in the current thread}
*
* @throws NoSuchElementException if the scoped value is not bound
*/
@ForceInline
@SuppressWarnings("unchecked")
public T get() {
Object[] objects;
if ((objects = scopedValueCache()) != null) {
// This code should perhaps be in class Cache. We do it
// here because the generated code is small and fast and
// we really want it to be inlined in the caller.
int n = (hash & Cache.SLOT_MASK) * 2;
if (objects[n] == this) {
return (T)objects[n + 1];
}
n = ((hash >>> Cache.INDEX_BITS) & Cache.SLOT_MASK) * 2;
if (objects[n] == this) {
return (T)objects[n + 1];
}
}
return slowGet();
}
@SuppressWarnings("unchecked")
private T slowGet() {
var value = findBinding();
if (value == Snapshot.NIL) {
throw new NoSuchElementException();
}
Cache.put(this, value);
return (T)value;
}
/**
* {@return {@code true} if this scoped value is bound in the current thread}
*/
public boolean isBound() {
Object[] objects = scopedValueCache();
if (objects != null) {
int n = (hash & Cache.SLOT_MASK) * 2;
if (objects[n] == this) {
return true;
}
n = ((hash >>> Cache.INDEX_BITS) & Cache.SLOT_MASK) * 2;
if (objects[n] == this) {
return true;
}
}
var value = findBinding();
boolean result = (value != Snapshot.NIL);
if (result) Cache.put(this, value);
return result;
}
/**
* Return the value of the scoped value or NIL if not bound.
*/
private Object findBinding() {
Object value = scopedValueBindings().find(this);
return value;
}
/**
* Returns the value of this scoped value if bound in the current thread, otherwise
* returns {@code other}.
*
* @param other the value to return if not bound, can be {@code null}
* @return the value of the scoped value if bound, otherwise {@code other}
*/
public T orElse(T other) {
Object obj = findBinding();
if (obj != Snapshot.NIL) {
@SuppressWarnings("unchecked")
T value = (T) obj;
return value;
} else {
return other;
}
}
/**
* Returns the value of this scoped value if bound in the current thread, otherwise
* throws an exception produced by the exception supplying function.
*
* @param <X> the type of the exception that may be thrown
* @param exceptionSupplier the supplying function that produces the exception to throw
* @return the value of the scoped value if bound in the current thread
* @throws X if the scoped value is not bound in the current thread
*/
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
Objects.requireNonNull(exceptionSupplier);
Object obj = findBinding();
if (obj != Snapshot.NIL) {
@SuppressWarnings("unchecked")
T value = (T) obj;
return value;
} else {
throw exceptionSupplier.get();
}
}
private static Object[] scopedValueCache() {
return JLA.scopedValueCache();
}
private static void setScopedValueCache(Object[] cache) {
JLA.setScopedValueCache(cache);
}
// Special value to indicate this is a newly-created Thread
// Note that his must match the declaration in j.l.Thread.
private static final Object NEW_THREAD_BINDINGS = Thread.class;
private static Snapshot scopedValueBindings() {
// Bindings can be in one of four states:
//
// 1: class Thread: this is a new Thread instance, and no
// scoped values have ever been bound in this Thread.
// 2: EmptySnapshot.SINGLETON: This is effectively an empty binding.
// 3: A Snapshot instance: this contains one or more scoped value
// bindings.
// 4: null: there may be some bindings in this Thread, but we don't know
// where they are. We must invoke JLA.findScopedValueBindings() to walk
// the stack to find them.
Object bindings = JLA.scopedValueBindings();
if (bindings == NEW_THREAD_BINDINGS) {
// This must be a new thread
return Snapshot.EMPTY_SNAPSHOT;
}
if (bindings == null) {
// Search the stack
bindings = JLA.findScopedValueBindings();
if (bindings == null) {
// Nothing on the stack.
bindings = Snapshot.EMPTY_SNAPSHOT;
}
}
assert (bindings != null);
JLA.setScopedValueBindings(bindings);
return (Snapshot) bindings;
}
private static int nextKey = 0xf0f0_f0f0;
// A Marsaglia xor-shift generator used to generate hashes. This one has full period, so
// it generates 2**32 - 1 hashes before it repeats. We're going to use the lowest n bits
// and the next n bits as cache indexes, so we make sure that those indexes map
// to different slots in the cache.
private static synchronized int generateKey() {
int x = nextKey;
do {
x ^= x >>> 12;
x ^= x << 9;
x ^= x >>> 23;
} while (Cache.primarySlot(x) == Cache.secondarySlot(x));
return (nextKey = x);
}
/**
* Return a bit mask that may be used to determine if this ScopedValue is
* bound in the current context. Each Carrier holds a bit mask which is
* the OR of all the bit masks of the bound ScopedValues.
* @return the bitmask
*/
int bitmask() {
return (1 << Cache.primaryIndex(this)) | (1 << (Cache.secondaryIndex(this) + Cache.TABLE_SIZE));
}
// Return true iff bitmask, considered as a set of bits, contains all
// of the bits in targetBits.
static boolean containsAll(int bitmask, int targetBits) {
return (bitmask & targetBits) == targetBits;
}
// A small fixed-size key-value cache. When a scoped value's get() method
// is invoked, we record the result of the lookup in this per-thread cache
// for fast access in future.
private static final class Cache {
static final int INDEX_BITS = 4; // Must be a power of 2
static final int TABLE_SIZE = 1 << INDEX_BITS;
static final int TABLE_MASK = TABLE_SIZE - 1;
static final int PRIMARY_MASK = (1 << TABLE_SIZE) - 1;
// The number of elements in the cache array, and a bit mask used to
// select elements from it.
private static final int CACHE_TABLE_SIZE, SLOT_MASK;
// The largest cache we allow. Must be a power of 2 and greater than
// or equal to 2.
private static final int MAX_CACHE_SIZE = 16;
static {
final String propertyName = "jdk.incubator.concurrent.ScopedValue.cacheSize";
var sizeString = GetPropertyAction.privilegedGetProperty(propertyName, "16");
var cacheSize = Integer.valueOf(sizeString);
if (cacheSize < 2 || cacheSize > MAX_CACHE_SIZE) {
cacheSize = MAX_CACHE_SIZE;
System.err.println(propertyName + " is out of range: is " + sizeString);
}
if ((cacheSize & (cacheSize - 1)) != 0) { // a power of 2
cacheSize = MAX_CACHE_SIZE;
System.err.println(propertyName + " must be an integer power of 2: is " + sizeString);
}
CACHE_TABLE_SIZE = cacheSize;
SLOT_MASK = cacheSize - 1;
}
static int primaryIndex(ScopedValue<?> key) {
return key.hash & TABLE_MASK;
}
static int secondaryIndex(ScopedValue<?> key) {
return (key.hash >> INDEX_BITS) & TABLE_MASK;
}
private static int primarySlot(ScopedValue<?> key) {
return key.hashCode() & SLOT_MASK;
}
private static int secondarySlot(ScopedValue<?> key) {
return (key.hash >> INDEX_BITS) & SLOT_MASK;
}
static int primarySlot(int hash) {
return hash & SLOT_MASK;
}
static int secondarySlot(int hash) {
return (hash >> INDEX_BITS) & SLOT_MASK;
}
static void put(ScopedValue<?> key, Object value) {
Object[] theCache = scopedValueCache();
if (theCache == null) {
theCache = new Object[CACHE_TABLE_SIZE * 2];
setScopedValueCache(theCache);
}
// Update the cache to replace one entry with the value we just looked up.
// Each value can be in one of two possible places in the cache.
// Pick a victim at (pseudo-)random.
int k1 = primarySlot(key);
int k2 = secondarySlot(key);
var usePrimaryIndex = chooseVictim();
int victim = usePrimaryIndex ? k1 : k2;
int other = usePrimaryIndex ? k2 : k1;
setKeyAndObjectAt(victim, key, value);
if (getKey(theCache, other) == key) {
setKeyAndObjectAt(other, key, value);
}
}
private static void setKeyAndObjectAt(int n, Object key, Object value) {
var cache = scopedValueCache();
cache[n * 2] = key;
cache[n * 2 + 1] = value;
}
private static void setKeyAndObjectAt(Object[] cache, int n, Object key, Object value) {
cache[n * 2] = key;
cache[n * 2 + 1] = value;
}
private static Object getKey(Object[] objs, int n) {
return objs[n * 2];
}
private static void setKey(Object[] objs, int n, Object key) {
objs[n * 2] = key;
}
private static final JavaUtilConcurrentTLRAccess THREAD_LOCAL_RANDOM_ACCESS
= SharedSecrets.getJavaUtilConcurrentTLRAccess();
// Return either true or false, at pseudo-random, with a bias towards true.
// This chooses either the primary or secondary cache slot, but the
// primary slot is approximately twice as likely to be chosen as the
// secondary one.
private static boolean chooseVictim() {
int r = THREAD_LOCAL_RANDOM_ACCESS.nextSecondaryThreadLocalRandomSeed();
return (r & 15) >= 5;
}
// Null a set of cache entries, indicated by the 1-bits given
static void invalidate(int toClearBits) {
toClearBits = (toClearBits >>> TABLE_SIZE) | (toClearBits & PRIMARY_MASK);
Object[] objects;
if ((objects = scopedValueCache()) != null) {
for (int bits = toClearBits; bits != 0; ) {
int index = Integer.numberOfTrailingZeros(bits);
setKeyAndObjectAt(objects, index & SLOT_MASK, null, null);
bits &= ~1 << index;
}
}
}
}
}

View File

@ -210,8 +210,8 @@ import jdk.internal.misc.ThreadFlock;
*
* <h2><a id="TreeStructure">Tree structure</a></h2>
*
* StructuredTaskScopes form a tree where parent-child relations are established
* implicitly when opening a new task scope:
* Task scopes form a tree where parent-child relations are established implicitly when
* opening a new task scope:
* <ul>
* <li> A parent-child relation is established when a thread started in a task scope
* opens its own task scope. A thread started in task scope "A" that opens task scope
@ -222,10 +222,45 @@ import jdk.internal.misc.ThreadFlock;
* scope "B" is the parent of the nested task scope "C".
* </ul>
*
* <p> The tree structure supports confinement checks. The phrase "threads contained in
* the task scope" in method descriptions means threads started in the task scope or
* descendant scopes. {@code StructuredTaskScope} does not define APIs that exposes the
* tree structure at this time.
* The <i>descendants</i> of a task scope are the child task scopes that it is a parent
* of, plus the descendants of the child task scopes, recursively.
*
* <p> The tree structure supports:
* <ul>
* <li> Inheritance of {@linkplain ScopedValue scoped values} across threads.
* <li> Confinement checks. The phrase "threads contained in the task scope" in method
* descriptions means threads started in the task scope or descendant scopes.
* </ul>
*
* <p> The following example demonstrates the inheritance of a scoped value. A scoped
* value {@code USERNAME} is bound to the value "{@code duke}". A {@code StructuredTaskScope}
* is created and its {@code fork} method invoked to start a thread to execute {@code
* childTask}. The thread inherits the scoped value <em>bindings</em> captured when
* creating the task scope. The code in {@code childTask} uses the value of the scoped
* value and so reads the value "{@code duke}".
* {@snippet lang=java :
* private static final ScopedValue<String> USERNAME = ScopedValue.newInstance();
*
* // @link substring="where" target="ScopedValue#where(ScopedValue, Object, Runnable)" :
* ScopedValue.where(USERNAME, "duke", () -> {
* try (var scope = new StructuredTaskScope<String>()) {
*
* scope.fork(() -> childTask()); // @highlight substring="fork"
* ...
* }
* });
*
* ...
*
* String childTask() {
* // @link substring="get" target="ScopedValue#get()" :
* String name = USERNAME.get(); // "duke"
* ...
* }
* }
*
* <p> {@code StructuredTaskScope} does not define APIs that exposes the tree structure
* at this time.
*
* <p> Unless otherwise specified, passing a {@code null} argument to a constructor
* or method in this class will cause a {@link NullPointerException} to be thrown.
@ -234,7 +269,7 @@ import jdk.internal.misc.ThreadFlock;
*
* <p> Actions in the owner thread of, or a thread contained in, the task scope prior to
* {@linkplain #fork forking} of a {@code Callable} task
* <a href="../../../../java.base/java/util/concurrent/package-summary.html#MemoryVisibility">
* <a href="{@docRoot}/java.base/java/util/concurrent/package-summary.html#MemoryVisibility">
* <i>happen-before</i></a> any actions taken by that task, which in turn <i>happen-before</i>
* the task result is retrieved via its {@code Future}, or <i>happen-before</i> any actions
* taken in a thread after {@linkplain #join() joining} of the task scope.
@ -280,6 +315,12 @@ public class StructuredTaskScope<T> implements AutoCloseable {
* tasks are {@linkplain #fork(Callable) forked}. The task scope is owned by the
* current thread.
*
* <p> This method captures the current thread's {@linkplain ScopedValue scoped value}
* bindings for inheritance by threads created in the task scope. The
* <a href="#TreeStructure">Tree Structure</a> section in the class description
* details how parent-child relations are established implicitly for the purpose of
* inheritance of scoped value bindings.
*
* @param name the name of the task scope, can be null
* @param factory the thread factory
*/
@ -367,16 +408,19 @@ public class StructuredTaskScope<T> implements AutoCloseable {
/**
* Starts a new thread to run the given task.
*
* <p> The new thread is created with the task scope's {@link ThreadFactory}.
* <p> The new thread is created with the task scope's {@link ThreadFactory}. It
* inherits the current thread's {@linkplain ScopedValue scoped value} bindings. The
* bindings must match the bindings captured when the task scope was created.
*
* <p> If the task completes before the task scope is {@link #shutdown() shutdown}
* then the {@link #handleComplete(Future) handle} method is invoked to consume the
* completed task. The {@code handleComplete} method is run when the task completes
* with a result or exception. If the {@code Future} {@link Future#cancel(boolean)
* cancel} method is used the cancel a task before the task scope is shut down, then
* the {@code handleComplete} method is run by the thread that invokes {@code cancel}.
* If the task scope shuts down at or around the same time that the task completes or
* is cancelled then the {@code handleComplete} method may or may not be invoked.
* then the {@link #handleComplete(Future) handleComplete} method is invoked to
* consume the completed task. The {@code handleComplete} method is run when the task
* completes with a result or exception. If the {@code Future}'s {@link
* Future#cancel(boolean) cancel} method is used to cancel a task before the task scope
* is shut down, then the {@code handleComplete} method is run by the thread that
* invokes {@code cancel}. If the task scope shuts down at or around the same time
* that the task completes or is cancelled then the {@code handleComplete} method may
* or may not be invoked.
*
* <p> If this task scope is {@linkplain #shutdown() shutdown} (or in the process
* of shutting down) then {@code fork} returns a {@code Future} representing a {@link
@ -395,6 +439,8 @@ public class StructuredTaskScope<T> implements AutoCloseable {
* @throws IllegalStateException if this task scope is closed
* @throws WrongThreadException if the current thread is not the owner or a thread
* contained in the task scope
* @throws StructureViolationException if the current scoped value bindings are not
* the same as when the task scope was created
* @throws RejectedExecutionException if the thread factory rejected creating a
* thread to run the task
*/
@ -628,6 +674,12 @@ public class StructuredTaskScope<T> implements AutoCloseable {
* scopes are closed then it closes the underlying construct of each nested task scope
* (in the reverse order that they were created in), closes this task scope, and then
* throws {@link StructureViolationException}.
*
* Similarly, if this method is called to close a task scope while executing with
* {@linkplain ScopedValue scoped value} bindings, and the task scope was created
* before the scoped values were bound, then {@code StructureViolationException} is
* thrown after closing the task scope.
*
* If a thread terminates without first closing task scopes that it owns then
* termination will cause the underlying construct of each of its open tasks scopes to
* be closed. Closing is performed in the reverse order that the task scopes were
@ -824,6 +876,12 @@ public class StructuredTaskScope<T> implements AutoCloseable {
* threads when tasks are {@linkplain #fork(Callable) forked}. The task scope is
* owned by the current thread.
*
* <p> This method captures the current thread's {@linkplain ScopedValue scoped value}
* bindings for inheritance by threads created in the task scope. The
* <a href="StructuredTaskScope.html#TreeStructure">Tree Structure</a> section in
* the class description details how parent-child relations are established
* implicitly for the purpose of inheritance of scoped value bindings.
*
* @param name the name of the task scope, can be null
* @param factory the thread factory
*/
@ -1000,6 +1058,12 @@ public class StructuredTaskScope<T> implements AutoCloseable {
* threads when tasks are {@linkplain #fork(Callable) forked}. The task scope
* is owned by the current thread.
*
* <p> This method captures the current thread's {@linkplain ScopedValue scoped value}
* bindings for inheritance by threads created in the task scope. The
* <a href="StructuredTaskScope.html#TreeStructure">Tree Structure</a> section in
* the class description details how parent-child relations are established
* implicitly for the purpose of inheritance of scoped value bindings.
*
* @param name the name of the task scope, can be null
* @param factory the thread factory
*/

View File

@ -61,7 +61,7 @@ public class framecnt01 {
// Test GetFrameCount on virtual live thread
Thread vThread = Thread.ofVirtual().name("VirtualThread-Live").start(() -> {
checkFrames(Thread.currentThread(), false, 9);
checkFrames(Thread.currentThread(), false, 10);
});
vThread.join();
@ -79,13 +79,13 @@ public class framecnt01 {
}
// this is too fragile, implementation can change at any time.
checkFrames(vThread1, false, 14);
checkFrames(vThread1, false, 15);
LockSupport.unpark(vThread1);
vThread1.join();
// Test GetFrameCount on live platform thread
Thread pThread = Thread.ofPlatform().name("PlatformThread-Live").start(() -> {
checkFrames(Thread.currentThread(), false, 5);
checkFrames(Thread.currentThread(), false, 6);
});
pThread.join();
@ -101,7 +101,7 @@ public class framecnt01 {
while(pThread1.getState() != Thread.State.WAITING) {
Thread.sleep(1);
}
checkFrames(pThread1, false, 5);
checkFrames(pThread1, false, 6);
LockSupport.unpark(pThread1);
pThread1.join();
@ -118,10 +118,11 @@ class FixedDepthThread implements Runnable {
Object checkFlag;
Thread thread;
// Each stack has 2 frames additional to expected depth
// Each stack has 3 frames additional to expected depth
// 0: FixedDepthThread: run()V
// 1: java/lang/Thread: run()V
static final int ADDITIONAL_STACK_COUNT = 2;
// 2: java/lang/Thread: runWith()V
static final int ADDITIONAL_STACK_COUNT = 3;
private FixedDepthThread(String name, int depth, Object checkFlag) {
this.thread = Thread.ofPlatform().name(name).unstarted(this);

View File

@ -36,7 +36,8 @@ static frame_info expected_virtual_frames[] = {
{"LGetStackTraceCurrentThreadTest;", "dummy", "()V"},
{"LGetStackTraceCurrentThreadTest;", "chain", "()V"},
{"LTask;", "run", "()V"},
{"Ljava/lang/VirtualThread;", "run", "(Ljava/lang/Runnable;)V"}
{"Ljava/lang/VirtualThread;", "runWith", "(Ljava/lang/Object;Ljava/lang/Runnable;)V"},
{"Ljava/lang/VirtualThread;", "run", "(Ljava/lang/Runnable;)V"},
};
static frame_info expected_platform_frames[] = {
@ -44,7 +45,8 @@ static frame_info expected_platform_frames[] = {
{"LGetStackTraceCurrentThreadTest;", "dummy", "()V"},
{"LGetStackTraceCurrentThreadTest;", "chain", "()V"},
{"LTask;", "run", "()V"},
{"Ljava/lang/Thread;", "run", "()V"}
{"Ljava/lang/Thread;", "runWith", "(Ljava/lang/Object;Ljava/lang/Runnable;)V"},
{"Ljava/lang/Thread;", "run", "()V"},
};
JNIEXPORT jint JNICALL

View File

@ -35,7 +35,8 @@ static frame_info expected_platform_frames[] = {
{"Lgetstacktr03;", "dummy", "()V"},
{"Lgetstacktr03;", "chain", "()V"},
{"Lgetstacktr03$Task;", "run", "()V"},
{"Ljava/lang/Thread;", "run", "()V"}
{"Ljava/lang/Thread;", "runWith", "(Ljava/lang/Object;Ljava/lang/Runnable;)V"},
{"Ljava/lang/Thread;", "run", "()V"},
};
static frame_info expected_virtual_frames[] = {
@ -43,6 +44,7 @@ static frame_info expected_virtual_frames[] = {
{"Lgetstacktr03;", "dummy", "()V"},
{"Lgetstacktr03;", "chain", "()V"},
{"Lgetstacktr03$Task;", "run", "()V"},
{"Ljava/lang/VirtualThread;", "runWith", "(Ljava/lang/Object;Ljava/lang/Runnable;)V"},
{"Ljava/lang/VirtualThread;", "run", "(Ljava/lang/Runnable;)V"},
{"Ljava/lang/VirtualThread$VThreadContinuation;", "lambda$new$0", "(Ljava/lang/VirtualThread;Ljava/lang/Runnable;)V"},
{"Ljava/lang/VirtualThread$VThreadContinuation$$Lambda$31.0x0000000800098810;", "run", "()V"},

View File

@ -38,6 +38,7 @@ static frame_info expected_platform_frames[] = {
{"Lgetstacktr04$TestThread;", "chain2", "()V"},
{"Lgetstacktr04$TestThread;", "chain1", "()V"},
{"Lgetstacktr04$TestThread;", "run", "()V"},
{"Ljava/lang/Thread;", "runWith", "(Ljava/lang/Object;Ljava/lang/Runnable;)V"},
{"Ljava/lang/Thread;", "run", "()V"},
};
@ -48,6 +49,7 @@ static frame_info expected_virtual_frames[] = {
{"Lgetstacktr04$TestThread;", "chain2", "()V"},
{"Lgetstacktr04$TestThread;", "chain1", "()V"},
{"Lgetstacktr04$TestThread;", "run", "()V"},
{"Ljava/lang/VirtualThread;", "runWith", "(Ljava/lang/Object;Ljava/lang/Runnable;)V"},
{"Ljava/lang/VirtualThread;", "run", "(Ljava/lang/Runnable;)V"},
{"Ljava/lang/VirtualThread$VThreadContinuation;", "lambda$new$0", "(Ljava/lang/VirtualThread;Ljava/lang/Runnable;)V"},
{"Ljava/lang/VirtualThread$VThreadContinuation$$Lambda;", "run", "()V"},

View File

@ -39,6 +39,7 @@ static frame_info expected_platform_frames[] = {
{"Lgetstacktr05$TestThread;", "chain2", "()V"},
{"Lgetstacktr05$TestThread;", "chain1", "()V"},
{"Lgetstacktr05$TestThread;", "run", "()V"},
{"Ljava/lang/Thread;", "runWith", "(Ljava/lang/Object;Ljava/lang/Runnable;)V"},
{"Ljava/lang/Thread;", "run", "()V"},
};
@ -48,6 +49,7 @@ static frame_info expected_virtual_frames[] = {
{"Lgetstacktr05$TestThread;", "chain2", "()V"},
{"Lgetstacktr05$TestThread;", "chain1", "()V"},
{"Lgetstacktr05$TestThread;", "run", "()V"},
{"Ljava/lang/VirtualThread;", "runWith", "(Ljava/lang/Object;Ljava/lang/Runnable;)V"},
{"Ljava/lang/VirtualThread;", "run", "(Ljava/lang/Runnable;)V"},
{"Ljava/lang/VirtualThread$VThreadContinuation;", "lambda$new$0", "(Ljava/lang/VirtualThread;Ljava/lang/Runnable;)V"},
{"Ljava/lang/VirtualThread$VThreadContinuation$$Lambda;", "run", "()V"},

View File

@ -43,6 +43,7 @@ static frame_info expected_platform_frames[] = {
{"Lgetstacktr06$TestThread;", "chain2", "()V"},
{"Lgetstacktr06$TestThread;", "chain1", "()V"},
{"Lgetstacktr06$TestThread;", "run", "()V"},
{"Ljava/lang/Thread;", "runWith", "(Ljava/lang/Object;Ljava/lang/Runnable;)V"},
{"Ljava/lang/Thread;", "run", "()V"},
};
@ -52,6 +53,7 @@ static frame_info expected_virtual_frames[] = {
{"Lgetstacktr06$TestThread;", "chain2", "()V"},
{"Lgetstacktr06$TestThread;", "chain1", "()V"},
{"Lgetstacktr06$TestThread;", "run", "()V"},
{"Ljava/lang/VirtualThread;", "runWith", "(Ljava/lang/Object;Ljava/lang/Runnable;)V"},
{"Ljava/lang/VirtualThread;", "run", "(Ljava/lang/Runnable;)V"},
{"Ljava/lang/VirtualThread$VThreadContinuation;", "lambda$new$0", "(Ljava/lang/VirtualThread;Ljava/lang/Runnable;)V"},
{"Ljava/lang/VirtualThread$VThreadContinuation$$Lambda;", "run", "()V"},

View File

@ -45,6 +45,7 @@ static frame_info expected_platform_frames[] = {
{"Lgetstacktr07$TestThread;", "chain2", "()V"},
{"Lgetstacktr07$TestThread;", "chain1", "()V"},
{"Lgetstacktr07$TestThread;", "run", "()V"},
{"Ljava/lang/Thread;", "runWith", "(Ljava/lang/Object;Ljava/lang/Runnable;)V"},
{"Ljava/lang/Thread;", "run", "()V"},
};
@ -55,6 +56,7 @@ static frame_info expected_virtual_frames[] = {
{"Lgetstacktr07$TestThread;", "chain2", "()V"},
{"Lgetstacktr07$TestThread;", "chain1", "()V"},
{"Lgetstacktr07$TestThread;", "run", "()V"},
{"Ljava/lang/VirtualThread;", "runWith", "(Ljava/lang/Object;Ljava/lang/Runnable;)V"},
{"Ljava/lang/VirtualThread;", "run", "(Ljava/lang/Runnable;)V"},
{"Ljava/lang/VirtualThread$VThreadContinuation;", "lambda$new$0", "(Ljava/lang/VirtualThread;Ljava/lang/Runnable;)V"},
{"Ljava/lang/VirtualThread$VThreadContinuation$$Lambda;", "run", "()V"},

View File

@ -44,6 +44,7 @@ static frame_info expected_platform_frames[] = {
{"Lgetstacktr08$TestThread;", "chain2", "()V"},
{"Lgetstacktr08$TestThread;", "chain1", "()V"},
{"Lgetstacktr08$TestThread;", "run", "()V"},
{"Ljava/lang/Thread;", "runWith", "(Ljava/lang/Object;Ljava/lang/Runnable;)V"},
{"Ljava/lang/Thread;", "run", "()V"},
};
@ -56,6 +57,7 @@ static frame_info expected_virtual_frames[] = {
{"Lgetstacktr08$TestThread;", "chain2", "()V"},
{"Lgetstacktr08$TestThread;", "chain1", "()V"},
{"Lgetstacktr08$TestThread;", "run", "()V"},
{"Ljava/lang/VirtualThread;", "runWith", "(Ljava/lang/Object;Ljava/lang/Runnable;)V"},
{"Ljava/lang/VirtualThread;", "run", "(Ljava/lang/Runnable;)V"},
{"Ljava/lang/VirtualThread$VThreadContinuation;", "lambda$new$0", "(Ljava/lang/VirtualThread;Ljava/lang/Runnable;)V"},
{"Ljava/lang/VirtualThread$VThreadContinuation$$Lambda;", "run", "()V"},

View File

@ -794,3 +794,4 @@ java/awt/event/MouseEvent/SpuriousExitEnter/SpuriousExitEnter.java 8254841 macos
java/awt/Focus/AppletInitialFocusTest/AppletInitialFocusTest1.java 8256289 windows-x64
java/awt/FullScreen/TranslucentWindow/TranslucentWindow.java 8258103 linux-all
java/awt/Focus/FrameMinimizeTest/FrameMinimizeTest.java 8016266 linux-x64

View File

@ -0,0 +1,164 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @summary Stress test ScopedValue with many bindings and rebinings
* @enablePreview
* @modules jdk.incubator.concurrent
* @library /test/lib
* @key randomness
* @run testng ManyBindings
*/
import jdk.incubator.concurrent.ScopedValue;
import jdk.incubator.concurrent.ScopedValue.Carrier;
import java.util.Arrays;
import java.util.Objects;
import java.util.Random;
import jdk.test.lib.RandomFactory;
import jdk.test.lib.thread.VThreadRunner;
import org.testng.annotations.Test;
import static org.testng.Assert.*;
@Test
public class ManyBindings {
private static final Random RND = RandomFactory.getRandom();
// number of scoped values to create
private static final int SCOPED_VALUE_COUNT = 16;
// recursive depth to test
private static final int MAX_DEPTH = 24;
/**
* Stress test bindings on platform thread.
*/
public void testPlatformThread() {
test();
}
/**
* Stress test bindings on virtual thread.
*/
public void testVirtualThread() throws Exception {
VThreadRunner.run(() -> test());
}
/**
* Scoped value and its expected value (or null if not bound).
*/
record KeyAndValue<T>(ScopedValue<T> key, T value) {
KeyAndValue() {
this(ScopedValue.newInstance(), null);
}
}
/**
* Stress test bindings on current thread.
*/
private void test() {
KeyAndValue<Integer>[] array = new KeyAndValue[SCOPED_VALUE_COUNT];
for (int i = 0; i < array.length; i++) {
array[i] = new KeyAndValue<>();
}
test(array, 1);
}
/**
* Test that the scoped values in the array have the expected value, then
* recursively call this method with some of the scoped values bound to a
* new value.
*
* @param array the scoped values and their expected value
* @param depth current recurive depth
*/
private void test(KeyAndValue<Integer>[] array, int depth) {
if (depth > MAX_DEPTH)
return;
// check that the scoped values have the expected values
check(array);
// try to pollute the cache
lotsOfReads(array);
// create a Carrier to bind/rebind some of the scoped values
int len = array.length;
Carrier carrier = null;
KeyAndValue<Integer>[] newArray = Arrays.copyOf(array, len);
int n = Math.max(1, RND.nextInt(len / 2));
while (n > 0) {
int index = RND.nextInt(len);
ScopedValue<Integer> key = array[index].key;
int newValue = RND.nextInt();
if (carrier == null) {
carrier = ScopedValue.where(key, newValue);
} else {
carrier = carrier.where(key, newValue);
}
newArray[index] = new KeyAndValue<>(key, newValue);
n--;
}
// invoke recursively
carrier.run(() -> {
test(newArray, depth+1);
});
// check that the scoped values have the origina values
check(array);
}
/**
* Check that the given scoped values have the expected value.
*/
private void check(KeyAndValue<Integer>[] array) {
for (int i = 0; i < array.length; i++) {
ScopedValue<Integer> key = array[i].key;
Integer value = array[i].value;
if (value == null) {
assertFalse(key.isBound());
} else {
assertEquals(key.get(), value);
}
}
}
/**
* Do lots of reads of the scoped values, to pollute the SV cache.
*/
private void lotsOfReads(KeyAndValue<Integer>[] array) {
for (int k = 0; k < 1000; k++) {
int index = RND.nextInt(array.length);
Integer value = array[index].value;
if (value != null) {
ScopedValue<Integer> key = array[index].key;
assertEquals(key.get(), value);
}
}
}
}

View File

@ -0,0 +1,442 @@
/*
* Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @summary Test ScopedValue API
* @enablePreview
* @modules jdk.incubator.concurrent
* @run testng ScopeValueAPI
*/
import jdk.incubator.concurrent.ScopedValue;
import java.util.NoSuchElementException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.testng.Assert.*;
@Test
public class ScopeValueAPI {
@DataProvider
public Object[][] factories() {
return new Object[][] {
{ Thread.ofPlatform().factory() },
{ Thread.ofVirtual().factory() },
};
}
/**
* Test that the run method is invoked.
*/
@Test(dataProvider = "factories")
public void testRun(ThreadFactory factory) throws Exception {
test(factory, () -> {
class Box { static boolean executed; }
ScopedValue<String> name = ScopedValue.newInstance();
ScopedValue.where(name, "duke", () -> { Box.executed = true; });
assertTrue(Box.executed);
});
}
/**
* Test the run method throwing an exception.
*/
@Test(dataProvider = "factories")
public void testRunThrows(ThreadFactory factory) throws Exception {
test(factory, () -> {
class FooException extends RuntimeException { }
ScopedValue<String> name = ScopedValue.newInstance();
Runnable op = () -> { throw new FooException(); };
assertThrows(FooException.class, () -> ScopedValue.where(name, "duke", op));
assertFalse(name.isBound());
});
}
/**
* Test that the call method is invoked.
*/
@Test(dataProvider = "factories")
public void testCall(ThreadFactory factory) throws Exception {
test(factory, () -> {
ScopedValue<String> name = ScopedValue.newInstance();
String result = ScopedValue.where(name, "duke", name::get);
assertEquals(result, "duke");
});
}
/**
* Test the call method throwing an exception.
*/
@Test(dataProvider = "factories")
public void testCallThrows(ThreadFactory factory) throws Exception {
test(factory, () -> {
class FooException extends RuntimeException { }
ScopedValue<String> name = ScopedValue.newInstance();
Callable<Void> op = () -> { throw new FooException(); };
assertThrows(FooException.class, () -> ScopedValue.where(name, "duke", op));
assertFalse(name.isBound());
});
}
/**
* Test get method.
*/
@Test(dataProvider = "factories")
public void testGet(ThreadFactory factory) throws Exception {
test(factory, () -> {
ScopedValue<String> name1 = ScopedValue.newInstance();
ScopedValue<String> name2 = ScopedValue.newInstance();
assertThrows(NoSuchElementException.class, name1::get);
assertThrows(NoSuchElementException.class, name2::get);
// run
ScopedValue.where(name1, "duke", () -> {
assertEquals(name1.get(), "duke");
assertThrows(NoSuchElementException.class, name2::get);
});
assertThrows(NoSuchElementException.class, name1::get);
assertThrows(NoSuchElementException.class, name2::get);
// call
ScopedValue.where(name1, "duke", () -> {
assertEquals(name1.get(), "duke");
assertThrows(NoSuchElementException.class, name2::get);
return null;
});
assertThrows(NoSuchElementException.class, name1::get);
assertThrows(NoSuchElementException.class, name2::get);
});
}
/**
* Test isBound method.
*/
@Test(dataProvider = "factories")
public void testIsBound(ThreadFactory factory) throws Exception {
test(factory, () -> {
ScopedValue<String> name1 = ScopedValue.newInstance();
ScopedValue<String> name2 = ScopedValue.newInstance();
assertFalse(name1.isBound());
assertFalse(name2.isBound());
// run
ScopedValue.where(name1, "duke", () -> {
assertTrue(name1.isBound());
assertFalse(name2.isBound());
});
assertFalse(name1.isBound());
assertFalse(name2.isBound());
// call
ScopedValue.where(name1, "duke", () -> {
assertTrue(name1.isBound());
assertFalse(name2.isBound());
return null;
});
assertFalse(name1.isBound());
assertFalse(name2.isBound());
});
}
/**
* Test orElse method.
*/
@Test(dataProvider = "factories")
public void testOrElse(ThreadFactory factory) throws Exception {
test(factory, () -> {
ScopedValue<String> name = ScopedValue.newInstance();
assertTrue(name.orElse(null) == null);
assertEquals(name.orElse("default"), "default");
// run
ScopedValue.where(name, "duke", () -> {
assertEquals(name.orElse(null), "duke");
assertEquals(name.orElse("default"), "duke");
});
// call
ScopedValue.where(name, "duke", () -> {
assertEquals(name.orElse(null), "duke");
assertEquals(name.orElse("default"), "duke");
return null;
});
});
}
/**
* Test orElseThrow method.
*/
@Test(dataProvider = "factories")
public void testOrElseThrow(ThreadFactory factory) throws Exception {
test(factory, () -> {
class FooException extends RuntimeException { }
ScopedValue<String> name = ScopedValue.newInstance();
assertThrows(FooException.class, () -> name.orElseThrow(FooException::new));
// run
ScopedValue.where(name, "duke", () -> {
assertEquals(name.orElseThrow(FooException::new), "duke");
});
// call
ScopedValue.where(name, "duke", () -> {
assertEquals(name.orElseThrow(FooException::new), "duke");
return null;
});
});
}
/**
* Test two bindings.
*/
@Test(dataProvider = "factories")
public void testTwoBindings(ThreadFactory factory) throws Exception {
test(factory, () -> {
ScopedValue<String> name = ScopedValue.newInstance();
ScopedValue<Integer> age = ScopedValue.newInstance();
// run
ScopedValue.where(name, "duke").where(age, 100).run(() -> {
assertTrue(name.isBound());
assertTrue(age.isBound());
assertEquals(name.get(), "duke");
assertEquals((int) age.get(), 100);
});
assertFalse(name.isBound());
assertFalse(age.isBound());
// call
ScopedValue.where(name, "duke").where(age, 100).call(() -> {
assertTrue(name.isBound());
assertTrue(age.isBound());
assertEquals(name.get(), "duke");
assertEquals((int) age.get(), 100);
return null;
});
assertFalse(name.isBound());
assertFalse(age.isBound());
});
}
/**
* Test rebinding.
*/
@Test(dataProvider = "factories")
public void testRebinding(ThreadFactory factory) throws Exception {
test(factory, () -> {
ScopedValue<String> name = ScopedValue.newInstance();
// run
ScopedValue.where(name, "duke", () -> {
assertTrue(name.isBound());
assertEquals(name.get(), "duke");
ScopedValue.where(name, "duchess", () -> {
assertTrue(name.isBound());
assertTrue("duchess".equals(name.get()));
});
assertTrue(name.isBound());
assertEquals(name.get(), "duke");
});
assertFalse(name.isBound());
// call
ScopedValue.where(name, "duke", () -> {
assertTrue(name.isBound());
assertEquals(name.get(), "duke");
ScopedValue.where(name, "duchess", () -> {
assertTrue(name.isBound());
assertTrue("duchess".equals(name.get()));
return null;
});
assertTrue(name.isBound());
assertEquals(name.get(), "duke");
return null;
});
assertFalse(name.isBound());
});
}
/**
* Test rebinding from null vaue to another value.
*/
@Test(dataProvider = "factories")
public void testRebindingFromNull(ThreadFactory factory) throws Exception {
test(factory, () -> {
ScopedValue<String> name = ScopedValue.newInstance();
// run
ScopedValue.where(name, null, () -> {
assertTrue(name.isBound());
assertEquals(name.get(), null);
ScopedValue.where(name, "duchess", () -> {
assertTrue(name.isBound());
assertTrue("duchess".equals(name.get()));
});
assertTrue(name.isBound());
assertTrue(name.get() == null);
});
assertFalse(name.isBound());
// call
ScopedValue.where(name, null, () -> {
assertTrue(name.isBound());
assertEquals(name.get(), null);
ScopedValue.where(name, "duchess", () -> {
assertTrue(name.isBound());
assertTrue("duchess".equals(name.get()));
return null;
});
assertTrue(name.isBound());
assertTrue(name.get() == null);
return null;
});
assertFalse(name.isBound());
});
}
/**
* Test rebinding to null value.
*/
@Test(dataProvider = "factories")
public void testRebindingToNull(ThreadFactory factory) throws Exception {
test(factory, () -> {
ScopedValue<String> name = ScopedValue.newInstance();
// run
ScopedValue.where(name, "duke", () -> {
assertTrue(name.isBound());
assertEquals(name.get(), "duke");
ScopedValue.where(name, null, () -> {
assertTrue(name.isBound());
assertTrue(name.get() == null);
});
assertTrue(name.isBound());
assertEquals(name.get(), "duke");
});
assertFalse(name.isBound());
// call
ScopedValue.where(name, "duke", () -> {
assertTrue(name.isBound());
assertEquals(name.get(), "duke");
ScopedValue.where(name, null, () -> {
assertTrue(name.isBound());
assertTrue(name.get() == null);
return null;
});
assertTrue(name.isBound());
assertEquals(name.get(), "duke");
return null;
});
assertFalse(name.isBound());
});
}
/**
* Test Carrier.get.
*/
@Test(dataProvider = "factories")
public void testCarrierGet(ThreadFactory factory) throws Exception {
test(factory, () -> {
ScopedValue<String> name = ScopedValue.newInstance();
ScopedValue<Integer> age = ScopedValue.newInstance();
// one scoped value
var carrier1 = ScopedValue.where(name, "duke");
assertEquals(carrier1.get(name), "duke");
assertThrows(NoSuchElementException.class, () -> carrier1.get(age));
// two scoped values
var carrier2 = carrier1.where(age, 20);
assertEquals(carrier2.get(name), "duke");
assertEquals((int) carrier2.get(age), 20);
});
}
/**
* Test NullPointerException.
*/
public void testNullPointerException() {
ScopedValue<String> name = ScopedValue.newInstance();
assertThrows(NullPointerException.class, () -> ScopedValue.where(null, "value"));
assertThrows(NullPointerException.class, () -> ScopedValue.where(null, "value", () -> { }));
assertThrows(NullPointerException.class, () -> ScopedValue.where(null, "value", () -> null));
assertThrows(NullPointerException.class, () -> name.orElseThrow(null));
var carrier = ScopedValue.where(name, "duke");
assertThrows(NullPointerException.class, () -> carrier.where(null, "value"));
assertThrows(NullPointerException.class, () -> carrier.get(null));
assertThrows(NullPointerException.class, () -> carrier.run(null));
assertThrows(NullPointerException.class, () -> carrier.call(null));
}
@FunctionalInterface
private interface ThrowingRunnable {
void run() throws Exception;
}
/**
* Run the given task in a thread created with the given thread factory.
* @throws Exception if the task throws an exception
*/
private static void test(ThreadFactory factory, ThrowingRunnable task) throws Exception {
try (var executor = Executors.newThreadPerTaskExecutor(factory)) {
var future = executor.submit(() -> {
task.run();
return null;
});
try {
future.get();
} catch (ExecutionException ee) {
Throwable cause = ee.getCause();
if (cause instanceof Exception e)
throw e;
if (cause instanceof Error e)
throw e;
throw new RuntimeException(cause);
}
}
}
}

View File

@ -0,0 +1,213 @@
/*
* Copyright (c) 2021, 2022 Red Hat, Inc. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @summary StressStackOverflow the recovery path for ScopedValue
* @modules jdk.incubator.concurrent
* @compile --enable-preview -source ${jdk.version} StressStackOverflow.java
* @run main/othervm/timeout=300 -XX:-TieredCompilation --enable-preview StressStackOverflow
* @run main/othervm/timeout=300 -XX:TieredStopAtLevel=1 --enable-preview StressStackOverflow
* @run main/othervm/timeout=300 --enable-preview StressStackOverflow
*/
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadLocalRandom;
import jdk.incubator.concurrent.ScopedValue;
import jdk.incubator.concurrent.StructureViolationException;
import jdk.incubator.concurrent.StructuredTaskScope;
public class StressStackOverflow {
public static final ScopedValue<Integer> el = ScopedValue.newInstance();
public static final ScopedValue<Integer> inheritedValue = ScopedValue.newInstance();
final ThreadLocalRandom tlr = ThreadLocalRandom.current();
static final TestFailureException testFailureException = new TestFailureException("Unexpected value for ScopedValue");
int ITERS = 1_000_000;
static class TestFailureException extends RuntimeException {
TestFailureException(String s) { super(s); }
}
// Test the ScopedValue recovery mechanism for stack overflows. We implement both Callable
// and Runnable interfaces. Which one gets tested depends on the constructor argument.
class DeepRecursion implements Callable, Runnable {
static enum Behaviour {CALL, RUN}
final Behaviour behaviour;
public DeepRecursion(Behaviour behaviour) {
this.behaviour = behaviour;
}
public void run() {
final var last = el.get();
ITERS--;
var nextRandomFloat = tlr.nextFloat();
try {
switch (behaviour) {
case CALL ->
ScopedValue.where(el, el.get() + 1).call(() -> fibonacci_pad(20, this));
case RUN ->
ScopedValue.where(el, el.get() + 1).run(() -> fibonacci_pad(20, this));
}
if (!last.equals(el.get())) {
throw testFailureException;
}
} catch (StackOverflowError e) {
if (nextRandomFloat <= 0.1) {
ScopedValue.where(el, el.get() + 1).run(this);
}
} catch (TestFailureException e) {
throw e;
} catch (Throwable throwable) {
// StackOverflowErrors cause many different failures. These include
// StructureViolationExceptions and InvocationTargetExceptions. This test
// checks that, no matter what the failure mode, scoped values are handled
// correctly.
} finally {
if (!last.equals(el.get())) {
throw testFailureException;
}
}
Thread.yield();
}
public Object call() {
run();
return null;
}
}
static final Runnable nop = new Runnable() {
public void run() { }
};
// Consume some stack.
//
// The double recursion used here prevents an optimizing JIT from
// inlining all the recursive calls, which would make it
// ineffective.
private long fibonacci_pad1(int n, Runnable op) {
if (n <= 1) {
op.run();
return n;
}
return fibonacci_pad1(n - 1, op) + fibonacci_pad1(n - 2, nop);
}
private static final Integer I_42 = 42;
long fibonacci_pad(int n, Runnable op) {
final var last = el.get();
try {
return fibonacci_pad1(tlr.nextInt(n), op);
} catch (StackOverflowError err) {
if (!inheritedValue.get().equals(I_42)) {
throw testFailureException;
}
if (!last.equals(el.get())) {
throw testFailureException;
}
throw err;
}
}
// Run op in a new thread. Platform or virtual threads are chosen at random.
void runInNewThread(Runnable op) {
var threadFactory
= (tlr.nextBoolean() ? Thread.ofPlatform() : Thread.ofVirtual()).factory();
try (var scope = new StructuredTaskScope<Object>("", threadFactory)) {
var future = scope.fork(() -> {
op.run();
return null;
});
future.get();
scope.join();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void run() {
try {
ScopedValue.where(inheritedValue, 42).where(el, 0).run(() -> {
try (var scope = new StructuredTaskScope<Object>()) {
try {
if (tlr.nextBoolean()) {
// Repeatedly test Scoped Values set by ScopedValue::call() and ScopedValue::run()
final var deepRecursion
= new DeepRecursion(tlr.nextBoolean() ? DeepRecursion.Behaviour.CALL : DeepRecursion.Behaviour.RUN);
deepRecursion.run();
} else {
// Recursively run ourself until we get a stack overflow
// Catch the overflow and make sure the recovery path works
// for values inherited from a StructuredTaskScope.
Runnable op = new Runnable() {
public void run() {
try {
fibonacci_pad(20, this);
} catch (StackOverflowError e) {
} catch (TestFailureException e) {
throw e;
} catch (Throwable throwable) {
// StackOverflowErrors cause many different failures. These include
// StructureViolationExceptions and InvocationTargetExceptions. This test
// checks that, no matter what the failure mode, scoped values are handled
// correctly.
} finally {
if (!inheritedValue.get().equals(I_42)) {
throw testFailureException;
}
}
}
};
runInNewThread(op);
}
scope.join();
} catch (StructureViolationException structureViolationException) {
// Can happen if a stack overflow prevented a StackableScope from
// being removed. We can continue.
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
} catch (StructureViolationException structureViolationException) {
// Can happen if a stack overflow prevented a StackableScope from
// being removed. We can continue.
}
}
public static void main(String[] args) {
var torture = new StressStackOverflow();
while (torture.ITERS > 0) {
torture.run();
}
System.out.println("OK");
}
}

View File

@ -0,0 +1,216 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @summary Basic tests for StructuredTaskScope with scoped values
* @enablePreview
* @modules jdk.incubator.concurrent
* @run testng WithScopedValue
*/
import jdk.incubator.concurrent.ScopedValue;
import jdk.incubator.concurrent.StructuredTaskScope;
import jdk.incubator.concurrent.StructureViolationException;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.testng.Assert.*;
@Test
public class WithScopedValue {
@DataProvider
public Object[][] factories() {
return new Object[][] {
{ Thread.ofPlatform().factory() },
{ Thread.ofVirtual().factory() },
};
}
/**
* Test that fork inherits a scoped value into a child thread.
*/
@Test(dataProvider = "factories")
public void testForkInheritsScopedValue1(ThreadFactory factory) throws Exception {
ScopedValue<String> name = ScopedValue.newInstance();
String value = ScopedValue.where(name, "x", () -> {
try (var scope = new StructuredTaskScope<String>(null, factory)) {
Future<String> future = scope.fork(() -> {
return name.get(); // child should read "x"
});
scope.join();
return future.resultNow();
}
});
assertEquals(value, "x");
}
/**
* Test that fork inherits a scoped value into a grandchild thread.
*/
@Test(dataProvider = "factories")
public void testForkInheritsScopedValue2(ThreadFactory factory) throws Exception {
ScopedValue<String> name = ScopedValue.newInstance();
String value = ScopedValue.where(name, "x", () -> {
try (var scope1 = new StructuredTaskScope<String>(null, factory)) {
Future<String> future1 = scope1.fork(() -> {
try (var scope2 = new StructuredTaskScope<String>(null, factory)) {
Future<String> future2 = scope2.fork(() -> {
return name.get(); // grandchild should read "x"
});
scope2.join();
return future2.resultNow();
}
});
scope1.join();
return future1.resultNow();
}
});
assertEquals(value, "x");
}
/**
* Test that fork inherits a rebound scoped value into a grandchild thread.
*/
@Test(dataProvider = "factories")
public void testForkInheritsScopedValue3(ThreadFactory factory) throws Exception {
ScopedValue<String> name = ScopedValue.newInstance();
String value = ScopedValue.where(name, "x", () -> {
try (var scope1 = new StructuredTaskScope<String>(null, factory)) {
Future<String> future1 = scope1.fork(() -> {
assertEquals(name.get(), "x"); // child should read "x"
// rebind name to "y"
String grandchildValue = ScopedValue.where(name, "y", () -> {
try (var scope2 = new StructuredTaskScope<String>(null, factory)) {
Future<String> future2 = scope2.fork(() -> {
return name.get(); // grandchild should read "y"
});
scope2.join();
return future2.resultNow();
}
});
assertEquals(name.get(), "x"); // child should read "x"
return grandchildValue;
});
scope1.join();
return future1.resultNow();
}
});
assertEquals(value, "y");
}
/**
* Test exiting a dynamic scope with an open task scope.
*/
public void testStructureViolation1() throws Exception {
ScopedValue<String> name = ScopedValue.newInstance();
class Box {
StructuredTaskScope<Object> scope;
}
var box = new Box();
try {
try {
ScopedValue.where(name, "x", () -> {
box.scope = new StructuredTaskScope<Object>();
});
fail();
} catch (StructureViolationException expected) { }
// underlying flock should be closed, fork should return a cancelled task
StructuredTaskScope<Object> scope = box.scope;
AtomicBoolean ran = new AtomicBoolean();
Future<Object> future = scope.fork(() -> {
ran.set(true);
return null;
});
assertTrue(future.isCancelled());
scope.join();
assertFalse(ran.get());
} finally {
StructuredTaskScope<Object> scope = box.scope;
if (scope != null) {
scope.close();
}
}
}
/**
* Test closing a StructuredTaskScope while executing in a dynamic scope.
*/
public void testStructureViolation2() throws Exception {
ScopedValue<String> name = ScopedValue.newInstance();
try (var scope = new StructuredTaskScope<String>()) {
ScopedValue.where(name, "x", () -> {
assertThrows(StructureViolationException.class, scope::close);
});
}
}
/**
* Test fork when a scoped value is bound after a StructuredTaskScope is created.
*/
public void testStructureViolation3() throws Exception {
ScopedValue<String> name = ScopedValue.newInstance();
try (var scope = new StructuredTaskScope<String>()) {
ScopedValue.where(name, "x", () -> {
assertThrows(StructureViolationException.class,
() -> scope.fork(() -> "foo"));
});
}
}
/**
* Test fork when a scoped value is re-bound after a StructuredTaskScope is created.
*/
public void testStructureViolation4() throws Exception {
ScopedValue<String> name1 = ScopedValue.newInstance();
ScopedValue<String> name2 = ScopedValue.newInstance();
// rebind
ScopedValue.where(name1, "x", () -> {
try (var scope = new StructuredTaskScope<String>()) {
ScopedValue.where(name1, "y", () -> {
assertThrows(StructureViolationException.class,
() -> scope.fork(() -> "foo"));
});
}
});
// new binding
ScopedValue.where(name1, "x", () -> {
try (var scope = new StructuredTaskScope<String>()) {
ScopedValue.where(name2, "y", () -> {
assertThrows(StructureViolationException.class,
() -> scope.fork(() -> "foo"));
});
}
});
}
}

View File

@ -0,0 +1,220 @@
/*
* Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @summary Test ThreadFlock with scoped values
* @enablePreview
* @modules java.base/jdk.internal.misc
* @modules jdk.incubator.concurrent
* @run testng WithScopedValue
*/
import jdk.internal.misc.ThreadFlock;
import jdk.incubator.concurrent.ScopedValue;
import jdk.incubator.concurrent.StructureViolationException;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicReference;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.testng.Assert.*;
@Test
public class WithScopedValue {
@DataProvider(name = "factories")
public Object[][] factories() {
var defaultThreadFactory = Executors.defaultThreadFactory();
var virtualThreadFactory = Thread.ofVirtual().factory();
return new Object[][]{
{ defaultThreadFactory, },
{ virtualThreadFactory, },
};
}
/**
* Test inheritance of a scoped value.
*/
@Test(dataProvider = "factories")
public void testInheritsScopedValue(ThreadFactory factory) throws Exception {
ScopedValue<String> name = ScopedValue.newInstance();
String value = ScopedValue.where(name, "duke", () -> {
var result = new AtomicReference<String>();
try (var flock = ThreadFlock.open(null)) {
Thread thread = factory.newThread(() -> {
// child
result.set(name.get());
});
flock.start(thread);
}
return result.get();
});
assertEquals(value, "duke");
}
/**
* Test exiting a dynamic scope with open thread flocks.
*/
public void testStructureViolation1() {
ScopedValue<String> name = ScopedValue.newInstance();
class Box {
ThreadFlock flock1;
ThreadFlock flock2;
}
var box = new Box();
try {
ScopedValue.where(name, "x1", () -> {
box.flock1 = ThreadFlock.open(null);
box.flock2 = ThreadFlock.open(null);
});
fail();
} catch (StructureViolationException expected) { }
assertTrue(box.flock1.isClosed());
assertTrue(box.flock2.isClosed());
}
/**
* Test closing a thread flock while in a dynamic scope and with enclosing thread
* flocks. This test closes enclosing flock1.
*/
public void testStructureViolation2() {
ScopedValue<String> name = ScopedValue.newInstance();
try (var flock1 = ThreadFlock.open("flock1")) {
ScopedValue.where(name, "x1", () -> {
try (var flock2 = ThreadFlock.open("flock2")) {
ScopedValue.where(name, "x2", () -> {
try (var flock3 = ThreadFlock.open("flock3")) {
ScopedValue.where(name, "x3", () -> {
var flock4 = ThreadFlock.open("flock4");
try {
flock1.close();
fail();
} catch (StructureViolationException expected) { }
assertTrue(flock1.isClosed());
assertTrue(flock2.isClosed());
assertTrue(flock3.isClosed());
assertTrue(flock4.isClosed());
});
}
});
}
});
}
}
/**
* Test closing a thread flock while in a dynamic scope and with enclosing thread
* flocks. This test closes enclosing flock2.
*/
public void testStructureViolation3() {
ScopedValue<String> name = ScopedValue.newInstance();
try (var flock1 = ThreadFlock.open("flock1")) {
ScopedValue.where(name, "x1", () -> {
try (var flock2 = ThreadFlock.open("flock2")) {
ScopedValue.where(name, "x2", () -> {
try (var flock3 = ThreadFlock.open("flock3")) {
ScopedValue.where(name, "x3", () -> {
var flock4 = ThreadFlock.open("flock4");
try {
flock2.close();
fail();
} catch (StructureViolationException expected) { }
assertFalse(flock1.isClosed());
assertTrue(flock2.isClosed());
assertTrue(flock3.isClosed());
assertTrue(flock4.isClosed());
});
}
});
}
});
}
}
/**
* Test closing a thread flock while in a dynamic scope and with enclosing thread
* flocks. This test closes enclosing flock3.
*/
public void testStructureViolation4() {
ScopedValue<String> name = ScopedValue.newInstance();
try (var flock1 = ThreadFlock.open("flock1")) {
ScopedValue.where(name, "x1", () -> {
try (var flock2 = ThreadFlock.open("flock2")) {
ScopedValue.where(name, "x2", () -> {
try (var flock3 = ThreadFlock.open("flock3")) {
ScopedValue.where(name, "x3", () -> {
var flock4 = ThreadFlock.open("flock4");
try {
flock3.close();
fail();
} catch (StructureViolationException expected) { }
assertFalse(flock1.isClosed());
assertFalse(flock2.isClosed());
assertTrue(flock3.isClosed());
assertTrue(flock4.isClosed());
});
}
});
}
});
}
}
/**
* Test start when a scoped value is bound after a thread flock is created.
*/
@Test(dataProvider = "factories")
public void testStructureViolation5(ThreadFactory factory) throws Exception {
ScopedValue<String> name = ScopedValue.newInstance();
try (var flock = ThreadFlock.open(null)) {
ScopedValue.where(name, "duke", () -> {
Thread thread = factory.newThread(() -> { });
expectThrows(StructureViolationException.class, () -> flock.start(thread));
});
}
}
/**
* Test start when a scoped value is re-bound after a thread flock is created.
*/
@Test(dataProvider = "factories")
public void testStructureViolation6(ThreadFactory factory) throws Exception {
ScopedValue<String> name = ScopedValue.newInstance();
ScopedValue.where(name, "duke", () -> {
try (var flock = ThreadFlock.open(null)) {
ScopedValue.where(name, "duchess", () -> {
Thread thread = factory.newThread(() -> { });
expectThrows(StructureViolationException.class, () -> flock.start(thread));
});
}
});
}
}

View File

@ -0,0 +1,217 @@
/*
* Copyright (c) 2022, red Hat, Inc. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.bench.jdk.incubator.concurrent;
import jdk.incubator.concurrent.ScopedValue;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import static org.openjdk.bench.jdk.incubator.concurrent.ScopedValuesData.*;
/**
* Tests ScopedValue
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Warmup(iterations=4, time=1)
@Measurement(iterations=10, time=1)
@Threads(1)
@Fork(value = 1,
jvmArgsPrepend = {"-Djmh.executor.class=org.openjdk.bench.jdk.incubator.concurrent.ScopedValuesExecutorService",
"-Djmh.executor=CUSTOM",
"-Djmh.blackhole.mode=COMPILER",
"--add-modules=jdk.incubator.concurrent",
"--enable-preview"})
@State(Scope.Thread)
@SuppressWarnings("preview")
public class ScopedValues {
private static final Integer THE_ANSWER = 42;
// Test 1: make sure ScopedValue.get() is hoisted out of loops.
@Benchmark
public void thousandAdds_ScopedValue(Blackhole bh) throws Exception {
int result = 0;
for (int i = 0; i < 1_000; i++) {
result += ScopedValuesData.sl1.get();
}
bh.consume(result);
}
@Benchmark
public void thousandAdds_ThreadLocal(Blackhole bh) throws Exception {
int result = 0;
for (int i = 0; i < 1_000; i++) {
result += ScopedValuesData.tl1.get();
}
bh.consume(result);
}
@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public int thousandIsBoundQueries(Blackhole bh) throws Exception {
var result = 0;
for (int i = 0; i < 1_000; i++) {
result += ScopedValuesData.sl1.isBound() ? 1 : 0;
}
return result;
}
@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public int thousandMaybeGets(Blackhole bh) throws Exception {
int result = 0;
for (int i = 0; i < 1_000; i++) {
if (ScopedValuesData.sl1.isBound()) {
result += ScopedValuesData.sl1.get();
}
}
return result;
}
// Test 2: stress the ScopedValue cache.
// The idea here is to use a bunch of bound values cyclically, which
// stresses the ScopedValue cache.
int combine(int n, int i1, int i2, int i3, int i4, int i5, int i6) {
return n + ((i1 ^ i2 >>> 6) + (i3 << 7) + i4 - i5 | i6);
}
@Benchmark
public int sixValues_ScopedValue() throws Exception {
int result = 0;
for (int i = 0 ; i < 166; i++) {
result = combine(result, sl1.get(), sl2.get(), sl3.get(), sl4.get(), sl5.get(), sl6.get());
}
return result;
}
@Benchmark
public int sixValues_ThreadLocal() throws Exception {
int result = 0;
for (int i = 0 ; i < 166; i++) {
result = combine(result, tl1.get(), tl2.get(), tl3.get(), tl4.get(), tl5.get(), tl6.get());
}
return result;
}
// Test 3: The cost of bind, then get
// This is the worst case for ScopedValues because we have to create
// a binding, link it in, then search the current bindings. In addition, we
// create a cache entry for the bound value, then we immediately have to
// destroy it.
@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public int CreateBindThenGetThenRemove_ScopedValue() throws Exception {
return ScopedValue.where(sl1, THE_ANSWER).call(sl1::get);
}
// Create a Carrier ahead of time: might be slightly faster
private static final ScopedValue.Carrier HOLD_42 = ScopedValue.where(sl1, 42);
@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public int bindThenGetThenRemove_ScopedValue() throws Exception {
return HOLD_42.call(sl1::get);
}
@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public int bindThenGetThenRemove_ThreadLocal() throws Exception {
try {
tl1.set(THE_ANSWER);
return tl1.get();
} finally {
tl1.remove();
}
}
// This has no exact equivalent in ScopedValue, but it's provided here for
// information.
@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public int bindThenGetNoRemove_ThreadLocal() throws Exception {
tl1.set(THE_ANSWER);
return tl1.get();
}
// Test 4: The cost of binding, but not using any result
@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public Object bind_ScopedValue() throws Exception {
return HOLD_42.call(this::getClass);
}
@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public Object bind_ThreadLocal() throws Exception {
try {
tl1.set(THE_ANSWER);
return this.getClass();
} finally {
tl1.remove();
}
}
// Simply set a ThreadLocal so that the caller can see it
// This has no exact equivalent in ScopedValue, but it's provided here for
// information.
@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void setNoRemove_ThreadLocal() throws Exception {
tl1.set(THE_ANSWER);
}
// This is the closest I can think of to setNoRemove_ThreadLocal in that it
// returns a value in a ScopedValue container. The container must already
// be bound to an AtomicReference for this to work.
@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void setNoRemove_ScopedValue() throws Exception {
sl_atomicRef.get().setPlain(THE_ANSWER);
}
// Test 5: A simple counter
@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void counter_ScopedValue() {
sl_atomicInt.get().setPlain(
sl_atomicInt.get().getPlain() + 1);
}
@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void counter_ThreadLocal() {
// Very slow:
// tl1.set(tl1.get() + 1);
var ctr = tl_atomicInt.get();
ctr.setPlain(ctr.getPlain() + 1);
}
}

View File

@ -0,0 +1,72 @@
/*
* Copyright (c) 2021, Red Hat, Inc. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.bench.jdk.incubator.concurrent;
import jdk.incubator.concurrent.ScopedValue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
@SuppressWarnings("preview")
public class ScopedValuesData {
static final ScopedValue<Integer> sl1 = ScopedValue.newInstance();
static final ThreadLocal<Integer> tl1 = new ThreadLocal<>();
static final ScopedValue<Integer> sl2 = ScopedValue.newInstance();
static final ScopedValue<Integer> sl3 = ScopedValue.newInstance();
static final ScopedValue<Integer> sl4 = ScopedValue.newInstance();
static final ScopedValue<Integer> sl5 = ScopedValue.newInstance();
static final ScopedValue<Integer> sl6 = ScopedValue.newInstance();
static final ScopedValue<AtomicInteger> sl_atomicInt = ScopedValue.newInstance();
static final ScopedValue<Integer> unbound = ScopedValue.newInstance();
static final ScopedValue<AtomicReference<Integer>> sl_atomicRef = ScopedValue.newInstance();
static final ThreadLocal<Integer> tl2 = new ThreadLocal<>();
static final ThreadLocal<Integer> tl3 = new ThreadLocal<>();
static final ThreadLocal<Integer> tl4 = new ThreadLocal<>();
static final ThreadLocal<Integer> tl5 = new ThreadLocal<>();
static final ThreadLocal<Integer> tl6 = new ThreadLocal<>();
static final ThreadLocal<AtomicInteger> tl_atomicInt = new ThreadLocal<>();
static final ScopedValue.Carrier VALUES = ScopedValue
.where(sl1, 42).where(sl2, 2).where(sl3, 3)
.where(sl4, 4).where(sl5, 5).where(sl6, 6);
public static void run(Runnable action) {
try {
tl1.set(42); tl2.set(2); tl3.set(3); tl4.set(4); tl5.set(5); tl6.set(6);
tl1.get(); // Create the ScopedValue cache as a side effect
tl_atomicInt.set(new AtomicInteger());
VALUES.where(sl_atomicInt, new AtomicInteger())
.where(sl_atomicRef, new AtomicReference<>())
.run(action);
} finally {
tl1.remove(); tl2.remove(); tl3.remove(); tl4.remove(); tl5.remove(); tl6.remove();
tl_atomicInt.remove();
}
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.bench.jdk.incubator.concurrent;
import java.util.concurrent.*;
public class ScopedValuesExecutorService extends ThreadPoolExecutor {
public ScopedValuesExecutorService(int corePoolSize, String prefix) {
super(1, 1, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(),
new AThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
}
}
class AThreadFactory implements ThreadFactory {
public Thread newThread(Runnable action) {
return new Thread() {
public void run() {
ScopedValuesData.run(action);
}
};
}
}