On Wed, 16 Feb 2022 00:16:48 GMT, Zhengyu Gu <z...@openjdk.org> wrote:

>> There are scenarios that JDWP agent can deadlock on `classTrackLock` 
>> monitor. Following is the scenario in bug report.
>> 
>> **Java Thread** 
>> -   loads a class and post `JVMTI_EVENT_CLASS_PREPARE` event
>> - JDWP event callback handler calls `classTrack_processUnloads()` to handle 
>> the event.
>> - `classTrack_processUnloads()` takes `classTrackLock` lock, then tries to 
>> allocate a new bag under the lock.
>> - bag allocation code calls` jvmtiAllocate()`, which may be blocked by 
>> ongoing safepoint due to state transition.
>> 
>> If the safepoint is GC safepoint (prior to JDK16) or 
>> `VM_JvmtiPostObjectFree`  safepoint (JDK16 or later)
>> 
>> **VM Thread**
>> - post `JVMTI_EVENT_OBJECT_FREE`
>> - JDWP event callback handler calls `cbTrackingObjectFree()` to handle the 
>> event
>> - `cbTrackingObjectFree()` tries to acquire `classTrackLock` lock, leads to 
>> deadlock
>> 
>> From my research, there are three events that may be posted at safepoints, 
>> `JVMTI_EVENT_GARBAGE_COLLECTION_START`, 
>> `JVMTI_EVENT_GARBAGE_COLLECTION_FINISH` and  `JVMTI_EVENT_OBJECT_FREE`, but 
>> only  `JVMTI_EVENT_OBJECT_FREE` is relevant to JDWP agent.
>> 
>> The solution I purpose here, is simply move allocation/deallocation code 
>> outside of `classTrackLock` lock.
>> 
>> 
>> Test:
>> - [x] tier1 
>> - [x] vmTestbase_nsk_jdi
>> - [x] vmTestbase_nsk_jdwp
>> - [x] vmTestbase_nsk_jvmti
>
> Zhengyu Gu has updated the pull request incrementally with one additional 
> commit since the last revision:
> 
>   Simplify classTrack_reset and revert bagSize check

processUnloads is continuously called on every event. It's purpose is to 
process pending CLASS_UNLOAD events that have accumulated since the last JVMTI 
event. This is a rather hacky solution. JVMTI does not actually send a 
CLASS_UNLOAD event. It doesn't support them. What it does support is 
OBJECT_FREE events. So when the debugger has enabled CLASS_UNLOAD events, the 
debug agent enables OBJECT_FEE events. It tags each j.l.Class instance so it 
will get the OBJECT_FREE event for them, and when the event comes in, the Class 
gets added to deletedSignatures so the agent can later notify the debugger 
about it.

As a side note, this OBJECT_FREE solution is somewhat new. The old solution was 
to always do an "allClasses" during initialization, and then diff that with 
"allClasses" every time there was a full GC to see which classes unloaded. I 
wonder now if there isn't a reason we just don't send the CLASS_UNLOAD when the 
OBJECT_FREE is received. It would probably greatly simplify classTrack.c, and 
will fix an outstanding bug we have where sometimes there is a lengthy delay 
before CLASS_UNLOAD events are sent. This is because once there is a GC, they 
events are not sent until the next JVMTI event. It's actually possible there 
would never be one if the only thing the debugger is requesting is CLASS_UNLOAD 
events.

Anyway, back to processUnloads synchronization. The reason for synchronization 
between processUnload and activate is because one thread could be handling some 
random JVMTI event (maybe a breakpoint) and is calling processUnloads, and at 
the same time another thread is handling a CLASS_UNLOAD event request from the 
debugger, and calling activate. But now that I think of it, this should be 
harmless even without synchronization. The processUnloads thread will see the 
allocated deletedSignatures if activate has gotten that far, otherwise it 
won't. It should behave correctly in either case. I'm not convince that 
activate even needs synchronization. The purpose of the classTrack 
synchronization is to prevent one thread in processUnloads from grabbing and 
using deletedSignatures, only to have another thread in processUnloads do the 
same, and delete deletedSignatures in the process, leaving the 1st thread 
holding onto a deallocated pointer. However, even this should not be possible 
because a
 ll callers to processUnloads have already grabbed the handlerLock.

What I'm tempted to say here is just have this PR keep all the synchronization 
and I'll file a CR to possibly do a lot of cleanup here. Either remove (maybe 
all of) the synchronization, or maybe even implement sending the CLASS_UNLOAD 
when the OBJECT_FREE comes in, which will make the synchronization question 
moot, and also fix that other bug I mentioned.

BTW, one other bit of nonsense. classTrack_activate() does:

    deletedSignatures = bagCreateBag(sizeof(char*), 1000);

This will be deleted the very first time an event comes in, even if no classes 
were unloaded, and processUnloads replaces it with:

    deletedSignatures = bagCreateBag(sizeof(char*), 10);

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

PR: https://git.openjdk.java.net/jdk/pull/7461

Reply via email to