Skip to content

Commit 71260b8

Browse files
replace Cleaner API with a local Thread
Cleaner is Java 9+, so it cannot be used on JRuby 9 because that is meant to run on Java 8 and up. The disadvantage wrt the Cleaner API is that this will leak a Thread if `terminate()` is never called. At least it's a Daemon Thread so it doesn't keep the JVM from exiting.
1 parent 3b0c8d1 commit 71260b8

File tree

1 file changed

+48
-6
lines changed

1 file changed

+48
-6
lines changed

core/src/main/java/org/jruby/embed/internal/ThreadLocalContext.java

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@
2929
*/
3030
package org.jruby.embed.internal;
3131

32-
import java.lang.ref.Cleaner;
33-
import java.lang.ref.Cleaner.Cleanable;
32+
import java.lang.ref.PhantomReference;
33+
import java.lang.ref.Reference;
34+
import java.lang.ref.ReferenceQueue;
3435
import java.util.concurrent.ConcurrentHashMap;
3536
import java.util.concurrent.atomic.AtomicReference;
3637
import java.util.function.Supplier;
@@ -43,8 +44,8 @@
4344
*/
4445
class ThreadLocalContext {
4546
private final ConcurrentHashMap<LocalContextCleaningAction, Object> contextRefs = new ConcurrentHashMap<>();
46-
private final Cleaner cleaner = Cleaner.create();
4747
private final Supplier<LocalContext> localContextFactory;
48+
private final Cleaner cleaner = new Cleaner();
4849

4950
public ThreadLocalContext(final Supplier<LocalContext> localContextFactory) {
5051
this.localContextFactory = localContextFactory;
@@ -61,13 +62,13 @@ protected AtomicReference<LocalContextCleaningAction> initialValue() {
6162
// GC'd (i.e. when this class gets GC'd because terminate() was never called).
6263
// see ThreadLocal JavaDoc: ref will stay reachable "as long as the thread is
6364
// alive and the ThreadLocal instance is accessible"
64-
final Cleanable cleanable = cleaner.register(ref, ctx);
65+
ctx.register(ref, cleaner);
6566
if (contextHolder == null)
6667
// boundary case if we're already terminating: clean up immediately, because
6768
// there is no more cleanup thread to do that later
6869
// the returned context will be null, but that's to be expected when operating
6970
// on an object that has been terminated
70-
cleanable.clean();
71+
ctx.run();
7172
return ref;
7273
}
7374
};
@@ -77,6 +78,7 @@ public LocalContext get() {
7778
}
7879

7980
public void terminate() {
81+
cleaner.interrupt();
8082
contextHolder = null;
8183
for (final LocalContextCleaningAction ref : contextRefs.keySet())
8284
ref.run();
@@ -88,11 +90,14 @@ public void terminate() {
8890
* everything that is GC-reachable from them will stay reachable until the
8991
* cleaning action has been run.
9092
*/
91-
private static class LocalContextCleaningAction extends AtomicReference<LocalContext> implements Runnable {
93+
static class LocalContextCleaningAction extends AtomicReference<LocalContext> implements Runnable {
9294
private static final long serialVersionUID = 1L;
9395

9496
private final ConcurrentHashMap<LocalContextCleaningAction, Object> contextRefs;
9597

98+
@SuppressWarnings("unused") // only used to make sure the PhantomReference doesn't get GC'd
99+
private CleanerReference phantomReference;
100+
96101
private LocalContextCleaningAction(final ConcurrentHashMap<LocalContextCleaningAction, Object> contextRefs,
97102
final LocalContext context) {
98103
super(context);
@@ -111,5 +116,42 @@ public void run() {
111116
lc.remove();
112117
contextRefs.remove(this);
113118
}
119+
120+
private void register(final AtomicReference<LocalContextCleaningAction> ref, final Cleaner cleaner) {
121+
phantomReference = new CleanerReference(ref, cleaner.q, this);
122+
}
123+
}
124+
125+
private static class CleanerReference extends PhantomReference<AtomicReference<LocalContextCleaningAction>> {
126+
private Runnable cleanup;
127+
128+
public CleanerReference(final AtomicReference<LocalContextCleaningAction> referent,
129+
final ReferenceQueue<AtomicReference<LocalContextCleaningAction>> q, final Runnable cleanup) {
130+
super(referent, q);
131+
this.cleanup = cleanup;
132+
}
133+
}
134+
135+
private static class Cleaner extends Thread {
136+
private final ReferenceQueue<AtomicReference<LocalContextCleaningAction>> q = new ReferenceQueue<>();
137+
138+
public Cleaner() {
139+
setName("JRuby-ThreadLocalContext-Cleaner-" + getId());
140+
setDaemon(true);
141+
start();
142+
}
143+
144+
@Override
145+
public void run() {
146+
while (!interrupted()) {
147+
final Reference<?> cleanable;
148+
try {
149+
cleanable = q.remove();
150+
} catch (InterruptedException e) {
151+
break;
152+
}
153+
((CleanerReference) cleanable).cleanup.run();
154+
}
155+
}
114156
}
115157
}

0 commit comments

Comments
 (0)