-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Description
Overview
The Structure
class constructor contains a deadlock scenario. The particular scenario we found can occur during the creation of two ByValue
structures both containing a member of a type Structure.ByValue
. I have prepared a minimal reproducible example. Running it multiple times should produce the attached output.
Random notes on the problem
This deadlock was introduced in this PR: #1626
I don't think the scenario we've encountered is the only possible one, given the multitude of execution paths in this file, its recursive nature, and the fact that there are 4 explicit locks + additional synchronization on the typeInfoMap
object. Given those factors, I think it's extremely hard to rule out deadlocks only by manual verification, and I would recommend using a single reentrant read-write lock for the whole file, if performance allows it.
Deadlocked stacktraces
Thread: Thread-1 (State: WAITING)
at java.base@11.0.28/jdk.internal.misc.Unsafe.park(Native Method)
at java.base@11.0.28/java.util.concurrent.locks.LockSupport.park(LockSupport.java:194)
at java.base@11.0.28/java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:885)
at java.base@11.0.28/java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireShared(AbstractQueuedSynchronizer.java:1009)
at java.base@11.0.28/java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireShared(AbstractQueuedSynchronizer.java:1324)
at java.base@11.0.28/java.util.concurrent.locks.ReentrantReadWriteLock$ReadLock.lock(ReentrantReadWriteLock.java:738)
at com.sun.jna.Structure.validateFields(Structure.java:1310)
at com.sun.jna.Structure.<init>(Structure.java:218)
at com.sun.jna.Structure.<init>(Structure.java:211)
at com.sun.jna.Structure.<init>(Structure.java:198)
at com.sun.jna.Structure.<init>(Structure.java:190)
at com.sun.jna.Structure$FFIType.<init>(Structure.java:2137)
at com.sun.jna.Structure$FFIType.get(Structure.java:2249)
at com.sun.jna.Structure$FFIType.get(Structure.java:2222)
at com.sun.jna.Structure.getTypeInfo(Structure.java:1914)
at com.sun.jna.Structure.getTypeInfo(Structure.java:1847)
at com.sun.jna.Structure.deriveLayout(Structure.java:1468)
at com.sun.jna.Structure.calculateSize(Structure.java:1237)
at com.sun.jna.Structure.calculateSize(Structure.java:1183)
at com.sun.jna.Structure.allocateMemory(Structure.java:433)
at com.sun.jna.Structure.<init>(Structure.java:223)
at com.sun.jna.Structure.<init>(Structure.java:211)
at com.sun.jna.Structure.<init>(Structure.java:198)
at com.sun.jna.Structure.<init>(Structure.java:190)
at com.example.jna.JnaDeadlock$s1.<init>(JnaDeadlock.java:17)
at com.example.jna.JnaDeadlock$s1$ByValue.<init>(JnaDeadlock.java:20)
at com.example.jna.JnaDeadlock.runT1(JnaDeadlock.java:67)
at com.example.jna.JnaDeadlock.lambda$0(JnaDeadlock.java:45)
at com.example.jna.JnaDeadlock$$Lambda$36/0x00000008401bec40.run(Unknown Source)
at java.base@11.0.28/java.lang.Thread.run(Thread.java:829)
Thread: Thread-2 (State: BLOCKED)
at com.sun.jna.Structure$FFIType.get(Structure.java:2234)
at com.sun.jna.Structure$FFIType.get(Structure.java:2222)
at com.sun.jna.Structure.getTypeInfo(Structure.java:1914)
at com.sun.jna.Structure.getTypeInfo(Structure.java:1847)
at com.sun.jna.Structure.deriveLayout(Structure.java:1468)
at com.sun.jna.Structure.calculateSize(Structure.java:1237)
at com.sun.jna.Structure.calculateSize(Structure.java:1183)
at com.sun.jna.Structure.allocateMemory(Structure.java:433)
at com.sun.jna.Structure.<init>(Structure.java:223)
at com.sun.jna.Structure.<init>(Structure.java:211)
at com.sun.jna.Structure.<init>(Structure.java:198)
at com.sun.jna.Structure.<init>(Structure.java:190)
at com.example.jna.JnaDeadlock$s2_embedded.<init>(JnaDeadlock.java:25)
at com.example.jna.JnaDeadlock$s2_embedded$ByValue.<init>(JnaDeadlock.java:28)
at java.base@11.0.28/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.base@11.0.28/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at java.base@11.0.28/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.base@11.0.28/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
at com.sun.jna.Klass.newInstance(Klass.java:48)
at com.sun.jna.Structure.newInstance(Structure.java:1979)
at com.sun.jna.Structure.newInstance(Structure.java:1965)
at com.sun.jna.Structure.size(Structure.java:1210)
at com.sun.jna.Native.getNativeSize(Native.java:1400)
at com.sun.jna.Structure.getNativeSize(Structure.java:2385)
at com.sun.jna.Structure.getNativeSize(Structure.java:2375)
at com.sun.jna.Structure.validateField(Structure.java:1298)
at com.sun.jna.Structure.lambda$validateFields$2(Structure.java:1325)
at com.sun.jna.Structure$$Lambda$39/0x00000008401bc040.apply(Unknown Source)
at java.base@11.0.28/java.util.Map.computeIfAbsent(Map.java:1003)
at com.sun.jna.Structure.validateFields(Structure.java:1323)
at com.sun.jna.Structure.<init>(Structure.java:218)
at com.sun.jna.Structure.<init>(Structure.java:211)
at com.sun.jna.Structure.<init>(Structure.java:198)
at com.sun.jna.Structure.<init>(Structure.java:190)
at com.example.jna.JnaDeadlock$s2.<init>(JnaDeadlock.java:33)
at com.example.jna.JnaDeadlock$s2$ByValue.<init>(JnaDeadlock.java:36)
at com.example.jna.JnaDeadlock.runT2(JnaDeadlock.java:75)
at com.example.jna.JnaDeadlock.lambda$1(JnaDeadlock.java:48)
at com.example.jna.JnaDeadlock$$Lambda$37/0x00000008401be040.run(Unknown Source)
at java.base@11.0.28/java.lang.Thread.run(Thread.java:829)
Minimal reproducible example
package com.example.jna;
import com.sun.jna.Structure;
import com.sun.jna.Structure.FieldOrder;
import java.util.concurrent.CountDownLatch;
public class JnaDeadlock {
@FieldOrder({ "f" })
public static class s1_embedded extends Structure {
public int f;
public static class ByValue extends s1_embedded implements Structure.ByValue {
}
}
@FieldOrder({ "embeddedByValue" })
public static class s1 extends Structure {
public s1_embedded.ByValue embeddedByValue;
public static class ByValue extends s1 implements Structure.ByValue {
}
}
@FieldOrder({ "f" })
public static class s2_embedded extends Structure {
public int f;
public static class ByValue extends s2_embedded implements Structure.ByValue {
}
}
@FieldOrder({ "embeddedByValue" })
public static class s2 extends Structure {
public s2_embedded.ByValue embeddedByValue;
public static class ByValue extends s2 implements Structure.ByValue {
}
}
public static void main(String[] args) throws Exception {
CountDownLatch startLatch = new CountDownLatch(1);
Thread[] threads = new Thread[2];
threads[0] = new Thread(() -> {
runT1(startLatch);
});
threads[1] = new Thread(() -> {
runT2(startLatch);
});
threads[0].start();
threads[1].start();
Thread monitorThread = new Thread(() -> {
monitorThread(threads);
});
monitorThread.setDaemon(true);
monitorThread.start();
// Release all threads to start at the same time
startLatch.countDown();
}
private static void runT1(CountDownLatch startLatch) {
try {
startLatch.await();
new s1.ByValue();
} catch (InterruptedException e) {
}
}
private static void runT2(CountDownLatch startLatch) {
try {
startLatch.await();
new s2.ByValue();
} catch (InterruptedException e) {
}
}
private static void monitorThread(Thread[] workers) {
try {
Thread.sleep(2000);
for (Thread worker : workers) {
StackTraceElement[] stackTrace = worker.getStackTrace();
System.out.println("\nThread: " + worker.getName() +
" (State: " + worker.getState() + ")");
for (StackTraceElement element : stackTrace) {
System.out.println(" at " + element);
}
}
} catch (InterruptedException e) {
}
}
}