-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Description
Describe the bug
Hello! I'm not a Java expert, but was happy to find this easy websocket implementation.
I've run into a race condition with closing a server, and immediately opening a new server on the same port.
This came up as part of my unit tests. Each test passes in isolation, but when they run quickly one after another, well, they started to throw java.net.BindException: Address already in use
exceptions.
I've built a really great repro case for you! I was really embarrassed to add a Thread.sleep()
call after my server.close()
call, so I created a repro.
The following code will run just fine. This is the naiive case of starting a server, closing it, and starting another server. The issue comes up in the particular way that my client is spamming the server to create new connections.
import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;
import org.junit.Test;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
public class WebsocketServerTest {
@Test
public void QuickStopTest() throws IOException, InterruptedException, URISyntaxException {
int port = 8765;
class SimpleServer extends WebSocketServer {
public SimpleServer(InetSocketAddress address) {
super(address);
}
@Override
public void onOpen(WebSocket conn, ClientHandshake handshake) {
conn.send("Welcome to the server!"); //This method sends a message to the new client
broadcast( "new connection: " + handshake.getResourceDescriptor() ); //This method sends a message to all clients connected
System.out.println("new connection to " + conn.getRemoteSocketAddress());
}
@Override
public void onClose(WebSocket conn, int code, String reason, boolean remote) {
System.out.println("closed " + conn.getRemoteSocketAddress() + " with exit code " + code + " additional info: " + reason);
}
@Override
public void onMessage(WebSocket conn, String message) {
System.out.println("received message from " + conn.getRemoteSocketAddress() + ": " + message);
}
@Override
public void onMessage( WebSocket conn, ByteBuffer message ) {
System.out.println("received ByteBuffer from " + conn.getRemoteSocketAddress());
}
@Override
public void onError(WebSocket conn, Exception ex) {
System.err.println("an error occured on connection " + conn.getRemoteSocketAddress() + ":" + ex);
}
@Override
public void onStart() {
System.out.println("server started successfully");
}
}
SimpleServer serverA = new SimpleServer(new InetSocketAddress("localhost", port));
SimpleServer serverB = new SimpleServer(new InetSocketAddress("localhost", port));
serverA.start();
Thread.sleep(3000);
serverA.stop();
serverB.start();
}
}
So that code passes.
BUT. When I have a server spamming connections, the code above will sometimes pass, sometimes fail with my "Address already in use error" and sometimes fail with a ClosedSelectorException
.
Here's the code which spams the connection. I did my best to help here, but I had to pull the code out of a Python script which was running, I couldn't repro with a java client.
(use python3 and install the websockets
module)
import asyncio
import websockets
# requirements.txt:
# websockets==6.0.0
async def websocket_loop():
"""
Processes messages from self.queue until mitmproxy shuts us down.
"""
while True:
try:
async with websockets.connect('ws://localhost:8765', max_size = None) as websocket:
while True:
# Make sure connection is still live.
await websocket.ping()
except websockets.exceptions.ConnectionClosed:
# disconnected from server
pass
except BrokenPipeError:
# Connect failed
pass
except IOError:
# disconnected from server mis-transfer
pass
except:
print("[mitmproxy-node plugin] Unexpected error:", sys.exc_info())
traceback.print_exc(file=sys.stdout)
worker_event_loop = asyncio.new_event_loop()
worker_event_loop.run_until_complete(websocket_loop())
Expected behavior
Well, my unit tests should be able to start servers on the same port as a previously stopped server without adding a 500ms sleep in between.
Debug log
The the debug log (set the log level to TRACE
) to help explain your problem.
^I could do this if you walk me through how.
Environment(please complete the following information):
- Version used: 2.0-m02
- Java version: 1.8
- Operating System and version: MacOS 10.14.4 (18E226)
- Endpoint Name and version: N/A (self hosted in repro case)
- Link to your project: coming soon...