Skip to content

Commit 9f53da4

Browse files
authored
Merge pull request #1391 from TooTallNate/daemon
2 parents de7b8b2 + c717bc7 commit 9f53da4

File tree

5 files changed

+131
-2
lines changed

5 files changed

+131
-2
lines changed

src/main/java/org/java_websocket/AbstractWebSocket.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,13 @@ public abstract class AbstractWebSocket extends WebSocketAdapter {
9090
*/
9191
private boolean websocketRunning = false;
9292

93+
/**
94+
* Attribute to start internal threads as daemon
95+
*
96+
* @since 1.5.6
97+
*/
98+
private boolean daemon = false;
99+
93100
/**
94101
* Attribute to sync on
95102
*/
@@ -182,7 +189,7 @@ protected void startConnectionLostTimer() {
182189
private void restartConnectionLostTimer() {
183190
cancelConnectionLostTimer();
184191
connectionLostCheckerService = Executors
185-
.newSingleThreadScheduledExecutor(new NamedThreadFactory("connectionLostChecker"));
192+
.newSingleThreadScheduledExecutor(new NamedThreadFactory("connectionLostChecker", daemon));
186193
Runnable connectionLostChecker = new Runnable() {
187194

188195
/**
@@ -308,4 +315,25 @@ public void setReuseAddr(boolean reuseAddr) {
308315
this.reuseAddr = reuseAddr;
309316
}
310317

318+
319+
/**
320+
* Getter for daemon
321+
*
322+
* @return whether internal threads are spawned in daemon mode
323+
* @since 1.5.6
324+
*/
325+
public boolean isDaemon() {
326+
return daemon;
327+
}
328+
329+
/**
330+
* Setter for daemon
331+
* <p>
332+
* Controls whether or not internal threads are spawned in daemon mode
333+
*
334+
* @since 1.5.6
335+
*/
336+
public void setDaemon(boolean daemon) {
337+
this.daemon = daemon;
338+
}
311339
}

src/main/java/org/java_websocket/client/WebSocketClient.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,7 @@ public void connect() {
373373
throw new IllegalStateException("WebSocketClient objects are not reuseable");
374374
}
375375
connectReadThread = new Thread(this);
376+
connectReadThread.setDaemon(isDaemon());
376377
connectReadThread.setName("WebSocketConnectReadThread-" + connectReadThread.getId());
377378
connectReadThread.start();
378379
}
@@ -515,6 +516,7 @@ public void run() {
515516
}
516517
}
517518
writeThread = new Thread(new WebsocketWriteThread(this));
519+
writeThread.setDaemon(isDaemon());
518520
writeThread.start();
519521

520522
byte[] rawbuffer = new byte[WebSocketImpl.RCVBUF];

src/main/java/org/java_websocket/server/WebSocketServer.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,9 @@ public void start() {
245245
if (selectorthread != null) {
246246
throw new IllegalStateException(getClass().getName() + " can only be started once.");
247247
}
248-
new Thread(this).start();
248+
Thread t = new Thread(this);
249+
t.setDaemon(isDaemon());
250+
t.start();
249251
}
250252

251253
public void stop(int timeout) throws InterruptedException {
@@ -326,6 +328,20 @@ public int getPort() {
326328
return port;
327329
}
328330

331+
@Override
332+
public void setDaemon(boolean daemon) {
333+
// pass it to the AbstractWebSocket too, to use it on the connectionLostChecker thread factory
334+
super.setDaemon(daemon);
335+
// we need to apply this to the decoders as well since they were created during the constructor
336+
for (WebSocketWorker w : decoders) {
337+
if (w.isAlive()) {
338+
throw new IllegalStateException("Cannot call setDaemon after server is already started!");
339+
} else {
340+
w.setDaemon(daemon);
341+
}
342+
}
343+
}
344+
329345
/**
330346
* Get the list of active drafts
331347
*

src/main/java/org/java_websocket/util/NamedThreadFactory.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,22 @@ public class NamedThreadFactory implements ThreadFactory {
3434
private final ThreadFactory defaultThreadFactory = Executors.defaultThreadFactory();
3535
private final AtomicInteger threadNumber = new AtomicInteger(1);
3636
private final String threadPrefix;
37+
private final boolean daemon;
3738

3839
public NamedThreadFactory(String threadPrefix) {
3940
this.threadPrefix = threadPrefix;
41+
this.daemon = false;
42+
}
43+
44+
public NamedThreadFactory(String threadPrefix, boolean daemon) {
45+
this.threadPrefix = threadPrefix;
46+
this.daemon = daemon;
4047
}
4148

4249
@Override
4350
public Thread newThread(Runnable runnable) {
4451
Thread thread = defaultThreadFactory.newThread(runnable);
52+
thread.setDaemon(daemon);
4553
thread.setName(threadPrefix + "-" + threadNumber);
4654
return thread;
4755
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package org.java_websocket.server;
2+
3+
import java.io.IOException;
4+
import java.net.*;
5+
import java.util.Set;
6+
import java.util.concurrent.CountDownLatch;
7+
import org.java_websocket.WebSocket;
8+
import org.java_websocket.handshake.*;
9+
import org.java_websocket.client.*;
10+
import org.java_websocket.server.WebSocketServer;
11+
import org.java_websocket.util.SocketUtil;
12+
import org.junit.Test;
13+
import static org.junit.Assert.assertTrue;
14+
15+
public class DaemonThreadTest {
16+
17+
@Test(timeout = 1000)
18+
public void test_AllCreatedThreadsAreDaemon() throws Throwable {
19+
20+
Set<Thread> threadSet1 = Thread.getAllStackTraces().keySet();
21+
final CountDownLatch ready = new CountDownLatch(1);
22+
23+
WebSocketServer server = new WebSocketServer(new InetSocketAddress(SocketUtil.getAvailablePort())) {
24+
@Override
25+
public void onOpen(WebSocket conn, ClientHandshake handshake) {}
26+
@Override
27+
public void onClose(WebSocket conn, int code, String reason, boolean remote) {}
28+
@Override
29+
public void onMessage(WebSocket conn, String message) {}
30+
@Override
31+
public void onError(WebSocket conn, Exception ex) {}
32+
@Override
33+
public void onStart() {}
34+
};
35+
server.setDaemon(true);
36+
server.setDaemon(false);
37+
server.setDaemon(true);
38+
server.start();
39+
40+
WebSocketClient client = new WebSocketClient(URI.create("ws://localhost:" + server.getPort())) {
41+
@Override
42+
public void onOpen(ServerHandshake handshake) {
43+
ready.countDown();
44+
}
45+
@Override
46+
public void onClose(int code, String reason, boolean remote) {}
47+
@Override
48+
public void onMessage(String message) {}
49+
@Override
50+
public void onError(Exception ex) {}
51+
};
52+
client.setDaemon(false);
53+
client.setDaemon(true);
54+
client.connect();
55+
56+
ready.await();
57+
Set<Thread> threadSet2 = Thread.getAllStackTraces().keySet();
58+
threadSet2.removeAll(threadSet1);
59+
60+
assertTrue("new threads created (no new threads indicates issue in test)", !threadSet2.isEmpty());
61+
62+
for (Thread t : threadSet2)
63+
assertTrue(t.getName(), t.isDaemon());
64+
65+
boolean exception = false;
66+
try {
67+
server.setDaemon(false);
68+
} catch(IllegalStateException e) {
69+
exception = true;
70+
}
71+
assertTrue("exception was thrown when calling setDaemon on a running server", exception);
72+
73+
server.stop();
74+
}
75+
}

0 commit comments

Comments
 (0)