On Thu, 16 Oct 2025 16:13:42 GMT, Patricio Chilano Mateo 
<[email protected]> wrote:

>> If a thread tries to initialize a class that is already being initialized by 
>> another thread, it will block until notified. Since at this blocking point 
>> there are native frames on the stack, a virtual thread cannot be unmounted 
>> and is pinned to its carrier. Besides harming scalability, this can, in some 
>> pathological cases, lead to a deadlock, for example, if the thread executing 
>> the class initialization method is blocked waiting for some unmounted 
>> virtual thread to run, but all carriers are blocked waiting for that class 
>> to be initialized.
>> 
>> As of JDK-8338383, virtual threads blocked in the VM on `ObjectMonitor` 
>> operations can be unmounted. Since synchronization on class initialization 
>> is implemented using `ObjectLocker`, we can reuse the same mechanism to 
>> unmount virtual threads on these cases too.
>> 
>> This patch adds support for unmounting virtual threads on some of the most 
>> common class initialization paths, specifically when calling 
>> `InterpreterRuntime::_new` (`new` bytecode), and 
>> `InterpreterRuntime::resolve_from_cache` for `invokestatic`, `getstatic` or 
>> `putstatic` bytecodes. In the future we might consider extending this 
>> mechanism to include initialization calls originating from native methods 
>> such as `Class.forName0`.
>> 
>> ### Summary of implementation
>> 
>> The ObjectLocker class was modified to not pin the continuation if we are 
>> coming from a preemptable path, which will be the case when calling 
>> `InstanceKlass::initialize_impl` from new method 
>> `InstanceKlass::initialize_preemptable`. This means that for these cases, a 
>> virtual thread can now be unmounted either when contending for the init_lock 
>> in the `ObjectLocker` constructor, or in the call to `wait_uninterruptibly`. 
>> Also, since the call to initialize a class includes a previous call to 
>> `link_class` which also uses `ObjectLocker` to protect concurrent calls from 
>> multiple threads, we will allow preemption there too.
>> 
>> If preempted, we will throw a pre-allocated exception which will get 
>> propagated with the `TRAPS/CHECK` macros all the way back to the VM entry 
>> point. The exception will be cleared and on return back to Java the virtual 
>> thread will go through the preempt stub and unmount. When running again, at 
>> the end of the thaw call we will identify this preemption case and redo the 
>> original VM call (either `InterpreterRuntime::_new` or 
>> `InterpreterRuntime::resolve_from_cache`). 
>> 
>> ### Notes
>> 
>> `InterpreterRuntime::call_VM_preemptable` used previously only for 
>> `InterpreterRuntime::mon...
>
> Patricio Chilano Mateo has updated the pull request incrementally with one 
> additional commit since the last revision:
> 
>   fix verify_frame_kind

Great piece of work @pchilano ! I've taken an initial pass through the main 
class/thread/objectMonitor pieces.

src/hotspot/share/interpreter/linkResolver.cpp line 1122:

> 1120:       resolved_klass->initialize(CHECK);
> 1121:     } else if (static_mode == StaticMode::initialize_klass_preemptable) 
> {
> 1122:       resolved_klass->initialize_preemptable(CHECK);

Why is this not CHECK_AND_CLEAR_PREEMPTED?

src/hotspot/share/interpreter/linkResolver.hpp line 192:

> 190: };
> 191: 
> 192: enum class StaticMode : uint8_t {

Need to think of a better name for this ... `ClassInitMode`?

src/hotspot/share/interpreter/linkResolver.hpp line 195:

> 193:   dont_initialize_klass,
> 194:   initialize_klass,
> 195:   initialize_klass_preemptable

And maybe more concisely:
Suggestion:

  dont_init
  init
  init_preemptable

?

src/hotspot/share/oops/instanceKlass.cpp line 1284:

> 1282:   // Block preemption once we are the initializer thread. Unmounting now
> 1283:   // would complicate the reentrant case (identity is platform thread).
> 1284:   NoPreemptMark npm(THREAD);

How does this affect unrelated "preemption" that may occur in the Java code 
called as part of `<clinit>`?

src/hotspot/share/oops/klass.hpp line 583:

> 581:   // initializes the klass
> 582:   virtual void initialize(TRAPS);
> 583:   virtual void initialize_preemptable(TRAPS);

Can we not define these on `instanceKlass` instead of `klass`? Seems really odd 
to declare virtual methods for which the base class version should never be 
called (they should be pure virtuals in that case I would think?).

src/hotspot/share/runtime/continuationEntry.cpp line 117:

> 115:   assert(thread->has_last_Java_frame(), "Wrong place to use this 
> assertion");
> 116: 
> 117:   if (preempted) return true;

I don't see the point of adding a new parameter just so the method can check it 
and return immediately. Shouldn't the caller be checking this?

src/hotspot/share/runtime/continuationFreezeThaw.cpp line 196:

> 194: static void do_deopt_after_thaw(JavaThread* thread);
> 195: static bool do_verify_after_thaw(JavaThread* thread, stackChunkOop 
> chunk, outputStream* st);
> 196: static void log_frames(JavaThread* thread, bool dolog = true);

Suggestion:

static void log_frames(JavaThread* thread, bool do_log = true);

src/hotspot/share/runtime/javaThread.hpp line 492:

> 490:   // throw IE at the end of thawing before returning to Java.
> 491:   bool _pending_interrupted_exception;
> 492:   // We allow preemption on some klass initializion calls.

Suggestion:

  // We allow preemption on some klass initialization calls.

src/hotspot/share/runtime/javaThread.hpp line 507:

> 505: 
> 506:   bool at_preemptable_init() { return _at_preemptable_init; }
> 507:   void set_at_preemptable_init(bool b) { _at_preemptable_init = b; }

If you are going align 503 and 504 then you may as well align these two lines 
as well

src/hotspot/share/runtime/javaThread.hpp line 522:

> 520:     JavaThread* _thread;
> 521:    public:
> 522:     AtRedoVMCall(JavaThread *t) : _thread(t) {

Suggestion:

    AtRedoVMCall(JavaThread* t) : _thread(t) {

src/hotspot/share/runtime/javaThread.hpp line 524:

> 522:     AtRedoVMCall(JavaThread *t) : _thread(t) {
> 523:       _thread->_interp_at_preemptable_vmcall_cnt++;
> 524:       assert(_thread->_interp_at_preemptable_vmcall_cnt > 0, "");

Suggestion:

      assert(_thread->_interp_at_preemptable_vmcall_cnt > 0, "Unexpected count: 
%d", _thread->_interp_at_preemptable_vmcall_cnt);

src/hotspot/share/runtime/javaThread.hpp line 528:

> 526:     ~AtRedoVMCall() {
> 527:       _thread->_interp_at_preemptable_vmcall_cnt--;
> 528:       assert(_thread->_interp_at_preemptable_vmcall_cnt >= 0, "");

Suggestion:

    assert(_thread->_interp_at_preemptable_vmcall_cnt > 0, "Unexpected count: 
%d", _thread->_interp_at_preemptable_vmcall_cnt);

src/hotspot/share/runtime/objectMonitor.cpp line 315:

> 313: }
> 314: 
> 315: // Keep object protected during ObjectLocker preemption.

I don't understand why this is necessary.

src/hotspot/share/runtime/synchronizer.cpp line 489:

> 487:     if (_thread->preempting()) {
> 488:       // If preemption was cancelled we acquired the monitor after 
> freezing
> 489:       // the frames. Redoing the vm call laterĀ in thaw will require us to

I don't quite follow the control flow here - at what point have we frozen any 
frames? Was that deep inside `enter`?

src/hotspot/share/runtime/synchronizer.hpp line 230:

> 228:   bool    _skip_exit;
> 229:  public:
> 230:   ObjectLocker(Handle obj, TRAPS);

I wonder if we should declare `PREEMPTABLE_TRAPS` as an indicator that the only 
exception expected to come out of a call is the preempted-exception?

src/java.base/share/classes/java/lang/VirtualThread.java line 172:

> 170: 
> 171:     // true when waiting in Object.wait, false for VM internal 
> uninterruptible Object.wait
> 172:     private volatile boolean interruptableWait;

Suggestion:

    private volatile boolean interruptibleWait;

src/java.base/share/classes/java/lang/VirtualThread.java line 605:

> 603:         if (s == WAITING || s == TIMED_WAITING) {
> 604:             int newState;
> 605:             boolean interruptable = interruptableWait;

Suggestion:

            boolean interruptible = interruptibleWait;

src/java.base/share/classes/jdk/internal/vm/PreemptedException.java line 31:

> 29:  * Internal exception used only by the VM.
> 30:  */
> 31: public class PreemptedException extends Exception {

Should probably extend `RuntimeException` so that it is not considered a 
checked-exception

-------------

PR Review: https://git.openjdk.org/jdk/pull/27802#pullrequestreview-3348320781
PR Review Comment: https://git.openjdk.org/jdk/pull/27802#discussion_r2438580798
PR Review Comment: https://git.openjdk.org/jdk/pull/27802#discussion_r2438444689
PR Review Comment: https://git.openjdk.org/jdk/pull/27802#discussion_r2438447733
PR Review Comment: https://git.openjdk.org/jdk/pull/27802#discussion_r2438469910
PR Review Comment: https://git.openjdk.org/jdk/pull/27802#discussion_r2438473899
PR Review Comment: https://git.openjdk.org/jdk/pull/27802#discussion_r2438480176
PR Review Comment: https://git.openjdk.org/jdk/pull/27802#discussion_r2438482435
PR Review Comment: https://git.openjdk.org/jdk/pull/27802#discussion_r2438506706
PR Review Comment: https://git.openjdk.org/jdk/pull/27802#discussion_r2438516134
PR Review Comment: https://git.openjdk.org/jdk/pull/27802#discussion_r2438518828
PR Review Comment: https://git.openjdk.org/jdk/pull/27802#discussion_r2438523339
PR Review Comment: https://git.openjdk.org/jdk/pull/27802#discussion_r2438524180
PR Review Comment: https://git.openjdk.org/jdk/pull/27802#discussion_r2438533049
PR Review Comment: https://git.openjdk.org/jdk/pull/27802#discussion_r2438542039
PR Review Comment: https://git.openjdk.org/jdk/pull/27802#discussion_r2438549455
PR Review Comment: https://git.openjdk.org/jdk/pull/27802#discussion_r2438568359
PR Review Comment: https://git.openjdk.org/jdk/pull/27802#discussion_r2438568876
PR Review Comment: https://git.openjdk.org/jdk/pull/27802#discussion_r2438571309

Reply via email to