On Tue, 25 Mar 2025 20:36:28 GMT, Chris Plummer <cjplum...@openjdk.org> wrote:

> Calling ThreadGroupReference.groups() from an event handler can cause a 
> deadlock. Details in first comment. Tested with :jdk_lang on all supported 
> platforms and tier1, tier2, tier3, and tier5 svc testing.

The reason is because this JDI API eventually ends up with JVMTI doing a java 
upcall to ThreadGroup.subgroupsAsArray(), which can trigger class loading the 
first time it is called. Thjis results in a ClassPrepareEvent that the debugger 
will get, but not process because its event handler thread is blocked on the 
ThreadGroupReference.groups(), so we have a deadlock.

The workaround is to make ThreadGroupReference.groups() not trigger any class 
loading. The fix is subtle. Before the fix, the java stack trace at the time of 
the ClasPrepareEvent looks like:

java.util.Arrays.copyOf(java.lang.Object[], int, java.lang.Class) bci:18 
line:3513
java.util.ArrayList.toArray(java.lang.Object[]) bci:21 line:401
java.lang.ThreadGroup.subgroupsAsArray() bci:10 line:751

This is ThreadGroup.subgroupsAsArray:

private ThreadGroup[] subgroupsAsArray() {
    List<ThreadGroup> groups = synchronizedSubgroups();
    return groups.toArray(new ThreadGroup[0]);
}

This is ArrayList.toArray():

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());  <--- Line 
401
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

And this is Arrays.copyOf():

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends 
T[]> newType) {
    @SuppressWarnings("unchecked")
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

Line 3513 is actually the return statement, so this seems incorrect of by a 
bit. Adding some tracing of ClassPrepareEvents shows that we are currently 
handling the following:

cbClassPrepare: java.lang.reflect.Array

So it looks like we took the Array.newInstance() path, which triggered class 
loading of java.lang.reflect.Array. After the fix we never end up in 
Arrays.copyOf(). Instead ArrayList.toArray() calls System.arraycopy() directly, 
avoiding any class loading..

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

PR Comment: https://git.openjdk.org/jdk/pull/24236#issuecomment-2752702624

Reply via email to