Skip to content

Commit 16975a3

Browse files
committed
Added test for per-thread cleaner cleanup (failing)
1 parent b2e1037 commit 16975a3

File tree

4 files changed

+110
-4
lines changed

4 files changed

+110
-4
lines changed

src/com/sun/jna/internal/Cleaner.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,8 @@ protected synchronized boolean remove(long n) {
121121
}
122122
}
123123

124-
private static class MasterCleaner extends Cleaner {
125-
private static MasterCleaner INSTANCE;
124+
static class MasterCleaner extends Cleaner {
125+
static MasterCleaner INSTANCE;
126126

127127
public static synchronized void add(Cleaner cleaner) {
128128
if (INSTANCE == null) {
@@ -146,7 +146,7 @@ private static synchronized boolean deleteIfEmpty() {
146146
return false;
147147
}
148148

149-
private final Map<CleanerImpl,Boolean> cleanerImpls = new ConcurrentHashMap<CleanerImpl,Boolean>();
149+
final Map<CleanerImpl,Boolean> cleanerImpls = new ConcurrentHashMap<CleanerImpl,Boolean>();
150150
private long lastNonEmpty = System.currentTimeMillis();
151151

152152
private MasterCleaner() {

test/com/sun/jna/CallbacksTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public static abstract class Condition<T> {
8888

8989
abstract boolean evaluate(T t);
9090
}
91-
protected static void waitForGc(Condition<?> condition) throws Exception {
91+
public static void waitForGc(Condition<?> condition) throws Exception {
9292
for (int i = 0; i < 2 * Cleaner.MASTER_CLEANUP_INTERVAL_MS / 10 + 5 && condition.evaluate(); ++i) {
9393
synchronized (CallbacksTest.class) { // cleanups happen in a different thread, make sure we see it here
9494
System.gc();
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package com.sun.jna;
2+
3+
import com.sun.jna.internal.MasterAccessor;
4+
import junit.framework.TestCase;
5+
6+
import java.util.function.Supplier;
7+
8+
public class MasterCleanerTest extends TestCase {
9+
private CallbacksTest.TestLibrary lib;
10+
11+
public static boolean waitFor(Supplier<Boolean> cond, long maxWaitMs) {
12+
long start = System.currentTimeMillis();
13+
while (System.currentTimeMillis() <= start + maxWaitMs && !cond.get()) {
14+
try {
15+
Thread.sleep(10);
16+
} catch (InterruptedException ignore) {}
17+
}
18+
return cond.get();
19+
}
20+
21+
@Override
22+
protected void setUp() {
23+
lib = Native.load("testlib", CallbacksTest.TestLibrary.class);
24+
}
25+
26+
@Override
27+
protected void tearDown() {
28+
lib = null;
29+
}
30+
31+
public void testGCCallbackOnFinalize() throws Exception {
32+
final boolean[] latch = { false };
33+
Thread thread = new Thread(() -> {
34+
CallbacksTest.TestLibrary.VoidCallback cb = new CallbacksTest.TestLibrary.VoidCallback() {
35+
@Override
36+
public void callback() {
37+
latch[0] = true;
38+
}
39+
};
40+
synchronized (latch) {
41+
lib.callVoidCallback(cb);
42+
latch.notifyAll();
43+
try {
44+
latch.wait();
45+
} catch (InterruptedException ignore) {}
46+
}
47+
});
48+
thread.start();
49+
50+
CallbackReference ref;
51+
synchronized (latch) {
52+
if (!latch[0]) {
53+
latch.wait();
54+
}
55+
assertTrue(latch[0]);
56+
// Assert thread is running and has registered a callback and master is running too
57+
assertTrue(thread.isAlive());
58+
assertTrue(MasterAccessor.masterIsRunning());
59+
assertFalse(MasterAccessor.getCleanerImpls().isEmpty());
60+
assertEquals(1, CallbackReference.callbackMap.size());
61+
ref = CallbackReference.callbackMap.values().iterator().next();
62+
CallbacksTest.waitForGc(new CallbacksTest.Condition<CallbackReference>(ref) {
63+
public boolean evaluate(CallbackReference _ref) {
64+
return _ref.get() != null || CallbackReference.callbackMap.containsValue(_ref);
65+
}
66+
});
67+
assertNotNull("Callback GC'd prematurely", ref.get());
68+
assertTrue("Callback no longer in map", CallbackReference.callbackMap.containsValue(ref));
69+
latch.notifyAll();
70+
}
71+
thread.join();
72+
73+
// thread is no longer running, dummy is collectable, master is still running
74+
final Pointer cbstruct = ref.cbstruct;
75+
assertTrue(MasterAccessor.masterIsRunning());
76+
CallbacksTest.waitForGc(new CallbacksTest.Condition<CallbackReference>(ref) {
77+
public boolean evaluate(CallbackReference _ref) {
78+
return _ref.get() != null || CallbackReference.callbackMap.containsValue(_ref);
79+
}
80+
});
81+
assertNull("Callback not GC'd", ref.get());
82+
assertFalse("Callback still in map", CallbackReference.callbackMap.containsValue(ref));
83+
84+
assertTrue("CleanerImpl still exists",
85+
waitFor(() -> MasterAccessor.getCleanerImpls().isEmpty(), 60_000)); // 1 minute
86+
87+
thread = null;
88+
// thread is collectable -> wait until master terminates
89+
assertTrue("Master still running",
90+
waitFor(() -> !MasterAccessor.masterIsRunning(), 60_000)); // 1 minute
91+
}
92+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.sun.jna.internal;
2+
3+
import java.util.Map;
4+
5+
public class MasterAccessor {
6+
public static synchronized boolean masterIsRunning() { // synchronized is for memory synch not mutex
7+
return Cleaner.MasterCleaner.INSTANCE != null;
8+
}
9+
10+
public static synchronized Map<?,Boolean> getCleanerImpls() { // synchronized is for memory synch not mutex
11+
Cleaner.MasterCleaner mc = Cleaner.MasterCleaner.INSTANCE;
12+
return mc == null ? null : mc.cleanerImpls;
13+
}
14+
}

0 commit comments

Comments
 (0)