Skip to content

Commit

Permalink
Provide way to start the client/server as daemons
Browse files Browse the repository at this point in the history
  • Loading branch information
newk5 authored and PhilipRoman committed Feb 3, 2024
1 parent de7b8b2 commit 4b743df
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 2 deletions.
30 changes: 29 additions & 1 deletion src/main/java/org/java_websocket/AbstractWebSocket.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ public abstract class AbstractWebSocket extends WebSocketAdapter {
*/
private boolean websocketRunning = false;

/**
* Attribute to start internal threads as daemon
*
* @since 1.5.6
*/
private boolean daemon = false;

/**
* Attribute to sync on
*/
Expand Down Expand Up @@ -182,7 +189,7 @@ protected void startConnectionLostTimer() {
private void restartConnectionLostTimer() {
cancelConnectionLostTimer();
connectionLostCheckerService = Executors
.newSingleThreadScheduledExecutor(new NamedThreadFactory("connectionLostChecker"));
.newSingleThreadScheduledExecutor(new NamedThreadFactory("connectionLostChecker", daemon));
Runnable connectionLostChecker = new Runnable() {

/**
Expand Down Expand Up @@ -308,4 +315,25 @@ public void setReuseAddr(boolean reuseAddr) {
this.reuseAddr = reuseAddr;
}


/**
* Getter for daemon
*
* @return whether internal threads are spawned in daemon mode
* @since 1.5.6
*/
public boolean isDaemon() {
return daemon;
}

/**
* Setter for daemon
* <p>
* Controls whether or not internal threads are spawned in daemon mode
*
* @since 1.5.6
*/
public void setDaemon(boolean daemon) {
this.daemon = daemon;
}
}
2 changes: 2 additions & 0 deletions src/main/java/org/java_websocket/client/WebSocketClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ public void connect() {
throw new IllegalStateException("WebSocketClient objects are not reuseable");
}
connectReadThread = new Thread(this);
connectReadThread.setDaemon(isDaemon());
connectReadThread.setName("WebSocketConnectReadThread-" + connectReadThread.getId());
connectReadThread.start();
}
Expand Down Expand Up @@ -515,6 +516,7 @@ public void run() {
}
}
writeThread = new Thread(new WebsocketWriteThread(this));
writeThread.setDaemon(isDaemon());
writeThread.start();

byte[] rawbuffer = new byte[WebSocketImpl.RCVBUF];
Expand Down
18 changes: 17 additions & 1 deletion src/main/java/org/java_websocket/server/WebSocketServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,9 @@ public void start() {
if (selectorthread != null) {
throw new IllegalStateException(getClass().getName() + " can only be started once.");
}
new Thread(this).start();
Thread t = new Thread(this);
t.setDaemon(isDaemon());
t.start();
}

public void stop(int timeout) throws InterruptedException {
Expand Down Expand Up @@ -326,6 +328,20 @@ public int getPort() {
return port;
}

@Override
public void setDaemon(boolean daemon) {
// pass it to the AbstractWebSocket too, to use it on the connectionLostChecker thread factory
super.setDaemon(daemon);
// we need to apply this to the decoders as well since they were created during the constructor
for (WebSocketWorker w : decoders) {
if (w.isAlive()) {
throw new IllegalStateException("Cannot call setDaemon after server is already started!");
} else
w.setDaemon(daemon);
}
}
}

/**
* Get the list of active drafts
*
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/org/java_websocket/util/NamedThreadFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,22 @@ public class NamedThreadFactory implements ThreadFactory {
private final ThreadFactory defaultThreadFactory = Executors.defaultThreadFactory();
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String threadPrefix;
private final boolean daemon;

public NamedThreadFactory(String threadPrefix) {
this.threadPrefix = threadPrefix;
this.daemon = false;
}

public NamedThreadFactory(String threadPrefix, boolean daemon) {
this.threadPrefix = threadPrefix;
this.daemon = daemon;
}

@Override
public Thread newThread(Runnable runnable) {
Thread thread = defaultThreadFactory.newThread(runnable);
thread.setDaemon(daemon);
thread.setName(threadPrefix + "-" + threadNumber);
return thread;
}
Expand Down
75 changes: 75 additions & 0 deletions src/test/java/org/java_websocket/server/DaemonThreadTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package org.java_websocket.server;

import java.io.IOException;
import java.net.*;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import org.java_websocket.WebSocket;
import org.java_websocket.handshake.*;
import org.java_websocket.client.*;
import org.java_websocket.server.WebSocketServer;
import org.java_websocket.util.SocketUtil;
import org.junit.Test;
import static org.junit.Assert.assertTrue;

public class DaemonThreadTest {

@Test(timeout = 1000)
public void test_AllCreatedThreadsAreDaemon() throws Throwable {

Set<Thread> threadSet1 = Thread.getAllStackTraces().keySet();
final CountDownLatch ready = new CountDownLatch(1);

WebSocketServer server = new WebSocketServer(new InetSocketAddress(SocketUtil.getAvailablePort())) {
@Override
public void onOpen(WebSocket conn, ClientHandshake handshake) {}
@Override
public void onClose(WebSocket conn, int code, String reason, boolean remote) {}
@Override
public void onMessage(WebSocket conn, String message) {}
@Override
public void onError(WebSocket conn, Exception ex) {}
@Override
public void onStart() {}
};
server.setDaemon(true);
server.setDaemon(false);
server.setDaemon(true);
server.start();

WebSocketClient client = new WebSocketClient(URI.create("ws://localhost:" + server.getPort())) {
@Override
public void onOpen(ServerHandshake handshake) {
ready.countDown();
}
@Override
public void onClose(int code, String reason, boolean remote) {}
@Override
public void onMessage(String message) {}
@Override
public void onError(Exception ex) {}
};
client.setDaemon(false);
client.setDaemon(true);
client.connect();

ready.await();
Set<Thread> threadSet2 = Thread.getAllStackTraces().keySet();
threadSet2.removeAll(threadSet1);

assertTrue("new threads created (no new threads indicates issue in test)", !threadSet2.isEmpty());

for (Thread t : threadSet2)
assertTrue(t.getName(), t.isDaemon());

boolean exception = false;
try {
server.setDaemon(false);
} catch(IllegalStateException e) {
exception = true;
}
assertTrue("exception was thrown when calling setDaemon on a running server", exception);

server.stop();
}
}

0 comments on commit 4b743df

Please sign in to comment.