Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide way to start the client/server as daemons #1391

Merged
merged 1 commit into from
Feb 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
}
}
Loading