On Thu, 17 Oct 2024 14:28:30 GMT, Patricio Chilano Mateo 
<pchilanom...@openjdk.org> wrote:

> This is the implementation of JEP 491: Synchronize Virtual Threads without 
> Pinning. See [JEP 491](https://bugs.openjdk.org/browse/JDK-8337395) for 
> further details.
> 
> In order to make the code review easier the changes have been split into the 
> following initial 4 commits:
> 
> - Changes to allow unmounting a virtual thread that is currently holding 
> monitors.
> - Changes to allow unmounting a virtual thread blocked on synchronized trying 
> to acquire the monitor.
> - Changes to allow unmounting a virtual thread blocked in `Object.wait()` and 
> its timed-wait variants.
> - Changes to tests, JFR pinned event, and other changes in the JDK libraries.
> 
> The changes fix pinning issues for all 4 ports that currently implement 
> continuations: x64, aarch64, riscv and ppc. Note: ppc changes were added 
> recently and stand in its own commit after the initial ones.
> 
> The changes fix pinning issues when using `LM_LIGHTWEIGHT`, i.e. the default 
> locking mode, (and `LM_MONITOR` which comes for free), but not when using 
> `LM_LEGACY` mode. Note that the `LockingMode` flag has already been 
> deprecated ([JDK-8334299](https://bugs.openjdk.org/browse/JDK-8334299)), with 
> the intention to remove `LM_LEGACY` code in future releases.
> 
> 
> ## Summary of changes
> 
> ### Unmount virtual thread while holding monitors
> 
> As stated in the JEP, currently when a virtual thread enters a synchronized 
> method or block, the JVM records the virtual thread's carrier platform thread 
> as holding the monitor, not the virtual thread itself. This prevents the 
> virtual thread from being unmounted from its carrier, as ownership 
> information would otherwise go wrong. In order to fix this limitation we will 
> do two things:
> 
> - We copy the oops stored in the LockStack of the carrier to the stackChunk 
> when freezing (and clear the LockStack). We copy the oops back to the 
> LockStack of the next carrier when thawing for the first time (and clear them 
> from the stackChunk). Note that we currently assume carriers don't hold 
> monitors while mounting virtual threads.
> 
> - For inflated monitors we now record the `java.lang.Thread.tid` of the owner 
> in the ObjectMonitor's `_owner` field instead of a JavaThread*. This allows 
> us to tie the owner of the monitor to a `java.lang.Thread` instance, rather 
> than to a JavaThread which is only created per platform thread. The tid is 
> already a 64 bit field so we can ignore issues of the counter wrapping around.
> 
> #### General notes about this part:
> 
> - Since virtual threads don't need to worry about holding monitors anymo...

First, congratulations on an exceptional piece of work @pchilano .  

Also thank you for the very clear breakdown and description in the PR as that 
helps immensely with trying to digest a change of this size.

The overall operational behaviour of this change seems very solid. My only 
concern is whether the unparker thread may become a bottleneck in some 
scenarios, but that is a bridge we will have to cross if we come to it.

My initial comments mainly come from just trying to understand the top-level 
changes around the use of the thread-id as the monitor owner. I have a number 
of suggestions on naming (mainly `is_x` versus `has_x`) and on documenting the 
API methods more clearly. None of which are showstoppers and some of which 
pre-exist. Unfortunately though you will need to fix the spelling of `succesor`.

Thanks

Thanks for those updates.

Thanks for updates. (I need to add a Review comment so I get a checkpoint to 
track further updates.)

Next batch of comments ...

Updates look good - thanks.

I think I have nothing further in terms of the review process. Great work!

Marked as reviewed by dholmes (Reviewer).

Marked as reviewed by dholmes (Reviewer).

> The tid is cached in the JavaThread object under _lock_id. It is set on 
> JavaThread creation and changed on mount/unmount.

Why do we need to cache it? Is it the implicit barriers related to accessing 
the `threadObj` oop each time?

Keeping this value up-to-date is a part I find quite confusing.

src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp line 2382:

> 2380:   __ bind(after_transition);
> 2381: 
> 2382:   if (LockingMode != LM_LEGACY && method->is_object_wait0()) {

It bothers me that we have to add a check for a specific native method in this 
code (notwithstanding there are already some checks in relation to hashCode). 
As a follow up I wonder if we can deal with wait-preemption by rewriting the 
Java code, instead of special casing the wait0 native code?

src/hotspot/share/classfile/javaClasses.cpp line 2082:

> 2080: }
> 2081: 
> 2082: bool java_lang_VirtualThread::set_onWaitingList(oop vthread, OopHandle& 
> list_head) {

Some comments here about the operation would be useful. The "waiting list" here 
is just a list of virtual threads that need unparking by the Unblocker thread - 
right?

I'm struggling to understand how a thread can already be on this list?

src/hotspot/share/classfile/javaClasses.cpp line 2086:

> 2084:   jboolean vthread_on_list = Atomic::load(addr);
> 2085:   if (!vthread_on_list) {
> 2086:     vthread_on_list = Atomic::cmpxchg(addr, (jboolean)JNI_FALSE, 
> (jboolean)JNI_TRUE);

It is not clear who the racing participants are here. How can the same thread 
be being placed on the list from two different actions?

src/hotspot/share/classfile/javaClasses.cpp line 2107:

> 2105: 
> 2106: jlong java_lang_VirtualThread::waitTimeout(oop vthread) {
> 2107:   return vthread->long_field(_timeout_offset);

Not sure what motivated the name change but it seems odd to have the method 
named differently to the field it accesses. ??

src/hotspot/share/code/nmethod.cpp line 711:

> 709:   // handle the case of an anchor explicitly set in continuation code 
> that doesn't have a callee
> 710:   JavaThread* thread = reg_map->thread();
> 711:   if ((thread->has_last_Java_frame() && fr.sp() == 
> thread->last_Java_sp()) JVMTI_ONLY(|| 
> (method()->is_continuation_enter_intrinsic() && 
> thread->on_monitor_waited_event()))) {

Suggestion:

  if ((thread->has_last_Java_frame() && fr.sp() == thread->last_Java_sp()) 
      JVMTI_ONLY(|| (method()->is_continuation_enter_intrinsic() && 
thread->on_monitor_waited_event()))) {

src/hotspot/share/prims/jvm.cpp line 4012:

> 4010:     }
> 4011:     ThreadBlockInVM tbivm(THREAD);
> 4012:     parkEvent->park();

What code does the unpark to wake this thread up? I can't quite see how this 
unparker thread operates as its logic seems dispersed.

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

> 887:     return f.is_native_frame() ? recurse_freeze_native_frame(f, caller) 
> : recurse_freeze_stub_frame(f, caller);
> 888:   } else {
> 889:     // frame can't be freezed. Most likely the call_stub or upcall_stub

Suggestion:

    // Frame can't be frozen. Most likely the call_stub or upcall_stub

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

> 163:   // ID used as owner for inflated monitors. Same as the j.l.Thread.tid 
> of the
> 164:   // current _vthread object, except during creation of the primordial 
> and JNI
> 165:   // attached thread cases where this field can have a temporal value.

Suggestion:

  // attached thread cases where this field can have a temporary value.

Presumably this is for when the attaching thread is executing the Thread 
constructor?

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

> 164:   // current _vthread object, except during creation of the primordial 
> and JNI
> 165:   // attached thread cases where this field can have a temporary value. 
> Also,
> 166:   // calls to VirtualThread.switchToCarrierThread will temporary change 
> _vthread

s/temporary change/temporarily change/

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

> 130: 
> 131: // 
> -----------------------------------------------------------------------------
> 132: // Theory of operations -- Monitors lists, thread residency, etc:

This comment block needs updating now owner is not a JavaThread*, and to 
account for vthread usage

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

> 1138: }
> 1139: 
> 1140: bool ObjectMonitor::resume_operation(JavaThread* current, ObjectWaiter* 
> node, ContinuationWrapper& cont) {

Explanatory comment would be good - thanks.

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

> 1530:   } else if (java_lang_VirtualThread::set_onWaitingList(vthread, 
> vthread_cxq_head())) {
> 1531:     // Virtual thread case.
> 1532:     Trigger->unpark();

So ignoring for the moment that I can't see how `set_onWaitingList` could 
return false here, the check is just an optimisation to reduce the number of 
unparks issued i.e. only unpark if the list has changed?

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

> 1671: 
> 1672:   ContinuationEntry* ce = current->last_continuation();
> 1673:   if (interruptible && ce != nullptr && ce->is_virtual_thread()) {

So IIUC this use of `interruptible` would be explained as follows:

// Some calls to wait() occur in contexts that still have to pin a vthread to 
its carrier.
// All such contexts perform non-interruptible waits, so by checking 
`interruptible` we know 
// this is a regular Object.wait call.

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

> 1704:     // on _WaitSetLock so it's not profitable to reduce the length of 
> the
> 1705:     // critical section.
> 1706: 

Please restore the blank line, else it looks like the comment block pertains to 
the `wait_reenter_begin`, but it doesn't.

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

> 2026:   // First time we run after being preempted on Object.wait().
> 2027:   // Check if we were interrupted or the wait timed-out, and in
> 2028:   // that case remove ourselves from the _WaitSet queue.

I'm not sure how to interpret this comment block - is this really two sentences 
because the first is not actually a sentence. Also unclear what "run" and 
"First time" relate to.

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

> 2052:   // Mark that we are at reenter so that we don't call this method 
> again.
> 2053:   node->_at_reenter = true;
> 2054:   assert(!has_owner(current), "invariant");

The position of this assert seems odd as it seems to be something that should 
hold at entry to this method.

src/hotspot/share/runtime/objectMonitor.hpp line 47:

> 45: // ParkEvent instead.  Beware, however, that the JVMTI code
> 46: // knows about ObjectWaiters, so we'll have to reconcile that code.
> 47: // See next_waiter(), first_waiter(), etc.

This to-do is likely no longer relevant with the current changes.

src/hotspot/share/runtime/objectMonitor.hpp line 174:

> 172: 
> 173:   int64_t volatile _owner;  // Either tid of owner, NO_OWNER, 
> ANONYMOUS_OWNER or DEFLATER_MARKER.
> 174:   volatile uint64_t _previous_owner_tid;  // thread id of the previous 
> owner of the monitor

Looks odd to have the current owner as `int64_t` but we save the previous owner 
as `uint64_t`. ??

src/hotspot/share/runtime/objectMonitor.hpp line 207:

> 205: 
> 206:   static void Initialize();
> 207:   static void Initialize2();

Please add comment why this needs to be deferred - and till after what?

src/hotspot/share/runtime/objectMonitor.hpp line 288:

> 286:   // Returns true if this OM has an owner, false otherwise.
> 287:   bool      has_owner() const;
> 288:   int64_t   owner() const;  // Returns null if DEFLATER_MARKER is 
> observed.

null is not an int64_t value.

src/hotspot/share/runtime/objectMonitor.hpp line 292:

> 290: 
> 291:   static int64_t owner_for(JavaThread* thread);
> 292:   static int64_t owner_for_oop(oop vthread);

Some comments describing this API would be good. I'm struggling a bit with the 
"owner for" terminology. I think `owner_from` would be better. And can't these 
just overload rather than using different names?

src/hotspot/share/runtime/objectMonitor.hpp line 299:

> 297:   // Simply set _owner field to new_value; current value must match 
> old_value.
> 298:   void      set_owner_from_raw(int64_t old_value, int64_t new_value);
> 299:   // Same as above but uses tid of current as new value.

By `tid` here (and elsewhere) you actually mean 
`thread->threadObj()->thread_id()` - right?

src/hotspot/share/runtime/objectMonitor.hpp line 302:

> 300:   // Simply set _owner field to new_value; current value must match 
> old_value.
> 301:   void      set_owner_from_raw(int64_t old_value, int64_t new_value);
> 302:   void      set_owner_from(int64_t old_value, JavaThread* current);

Again some comments describing API would good. The old API had vague names like 
old_value and new_value because of the different forms the owner value could 
take. Now it is always a thread-id we can do better I think. The distinction 
between the raw and non-raw forms is unclear and the latter is not covered by 
the initial comment.

src/hotspot/share/runtime/objectMonitor.hpp line 302:

> 300:   void      set_owner_from(int64_t old_value, JavaThread* current);
> 301:   // Set _owner field to tid of current thread; current value must be 
> ANONYMOUS_OWNER.
> 302:   void      set_owner_from_BasicLock(JavaThread* current);

Shouldn't tid there be the basicLock?

src/hotspot/share/runtime/objectMonitor.hpp line 303:

> 301:   void      set_owner_from_raw(int64_t old_value, int64_t new_value);
> 302:   void      set_owner_from(int64_t old_value, JavaThread* current);
> 303:   // Simply set _owner field to current; current value must match 
> basic_lock_p.

Comment is no longer accurate

src/hotspot/share/runtime/objectMonitor.hpp line 309:

> 307:   // _owner field. Returns the prior value of the _owner field.
> 308:   int64_t   try_set_owner_from_raw(int64_t old_value, int64_t new_value);
> 309:   int64_t   try_set_owner_from(int64_t old_value, JavaThread* current);

Similar to set_owner* need better comments describing API.

src/hotspot/share/runtime/objectMonitor.hpp line 311:

> 309:   int64_t   try_set_owner_from(int64_t old_value, JavaThread* current);
> 310: 
> 311:   bool      is_succesor(JavaThread* thread);

I think `has_successor` is more appropriate here as it is not the monitor that 
is the successor.

src/hotspot/share/runtime/objectMonitor.hpp line 312:

> 310:   void      set_successor(JavaThread* thread);
> 311:   void      set_successor(oop vthread);
> 312:   void      clear_successor();

Needs descriptive comments, or at least a preceding comment explaining what a 
"successor" is.

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

> 313:   void      set_succesor(oop vthread);
> 314:   void      clear_succesor();
> 315:   bool      has_succesor();

Sorry but `successor` has two `s` before `or`.

src/hotspot/share/runtime/objectMonitor.hpp line 317:

> 315:   bool      has_succesor();
> 316: 
> 317:   bool is_owner(JavaThread* thread) const { return owner() == 
> owner_for(thread); }

Again `has_owner` seems more appropriate

src/hotspot/share/runtime/objectMonitor.hpp line 323:

> 321:   }
> 322: 
> 323:   bool is_owner_anonymous() const { return owner_raw() == 
> ANONYMOUS_OWNER; }

Again I struggle with the pre-existing `is_owner` formulation here. The target 
of the expression is a monitor and we are asking if the monitor has an 
anonymous owner.

src/hotspot/share/runtime/objectMonitor.hpp line 333:

> 331:   bool is_stack_locker(JavaThread* current);
> 332:   BasicLock* stack_locker() const;
> 333:   void set_stack_locker(BasicLock* locker);

Again `is` versus `has`, plus some general comments describing the API.

src/hotspot/share/runtime/objectMonitor.hpp line 334:

> 332: 
> 333:   // Returns true if BasicLock* stored in _stack_locker
> 334:   // points to current's stack, false othwerwise.

Suggestion:

  // points to current's stack, false otherwise.

src/hotspot/share/runtime/objectMonitor.hpp line 349:

> 347:   ObjectWaiter* first_waiter()                                         { 
> return _WaitSet; }
> 348:   ObjectWaiter* next_waiter(ObjectWaiter* o)                           { 
> return o->_next; }
> 349:   JavaThread* thread_of_waiter(ObjectWaiter* o)                        { 
> return o->_thread; }

This no longer looks correct if the waiter is a vthread. ??

src/hotspot/share/runtime/objectMonitor.inline.hpp line 110:

> 108: }
> 109: 
> 110: // Returns null if DEFLATER_MARKER is observed.

Comment needs updating

src/hotspot/share/runtime/objectMonitor.inline.hpp line 130:

> 128: // Returns true if owner field == DEFLATER_MARKER and false otherwise.
> 129: // This accessor is called when we really need to know if the owner
> 130: // field == DEFLATER_MARKER and any non-null value won't do the trick.

Comment needs updating

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

> 668:   // Top native frames in the stack will not be seen if we attempt
> 669:   // preemption, since we start walking from the last Java anchor.
> 670:   NoPreemptMark npm(current);

Don't we still pin for JNI monitor usage?

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

> 1438: }
> 1439: 
> 1440: ObjectMonitor* ObjectSynchronizer::inflate_impl(JavaThread* 
> inflating_thread, oop object, const InflateCause cause) {

`inflating_thread` doesn't sound right as it is always the current thread that 
is doing the inflating. The passed in thread may be a different thread trying 
to acquire the monitor ... perhaps `contending_thread`?

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

> 170: 
> 171:   // Iterate ObjectMonitors where the owner is thread; this does NOT 
> include
> 172:   // ObjectMonitors where owner is set to a stack lock address in thread.

Comment needs updating

src/hotspot/share/runtime/threadIdentifier.cpp line 30:

> 28: 
> 29: // starting at 3, excluding reserved values defined in ObjectMonitor.hpp
> 30: static const int64_t INITIAL_TID = 3;

Can we express this in terms of those reserved values, or are they inaccessible?

src/hotspot/share/services/threadService.cpp line 467:

> 465:         if (waitingToLockMonitor->has_owner()) {
> 466:           currentThread = Threads::owning_thread_from_monitor(t_list, 
> waitingToLockMonitor);
> 467:           // If currentThread is nullptr we would like to know if the 
> owner

Suggestion:

          // If currentThread is null we would like to know if the owner

src/hotspot/share/services/threadService.cpp line 474:

> 472:           // vthread we never record this as a deadlock. Note: unless 
> there
> 473:           // is a bug in the VM, or a thread exits without releasing 
> monitors
> 474:           // acquired through JNI, nullptr should imply unmounted 
> vthread owner.

Suggestion:

          // acquired through JNI, null should imply an unmounted vthread owner.

src/java.base/share/classes/java/lang/Object.java line 383:

> 381:             try {
> 382:                 wait0(timeoutMillis);
> 383:             } catch (InterruptedException e) {

I had expected to see a call to a new `wait0` method that returned a value 
indicating whether the wait was completed or else we had to park. Instead we 
had to put special logic in the native-call-wrapper code in the VM to detect 
returning from wait0 and changing the return address. I'm still unclear where 
that modified return address actually takes us.

src/java.base/share/classes/java/lang/Thread.java line 654:

> 652:      * {@link Thread#PRIMORDIAL_TID}&nbsp;+1 as this class cannot be 
> used during
> 653:      * early startup to generate the identifier for the primordial 
> thread. The
> 654:      * counter is off-heap and shared with the VM to allow it assign 
> thread

Suggestion:

     * counter is off-heap and shared with the VM to allow it to assign thread

src/java.base/share/classes/java/lang/Thread.java line 655:

> 653:      * early startup to generate the identifier for the primordial 
> thread. The
> 654:      * counter is off-heap and shared with the VM to allow it assign 
> thread
> 655:      * identifiers to non-Java threads.

Why do non-JavaThreads need an identifier of this kind?

src/java.base/share/classes/java/lang/Thread.java line 731:

> 729: 
> 730:         if (attached && VM.initLevel() < 1) {
> 731:             this.tid = 3;  // primordial thread

The comment before the `ThreadIdentifiers` class needs updating to account for 
this change.

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

> 107:      *
> 108:      *   RUNNING -> BLOCKING       // blocking on monitor enter
> 109:      *  BLOCKING -> BLOCKED        // blocked on monitor enter

Should this say something similar to the parked case, about the "yield" being 
successful?

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

> 108:      *   RUNNING -> BLOCKING       // blocking on monitor enter
> 109:      *  BLOCKING -> BLOCKED        // blocked on monitor enter
> 110:      *   BLOCKED -> UNBLOCKED      // unblocked, may be scheduled to 
> continue

Does this mean it now owns the monitor, or just it is able to re-contest for 
monitor entry?

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

> 109:      *  BLOCKING -> BLOCKED        // blocked on monitor enter
> 110:      *   BLOCKED -> UNBLOCKED      // unblocked, may be scheduled to 
> continue
> 111:      * UNBLOCKED -> RUNNING        // continue execution after blocked 
> on monitor enter

Presumably this one means it acquired the monitor?

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

> 113:      *   RUNNING -> WAITING        // transitional state during wait on 
> monitor
> 114:      *   WAITING -> WAITED         // waiting on monitor
> 115:      *    WAITED -> BLOCKED        // notified, waiting to be unblocked 
> by monitor owner

Waiting to re-enter the monitor?

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

> 176:     // timed-wait support
> 177:     private long waitTimeout;
> 178:     private byte timedWaitNonce;

Strange name - what does this mean?

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

> 528:                 && carrier == Thread.currentCarrierThread();
> 529:         carrier.setCurrentThread(carrier);
> 530:         Thread.setCurrentLockId(this.threadId()); // keep lock ID of 
> virtual thread

I'm struggling to understand the different threads in play when this is called 
and what the method actual does to which threads. ??

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

> 629:         // Object.wait
> 630:         if (s == WAITING || s == TIMED_WAITING) {
> 631:             byte nonce;

Suggestion:

            byte seqNo;

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

> 946:      * This method does nothing if the thread has been woken by notify 
> or interrupt.
> 947:      */
> 948:     private void waitTimeoutExpired(byte nounce) {

I assume you meant `nonce` here, but please change to `seqNo`.

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

> 950:         for (;;) {
> 951:             boolean unblocked = false;
> 952:             synchronized (timedWaitLock()) {

Where is the overall design of the timed-wait protocol and it use of 
synchronization described?

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

> 1395: 
> 1396:     /**
> 1397:      * Returns a lock object to coordinating timed-wait setup and 
> timeout handling.

Suggestion:

     * Returns a lock object for coordinating timed-wait setup and timeout 
handling.

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

Changes requested by dholmes (Reviewer).

PR Review: https://git.openjdk.org/jdk/pull/21565#pullrequestreview-2384039238
PR Review: https://git.openjdk.org/jdk/pull/21565#pullrequestreview-2387241944
PR Review: https://git.openjdk.org/jdk/pull/21565#pullrequestreview-2393910702
PR Review: https://git.openjdk.org/jdk/pull/21565#pullrequestreview-2393922768
Marked as reviewed by dholmes (Reviewer).

PR Review: https://git.openjdk.org/jdk/pull/21565#pullrequestreview-2406338095
PR Review: https://git.openjdk.org/jdk/pull/21565#pullrequestreview-2409348761
PR Review: https://git.openjdk.org/jdk/pull/21565#pullrequestreview-2417279456
PR Comment: https://git.openjdk.org/jdk/pull/21565#issuecomment-2431004707
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1818251880
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1815838204
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1815839094
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1827128518
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1815840245
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1814306675
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1825344054
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1805616004
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1814260043
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1815985700
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1815998417
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1816002660
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1816009160
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1816014286
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1816017269
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1816018848
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1810025380
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1815956322
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1816040287
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1810027786
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1810029858
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1811912133
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1810032387
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1811913172
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1810033016
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1810035434
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1810037658
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1815959203
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1810036007
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1810041017
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1810046285
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1810049295
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1811914377
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1815960013
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1815967260
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1815969101
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1816043275
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1816047142
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1816041444
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1810068395
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1825344940
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1825345446
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1814294622
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1814158735
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1814159210
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1810076019
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1810111255
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1810113028
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1810113953
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1810114488
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1810116177
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1810131339
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1814169150
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1814170953
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1814171503
PR Review Comment: https://git.openjdk.org/jdk/pull/21565#discussion_r1814172621

Reply via email to