Skip to content

Commit c8c2d27

Browse files
Shutdown CleanerThread once the last cleanable is removed
1 parent 2372067 commit c8c2d27

File tree

2 files changed

+73
-48
lines changed

2 files changed

+73
-48
lines changed

CHANGES.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Features
1212
* [#1548](https://github.com/java-native-access/jna/pull/1548): Make interface `c.s.j.p.mac.XAttr public` - [@matthiasblaesing](https://github.com/matthiasblaesing).
1313
* [#1551](https://github.com/java-native-access/jna/pull/1551): Add `c.s.j.p.bsd.ExtAttr` and `c.s.j.p.bsd.ExtAttrUtil` to wrap BSD [<sys/extattr.h>](https://man.freebsd.org/cgi/man.cgi?query=extattr&sektion=2) system calls. [@rednoah](https://github.com/rednoah).
1414
* [#1517](https://github.com/java-native-access/jna/pull/1517): Add missing `O_*` (e.g. `O_APPEND`, `O_SYNC`, `O_DIRECT`, ...) to `c.s.j.p.linux.Fcntl` - [@matthiasblaesing](https://github.com/matthiasblaesing).
15+
* [#1521](https://github.com/java-native-access/jna/issues/1521): Shutdown CleanerThread once the last cleanable is removed - [@matthiasblaesing](https://github.com/matthiasblaesing).
1516

1617
Bug Fixes
1718
---------

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

+72-48
Original file line numberDiff line numberDiff line change
@@ -45,35 +45,11 @@ public static Cleaner getCleaner() {
4545
}
4646

4747
private final ReferenceQueue<Object> referenceQueue;
48-
private final Thread cleanerThread;
48+
private Thread cleanerThread;
4949
private CleanerRef firstCleanable;
5050

5151
private Cleaner() {
5252
referenceQueue = new ReferenceQueue<Object>();
53-
cleanerThread = new Thread() {
54-
@Override
55-
public void run() {
56-
while(true) {
57-
try {
58-
Reference<? extends Object> ref = referenceQueue.remove();
59-
if(ref instanceof CleanerRef) {
60-
((CleanerRef) ref).clean();
61-
}
62-
} catch (InterruptedException ex) {
63-
// Can be raised on shutdown. If anyone else messes with
64-
// our reference queue, well, there is no way to separate
65-
// the two cases.
66-
// https://groups.google.com/g/jna-users/c/j0fw96PlOpM/m/vbwNIb2pBQAJ
67-
break;
68-
} catch (Exception ex) {
69-
Logger.getLogger(Cleaner.class.getName()).log(Level.SEVERE, null, ex);
70-
}
71-
}
72-
}
73-
};
74-
cleanerThread.setName("JNA Cleaner");
75-
cleanerThread.setDaemon(true);
76-
cleanerThread.start();
7753
}
7854

7955
public synchronized Cleanable register(Object obj, Runnable cleanupTask) {
@@ -83,34 +59,43 @@ public synchronized Cleanable register(Object obj, Runnable cleanupTask) {
8359
}
8460

8561
private synchronized CleanerRef add(CleanerRef ref) {
86-
if(firstCleanable == null) {
87-
firstCleanable = ref;
88-
} else {
89-
ref.setNext(firstCleanable);
90-
firstCleanable.setPrevious(ref);
91-
firstCleanable = ref;
62+
synchronized (referenceQueue) {
63+
if (firstCleanable == null) {
64+
firstCleanable = ref;
65+
} else {
66+
ref.setNext(firstCleanable);
67+
firstCleanable.setPrevious(ref);
68+
firstCleanable = ref;
69+
}
70+
if (cleanerThread == null) {
71+
Logger.getLogger(Cleaner.class.getName()).log(Level.FINE, "Starting CleanerThread");
72+
cleanerThread = new CleanerThread();
73+
cleanerThread.start();
74+
}
75+
return ref;
9276
}
93-
return ref;
9477
}
9578

9679
private synchronized boolean remove(CleanerRef ref) {
97-
boolean inChain = false;
98-
if(ref == firstCleanable) {
99-
firstCleanable = ref.getNext();
100-
inChain = true;
101-
}
102-
if(ref.getPrevious() != null) {
103-
ref.getPrevious().setNext(ref.getNext());
104-
}
105-
if(ref.getNext() != null) {
106-
ref.getNext().setPrevious(ref.getPrevious());
107-
}
108-
if(ref.getPrevious() != null || ref.getNext() != null) {
109-
inChain = true;
80+
synchronized (referenceQueue) {
81+
boolean inChain = false;
82+
if (ref == firstCleanable) {
83+
firstCleanable = ref.getNext();
84+
inChain = true;
85+
}
86+
if (ref.getPrevious() != null) {
87+
ref.getPrevious().setNext(ref.getNext());
88+
}
89+
if (ref.getNext() != null) {
90+
ref.getNext().setPrevious(ref.getPrevious());
91+
}
92+
if (ref.getPrevious() != null || ref.getNext() != null) {
93+
inChain = true;
94+
}
95+
ref.setNext(null);
96+
ref.setPrevious(null);
97+
return inChain;
11098
}
111-
ref.setNext(null);
112-
ref.setPrevious(null);
113-
return inChain;
11499
}
115100

116101
private static class CleanerRef extends PhantomReference<Object> implements Cleanable {
@@ -125,6 +110,7 @@ public CleanerRef(Cleaner cleaner, Object referent, ReferenceQueue<? super Objec
125110
this.cleanupTask = cleanupTask;
126111
}
127112

113+
@Override
128114
public void clean() {
129115
if(cleaner.remove(this)) {
130116
cleanupTask.run();
@@ -151,4 +137,42 @@ void setNext(CleanerRef next) {
151137
public static interface Cleanable {
152138
public void clean();
153139
}
140+
141+
private class CleanerThread extends Thread {
142+
143+
private static final long CLEANER_LINGER_TIME = 30000;
144+
145+
public CleanerThread() {
146+
super("JNA Cleaner");
147+
setDaemon(true);
148+
}
149+
150+
@Override
151+
public void run() {
152+
while (true) {
153+
try {
154+
Reference<? extends Object> ref = referenceQueue.remove(CLEANER_LINGER_TIME);
155+
if (ref instanceof CleanerRef) {
156+
((CleanerRef) ref).clean();
157+
} else if (ref == null) {
158+
synchronized (referenceQueue) {
159+
if (firstCleanable == null) {
160+
cleanerThread = null;
161+
Logger.getLogger(Cleaner.class.getName()).log(Level.FINE, "Shutting down CleanerThread");
162+
break;
163+
}
164+
}
165+
}
166+
} catch (InterruptedException ex) {
167+
// Can be raised on shutdown. If anyone else messes with
168+
// our reference queue, well, there is no way to separate
169+
// the two cases.
170+
// https://groups.google.com/g/jna-users/c/j0fw96PlOpM/m/vbwNIb2pBQAJ
171+
break;
172+
} catch (Exception ex) {
173+
Logger.getLogger(Cleaner.class.getName()).log(Level.SEVERE, null, ex);
174+
}
175+
}
176+
}
177+
}
154178
}

0 commit comments

Comments
 (0)