8286666: JEP 429: Implementation of Scoped Values (Incubator)
Reviewed-by: psandoz, dlong, alanb, mcimadamore
This commit is contained in:
parent
ccc69af966
commit
221e1a4260
@ -194,8 +194,6 @@ JVM_RegisterLambdaProxyClassForArchiving
|
|||||||
JVM_RegisterSignal
|
JVM_RegisterSignal
|
||||||
JVM_ReleaseUTF
|
JVM_ReleaseUTF
|
||||||
JVM_ReportFinalizationComplete
|
JVM_ReportFinalizationComplete
|
||||||
JVM_ExtentLocalCache
|
|
||||||
JVM_SetExtentLocalCache
|
|
||||||
JVM_SetArrayElement
|
JVM_SetArrayElement
|
||||||
JVM_SetClassSigners
|
JVM_SetClassSigners
|
||||||
JVM_SetNativeThreadName
|
JVM_SetNativeThreadName
|
||||||
@ -225,4 +223,10 @@ JVM_VirtualThreadMountEnd
|
|||||||
JVM_VirtualThreadUnmountBegin
|
JVM_VirtualThreadUnmountBegin
|
||||||
JVM_VirtualThreadUnmountEnd
|
JVM_VirtualThreadUnmountEnd
|
||||||
JVM_VirtualThreadHideFrames
|
JVM_VirtualThreadHideFrames
|
||||||
|
|
||||||
|
# Scoped values
|
||||||
|
JVM_EnsureMaterializedForStackWalk_func
|
||||||
|
JVM_FindScopedValueBindings
|
||||||
|
JVM_ScopedValueCache
|
||||||
|
JVM_SetScopedValueCache
|
||||||
#
|
#
|
||||||
|
@ -3632,6 +3632,11 @@ encode %{
|
|||||||
ciEnv::current()->record_failure("CodeCache is full");
|
ciEnv::current()->record_failure("CodeCache is full");
|
||||||
return;
|
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 {
|
} else {
|
||||||
int method_index = resolved_method_index(cbuf);
|
int method_index = resolved_method_index(cbuf);
|
||||||
RelocationHolder rspec = _optimized_virtual ? opt_virtual_call_Relocation::spec(method_index)
|
RelocationHolder rspec = _optimized_virtual ? opt_virtual_call_Relocation::spec(method_index)
|
||||||
|
@ -2164,13 +2164,19 @@ encode %{
|
|||||||
// determine who we intended to call.
|
// determine who we intended to call.
|
||||||
MacroAssembler _masm(&cbuf);
|
MacroAssembler _masm(&cbuf);
|
||||||
cbuf.set_insts_mark();
|
cbuf.set_insts_mark();
|
||||||
$$$emit8$primary;
|
|
||||||
|
|
||||||
if (!_method) {
|
if (!_method) {
|
||||||
|
$$$emit8$primary;
|
||||||
emit_d32_reloc(cbuf, (int) ($meth$$method - ((intptr_t) cbuf.insts_end()) - 4),
|
emit_d32_reloc(cbuf, (int) ($meth$$method - ((intptr_t) cbuf.insts_end()) - 4),
|
||||||
runtime_call_Relocation::spec(),
|
runtime_call_Relocation::spec(),
|
||||||
RELOC_DISP32);
|
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 {
|
} else {
|
||||||
|
$$$emit8$primary;
|
||||||
int method_index = resolved_method_index(cbuf);
|
int method_index = resolved_method_index(cbuf);
|
||||||
RelocationHolder rspec = _optimized_virtual ? opt_virtual_call_Relocation::spec(method_index)
|
RelocationHolder rspec = _optimized_virtual ? opt_virtual_call_Relocation::spec(method_index)
|
||||||
: static_call_Relocation::spec(method_index);
|
: static_call_Relocation::spec(method_index);
|
||||||
|
@ -154,7 +154,7 @@ bool Compiler::is_intrinsic_supported(const methodHandle& method) {
|
|||||||
case vmIntrinsics::_getModifiers:
|
case vmIntrinsics::_getModifiers:
|
||||||
case vmIntrinsics::_currentCarrierThread:
|
case vmIntrinsics::_currentCarrierThread:
|
||||||
case vmIntrinsics::_currentThread:
|
case vmIntrinsics::_currentThread:
|
||||||
case vmIntrinsics::_extentLocalCache:
|
case vmIntrinsics::_scopedValueCache:
|
||||||
case vmIntrinsics::_dabs:
|
case vmIntrinsics::_dabs:
|
||||||
case vmIntrinsics::_dsqrt:
|
case vmIntrinsics::_dsqrt:
|
||||||
case vmIntrinsics::_dsqrt_strict:
|
case vmIntrinsics::_dsqrt_strict:
|
||||||
|
@ -1428,8 +1428,8 @@ void LIRGenerator::do_getObjectSize(Intrinsic* x) {
|
|||||||
__ branch_destination(L_done->label());
|
__ branch_destination(L_done->label());
|
||||||
}
|
}
|
||||||
|
|
||||||
void LIRGenerator::do_extentLocalCache(Intrinsic* x) {
|
void LIRGenerator::do_scopedValueCache(Intrinsic* x) {
|
||||||
do_JavaThreadField(x, JavaThread::extentLocalCache_offset());
|
do_JavaThreadField(x, JavaThread::scopedValueCache_offset());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Example: Thread.currentCarrierThread()
|
// Example: Thread.currentCarrierThread()
|
||||||
@ -2948,7 +2948,7 @@ void LIRGenerator::do_Intrinsic(Intrinsic* x) {
|
|||||||
case vmIntrinsics::_getObjectSize: do_getObjectSize(x); break;
|
case vmIntrinsics::_getObjectSize: do_getObjectSize(x); break;
|
||||||
case vmIntrinsics::_currentCarrierThread: do_currentCarrierThread(x); break;
|
case vmIntrinsics::_currentCarrierThread: do_currentCarrierThread(x); break;
|
||||||
case vmIntrinsics::_currentThread: do_vthread(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::_dlog: // fall through
|
||||||
case vmIntrinsics::_dlog10: // fall through
|
case vmIntrinsics::_dlog10: // fall through
|
||||||
|
@ -257,7 +257,7 @@ class LIRGenerator: public InstructionVisitor, public BlockClosure {
|
|||||||
void do_getClass(Intrinsic* x);
|
void do_getClass(Intrinsic* x);
|
||||||
void do_getObjectSize(Intrinsic* x);
|
void do_getObjectSize(Intrinsic* x);
|
||||||
void do_currentCarrierThread(Intrinsic* x);
|
void do_currentCarrierThread(Intrinsic* x);
|
||||||
void do_extentLocalCache(Intrinsic* x);
|
void do_scopedValueCache(Intrinsic* x);
|
||||||
void do_vthread(Intrinsic* x);
|
void do_vthread(Intrinsic* x);
|
||||||
void do_JavaThreadField(Intrinsic* x, ByteSize offset);
|
void do_JavaThreadField(Intrinsic* x, ByteSize offset);
|
||||||
void do_FmaIntrinsic(Intrinsic* x);
|
void do_FmaIntrinsic(Intrinsic* x);
|
||||||
|
@ -1683,7 +1683,7 @@ int java_lang_Thread::_interrupted_offset;
|
|||||||
int java_lang_Thread::_tid_offset;
|
int java_lang_Thread::_tid_offset;
|
||||||
int java_lang_Thread::_continuation_offset;
|
int java_lang_Thread::_continuation_offset;
|
||||||
int java_lang_Thread::_park_blocker_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;)
|
JFR_ONLY(int java_lang_Thread::_jfr_epoch_offset;)
|
||||||
|
|
||||||
#define THREAD_FIELDS_DO(macro) \
|
#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(_tid_offset, k, "tid", long_signature, false); \
|
||||||
macro(_park_blocker_offset, k, "parkBlocker", object_signature, false); \
|
macro(_park_blocker_offset, k, "parkBlocker", object_signature, false); \
|
||||||
macro(_continuation_offset, k, "cont", continuation_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() {
|
void java_lang_Thread::compute_offsets() {
|
||||||
assert(_holder_offset == 0, "offsets should be initialized only once");
|
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);
|
java_thread->address_field_put(_jvmti_thread_state_offset, (address)state);
|
||||||
}
|
}
|
||||||
|
|
||||||
void java_lang_Thread::clear_extentLocalBindings(oop java_thread) {
|
void java_lang_Thread::clear_scopedValueBindings(oop java_thread) {
|
||||||
java_thread->obj_field_put(_extentLocalBindings_offset, NULL);
|
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) {
|
oop java_lang_Thread::holder(oop java_thread) {
|
||||||
|
@ -352,7 +352,7 @@ class java_lang_Thread : AllStatic {
|
|||||||
static int _tid_offset;
|
static int _tid_offset;
|
||||||
static int _continuation_offset;
|
static int _continuation_offset;
|
||||||
static int _park_blocker_offset;
|
static int _park_blocker_offset;
|
||||||
static int _extentLocalBindings_offset;
|
static int _scopedValueBindings_offset;
|
||||||
JFR_ONLY(static int _jfr_epoch_offset;)
|
JFR_ONLY(static int _jfr_epoch_offset;)
|
||||||
|
|
||||||
static void compute_offsets();
|
static void compute_offsets();
|
||||||
@ -398,8 +398,8 @@ class java_lang_Thread : AllStatic {
|
|||||||
static JvmtiThreadState* jvmti_thread_state(oop java_thread);
|
static JvmtiThreadState* jvmti_thread_state(oop java_thread);
|
||||||
static void set_jvmti_thread_state(oop java_thread, JvmtiThreadState* state);
|
static void set_jvmti_thread_state(oop java_thread, JvmtiThreadState* state);
|
||||||
|
|
||||||
// Clear all extent local bindings on error
|
// Clear all scoped value bindings on error
|
||||||
static void clear_extentLocalBindings(oop java_thread);
|
static void clear_scopedValueBindings(oop java_thread);
|
||||||
|
|
||||||
// Blocker object responsible for thread parking
|
// Blocker object responsible for thread parking
|
||||||
static oop park_blocker(oop java_thread);
|
static oop park_blocker(oop java_thread);
|
||||||
|
@ -77,7 +77,7 @@ bool vmIntrinsics::preserves_state(vmIntrinsics::ID id) {
|
|||||||
case vmIntrinsics::_isInstance:
|
case vmIntrinsics::_isInstance:
|
||||||
case vmIntrinsics::_currentCarrierThread:
|
case vmIntrinsics::_currentCarrierThread:
|
||||||
case vmIntrinsics::_currentThread:
|
case vmIntrinsics::_currentThread:
|
||||||
case vmIntrinsics::_extentLocalCache:
|
case vmIntrinsics::_scopedValueCache:
|
||||||
case vmIntrinsics::_dabs:
|
case vmIntrinsics::_dabs:
|
||||||
case vmIntrinsics::_fabs:
|
case vmIntrinsics::_fabs:
|
||||||
case vmIntrinsics::_iabs:
|
case vmIntrinsics::_iabs:
|
||||||
@ -127,8 +127,8 @@ bool vmIntrinsics::can_trap(vmIntrinsics::ID id) {
|
|||||||
case vmIntrinsics::_currentCarrierThread:
|
case vmIntrinsics::_currentCarrierThread:
|
||||||
case vmIntrinsics::_currentThread:
|
case vmIntrinsics::_currentThread:
|
||||||
case vmIntrinsics::_setCurrentThread:
|
case vmIntrinsics::_setCurrentThread:
|
||||||
case vmIntrinsics::_extentLocalCache:
|
case vmIntrinsics::_scopedValueCache:
|
||||||
case vmIntrinsics::_setExtentLocalCache:
|
case vmIntrinsics::_setScopedValueCache:
|
||||||
case vmIntrinsics::_dabs:
|
case vmIntrinsics::_dabs:
|
||||||
case vmIntrinsics::_fabs:
|
case vmIntrinsics::_fabs:
|
||||||
case vmIntrinsics::_iabs:
|
case vmIntrinsics::_iabs:
|
||||||
@ -265,8 +265,8 @@ bool vmIntrinsics::disabled_by_jvm_flags(vmIntrinsics::ID id) {
|
|||||||
if (!InlineThreadNatives) return true;
|
if (!InlineThreadNatives) return true;
|
||||||
break;
|
break;
|
||||||
case vmIntrinsics::_setCurrentThread:
|
case vmIntrinsics::_setCurrentThread:
|
||||||
case vmIntrinsics::_extentLocalCache:
|
case vmIntrinsics::_scopedValueCache:
|
||||||
case vmIntrinsics::_setExtentLocalCache:
|
case vmIntrinsics::_setScopedValueCache:
|
||||||
case vmIntrinsics::_floatToRawIntBits:
|
case vmIntrinsics::_floatToRawIntBits:
|
||||||
case vmIntrinsics::_intBitsToFloat:
|
case vmIntrinsics::_intBitsToFloat:
|
||||||
case vmIntrinsics::_doubleToRawLongBits:
|
case vmIntrinsics::_doubleToRawLongBits:
|
||||||
|
@ -270,7 +270,7 @@ class methodHandle;
|
|||||||
do_intrinsic(_identityHashCode, java_lang_System, identityHashCode_name, object_int_signature, F_SN) \
|
do_intrinsic(_identityHashCode, java_lang_System, identityHashCode_name, object_int_signature, F_SN) \
|
||||||
do_name( identityHashCode_name, "identityHashCode") \
|
do_name( identityHashCode_name, "identityHashCode") \
|
||||||
do_intrinsic(_currentTimeMillis, java_lang_System, currentTimeMillis_name, void_long_signature, F_SN) \
|
do_intrinsic(_currentTimeMillis, java_lang_System, currentTimeMillis_name, void_long_signature, F_SN) \
|
||||||
\
|
\
|
||||||
do_name( currentTimeMillis_name, "currentTimeMillis") \
|
do_name( currentTimeMillis_name, "currentTimeMillis") \
|
||||||
do_intrinsic(_nanoTime, java_lang_System, nanoTime_name, void_long_signature, F_SN) \
|
do_intrinsic(_nanoTime, java_lang_System, nanoTime_name, void_long_signature, F_SN) \
|
||||||
do_name( nanoTime_name, "nanoTime") \
|
do_name( nanoTime_name, "nanoTime") \
|
||||||
@ -286,12 +286,15 @@ class methodHandle;
|
|||||||
do_intrinsic(_currentThread, java_lang_Thread, currentThread_name, currentThread_signature, F_SN) \
|
do_intrinsic(_currentThread, java_lang_Thread, currentThread_name, currentThread_signature, F_SN) \
|
||||||
do_name( currentThread_name, "currentThread") \
|
do_name( currentThread_name, "currentThread") \
|
||||||
do_signature(currentThread_signature, "()Ljava/lang/Thread;") \
|
do_signature(currentThread_signature, "()Ljava/lang/Thread;") \
|
||||||
do_intrinsic(_extentLocalCache, java_lang_Thread, extentLocalCache_name, extentLocalCache_signature, F_SN) \
|
do_intrinsic(_scopedValueCache, java_lang_Thread, scopedValueCache_name, scopedValueCache_signature, F_SN) \
|
||||||
do_name( extentLocalCache_name, "extentLocalCache") \
|
do_name( scopedValueCache_name, "scopedValueCache") \
|
||||||
do_signature(extentLocalCache_signature, "()[Ljava/lang/Object;") \
|
do_signature(scopedValueCache_signature, "()[Ljava/lang/Object;") \
|
||||||
do_intrinsic(_setExtentLocalCache, java_lang_Thread, setExtentLocalCache_name, setExtentLocalCache_signature, F_SN) \
|
do_intrinsic(_setScopedValueCache, java_lang_Thread, setScopedValueCache_name, setScopedValueCache_signature, F_SN) \
|
||||||
do_name( setExtentLocalCache_name, "setExtentLocalCache") \
|
do_name( setScopedValueCache_name, "setScopedValueCache") \
|
||||||
do_signature(setExtentLocalCache_signature, "([Ljava/lang/Object;)V") \
|
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_intrinsic(_setCurrentThread, java_lang_Thread, setCurrentThread_name, thread_void_signature, F_RN) \
|
||||||
do_name( setCurrentThread_name, "setCurrentThread") \
|
do_name( setCurrentThread_name, "setCurrentThread") \
|
||||||
\
|
\
|
||||||
@ -331,6 +334,9 @@ class methodHandle;
|
|||||||
do_name( onSpinWait_name, "onSpinWait") \
|
do_name( onSpinWait_name, "onSpinWait") \
|
||||||
do_alias( onSpinWait_signature, void_method_signature) \
|
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_intrinsic(_copyOf, java_util_Arrays, copyOf_name, copyOf_signature, F_S) \
|
||||||
do_name( copyOf_name, "copyOf") \
|
do_name( copyOf_name, "copyOf") \
|
||||||
do_signature(copyOf_signature, "([Ljava/lang/Object;ILjava/lang/Class;)[Ljava/lang/Object;") \
|
do_signature(copyOf_signature, "([Ljava/lang/Object;ILjava/lang/Class;)[Ljava/lang/Object;") \
|
||||||
|
@ -156,6 +156,8 @@
|
|||||||
template(jdk_internal_loader_BuiltinClassLoader, "jdk/internal/loader/BuiltinClassLoader") \
|
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_AppClassLoader, "jdk/internal/loader/ClassLoaders$AppClassLoader") \
|
||||||
template(jdk_internal_loader_ClassLoaders_PlatformClassLoader, "jdk/internal/loader/ClassLoaders$PlatformClassLoader") \
|
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 */ \
|
/* Java runtime version access */ \
|
||||||
template(java_lang_VersionProps, "java/lang/VersionProps") \
|
template(java_lang_VersionProps, "java/lang/VersionProps") \
|
||||||
@ -396,6 +398,7 @@
|
|||||||
template(group_name, "group") \
|
template(group_name, "group") \
|
||||||
template(daemon_name, "daemon") \
|
template(daemon_name, "daemon") \
|
||||||
template(run_method_name, "run") \
|
template(run_method_name, "run") \
|
||||||
|
template(runWith_method_name, "runWith") \
|
||||||
template(interrupt_method_name, "interrupt") \
|
template(interrupt_method_name, "interrupt") \
|
||||||
template(exit_method_name, "exit") \
|
template(exit_method_name, "exit") \
|
||||||
template(remove_method_name, "remove") \
|
template(remove_method_name, "remove") \
|
||||||
@ -601,6 +604,7 @@
|
|||||||
template(string_array_string_array_void_signature, "([Ljava/lang/String;[Ljava/lang/String;)V") \
|
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_throwable_void_signature, "(Ljava/lang/Thread;Ljava/lang/Throwable;)V") \
|
||||||
template(thread_void_signature, "(Ljava/lang/Thread;)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_runnable_void_signature, "(Ljava/lang/ThreadGroup;Ljava/lang/Runnable;)V") \
|
||||||
template(threadgroup_string_void_signature, "(Ljava/lang/ThreadGroup;Ljava/lang/String;)V") \
|
template(threadgroup_string_void_signature, "(Ljava/lang/ThreadGroup;Ljava/lang/String;)V") \
|
||||||
template(void_threadgroup_array_signature, "()[Ljava/lang/ThreadGroup;") \
|
template(void_threadgroup_array_signature, "()[Ljava/lang/ThreadGroup;") \
|
||||||
|
@ -310,10 +310,13 @@ JNIEXPORT jobjectArray JNICALL
|
|||||||
JVM_DumpThreads(JNIEnv *env, jclass threadClass, jobjectArray threads);
|
JVM_DumpThreads(JNIEnv *env, jclass threadClass, jobjectArray threads);
|
||||||
|
|
||||||
JNIEXPORT jobject JNICALL
|
JNIEXPORT jobject JNICALL
|
||||||
JVM_ExtentLocalCache(JNIEnv *env, jclass threadClass);
|
JVM_ScopedValueCache(JNIEnv *env, jclass threadClass);
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
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
|
JNIEXPORT jlong JNICALL
|
||||||
JVM_GetNextThreadIdOffset(JNIEnv *env, jclass threadClass);
|
JVM_GetNextThreadIdOffset(JNIEnv *env, jclass threadClass);
|
||||||
@ -742,6 +745,8 @@ JVM_GetInheritedAccessControlContext(JNIEnv *env, jclass cls);
|
|||||||
#define JVM_EnsureMaterializedForStackWalk(env, value) \
|
#define JVM_EnsureMaterializedForStackWalk(env, value) \
|
||||||
do {} while(0) // Nothing to do. The fact that the value escaped
|
do {} while(0) // Nothing to do. The fact that the value escaped
|
||||||
// through a native method is enough.
|
// through a native method is enough.
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
JVM_EnsureMaterializedForStackWalk_func(JNIEnv* env, jobject vthread, jobject value);
|
||||||
|
|
||||||
JNIEXPORT jobject JNICALL
|
JNIEXPORT jobject JNICALL
|
||||||
JVM_GetStackAccessControlContext(JNIEnv *env, jclass cls);
|
JVM_GetStackAccessControlContext(JNIEnv *env, jclass cls);
|
||||||
|
@ -372,6 +372,9 @@ JRT_ENTRY(void, InterpreterRuntime::throw_StackOverflowError(JavaThread* current
|
|||||||
CHECK);
|
CHECK);
|
||||||
// Increment counter for hs_err file reporting
|
// Increment counter for hs_err file reporting
|
||||||
Atomic::inc(&Exceptions::_stack_overflow_errors);
|
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);
|
THROW_HANDLE(exception);
|
||||||
JRT_END
|
JRT_END
|
||||||
|
|
||||||
@ -383,6 +386,9 @@ JRT_ENTRY(void, InterpreterRuntime::throw_delayed_StackOverflowError(JavaThread*
|
|||||||
Universe::delayed_stack_overflow_error_message());
|
Universe::delayed_stack_overflow_error_message());
|
||||||
// Increment counter for hs_err file reporting
|
// Increment counter for hs_err file reporting
|
||||||
Atomic::inc(&Exceptions::_stack_overflow_errors);
|
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);
|
THROW_HANDLE(exception);
|
||||||
JRT_END
|
JRT_END
|
||||||
|
|
||||||
|
@ -1609,12 +1609,12 @@ C2V_VMENTRY(void, materializeVirtualObjects, (JNIEnv* env, jobject, jobject _hs_
|
|||||||
for (int frame_index = 0; frame_index < virtualFrames->length(); frame_index++) {
|
for (int frame_index = 0; frame_index < virtualFrames->length(); frame_index++) {
|
||||||
compiledVFrame* cvf = virtualFrames->at(frame_index);
|
compiledVFrame* cvf = virtualFrames->at(frame_index);
|
||||||
|
|
||||||
GrowableArray<ScopeValue*>* extentLocals = cvf->scope()->locals();
|
GrowableArray<ScopeValue*>* scopedValues = cvf->scope()->locals();
|
||||||
StackValueCollection* locals = cvf->locals();
|
StackValueCollection* locals = cvf->locals();
|
||||||
if (locals != NULL) {
|
if (locals != NULL) {
|
||||||
for (int i2 = 0; i2 < locals->size(); i2++) {
|
for (int i2 = 0; i2 < locals->size(); i2++) {
|
||||||
StackValue* var = locals->at(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;
|
jvalue val;
|
||||||
val.l = cast_from_oop<jobject>(locals->at(i2)->get_obj()());
|
val.l = cast_from_oop<jobject>(locals->at(i2)->get_obj()());
|
||||||
cvf->update_local(T_OBJECT, i2, val);
|
cvf->update_local(T_OBJECT, i2, val);
|
||||||
|
@ -174,7 +174,7 @@
|
|||||||
\
|
\
|
||||||
nonstatic_field(JavaThread, _threadObj, OopHandle) \
|
nonstatic_field(JavaThread, _threadObj, OopHandle) \
|
||||||
nonstatic_field(JavaThread, _vthread, OopHandle) \
|
nonstatic_field(JavaThread, _vthread, OopHandle) \
|
||||||
nonstatic_field(JavaThread, _extentLocalCache, OopHandle) \
|
nonstatic_field(JavaThread, _scopedValueCache, OopHandle) \
|
||||||
nonstatic_field(JavaThread, _anchor, JavaFrameAnchor) \
|
nonstatic_field(JavaThread, _anchor, JavaFrameAnchor) \
|
||||||
nonstatic_field(JavaThread, _vm_result, oop) \
|
nonstatic_field(JavaThread, _vm_result, oop) \
|
||||||
nonstatic_field(JavaThread, _stack_overflow_state._stack_overflow_limit, address) \
|
nonstatic_field(JavaThread, _stack_overflow_state._stack_overflow_limit, address) \
|
||||||
|
@ -70,7 +70,6 @@ public:
|
|||||||
|
|
||||||
inline oop xchg(oop new_value);
|
inline oop xchg(oop new_value);
|
||||||
|
|
||||||
// Used only for removing handle.
|
|
||||||
oop* ptr_raw() const { return _obj; }
|
oop* ptr_raw() const { return _obj; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -680,8 +680,8 @@ bool C2Compiler::is_intrinsic_supported(const methodHandle& method, bool is_virt
|
|||||||
case vmIntrinsics::_currentCarrierThread:
|
case vmIntrinsics::_currentCarrierThread:
|
||||||
case vmIntrinsics::_currentThread:
|
case vmIntrinsics::_currentThread:
|
||||||
case vmIntrinsics::_setCurrentThread:
|
case vmIntrinsics::_setCurrentThread:
|
||||||
case vmIntrinsics::_extentLocalCache:
|
case vmIntrinsics::_scopedValueCache:
|
||||||
case vmIntrinsics::_setExtentLocalCache:
|
case vmIntrinsics::_setScopedValueCache:
|
||||||
#ifdef JFR_HAVE_INTRINSICS
|
#ifdef JFR_HAVE_INTRINSICS
|
||||||
case vmIntrinsics::_counterTime:
|
case vmIntrinsics::_counterTime:
|
||||||
case vmIntrinsics::_getEventWriter:
|
case vmIntrinsics::_getEventWriter:
|
||||||
|
@ -472,8 +472,8 @@ bool LibraryCallKit::try_to_inline(int predicate) {
|
|||||||
case vmIntrinsics::_currentThread: return inline_native_currentThread();
|
case vmIntrinsics::_currentThread: return inline_native_currentThread();
|
||||||
case vmIntrinsics::_setCurrentThread: return inline_native_setCurrentThread();
|
case vmIntrinsics::_setCurrentThread: return inline_native_setCurrentThread();
|
||||||
|
|
||||||
case vmIntrinsics::_extentLocalCache: return inline_native_extentLocalCache();
|
case vmIntrinsics::_scopedValueCache: return inline_native_scopedValueCache();
|
||||||
case vmIntrinsics::_setExtentLocalCache: return inline_native_setExtentLocalCache();
|
case vmIntrinsics::_setScopedValueCache: return inline_native_setScopedValueCache();
|
||||||
|
|
||||||
#ifdef JFR_HAVE_INTRINSICS
|
#ifdef JFR_HAVE_INTRINSICS
|
||||||
case vmIntrinsics::_counterTime: return inline_native_time_funcs(CAST_FROM_FN_PTR(address, JfrTime::time_function()), "counterTime");
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Node* LibraryCallKit::extentLocalCache_helper() {
|
Node* LibraryCallKit::scopedValueCache_helper() {
|
||||||
ciKlass *objects_klass = ciObjArrayKlass::make(env()->Object_klass());
|
ciKlass *objects_klass = ciObjArrayKlass::make(env()->Object_klass());
|
||||||
const TypeOopPtr *etype = TypeOopPtr::make_from_klass(env()->Object_klass());
|
const TypeOopPtr *etype = TypeOopPtr::make_from_klass(env()->Object_klass());
|
||||||
|
|
||||||
bool xk = etype->klass_is_exact();
|
bool xk = etype->klass_is_exact();
|
||||||
|
|
||||||
Node* thread = _gvn.transform(new ThreadLocalNode());
|
Node* thread = _gvn.transform(new ThreadLocalNode());
|
||||||
Node* p = basic_plus_adr(top()/*!oop*/, thread, in_bytes(JavaThread::extentLocalCache_offset()));
|
Node* p = basic_plus_adr(top()/*!oop*/, thread, in_bytes(JavaThread::scopedValueCache_offset()));
|
||||||
return _gvn.transform(LoadNode::make(_gvn, NULL, immutable_memory(), p, p->bottom_type()->is_ptr(),
|
// We cannot use immutable_memory() because we might flip onto a
|
||||||
TypeRawPtr::NOTNULL, T_ADDRESS, MemNode::unordered));
|
// 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------------------
|
//------------------------inline_native_scopedValueCache------------------
|
||||||
bool LibraryCallKit::inline_native_extentLocalCache() {
|
bool LibraryCallKit::inline_native_scopedValueCache() {
|
||||||
ciKlass *objects_klass = ciObjArrayKlass::make(env()->Object_klass());
|
ciKlass *objects_klass = ciObjArrayKlass::make(env()->Object_klass());
|
||||||
const TypeOopPtr *etype = TypeOopPtr::make_from_klass(env()->Object_klass());
|
const TypeOopPtr *etype = TypeOopPtr::make_from_klass(env()->Object_klass());
|
||||||
const TypeAry* arr0 = TypeAry::make(etype, TypeInt::POS);
|
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.
|
// type of the result BotPTR.
|
||||||
bool xk = etype->klass_is_exact();
|
bool xk = etype->klass_is_exact();
|
||||||
const Type* objects_type = TypeAryPtr::make(TypePtr::BotPTR, arr0, objects_klass, xk, 0);
|
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));
|
set_result(access_load(cache_obj_handle, objects_type, T_OBJECT, IN_NATIVE));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------inline_native_setExtentLocalCache------------------
|
//------------------------inline_native_setScopedValueCache------------------
|
||||||
bool LibraryCallKit::inline_native_setExtentLocalCache() {
|
bool LibraryCallKit::inline_native_setScopedValueCache() {
|
||||||
Node* arr = argument(0);
|
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();
|
const TypePtr *adr_type = _gvn.type(cache_obj_handle)->isa_ptr();
|
||||||
store_to_memory(control(), cache_obj_handle, arr, T_OBJECT, adr_type,
|
store_to_memory(control(), cache_obj_handle, arr, T_OBJECT, adr_type,
|
||||||
|
@ -239,9 +239,9 @@ class LibraryCallKit : public GraphKit {
|
|||||||
bool inline_native_currentThread();
|
bool inline_native_currentThread();
|
||||||
bool inline_native_setCurrentThread();
|
bool inline_native_setCurrentThread();
|
||||||
|
|
||||||
bool inline_native_extentLocalCache();
|
bool inline_native_scopedValueCache();
|
||||||
Node* extentLocalCache_helper();
|
Node* scopedValueCache_helper();
|
||||||
bool inline_native_setExtentLocalCache();
|
bool inline_native_setScopedValueCache();
|
||||||
|
|
||||||
bool inline_native_time_funcs(address method, const char* funcName);
|
bool inline_native_time_funcs(address method, const char* funcName);
|
||||||
#ifdef JFR_HAVE_INTRINSICS
|
#ifdef JFR_HAVE_INTRINSICS
|
||||||
|
@ -865,7 +865,7 @@ bool LoadNode::is_immutable_value(Node* adr) {
|
|||||||
in_bytes(JavaThread::osthread_offset()),
|
in_bytes(JavaThread::osthread_offset()),
|
||||||
in_bytes(JavaThread::threadObj_offset()),
|
in_bytes(JavaThread::threadObj_offset()),
|
||||||
in_bytes(JavaThread::vthread_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++) {
|
for (size_t i = 0; i < sizeof offsets / sizeof offsets[0]; i++) {
|
||||||
|
@ -1363,6 +1363,54 @@ JVM_ENTRY(jobject, JVM_GetStackAccessControlContext(JNIEnv *env, jclass cls))
|
|||||||
return JNIHandles::make_local(THREAD, result);
|
return JNIHandles::make_local(THREAD, result);
|
||||||
JVM_END
|
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))
|
JVM_ENTRY(jboolean, JVM_IsArrayClass(JNIEnv *env, jclass cls))
|
||||||
Klass* k = java_lang_Class::as_Klass(JNIHandles::resolve_non_null(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_END
|
||||||
|
|
||||||
JVM_ENTRY(jobject, JVM_ExtentLocalCache(JNIEnv* env, jclass threadClass))
|
JVM_ENTRY(jobject, JVM_ScopedValueCache(JNIEnv* env, jclass threadClass))
|
||||||
oop theCache = thread->extentLocalCache();
|
oop theCache = thread->scopedValueCache();
|
||||||
if (theCache) {
|
|
||||||
arrayOop objs = arrayOop(theCache);
|
|
||||||
assert(objs->length() == ExtentLocalCacheSize * 2, "wrong length");
|
|
||||||
}
|
|
||||||
return JNIHandles::make_local(THREAD, theCache);
|
return JNIHandles::make_local(THREAD, theCache);
|
||||||
JVM_END
|
JVM_END
|
||||||
|
|
||||||
JVM_ENTRY(void, JVM_SetExtentLocalCache(JNIEnv* env, jclass threadClass,
|
JVM_ENTRY(void, JVM_SetScopedValueCache(JNIEnv* env, jclass threadClass,
|
||||||
jobject theCache))
|
jobject theCache))
|
||||||
arrayOop objs = arrayOop(JNIHandles::resolve(theCache));
|
arrayOop objs = arrayOop(JNIHandles::resolve(theCache));
|
||||||
if (objs != NULL) {
|
thread->set_scopedValueCache(objs);
|
||||||
assert(objs->length() == ExtentLocalCacheSize * 2, "wrong length");
|
|
||||||
}
|
|
||||||
thread->set_extentLocalCache(objs);
|
|
||||||
JVM_END
|
JVM_END
|
||||||
|
|
||||||
// java.lang.SecurityManager ///////////////////////////////////////////////////////////////////////
|
// java.lang.SecurityManager ///////////////////////////////////////////////////////////////////////
|
||||||
@ -4019,3 +4060,12 @@ JVM_ENTRY(jint, JVM_GetClassFileVersion(JNIEnv* env, jclass current))
|
|||||||
InstanceKlass* ik = InstanceKlass::cast(c);
|
InstanceKlass* ik = InstanceKlass::cast(c);
|
||||||
return (ik->minor_version() << 16) | ik->major_version();
|
return (ik->minor_version() << 16) | ik->major_version();
|
||||||
JVM_END
|
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
|
||||||
|
@ -441,11 +441,8 @@ Deoptimization::UnrollBlock* Deoptimization::fetch_unroll_info_helper(JavaThread
|
|||||||
vframeArray* array = create_vframeArray(current, deoptee, &map, chunk, realloc_failures);
|
vframeArray* array = create_vframeArray(current, deoptee, &map, chunk, realloc_failures);
|
||||||
#if COMPILER2_OR_JVMCI
|
#if COMPILER2_OR_JVMCI
|
||||||
if (realloc_failures) {
|
if (realloc_failures) {
|
||||||
// FIXME: This very crudely destroys all ExtentLocal bindings. This
|
// This destroys all ScopedValue bindings.
|
||||||
// is better than a bound value escaping, but far from ideal.
|
current->clear_scopedValueBindings();
|
||||||
oop java_thread = current->threadObj();
|
|
||||||
current->set_extentLocalCache(NULL);
|
|
||||||
java_lang_Thread::clear_extentLocalBindings(java_thread);
|
|
||||||
pop_frames_failed_reallocs(current, array);
|
pop_frames_failed_reallocs(current, array);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -92,18 +92,6 @@ JVMFlag::Error VMPageSizeConstraintFunc(uintx value, bool verbose) {
|
|||||||
return JVMFlag::SUCCESS;
|
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) {
|
JVMFlag::Error NUMAInterleaveGranularityConstraintFunc(size_t value, bool verbose) {
|
||||||
size_t min = os::vm_allocation_granularity();
|
size_t min = os::vm_allocation_granularity();
|
||||||
size_t max = NOT_LP64(2*G) LP64_ONLY(8192*G);
|
size_t max = NOT_LP64(2*G) LP64_ONLY(8192*G);
|
||||||
|
@ -37,7 +37,6 @@
|
|||||||
f(int, ObjectAlignmentInBytesConstraintFunc) \
|
f(int, ObjectAlignmentInBytesConstraintFunc) \
|
||||||
f(intx, ContendedPaddingWidthConstraintFunc) \
|
f(intx, ContendedPaddingWidthConstraintFunc) \
|
||||||
f(intx, PerfDataSamplingIntervalFunc) \
|
f(intx, PerfDataSamplingIntervalFunc) \
|
||||||
f(intx, ExtentLocalCacheSizeConstraintFunc) \
|
|
||||||
f(uintx, VMPageSizeConstraintFunc) \
|
f(uintx, VMPageSizeConstraintFunc) \
|
||||||
f(size_t, NUMAInterleaveGranularityConstraintFunc)
|
f(size_t, NUMAInterleaveGranularityConstraintFunc)
|
||||||
|
|
||||||
|
@ -1947,11 +1947,6 @@ const int ObjectAlignmentInBytes = 8;
|
|||||||
develop(bool, UseContinuationFastPath, true, \
|
develop(bool, UseContinuationFastPath, true, \
|
||||||
"Use fast-path frame walking in continuations") \
|
"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), \
|
develop(int, VerifyMetaspaceInterval, DEBUG_ONLY(500) NOT_DEBUG(0), \
|
||||||
"Run periodic metaspace verifications (0 - none, " \
|
"Run periodic metaspace verifications (0 - none, " \
|
||||||
"1 - always, >1 every nth interval)") \
|
"1 - always, >1 every nth interval)") \
|
||||||
|
@ -157,7 +157,7 @@ void JavaThread::set_threadOopHandles(oop p) {
|
|||||||
_threadObj = OopHandle(_thread_oop_storage, p);
|
_threadObj = OopHandle(_thread_oop_storage, p);
|
||||||
_vthread = OopHandle(_thread_oop_storage, p);
|
_vthread = OopHandle(_thread_oop_storage, p);
|
||||||
_jvmti_vthread = OopHandle(_thread_oop_storage, NULL);
|
_jvmti_vthread = OopHandle(_thread_oop_storage, NULL);
|
||||||
_extentLocalCache = OopHandle(_thread_oop_storage, NULL);
|
_scopedValueCache = OopHandle(_thread_oop_storage, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
oop JavaThread::threadObj() const {
|
oop JavaThread::threadObj() const {
|
||||||
@ -186,13 +186,26 @@ void JavaThread::set_jvmti_vthread(oop p) {
|
|||||||
_jvmti_vthread.replace(p);
|
_jvmti_vthread.replace(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
oop JavaThread::extentLocalCache() const {
|
oop JavaThread::scopedValueCache() const {
|
||||||
return _extentLocalCache.resolve();
|
return _scopedValueCache.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
void JavaThread::set_extentLocalCache(oop p) {
|
void JavaThread::set_scopedValueCache(oop p) {
|
||||||
assert(_thread_oop_storage != NULL, "not yet initialized");
|
if (_scopedValueCache.ptr_raw() != NULL) { // i.e. if the OopHandle has been allocated
|
||||||
_extentLocalCache.replace(p);
|
_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,
|
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
|
// We cannot call Exceptions::_throw(...) here because we cannot block
|
||||||
set_pending_exception(java_throwable, __FILE__, __LINE__);
|
set_pending_exception(java_throwable, __FILE__, __LINE__);
|
||||||
|
|
||||||
// Clear any extent-local bindings
|
clear_scopedValueBindings();
|
||||||
set_extentLocalCache(NULL);
|
|
||||||
oop threadOop = threadObj();
|
|
||||||
assert(threadOop != NULL, "must be");
|
|
||||||
java_lang_Thread::clear_extentLocalBindings(threadOop);
|
|
||||||
|
|
||||||
LogTarget(Info, exceptions) lt;
|
LogTarget(Info, exceptions) lt;
|
||||||
if (lt.is_enabled()) {
|
if (lt.is_enabled()) {
|
||||||
@ -2097,7 +2106,7 @@ void JavaThread::add_oop_handles_for_release() {
|
|||||||
new_head->add(_threadObj);
|
new_head->add(_threadObj);
|
||||||
new_head->add(_vthread);
|
new_head->add(_vthread);
|
||||||
new_head->add(_jvmti_vthread);
|
new_head->add(_jvmti_vthread);
|
||||||
new_head->add(_extentLocalCache);
|
new_head->add(_scopedValueCache);
|
||||||
_oop_handle_list = new_head;
|
_oop_handle_list = new_head;
|
||||||
Service_lock->notify_all();
|
Service_lock->notify_all();
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,7 @@ class JavaThread: public Thread {
|
|||||||
OopHandle _threadObj; // The Java level thread object
|
OopHandle _threadObj; // The Java level thread object
|
||||||
OopHandle _vthread; // the value returned by Thread.currentThread(): the virtual thread, if mounted, otherwise _threadObj
|
OopHandle _vthread; // the value returned by Thread.currentThread(): the virtual thread, if mounted, otherwise _threadObj
|
||||||
OopHandle _jvmti_vthread;
|
OopHandle _jvmti_vthread;
|
||||||
OopHandle _extentLocalCache;
|
OopHandle _scopedValueCache;
|
||||||
|
|
||||||
static OopStorage* _thread_oop_storage;
|
static OopStorage* _thread_oop_storage;
|
||||||
|
|
||||||
@ -520,8 +520,9 @@ private:
|
|||||||
void set_threadOopHandles(oop p);
|
void set_threadOopHandles(oop p);
|
||||||
oop vthread() const;
|
oop vthread() const;
|
||||||
void set_vthread(oop p);
|
void set_vthread(oop p);
|
||||||
oop extentLocalCache() const;
|
oop scopedValueCache() const;
|
||||||
void set_extentLocalCache(oop p);
|
void set_scopedValueCache(oop p);
|
||||||
|
void clear_scopedValueBindings();
|
||||||
oop jvmti_vthread() const;
|
oop jvmti_vthread() const;
|
||||||
void set_jvmti_vthread(oop p);
|
void set_jvmti_vthread(oop p);
|
||||||
|
|
||||||
@ -744,7 +745,7 @@ private:
|
|||||||
void clr_do_not_unlock(void) { _do_not_unlock_if_synchronized = false; }
|
void clr_do_not_unlock(void) { _do_not_unlock_if_synchronized = false; }
|
||||||
bool do_not_unlock(void) { return _do_not_unlock_if_synchronized; }
|
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
|
// For assembly stub generation
|
||||||
static ByteSize threadObj_offset() { return byte_offset_of(JavaThread, _threadObj); }
|
static ByteSize threadObj_offset() { return byte_offset_of(JavaThread, _threadObj); }
|
||||||
|
@ -884,9 +884,10 @@ void SharedRuntime::throw_StackOverflowError_common(JavaThread* current, bool de
|
|||||||
if (StackTraceInThrowable) {
|
if (StackTraceInThrowable) {
|
||||||
java_lang_Throwable::fill_in_stack_trace(exception);
|
java_lang_Throwable::fill_in_stack_trace(exception);
|
||||||
}
|
}
|
||||||
// Remove the ExtentLocal cache in case we got a StackOverflowError
|
// Remove the ScopedValue bindings in case we got a
|
||||||
// while we were trying to remove ExtentLocal bindings.
|
// StackOverflowError while we were trying to remove ScopedValue
|
||||||
current->set_extentLocalCache(NULL);
|
// bindings.
|
||||||
|
current->clear_scopedValueBindings();
|
||||||
// Increment counter for hs_err file reporting
|
// Increment counter for hs_err file reporting
|
||||||
Atomic::inc(&Exceptions::_stack_overflow_errors);
|
Atomic::inc(&Exceptions::_stack_overflow_errors);
|
||||||
throw_and_post_jvmti_exception(current, exception);
|
throw_and_post_jvmti_exception(current, exception);
|
||||||
|
@ -145,12 +145,12 @@ void compiledVFrame::update_deferred_value(BasicType type, int index, jvalue val
|
|||||||
// original update is kept.
|
// original update is kept.
|
||||||
void compiledVFrame::create_deferred_updates_after_object_deoptimization() {
|
void compiledVFrame::create_deferred_updates_after_object_deoptimization() {
|
||||||
// locals
|
// locals
|
||||||
GrowableArray<ScopeValue*>* extentLocals = scope()->locals();
|
GrowableArray<ScopeValue*>* scopedValues = scope()->locals();
|
||||||
StackValueCollection* lcls = locals();
|
StackValueCollection* lcls = locals();
|
||||||
if (lcls != NULL) {
|
if (lcls != NULL) {
|
||||||
for (int i2 = 0; i2 < lcls->size(); i2++) {
|
for (int i2 = 0; i2 < lcls->size(); i2++) {
|
||||||
StackValue* var = lcls->at(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;
|
jvalue val;
|
||||||
val.l = cast_from_oop<jobject>(lcls->at(i2)->get_obj()());
|
val.l = cast_from_oop<jobject>(lcls->at(i2)->get_obj()());
|
||||||
update_local(T_OBJECT, i2, val);
|
update_local(T_OBJECT, i2, val);
|
||||||
|
@ -710,7 +710,7 @@
|
|||||||
nonstatic_field(JavaThread, _threadObj, OopHandle) \
|
nonstatic_field(JavaThread, _threadObj, OopHandle) \
|
||||||
nonstatic_field(JavaThread, _vthread, OopHandle) \
|
nonstatic_field(JavaThread, _vthread, OopHandle) \
|
||||||
nonstatic_field(JavaThread, _jvmti_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, _anchor, JavaFrameAnchor) \
|
||||||
nonstatic_field(JavaThread, _vm_result, oop) \
|
nonstatic_field(JavaThread, _vm_result, oop) \
|
||||||
nonstatic_field(JavaThread, _vm_result_2, Metadata*) \
|
nonstatic_field(JavaThread, _vm_result_2, Metadata*) \
|
||||||
|
@ -160,8 +160,14 @@ void Exceptions::_throw(JavaThread* thread, const char* file, int line, Handle h
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (h_exception->is_a(vmClasses::OutOfMemoryError_klass())) {
|
if (h_exception->is_a(vmClasses::VirtualMachineError_klass())) {
|
||||||
count_out_of_memory_exceptions(h_exception);
|
// 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())) {
|
if (h_exception->is_a(vmClasses::LinkageError_klass())) {
|
||||||
|
@ -86,6 +86,7 @@ import jdk.internal.vm.Continuation;
|
|||||||
import jdk.internal.vm.ContinuationScope;
|
import jdk.internal.vm.ContinuationScope;
|
||||||
import jdk.internal.vm.StackableScope;
|
import jdk.internal.vm.StackableScope;
|
||||||
import jdk.internal.vm.ThreadContainer;
|
import jdk.internal.vm.ThreadContainer;
|
||||||
|
import jdk.internal.vm.annotation.ForceInline;
|
||||||
import jdk.internal.vm.annotation.IntrinsicCandidate;
|
import jdk.internal.vm.annotation.IntrinsicCandidate;
|
||||||
import jdk.internal.vm.annotation.Stable;
|
import jdk.internal.vm.annotation.Stable;
|
||||||
import jdk.internal.vm.annotation.ChangesCurrentThread;
|
import jdk.internal.vm.annotation.ChangesCurrentThread;
|
||||||
@ -2578,20 +2579,29 @@ public final class System {
|
|||||||
return ((ThreadLocal<?>)local).isCarrierThreadLocalPresent();
|
return ((ThreadLocal<?>)local).isCarrierThreadLocalPresent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object[] extentLocalCache() {
|
public Object[] scopedValueCache() {
|
||||||
return Thread.extentLocalCache();
|
return Thread.scopedValueCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setExtentLocalCache(Object[] cache) {
|
public void setScopedValueCache(Object[] cache) {
|
||||||
Thread.setExtentLocalCache(cache);
|
Thread.setScopedValueCache(cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object extentLocalBindings() {
|
public Object scopedValueBindings() {
|
||||||
return Thread.extentLocalBindings();
|
return Thread.scopedValueBindings();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setExtentLocalBindings(Object bindings) {
|
public Object findScopedValueBindings() {
|
||||||
Thread.setExtentLocalBindings(bindings);
|
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) {
|
public Continuation getContinuation(Thread thread) {
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
package java.lang;
|
package java.lang;
|
||||||
|
|
||||||
|
import java.lang.ref.Reference;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.security.AccessController;
|
import java.security.AccessController;
|
||||||
import java.security.AccessControlContext;
|
import java.security.AccessControlContext;
|
||||||
@ -49,9 +50,11 @@ import jdk.internal.misc.VM;
|
|||||||
import jdk.internal.reflect.CallerSensitive;
|
import jdk.internal.reflect.CallerSensitive;
|
||||||
import jdk.internal.reflect.Reflection;
|
import jdk.internal.reflect.Reflection;
|
||||||
import jdk.internal.vm.Continuation;
|
import jdk.internal.vm.Continuation;
|
||||||
import jdk.internal.vm.ExtentLocalContainer;
|
import jdk.internal.vm.ScopedValueContainer;
|
||||||
import jdk.internal.vm.StackableScope;
|
import jdk.internal.vm.StackableScope;
|
||||||
import jdk.internal.vm.ThreadContainer;
|
import jdk.internal.vm.ThreadContainer;
|
||||||
|
import jdk.internal.vm.annotation.ForceInline;
|
||||||
|
import jdk.internal.vm.annotation.Hidden;
|
||||||
import jdk.internal.vm.annotation.IntrinsicCandidate;
|
import jdk.internal.vm.annotation.IntrinsicCandidate;
|
||||||
import sun.nio.ch.Interruptible;
|
import sun.nio.ch.Interruptible;
|
||||||
import sun.security.util.SecurityConstants;
|
import sun.security.util.SecurityConstants;
|
||||||
@ -279,34 +282,44 @@ public class Thread implements Runnable {
|
|||||||
ThreadLocal.ThreadLocalMap inheritableThreadLocals;
|
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() {
|
// Special value to indicate this is a newly-created Thread
|
||||||
return currentThread().extentLocalBindings;
|
// 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) {
|
static void setScopedValueBindings(Object bindings) {
|
||||||
currentThread().extentLocalBindings = 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.
|
* Invoked when starting a thread.
|
||||||
*/
|
*/
|
||||||
void inheritExtentLocalBindings(ThreadContainer container) {
|
void inheritScopedValueBindings(ThreadContainer container) {
|
||||||
ExtentLocalContainer.BindingsSnapshot snapshot;
|
ScopedValueContainer.BindingsSnapshot snapshot;
|
||||||
if (container.owner() != null
|
if (container.owner() != null
|
||||||
&& (snapshot = container.extentLocalBindings()) != null) {
|
&& (snapshot = container.scopedValueBindings()) != null) {
|
||||||
|
|
||||||
// bindings established for running/calling an operation
|
// bindings established for running/calling an operation
|
||||||
Object bindings = snapshot.extentLocalBindings();
|
Object bindings = snapshot.scopedValueBindings();
|
||||||
if (currentThread().extentLocalBindings != bindings) {
|
if (currentThread().scopedValueBindings != bindings) {
|
||||||
StructureViolationExceptions.throwException("Extent local bindings have changed");
|
StructureViolationExceptions.throwException("Scoped value bindings have changed");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.extentLocalBindings = bindings;
|
this.scopedValueBindings = bindings;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,13 +406,16 @@ public class Thread implements Runnable {
|
|||||||
@IntrinsicCandidate
|
@IntrinsicCandidate
|
||||||
native void setCurrentThread(Thread thread);
|
native void setCurrentThread(Thread thread);
|
||||||
|
|
||||||
// ExtentLocal support:
|
// ScopedValue support:
|
||||||
|
|
||||||
@IntrinsicCandidate
|
@IntrinsicCandidate
|
||||||
static native Object[] extentLocalCache();
|
static native Object[] scopedValueCache();
|
||||||
|
|
||||||
@IntrinsicCandidate
|
@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
|
* 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();
|
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();
|
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
|
// create a FieldHolder object, needed when bound to an OS thread
|
||||||
if (bound) {
|
if (bound) {
|
||||||
ThreadGroup g = Constants.VTHREAD_GROUP;
|
ThreadGroup g = Constants.VTHREAD_GROUP;
|
||||||
@ -1564,8 +1587,8 @@ public class Thread implements Runnable {
|
|||||||
boolean started = false;
|
boolean started = false;
|
||||||
container.onStart(this); // may throw
|
container.onStart(this); // may throw
|
||||||
try {
|
try {
|
||||||
// extent locals may be inherited
|
// scoped values may be inherited
|
||||||
inheritExtentLocalBindings(container);
|
inheritScopedValueBindings(container);
|
||||||
|
|
||||||
start0();
|
start0();
|
||||||
started = true;
|
started = true;
|
||||||
@ -1596,10 +1619,24 @@ public class Thread implements Runnable {
|
|||||||
public void run() {
|
public void run() {
|
||||||
Runnable task = holder.task;
|
Runnable task = holder.task;
|
||||||
if (task != null) {
|
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.
|
* Null out reference after Thread termination.
|
||||||
*/
|
*/
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
*/
|
*/
|
||||||
package java.lang;
|
package java.lang;
|
||||||
|
|
||||||
|
import java.lang.ref.Reference;
|
||||||
import java.security.AccessController;
|
import java.security.AccessController;
|
||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@ -53,6 +54,8 @@ import jdk.internal.vm.StackableScope;
|
|||||||
import jdk.internal.vm.ThreadContainer;
|
import jdk.internal.vm.ThreadContainer;
|
||||||
import jdk.internal.vm.ThreadContainers;
|
import jdk.internal.vm.ThreadContainers;
|
||||||
import jdk.internal.vm.annotation.ChangesCurrentThread;
|
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 jdk.internal.vm.annotation.JvmtiMountTransition;
|
||||||
import sun.nio.ch.Interruptible;
|
import sun.nio.ch.Interruptible;
|
||||||
import sun.security.action.GetPropertyAction;
|
import sun.security.action.GetPropertyAction;
|
||||||
@ -283,13 +286,13 @@ final class VirtualThread extends BaseVirtualThread {
|
|||||||
event.commit();
|
event.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Object bindings = scopedValueBindings();
|
||||||
try {
|
try {
|
||||||
task.run();
|
runWith(bindings, task);
|
||||||
} catch (Throwable exc) {
|
} catch (Throwable exc) {
|
||||||
dispatchUncaughtException(exc);
|
dispatchUncaughtException(exc);
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// pop any remaining scopes from the stack, this may block
|
// pop any remaining scopes from the stack, this may block
|
||||||
StackableScope.popAll();
|
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
|
* Mounts this virtual thread onto the current platform thread. On
|
||||||
* return, the current thread is the virtual thread.
|
* return, the current thread is the virtual thread.
|
||||||
@ -488,8 +499,8 @@ final class VirtualThread extends BaseVirtualThread {
|
|||||||
boolean started = false;
|
boolean started = false;
|
||||||
container.onStart(this); // may throw
|
container.onStart(this); // may throw
|
||||||
try {
|
try {
|
||||||
// extent locals may be inherited
|
// scoped values may be inherited
|
||||||
inheritExtentLocalBindings(container);
|
inheritScopedValueBindings(container);
|
||||||
|
|
||||||
// submit task to run thread
|
// submit task to run thread
|
||||||
submitRunContinuation();
|
submitRunContinuation();
|
||||||
|
@ -401,7 +401,7 @@ public final class ThreadLocalRandom extends Random {
|
|||||||
= new AtomicLong(RandomSupport.mixMurmur64(System.currentTimeMillis()) ^
|
= new AtomicLong(RandomSupport.mixMurmur64(System.currentTimeMillis()) ^
|
||||||
RandomSupport.mixMurmur64(System.nanoTime()));
|
RandomSupport.mixMurmur64(System.nanoTime()));
|
||||||
|
|
||||||
// used by ExtentLocal
|
// used by ScopedValue
|
||||||
private static class Access {
|
private static class Access {
|
||||||
static {
|
static {
|
||||||
SharedSecrets.setJavaUtilConcurrentTLRAccess(
|
SharedSecrets.setJavaUtilConcurrentTLRAccess(
|
||||||
|
@ -476,24 +476,28 @@ public interface JavaLangAccess {
|
|||||||
boolean isCarrierThreadLocalPresent(CarrierThreadLocal<?> local);
|
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
|
* Returns the innermost mounted continuation
|
||||||
|
@ -35,7 +35,7 @@ import java.util.concurrent.locks.LockSupport;
|
|||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import jdk.internal.access.JavaLangAccess;
|
import jdk.internal.access.JavaLangAccess;
|
||||||
import jdk.internal.access.SharedSecrets;
|
import jdk.internal.access.SharedSecrets;
|
||||||
import jdk.internal.vm.ExtentLocalContainer;
|
import jdk.internal.vm.ScopedValueContainer;
|
||||||
import jdk.internal.vm.ThreadContainer;
|
import jdk.internal.vm.ThreadContainer;
|
||||||
import jdk.internal.vm.ThreadContainers;
|
import jdk.internal.vm.ThreadContainers;
|
||||||
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
||||||
@ -99,7 +99,7 @@ public class ThreadFlock implements AutoCloseable {
|
|||||||
private volatile int threadCount;
|
private volatile int threadCount;
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
private final ExtentLocalContainer.BindingsSnapshot extentLocalBindings;
|
private final ScopedValueContainer.BindingsSnapshot scopedValueBindings;
|
||||||
private final ThreadContainerImpl container; // encapsulate for now
|
private final ThreadContainerImpl container; // encapsulate for now
|
||||||
|
|
||||||
// state
|
// state
|
||||||
@ -111,7 +111,7 @@ public class ThreadFlock implements AutoCloseable {
|
|||||||
|
|
||||||
ThreadFlock(String name) {
|
ThreadFlock(String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.extentLocalBindings = ExtentLocalContainer.captureBindings();
|
this.scopedValueBindings = ScopedValueContainer.captureBindings();
|
||||||
this.container = new ThreadContainerImpl(this);
|
this.container = new ThreadContainerImpl(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,8 +119,8 @@ public class ThreadFlock implements AutoCloseable {
|
|||||||
return threadCount;
|
return threadCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExtentLocalContainer.BindingsSnapshot extentLocalBindings() {
|
private ScopedValueContainer.BindingsSnapshot scopedValueBindings() {
|
||||||
return extentLocalBindings;
|
return scopedValueBindings;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void incrementThreadCount() {
|
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
|
* Opens a new thread flock. The flock is owned by the current thread. It can be
|
||||||
* named to aid debugging.
|
* 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.
|
* bindings for inheritance by threads created in the flock.
|
||||||
*
|
*
|
||||||
* <p> For the purposes of containment, monitoring, and debugging, the parent
|
* <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.
|
* 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.
|
* 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
|
* <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
|
* @throws WrongThreadException if the current thread is not the owner or a thread
|
||||||
* contained in the flock
|
* contained in the flock
|
||||||
* @throws jdk.incubator.concurrent.StructureViolationException if the current
|
* @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) {
|
public Thread start(Thread thread) {
|
||||||
ensureOwnerOrContainsThread();
|
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
|
* <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
|
* 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 the nested flocks (in the reverse order that they were created in),
|
||||||
* closes this flock, and then throws {@link
|
* closes this flock, and then throws {@code StructureViolationException}.
|
||||||
* jdk.incubator.concurrent.StructureViolationException}.
|
* Similarly, if this method is called to close a thread flock while executing with
|
||||||
* Similarly, if called to close a flock that <em>encloses</em> {@linkplain
|
* scoped value bindings, and the thread flock was created before the scoped values
|
||||||
* jdk.incubator.concurrent.ExtentLocal.Carrier#run(Runnable) operations} with
|
* were bound, then {@code StructureViolationException} is thrown after closing the
|
||||||
* extent-local bindings then it also throws {@code StructureViolationException}
|
* thread flock.
|
||||||
* after closing the flock.
|
|
||||||
*
|
*
|
||||||
* @throws WrongThreadException if invoked by a thread that is not the owner
|
* @throws WrongThreadException if invoked by a thread that is not the owner
|
||||||
* @throws jdk.incubator.concurrent.StructureViolationException if a structure
|
* @throws jdk.incubator.concurrent.StructureViolationException if a structure
|
||||||
@ -585,8 +584,8 @@ public class ThreadFlock implements AutoCloseable {
|
|||||||
return flock.toString();
|
return flock.toString();
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public ExtentLocalContainer.BindingsSnapshot extentLocalBindings() {
|
public ScopedValueContainer.BindingsSnapshot scopedValueBindings() {
|
||||||
return flock.extentLocalBindings();
|
return flock.scopedValueBindings();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ import jdk.internal.access.SharedSecrets;
|
|||||||
*/
|
*/
|
||||||
public class Continuation {
|
public class Continuation {
|
||||||
private static final Unsafe U = Unsafe.getUnsafe();
|
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();
|
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
|
||||||
static {
|
static {
|
||||||
ContinuationSupport.ensureSupported();
|
ContinuationSupport.ensureSupported();
|
||||||
@ -54,8 +54,8 @@ public class Continuation {
|
|||||||
|
|
||||||
StackChunk.init(); // ensure StackChunk class is initialized
|
StackChunk.init(); // ensure StackChunk class is initialized
|
||||||
|
|
||||||
String value = GetPropertyAction.privilegedGetProperty("jdk.preserveExtentLocalCache");
|
String value = GetPropertyAction.privilegedGetProperty("jdk.preserveScopedValueCache");
|
||||||
PRESERVE_EXTENT_LOCAL_CACHE = (value == null) || Boolean.parseBoolean(value);
|
PRESERVE_SCOPED_VALUE_CACHE = (value == null) || Boolean.parseBoolean(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final VarHandle MOUNTED;
|
private static final VarHandle MOUNTED;
|
||||||
@ -129,7 +129,7 @@ public class Continuation {
|
|||||||
private Object yieldInfo;
|
private Object yieldInfo;
|
||||||
private boolean preempted;
|
private boolean preempted;
|
||||||
|
|
||||||
private Object[] extentLocalCache;
|
private Object[] scopedValueCache;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a continuation
|
* Constructs a continuation
|
||||||
@ -238,7 +238,7 @@ public class Continuation {
|
|||||||
public final void run() {
|
public final void run() {
|
||||||
while (true) {
|
while (true) {
|
||||||
mount();
|
mount();
|
||||||
JLA.setExtentLocalCache(extentLocalCache);
|
JLA.setScopedValueCache(scopedValueCache);
|
||||||
|
|
||||||
if (done)
|
if (done)
|
||||||
throw new IllegalStateException("Continuation terminated");
|
throw new IllegalStateException("Continuation terminated");
|
||||||
@ -270,12 +270,12 @@ public class Continuation {
|
|||||||
postYieldCleanup();
|
postYieldCleanup();
|
||||||
|
|
||||||
unmount();
|
unmount();
|
||||||
if (PRESERVE_EXTENT_LOCAL_CACHE) {
|
if (PRESERVE_SCOPED_VALUE_CACHE) {
|
||||||
extentLocalCache = JLA.extentLocalCache();
|
scopedValueCache = JLA.scopedValueCache();
|
||||||
} else {
|
} else {
|
||||||
extentLocalCache = null;
|
scopedValueCache = null;
|
||||||
}
|
}
|
||||||
JLA.setExtentLocalCache(null);
|
JLA.setScopedValueCache(null);
|
||||||
} catch (Throwable e) { e.printStackTrace(); System.exit(1); }
|
} catch (Throwable e) { e.printStackTrace(); System.exit(1); }
|
||||||
}
|
}
|
||||||
// we're now in the parent continuation
|
// we're now in the parent continuation
|
||||||
|
@ -33,26 +33,26 @@ import jdk.internal.vm.annotation.DontInline;
|
|||||||
import jdk.internal.vm.annotation.ReservedStackAccess;
|
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
|
* 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 ExtentLocalContainer
|
* on the scope stack. It also defines a method to get the latest ScopedValueContainer
|
||||||
* and a method to return a snapshot of the extent local bindings.
|
* 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();
|
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
|
||||||
static {
|
static {
|
||||||
Unsafe.getUnsafe().ensureClassInitialized(StructureViolationExceptions.class);
|
Unsafe.getUnsafe().ensureClassInitialized(StructureViolationExceptions.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExtentLocalContainer() {
|
private ScopedValueContainer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the "latest" ExtentLocalContainer for the current Thread. This may be on
|
* Returns the "latest" ScopedValueContainer for the current Thread. This may be on
|
||||||
* the current thread's scope task or ma require walking up the tree to find it.
|
* 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();
|
StackableScope scope = head();
|
||||||
if (scope == null) {
|
if (scope == null) {
|
||||||
scope = JLA.threadContainer(Thread.currentThread());
|
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
|
* may be on the current thread's scope task or may require walking up the
|
||||||
* tree to find it.
|
* tree to find it.
|
||||||
*/
|
*/
|
||||||
public static ExtentLocalContainer latest() {
|
public static ScopedValueContainer latest() {
|
||||||
return latest(ExtentLocalContainer.class);
|
return latest(ScopedValueContainer.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A snapshot of the extent local bindings. The snapshot includes the bindings
|
* A snapshot of the scoped value bindings. The snapshot includes the bindings
|
||||||
* established for the current thread and extent local container.
|
* established for the current thread and scoped value container.
|
||||||
*/
|
*/
|
||||||
public record BindingsSnapshot(Object extentLocalBindings,
|
public record BindingsSnapshot(Object scopedValueBindings,
|
||||||
ExtentLocalContainer container) { }
|
ScopedValueContainer container) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the extent local bindings for the current thread.
|
* Returns the scoped value bindings for the current thread.
|
||||||
*/
|
*/
|
||||||
public static BindingsSnapshot captureBindings() {
|
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) {
|
public static void run(Runnable op) {
|
||||||
if (head() == null) {
|
if (head() == null) {
|
||||||
// no need to push scope when stack is empty
|
// no need to push scope when stack is empty
|
||||||
runWithoutScope(op);
|
runWithoutScope(op);
|
||||||
} else {
|
} 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 {
|
public static <V> V call(Callable<V> op) throws Exception {
|
||||||
if (head() == null) {
|
if (head() == null) {
|
||||||
// no need to push scope when stack is empty
|
// no need to push scope when stack is empty
|
||||||
return callWithoutScope(op);
|
return callWithoutScope(op);
|
||||||
} else {
|
} 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
|
* Throws {@code ex} if not null. StructureViolationException is thrown or added
|
||||||
* as a suppressed exception when {@code atTop} is false.
|
* as a suppressed exception when {@code atTop} is false.
|
||||||
*/
|
*/
|
||||||
@DontInline @ReservedStackAccess
|
|
||||||
private static void throwIfFailed(Throwable ex, boolean atTop) {
|
private static void throwIfFailed(Throwable ex, boolean atTop) {
|
||||||
if (ex != null || !atTop) {
|
if (ex != null || !atTop) {
|
||||||
if (!atTop) {
|
if (!atTop) {
|
@ -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;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -167,6 +167,7 @@ module java.base {
|
|||||||
jdk.jlink,
|
jdk.jlink,
|
||||||
jdk.jfr,
|
jdk.jfr,
|
||||||
jdk.net,
|
jdk.net,
|
||||||
|
jdk.incubator.concurrent,
|
||||||
jdk.sctp,
|
jdk.sctp,
|
||||||
jdk.crypto.cryptoki;
|
jdk.crypto.cryptoki;
|
||||||
exports jdk.internal.foreign to
|
exports jdk.internal.foreign to
|
||||||
@ -247,12 +248,14 @@ module java.base {
|
|||||||
jdk.unsupported;
|
jdk.unsupported;
|
||||||
exports jdk.internal.vm to
|
exports jdk.internal.vm to
|
||||||
java.management,
|
java.management,
|
||||||
|
jdk.incubator.concurrent,
|
||||||
jdk.internal.jvmstat,
|
jdk.internal.jvmstat,
|
||||||
jdk.management,
|
jdk.management,
|
||||||
jdk.management.agent;
|
jdk.management.agent;
|
||||||
exports jdk.internal.vm.annotation to
|
exports jdk.internal.vm.annotation to
|
||||||
java.instrument,
|
java.instrument,
|
||||||
jdk.internal.vm.ci,
|
jdk.internal.vm.ci,
|
||||||
|
jdk.incubator.concurrent,
|
||||||
jdk.incubator.vector,
|
jdk.incubator.vector,
|
||||||
jdk.jfr,
|
jdk.jfr,
|
||||||
jdk.unsupported;
|
jdk.unsupported;
|
||||||
@ -307,7 +310,8 @@ module java.base {
|
|||||||
exports sun.security.action to
|
exports sun.security.action to
|
||||||
java.desktop,
|
java.desktop,
|
||||||
java.security.jgss,
|
java.security.jgss,
|
||||||
jdk.crypto.ec;
|
jdk.crypto.ec,
|
||||||
|
jdk.incubator.concurrent;
|
||||||
exports sun.security.internal.interfaces to
|
exports sun.security.internal.interfaces to
|
||||||
jdk.crypto.cryptoki;
|
jdk.crypto.cryptoki;
|
||||||
exports sun.security.internal.spec to
|
exports sun.security.internal.spec to
|
||||||
|
@ -50,9 +50,12 @@ static JNINativeMethod methods[] = {
|
|||||||
{"dumpThreads", "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
|
{"dumpThreads", "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
|
||||||
{"getStackTrace0", "()" OBJ, (void *)&JVM_GetStackTrace},
|
{"getStackTrace0", "()" OBJ, (void *)&JVM_GetStackTrace},
|
||||||
{"setNativeName", "(" STR ")V", (void *)&JVM_SetNativeThreadName},
|
{"setNativeName", "(" STR ")V", (void *)&JVM_SetNativeThreadName},
|
||||||
{"extentLocalCache", "()[" OBJ, (void *)&JVM_ExtentLocalCache},
|
{"scopedValueCache", "()[" OBJ, (void *)&JVM_ScopedValueCache},
|
||||||
{"setExtentLocalCache", "([" OBJ ")V",(void *)&JVM_SetExtentLocalCache},
|
{"setScopedValueCache", "([" OBJ ")V",(void *)&JVM_SetScopedValueCache},
|
||||||
{"getNextThreadIdOffset", "()J", (void *)&JVM_GetNextThreadIdOffset}
|
{"getNextThreadIdOffset", "()J", (void *)&JVM_GetNextThreadIdOffset},
|
||||||
|
{"findScopedValueBindings", "()" OBJ, (void *)&JVM_FindScopedValueBindings},
|
||||||
|
{"ensureMaterializedForStackWalk",
|
||||||
|
"(" OBJ ")V", (void*)&JVM_EnsureMaterializedForStackWalk_func},
|
||||||
};
|
};
|
||||||
|
|
||||||
#undef THD
|
#undef THD
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -210,8 +210,8 @@ import jdk.internal.misc.ThreadFlock;
|
|||||||
*
|
*
|
||||||
* <h2><a id="TreeStructure">Tree structure</a></h2>
|
* <h2><a id="TreeStructure">Tree structure</a></h2>
|
||||||
*
|
*
|
||||||
* StructuredTaskScopes form a tree where parent-child relations are established
|
* Task scopes form a tree where parent-child relations are established implicitly when
|
||||||
* implicitly when opening a new task scope:
|
* opening a new task scope:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li> A parent-child relation is established when a thread started in a task scope
|
* <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
|
* 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".
|
* scope "B" is the parent of the nested task scope "C".
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <p> The tree structure supports confinement checks. The phrase "threads contained in
|
* The <i>descendants</i> of a task scope are the child task scopes that it is a parent
|
||||||
* the task scope" in method descriptions means threads started in the task scope or
|
* of, plus the descendants of the child task scopes, recursively.
|
||||||
* descendant scopes. {@code StructuredTaskScope} does not define APIs that exposes the
|
*
|
||||||
* tree structure at this time.
|
* <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
|
* <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.
|
* 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
|
* <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
|
* {@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>
|
* <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
|
* 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.
|
* 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
|
* tasks are {@linkplain #fork(Callable) forked}. The task scope is owned by the
|
||||||
* current thread.
|
* 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 name the name of the task scope, can be null
|
||||||
* @param factory the thread factory
|
* @param factory the thread factory
|
||||||
*/
|
*/
|
||||||
@ -367,16 +408,19 @@ public class StructuredTaskScope<T> implements AutoCloseable {
|
|||||||
/**
|
/**
|
||||||
* Starts a new thread to run the given task.
|
* 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}
|
* <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
|
* then the {@link #handleComplete(Future) handleComplete} method is invoked to
|
||||||
* completed task. The {@code handleComplete} method is run when the task completes
|
* consume the completed task. The {@code handleComplete} method is run when the task
|
||||||
* with a result or exception. If the {@code Future} {@link Future#cancel(boolean)
|
* completes with a result or exception. If the {@code Future}'s {@link
|
||||||
* cancel} method is used the cancel a task before the task scope is shut down, then
|
* Future#cancel(boolean) cancel} method is used to cancel a task before the task scope
|
||||||
* the {@code handleComplete} method is run by the thread that invokes {@code cancel}.
|
* is shut down, then the {@code handleComplete} method is run by the thread that
|
||||||
* If the task scope shuts down at or around the same time that the task completes or
|
* invokes {@code cancel}. If the task scope shuts down at or around the same time
|
||||||
* is cancelled then the {@code handleComplete} method may or may not be invoked.
|
* 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
|
* <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
|
* 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 IllegalStateException if this task scope is closed
|
||||||
* @throws WrongThreadException if the current thread is not the owner or a thread
|
* @throws WrongThreadException if the current thread is not the owner or a thread
|
||||||
* contained in the task scope
|
* 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
|
* @throws RejectedExecutionException if the thread factory rejected creating a
|
||||||
* thread to run the task
|
* 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
|
* 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
|
* (in the reverse order that they were created in), closes this task scope, and then
|
||||||
* throws {@link StructureViolationException}.
|
* 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
|
* 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
|
* 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
|
* 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
|
* threads when tasks are {@linkplain #fork(Callable) forked}. The task scope is
|
||||||
* owned by the current thread.
|
* 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 name the name of the task scope, can be null
|
||||||
* @param factory the thread factory
|
* @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
|
* threads when tasks are {@linkplain #fork(Callable) forked}. The task scope
|
||||||
* is owned by the current thread.
|
* 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 name the name of the task scope, can be null
|
||||||
* @param factory the thread factory
|
* @param factory the thread factory
|
||||||
*/
|
*/
|
||||||
|
@ -61,7 +61,7 @@ public class framecnt01 {
|
|||||||
|
|
||||||
// Test GetFrameCount on virtual live thread
|
// Test GetFrameCount on virtual live thread
|
||||||
Thread vThread = Thread.ofVirtual().name("VirtualThread-Live").start(() -> {
|
Thread vThread = Thread.ofVirtual().name("VirtualThread-Live").start(() -> {
|
||||||
checkFrames(Thread.currentThread(), false, 9);
|
checkFrames(Thread.currentThread(), false, 10);
|
||||||
});
|
});
|
||||||
vThread.join();
|
vThread.join();
|
||||||
|
|
||||||
@ -79,13 +79,13 @@ public class framecnt01 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// this is too fragile, implementation can change at any time.
|
// this is too fragile, implementation can change at any time.
|
||||||
checkFrames(vThread1, false, 14);
|
checkFrames(vThread1, false, 15);
|
||||||
LockSupport.unpark(vThread1);
|
LockSupport.unpark(vThread1);
|
||||||
vThread1.join();
|
vThread1.join();
|
||||||
|
|
||||||
// Test GetFrameCount on live platform thread
|
// Test GetFrameCount on live platform thread
|
||||||
Thread pThread = Thread.ofPlatform().name("PlatformThread-Live").start(() -> {
|
Thread pThread = Thread.ofPlatform().name("PlatformThread-Live").start(() -> {
|
||||||
checkFrames(Thread.currentThread(), false, 5);
|
checkFrames(Thread.currentThread(), false, 6);
|
||||||
});
|
});
|
||||||
pThread.join();
|
pThread.join();
|
||||||
|
|
||||||
@ -101,7 +101,7 @@ public class framecnt01 {
|
|||||||
while(pThread1.getState() != Thread.State.WAITING) {
|
while(pThread1.getState() != Thread.State.WAITING) {
|
||||||
Thread.sleep(1);
|
Thread.sleep(1);
|
||||||
}
|
}
|
||||||
checkFrames(pThread1, false, 5);
|
checkFrames(pThread1, false, 6);
|
||||||
LockSupport.unpark(pThread1);
|
LockSupport.unpark(pThread1);
|
||||||
pThread1.join();
|
pThread1.join();
|
||||||
|
|
||||||
@ -118,10 +118,11 @@ class FixedDepthThread implements Runnable {
|
|||||||
Object checkFlag;
|
Object checkFlag;
|
||||||
Thread thread;
|
Thread thread;
|
||||||
|
|
||||||
// Each stack has 2 frames additional to expected depth
|
// Each stack has 3 frames additional to expected depth
|
||||||
// 0: FixedDepthThread: run()V
|
// 0: FixedDepthThread: run()V
|
||||||
// 1: java/lang/Thread: 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) {
|
private FixedDepthThread(String name, int depth, Object checkFlag) {
|
||||||
this.thread = Thread.ofPlatform().name(name).unstarted(this);
|
this.thread = Thread.ofPlatform().name(name).unstarted(this);
|
||||||
|
@ -36,7 +36,8 @@ static frame_info expected_virtual_frames[] = {
|
|||||||
{"LGetStackTraceCurrentThreadTest;", "dummy", "()V"},
|
{"LGetStackTraceCurrentThreadTest;", "dummy", "()V"},
|
||||||
{"LGetStackTraceCurrentThreadTest;", "chain", "()V"},
|
{"LGetStackTraceCurrentThreadTest;", "chain", "()V"},
|
||||||
{"LTask;", "run", "()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[] = {
|
static frame_info expected_platform_frames[] = {
|
||||||
@ -44,7 +45,8 @@ static frame_info expected_platform_frames[] = {
|
|||||||
{"LGetStackTraceCurrentThreadTest;", "dummy", "()V"},
|
{"LGetStackTraceCurrentThreadTest;", "dummy", "()V"},
|
||||||
{"LGetStackTraceCurrentThreadTest;", "chain", "()V"},
|
{"LGetStackTraceCurrentThreadTest;", "chain", "()V"},
|
||||||
{"LTask;", "run", "()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
|
JNIEXPORT jint JNICALL
|
||||||
|
@ -35,7 +35,8 @@ static frame_info expected_platform_frames[] = {
|
|||||||
{"Lgetstacktr03;", "dummy", "()V"},
|
{"Lgetstacktr03;", "dummy", "()V"},
|
||||||
{"Lgetstacktr03;", "chain", "()V"},
|
{"Lgetstacktr03;", "chain", "()V"},
|
||||||
{"Lgetstacktr03$Task;", "run", "()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[] = {
|
static frame_info expected_virtual_frames[] = {
|
||||||
@ -43,6 +44,7 @@ static frame_info expected_virtual_frames[] = {
|
|||||||
{"Lgetstacktr03;", "dummy", "()V"},
|
{"Lgetstacktr03;", "dummy", "()V"},
|
||||||
{"Lgetstacktr03;", "chain", "()V"},
|
{"Lgetstacktr03;", "chain", "()V"},
|
||||||
{"Lgetstacktr03$Task;", "run", "()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;", "run", "(Ljava/lang/Runnable;)V"},
|
||||||
{"Ljava/lang/VirtualThread$VThreadContinuation;", "lambda$new$0", "(Ljava/lang/VirtualThread;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"},
|
{"Ljava/lang/VirtualThread$VThreadContinuation$$Lambda$31.0x0000000800098810;", "run", "()V"},
|
||||||
|
@ -38,6 +38,7 @@ static frame_info expected_platform_frames[] = {
|
|||||||
{"Lgetstacktr04$TestThread;", "chain2", "()V"},
|
{"Lgetstacktr04$TestThread;", "chain2", "()V"},
|
||||||
{"Lgetstacktr04$TestThread;", "chain1", "()V"},
|
{"Lgetstacktr04$TestThread;", "chain1", "()V"},
|
||||||
{"Lgetstacktr04$TestThread;", "run", "()V"},
|
{"Lgetstacktr04$TestThread;", "run", "()V"},
|
||||||
|
{"Ljava/lang/Thread;", "runWith", "(Ljava/lang/Object;Ljava/lang/Runnable;)V"},
|
||||||
{"Ljava/lang/Thread;", "run", "()V"},
|
{"Ljava/lang/Thread;", "run", "()V"},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -48,6 +49,7 @@ static frame_info expected_virtual_frames[] = {
|
|||||||
{"Lgetstacktr04$TestThread;", "chain2", "()V"},
|
{"Lgetstacktr04$TestThread;", "chain2", "()V"},
|
||||||
{"Lgetstacktr04$TestThread;", "chain1", "()V"},
|
{"Lgetstacktr04$TestThread;", "chain1", "()V"},
|
||||||
{"Lgetstacktr04$TestThread;", "run", "()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;", "run", "(Ljava/lang/Runnable;)V"},
|
||||||
{"Ljava/lang/VirtualThread$VThreadContinuation;", "lambda$new$0", "(Ljava/lang/VirtualThread;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"},
|
{"Ljava/lang/VirtualThread$VThreadContinuation$$Lambda;", "run", "()V"},
|
||||||
|
@ -39,6 +39,7 @@ static frame_info expected_platform_frames[] = {
|
|||||||
{"Lgetstacktr05$TestThread;", "chain2", "()V"},
|
{"Lgetstacktr05$TestThread;", "chain2", "()V"},
|
||||||
{"Lgetstacktr05$TestThread;", "chain1", "()V"},
|
{"Lgetstacktr05$TestThread;", "chain1", "()V"},
|
||||||
{"Lgetstacktr05$TestThread;", "run", "()V"},
|
{"Lgetstacktr05$TestThread;", "run", "()V"},
|
||||||
|
{"Ljava/lang/Thread;", "runWith", "(Ljava/lang/Object;Ljava/lang/Runnable;)V"},
|
||||||
{"Ljava/lang/Thread;", "run", "()V"},
|
{"Ljava/lang/Thread;", "run", "()V"},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -48,6 +49,7 @@ static frame_info expected_virtual_frames[] = {
|
|||||||
{"Lgetstacktr05$TestThread;", "chain2", "()V"},
|
{"Lgetstacktr05$TestThread;", "chain2", "()V"},
|
||||||
{"Lgetstacktr05$TestThread;", "chain1", "()V"},
|
{"Lgetstacktr05$TestThread;", "chain1", "()V"},
|
||||||
{"Lgetstacktr05$TestThread;", "run", "()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;", "run", "(Ljava/lang/Runnable;)V"},
|
||||||
{"Ljava/lang/VirtualThread$VThreadContinuation;", "lambda$new$0", "(Ljava/lang/VirtualThread;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"},
|
{"Ljava/lang/VirtualThread$VThreadContinuation$$Lambda;", "run", "()V"},
|
||||||
|
@ -43,6 +43,7 @@ static frame_info expected_platform_frames[] = {
|
|||||||
{"Lgetstacktr06$TestThread;", "chain2", "()V"},
|
{"Lgetstacktr06$TestThread;", "chain2", "()V"},
|
||||||
{"Lgetstacktr06$TestThread;", "chain1", "()V"},
|
{"Lgetstacktr06$TestThread;", "chain1", "()V"},
|
||||||
{"Lgetstacktr06$TestThread;", "run", "()V"},
|
{"Lgetstacktr06$TestThread;", "run", "()V"},
|
||||||
|
{"Ljava/lang/Thread;", "runWith", "(Ljava/lang/Object;Ljava/lang/Runnable;)V"},
|
||||||
{"Ljava/lang/Thread;", "run", "()V"},
|
{"Ljava/lang/Thread;", "run", "()V"},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -52,6 +53,7 @@ static frame_info expected_virtual_frames[] = {
|
|||||||
{"Lgetstacktr06$TestThread;", "chain2", "()V"},
|
{"Lgetstacktr06$TestThread;", "chain2", "()V"},
|
||||||
{"Lgetstacktr06$TestThread;", "chain1", "()V"},
|
{"Lgetstacktr06$TestThread;", "chain1", "()V"},
|
||||||
{"Lgetstacktr06$TestThread;", "run", "()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;", "run", "(Ljava/lang/Runnable;)V"},
|
||||||
{"Ljava/lang/VirtualThread$VThreadContinuation;", "lambda$new$0", "(Ljava/lang/VirtualThread;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"},
|
{"Ljava/lang/VirtualThread$VThreadContinuation$$Lambda;", "run", "()V"},
|
||||||
|
@ -45,6 +45,7 @@ static frame_info expected_platform_frames[] = {
|
|||||||
{"Lgetstacktr07$TestThread;", "chain2", "()V"},
|
{"Lgetstacktr07$TestThread;", "chain2", "()V"},
|
||||||
{"Lgetstacktr07$TestThread;", "chain1", "()V"},
|
{"Lgetstacktr07$TestThread;", "chain1", "()V"},
|
||||||
{"Lgetstacktr07$TestThread;", "run", "()V"},
|
{"Lgetstacktr07$TestThread;", "run", "()V"},
|
||||||
|
{"Ljava/lang/Thread;", "runWith", "(Ljava/lang/Object;Ljava/lang/Runnable;)V"},
|
||||||
{"Ljava/lang/Thread;", "run", "()V"},
|
{"Ljava/lang/Thread;", "run", "()V"},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -55,6 +56,7 @@ static frame_info expected_virtual_frames[] = {
|
|||||||
{"Lgetstacktr07$TestThread;", "chain2", "()V"},
|
{"Lgetstacktr07$TestThread;", "chain2", "()V"},
|
||||||
{"Lgetstacktr07$TestThread;", "chain1", "()V"},
|
{"Lgetstacktr07$TestThread;", "chain1", "()V"},
|
||||||
{"Lgetstacktr07$TestThread;", "run", "()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;", "run", "(Ljava/lang/Runnable;)V"},
|
||||||
{"Ljava/lang/VirtualThread$VThreadContinuation;", "lambda$new$0", "(Ljava/lang/VirtualThread;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"},
|
{"Ljava/lang/VirtualThread$VThreadContinuation$$Lambda;", "run", "()V"},
|
||||||
|
@ -44,6 +44,7 @@ static frame_info expected_platform_frames[] = {
|
|||||||
{"Lgetstacktr08$TestThread;", "chain2", "()V"},
|
{"Lgetstacktr08$TestThread;", "chain2", "()V"},
|
||||||
{"Lgetstacktr08$TestThread;", "chain1", "()V"},
|
{"Lgetstacktr08$TestThread;", "chain1", "()V"},
|
||||||
{"Lgetstacktr08$TestThread;", "run", "()V"},
|
{"Lgetstacktr08$TestThread;", "run", "()V"},
|
||||||
|
{"Ljava/lang/Thread;", "runWith", "(Ljava/lang/Object;Ljava/lang/Runnable;)V"},
|
||||||
{"Ljava/lang/Thread;", "run", "()V"},
|
{"Ljava/lang/Thread;", "run", "()V"},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -56,6 +57,7 @@ static frame_info expected_virtual_frames[] = {
|
|||||||
{"Lgetstacktr08$TestThread;", "chain2", "()V"},
|
{"Lgetstacktr08$TestThread;", "chain2", "()V"},
|
||||||
{"Lgetstacktr08$TestThread;", "chain1", "()V"},
|
{"Lgetstacktr08$TestThread;", "chain1", "()V"},
|
||||||
{"Lgetstacktr08$TestThread;", "run", "()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;", "run", "(Ljava/lang/Runnable;)V"},
|
||||||
{"Ljava/lang/VirtualThread$VThreadContinuation;", "lambda$new$0", "(Ljava/lang/VirtualThread;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"},
|
{"Ljava/lang/VirtualThread$VThreadContinuation$$Lambda;", "run", "()V"},
|
||||||
|
@ -794,3 +794,4 @@ java/awt/event/MouseEvent/SpuriousExitEnter/SpuriousExitEnter.java 8254841 macos
|
|||||||
java/awt/Focus/AppletInitialFocusTest/AppletInitialFocusTest1.java 8256289 windows-x64
|
java/awt/Focus/AppletInitialFocusTest/AppletInitialFocusTest1.java 8256289 windows-x64
|
||||||
java/awt/FullScreen/TranslucentWindow/TranslucentWindow.java 8258103 linux-all
|
java/awt/FullScreen/TranslucentWindow/TranslucentWindow.java 8258103 linux-all
|
||||||
java/awt/Focus/FrameMinimizeTest/FrameMinimizeTest.java 8016266 linux-x64
|
java/awt/Focus/FrameMinimizeTest/FrameMinimizeTest.java 8016266 linux-x64
|
||||||
|
|
||||||
|
164
test/jdk/jdk/incubator/concurrent/ScopedValue/ManyBindings.java
Normal file
164
test/jdk/jdk/incubator/concurrent/ScopedValue/ManyBindings.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
442
test/jdk/jdk/incubator/concurrent/ScopedValue/ScopeValueAPI.java
Normal file
442
test/jdk/jdk/incubator/concurrent/ScopedValue/ScopeValueAPI.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
@ -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"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
220
test/jdk/jdk/internal/misc/ThreadFlock/WithScopedValue.java
Normal file
220
test/jdk/jdk/internal/misc/ThreadFlock/WithScopedValue.java
Normal 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));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user