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