On Thu, 18 Jun 2026 23:33:31 GMT, Man Cao <[email protected]> wrote:

> Hi all,
> 
> Could anyone review this change contributed by colleague Liz Looney 
> <[email protected]>, that fixes a long-standing data race on 
> `com.sun.tools.jdi.VirtualMachineImpl.shutdown`?
> 
> -Man
> 
> ---------
> - [x] I confirm that I make this contribution in accordance with the [OpenJDK 
> Interim AI Policy](https://openjdk.org/legal/ai).

I've been coincidentally working in this code for #31421 and the related #31586 
PR, and I've uncovered other issues with processBatchedDisposes() during 
shutdown/disconnect.

I think the purpose of the shutdown check is to avoid getting a 
VMDisconnectedException when the JDWP.VirtualMachine.DisposeObjects.process() 
line is executed. However, the check is not enough since shutdown can still be 
set true after the check is made and before process() is called. We really need 
some sort of synchronzation that includes shutdown and the process() call. 
However, that still is not enough to prevent VMDisconnectedException from 
happening here. VirtualMachineImpl.objectMirror() calls processQueue(). 
processQueue() was modified by JDK-8297638 in 2023 to call 
processBatchedDisposes() if any unreferenced ObjectReferenceImpls were found. 
processBatchedDisposes() can throw VMDisconnectedException. See the stack trace 
at the end of this comment.

The issue here is the JDI user has called EventQueue.remove(), and in this case 
should be getting a ClassPrepareEvent, and since the debuggee has already 
disconnected by this time, it should be followed by a VMDisconnectEvent. These 
should be delivered without producing a VMDisconnectedException. But due to the 
processBatchedDisposes() call now being in the path, a VMDisconnectedException 
is being thrown while trying to send a VirtualMachine.DisposeObjects.

I'm seeing this issue a lot in my testing, but only after modifying the 
referenceQueue used to track ObjectReferences to use weakrefs instead of 
softrefs (so they are more readily collected), and also making it so 
VirtualMachine.DisposeObjects is called if there are any batched disposes 
instead of waiting until there are 50. This makes the scenario described above 
much more likely to happen since VirtualMachine.DisposeObjects is executed much 
more readily and frequently.

I think the fix is as simple as catching VMDisconnectedException around the 
process() call and ignoring it. If the debuggee is disconnected, the ObjectIDs 
are already explicity disposed of, and the VMDisconnectedException can be 
ignored. This allows the debugger to fetch the last remaining events that are 
already queued, including VMDisconnectEvent and VMDeathEvent. I think that will 
also allow us to get rid of the shudown related logic. Even if we've already 
done a dispose() or exit(), it is safe to attempt the 
VirtualMachine.DisposeObjects and allow it to fail with VMDisconnectedException.

Also see the comment in validateVM(), which seems to support this approach. If 
this change is made to ignore VMDisconnectedException, then we don't need the 
shutdown check. I'm still working on this fix. Testing turned up some test 
issue that I'm still trying to resolve.


com.sun.jdi.VMDisconnectedException
        at jdk.jdi/com.sun.tools.jdi.TargetVM.waitForReply(TargetVM.java:305)
        at 
jdk.jdi/com.sun.tools.jdi.VirtualMachineImpl.waitForTargetReply(VirtualMachineImpl.java:1174)
        at 
jdk.jdi/com.sun.tools.jdi.PacketStream.waitForReply(PacketStream.java:89)
        at 
jdk.jdi/com.sun.tools.jdi.JDWP$VirtualMachine$DisposeObjects.waitForReply(JDWP.java:1004)
        at 
jdk.jdi/com.sun.tools.jdi.JDWP$VirtualMachine$DisposeObjects.process(JDWP.java:979)
        at 
jdk.jdi/com.sun.tools.jdi.VirtualMachineImpl.processBatchedDisposes(VirtualMachineImpl.java:1351)
        at 
jdk.jdi/com.sun.tools.jdi.VirtualMachineImpl.processQueue(VirtualMachineImpl.java:1384)
        at 
jdk.jdi/com.sun.tools.jdi.VirtualMachineImpl.objectMirror(VirtualMachineImpl.java:1391)
        at 
jdk.jdi/com.sun.tools.jdi.VirtualMachineImpl.threadMirror(VirtualMachineImpl.java:1479)
        at 
jdk.jdi/com.sun.tools.jdi.PacketStream.readThreadReference(PacketStream.java:457)
        at 
jdk.jdi/com.sun.tools.jdi.JDWP$Event$Composite$Events$ClassPrepare.<init>(JDWP.java:8627)
        at 
jdk.jdi/com.sun.tools.jdi.JDWP$Event$Composite$Events.<init>(JDWP.java:7872)
        at jdk.jdi/com.sun.tools.jdi.JDWP$Event$Composite.<init>(JDWP.java:8901)
        at jdk.jdi/com.sun.tools.jdi.EventSetImpl.build(EventSetImpl.java:660)
        at 
jdk.jdi/com.sun.tools.jdi.EventQueueImpl.removeUnfiltered(EventQueueImpl.java:212)
        at 
jdk.jdi/com.sun.tools.jdi.EventQueueImpl.remove(EventQueueImpl.java:97)
        at nsk.share.jdi.JDIBase.getEventSet(JDIBase.java:169)
        at 
nsk.jdi.StepRequest.addInstanceFilter.instancefilter003.runTest(instancefilter003.java:211)
        at 
nsk.jdi.StepRequest.addInstanceFilter.instancefilter003.runThis(instancefilter003.java:151)
        at 
nsk.jdi.StepRequest.addInstanceFilter.instancefilter003.run(instancefilter003.java:94)
        at 
nsk.jdi.StepRequest.addInstanceFilter.instancefilter003.main(instancefilter003.java:85)
        at 
java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
        at java.base/java.lang.reflect.Method.invoke(Method.java:583)
        at 
com.sun.javatest.regtest.agent.MainActionHelper$AgentVMRunnable.run(MainActionHelper.java:335)
        at java.base/java.lang.Thread.run(Thread.java:1527)

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

PR Comment: https://git.openjdk.org/jdk/pull/31582#issuecomment-4770681842

Reply via email to