diff --git a/.classpath b/.classpath
index 4c11341e1..8485df73d 100644
--- a/.classpath
+++ b/.classpath
@@ -20,7 +20,7 @@
-
-
+
+
diff --git a/build.xml b/build.xml
index f0084fa30..0733bcbd5 100644
--- a/build.xml
+++ b/build.xml
@@ -6,7 +6,7 @@
-
+
@@ -57,7 +57,7 @@
-
+
@@ -72,6 +72,12 @@
dest="${lib.dir}/somns-deps-dev.jar" />
+
+
+
+
connection;
-
- public BinaryWebSocketHandler(final InetSocketAddress address) {
- super(address, NUM_THREADS);
- connection = new CompletableFuture<>();
- }
-
- @Override
- public void onClose(final WebSocket conn, final int arg1, final String arg2, final boolean arg3) {
- // no-op
- }
-
- @Override
- public void onError(final WebSocket conn, final Exception ex) {
- WebDebugger.log("error:");
- ex.printStackTrace();
- }
-
- @Override
- public void onMessage(final WebSocket conn, final String msg) {
- // no-op
- }
-
- @Override
- public void onOpen(final WebSocket conn, final ClientHandshake handshake) {
- connection.complete(conn);
- }
-
- public CompletableFuture getConnection() {
- return connection;
- }
-}
diff --git a/src/tools/debugger/FrontendConnector.java b/src/tools/debugger/FrontendConnector.java
index ce33b06b5..b1c3dedec 100644
--- a/src/tools/debugger/FrontendConnector.java
+++ b/src/tools/debugger/FrontendConnector.java
@@ -1,6 +1,7 @@
package tools.debugger;
import java.io.IOException;
+import java.net.BindException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@@ -9,7 +10,7 @@
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
+import java.util.function.Function;
import org.java_websocket.WebSocket;
@@ -27,6 +28,8 @@
import tools.Tagging;
import tools.TraceData;
import tools.concurrency.ActorExecutionTrace;
+import tools.debugger.WebSocketHandler.MessageHandler;
+import tools.debugger.WebSocketHandler.TraceHandler;
import tools.debugger.entities.ActivityType;
import tools.debugger.entities.BreakpointType;
import tools.debugger.entities.EntityType;
@@ -64,8 +67,8 @@ public class FrontendConnector {
/**
* Receives requests from the client.
*/
- private final WebSocketHandler receiver;
- private final BinaryWebSocketHandler binaryHandler;
+ private final MessageHandler receiver;
+ private final TraceHandler binaryHandler;
/**
* Sends requests to the client.
@@ -80,9 +83,10 @@ public class FrontendConnector {
private CompletableFuture clientConnected;
private final Gson gson;
- private static final int MESSAGE_PORT = 7977;
- private static final int BINARY_PORT = 7978;
- private static final int DEBUGGER_PORT = 8888;
+ private static final int MESSAGE_PORT = 7977;
+ private static final int BINARY_PORT = 7978;
+ private static final int DEBUGGER_PORT = 8888;
+ private static final int EPHEMERAL_PORT = 0;
private final ArrayList notReady = new ArrayList<>(); // TODO rename: toBeSend
@@ -98,15 +102,16 @@ public FrontendConnector(final Breakpoints breakpoints,
try {
log("[DEBUGGER] Initialize HTTP and WebSocket Server for Debugger");
- receiver = initializeWebSocket(MESSAGE_PORT, clientConnected);
- log("[DEBUGGER] Started WebSocket Server");
-
- binaryHandler = new BinaryWebSocketHandler(new InetSocketAddress(BINARY_PORT));
- binaryHandler.start();
-
- contentServer = initializeHttpServer(DEBUGGER_PORT);
+ receiver = initializeWebSocket(MESSAGE_PORT, port -> new MessageHandler(port, this, gson));
+ binaryHandler = initializeWebSocket(BINARY_PORT, port -> new TraceHandler(port));
+ log("[DEBUGGER] Started WebSocket Servers");
+ log("[DEBUGGER] Message Handler: " + receiver.getPort());
+ log("[DEBUGGER] Trace Handler: " + binaryHandler.getPort());
+
+ contentServer = initializeHttpServer(DEBUGGER_PORT,
+ receiver.getPort(), binaryHandler.getPort());
log("[DEBUGGER] Started HTTP Server");
- log("[DEBUGGER] URL: http://localhost:" + DEBUGGER_PORT + "/index.html");
+ log("[DEBUGGER] URL: http://localhost:" + contentServer.getAddress().getPort() + "/index.html");
} catch (IOException e) {
log("Failed starting WebSocket and/or HTTP Server");
throw new RuntimeException(e);
@@ -119,23 +124,49 @@ public Breakpoints getBreakpoints() {
return breakpoints;
}
- private WebSocketHandler initializeWebSocket(final int port,
- final Future clientConnected) {
- InetSocketAddress address = new InetSocketAddress(port);
- WebSocketHandler server = new WebSocketHandler(address, this, gson);
+ private T tryInitializingWebSocket(final T server) throws Throwable {
server.start();
+ try {
+ server.awaitStartup();
+ } catch (ExecutionException e) {
+ throw e.getCause();
+ }
return server;
}
- private HttpServer initializeHttpServer(final int port) throws IOException {
+ private T initializeWebSocket(final int port, final Function ctor) {
+ try {
+ return tryInitializingWebSocket(ctor.apply(port));
+ } catch (BindException e) {
+ try {
+ return tryInitializingWebSocket(ctor.apply(EPHEMERAL_PORT));
+ } catch (Throwable e1) {
+ throw new RuntimeException(e);
+ }
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private HttpServer tryInitializingHttpServer(final int port,
+ final int debuggerPort, final int tracePort) throws IOException {
InetSocketAddress address = new InetSocketAddress(port);
HttpServer httpServer = HttpServer.create(address, 0);
- httpServer.createContext("/", new WebResourceHandler());
+ httpServer.createContext("/", new WebResourceHandler(debuggerPort, tracePort));
httpServer.setExecutor(null);
httpServer.start();
return httpServer;
}
+ private HttpServer initializeHttpServer(final int port,
+ final int debuggerPort, final int tracePort) throws IOException {
+ try {
+ return tryInitializingHttpServer(port, debuggerPort, tracePort);
+ } catch (BindException e) {
+ return tryInitializingHttpServer(EPHEMERAL_PORT, debuggerPort, tracePort);
+ }
+ }
+
private void ensureConnectionIsAvailable() {
assert receiver != null;
assert sender != null;
@@ -278,24 +309,25 @@ static void log(final String str) {
}
public void completeConnection(final WebSocket conn) {
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> closeAllSockets()));
+
clientConnected.complete(conn);
send(InitializationResponse.create(EntityType.values(),
ActivityType.values(), BreakpointType.values(), SteppingType.values()));
}
- public void shutdown() {
- int delaySec = 5;
- contentServer.stop(delaySec);
+ private void closeAllSockets() {
+ final int delay = 0;
+ contentServer.stop(delay);
sender.close();
if (binarySender != null) {
binarySender.close();
}
try {
- int delayMsec = 1000;
- receiver.stop(delayMsec);
+ receiver.stop(delay);
if (binarySender != null) {
- binaryHandler.stop(delayMsec);
+ binaryHandler.stop(delay);
}
} catch (InterruptedException e) { }
}
diff --git a/src/tools/debugger/WebDebugger.java b/src/tools/debugger/WebDebugger.java
index c0fdf2666..851020473 100644
--- a/src/tools/debugger/WebDebugger.java
+++ b/src/tools/debugger/WebDebugger.java
@@ -155,7 +155,7 @@ public static void log(final String str) {
@Override
protected void onDispose(final Env env) {
- connector.shutdown();
+ /* NOOP: we close sockets with a VM shutdown hook */
}
@Override
diff --git a/src/tools/debugger/WebResourceHandler.java b/src/tools/debugger/WebResourceHandler.java
index 399539199..797bbc61b 100644
--- a/src/tools/debugger/WebResourceHandler.java
+++ b/src/tools/debugger/WebResourceHandler.java
@@ -5,6 +5,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.nio.charset.Charset;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
@@ -12,6 +13,13 @@
import som.vm.NotYetImplementedException;
class WebResourceHandler implements HttpHandler {
+ private final int debuggerPort;
+ private final int tracePort;
+
+ WebResourceHandler(final int debuggerPort, final int tracePort) {
+ this.debuggerPort = debuggerPort;
+ this.tracePort = tracePort;
+ }
@Override
public void handle(final HttpExchange exchange) throws IOException {
@@ -36,10 +44,17 @@ public void handle(final HttpExchange exchange) throws IOException {
}
exchange.sendResponseHeaders(200, f.length());
copy(f, exchange.getResponseBody());
+ exchange.close();
return;
}
switch (requestedFile) {
+ case "/ports.json":
+ String jsonPorts = "{\"dbgPort\":" + debuggerPort + ",\"tracePort\":" + tracePort + "\"}";
+ exchange.sendResponseHeaders(200, jsonPorts.length());
+ exchange.getResponseBody().write(jsonPorts.getBytes(Charset.forName("UTF-8")));
+ exchange.close();
+ return;
case "/favicon.ico":
exchange.sendResponseHeaders(404, 0);
exchange.close();
diff --git a/src/tools/debugger/WebSocketHandler.java b/src/tools/debugger/WebSocketHandler.java
index 99bf50cf4..582db420f 100644
--- a/src/tools/debugger/WebSocketHandler.java
+++ b/src/tools/debugger/WebSocketHandler.java
@@ -1,6 +1,9 @@
package tools.debugger;
+import java.net.BindException;
import java.net.InetSocketAddress;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
@@ -12,17 +15,27 @@
import tools.debugger.message.Message.IncommingMessage;
-public class WebSocketHandler extends WebSocketServer {
+public abstract class WebSocketHandler extends WebSocketServer {
private static final int NUM_THREADS = 1;
- private final FrontendConnector connector;
- private final Gson gson;
+ private final CompletableFuture connectionPort;
- WebSocketHandler(final InetSocketAddress address,
- final FrontendConnector connector, final Gson gson) {
- super(address, NUM_THREADS);
- this.connector = connector;
- this.gson = gson;
+ WebSocketHandler(final int port) {
+ super(new InetSocketAddress(port), NUM_THREADS);
+ this.connectionPort = new CompletableFuture<>();
+ }
+
+ @Override
+ public void onStart() {
+ connectionPort.complete(getPort());
+ }
+
+ public int awaitStartup() throws ExecutionException {
+ while (true) {
+ try {
+ return connectionPort.get();
+ } catch (InterruptedException e) { /* Retry on interrupt. */ }
+ }
}
@Override
@@ -34,20 +47,57 @@ public void onClose(final WebSocket conn, final int code, final String reason,
WebDebugger.log("onClose: code=" + code + " " + reason);
}
- @Override
- public void onMessage(final WebSocket conn, final String message) {
- try {
- IncommingMessage respond = gson.fromJson(message, IncommingMessage.class);
- respond.process(connector, conn);
- } catch (Throwable ex) {
- VM.errorPrint("Error while processing msg:" + message);
- ex.printStackTrace();
- }
- }
-
@Override
public void onError(final WebSocket conn, final Exception ex) {
+ if (ex instanceof BindException) {
+ connectionPort.completeExceptionally(ex);
+ return;
+ }
WebDebugger.log("error:");
ex.printStackTrace();
}
+
+ public static class MessageHandler extends WebSocketHandler {
+ private final FrontendConnector connector;
+ private final Gson gson;
+
+ public MessageHandler(final int port, final FrontendConnector connector,
+ final Gson gson) {
+ super(port);
+ this.connector = connector;
+ this.gson = gson;
+ }
+
+ @Override
+ public void onMessage(final WebSocket conn, final String message) {
+ try {
+ IncommingMessage respond = gson.fromJson(message, IncommingMessage.class);
+ respond.process(connector, conn);
+ } catch (Throwable ex) {
+ VM.errorPrint("Error while processing msg:" + message);
+ ex.printStackTrace();
+ }
+ }
+ }
+
+ public static class TraceHandler extends WebSocketHandler {
+ private final CompletableFuture connection;
+
+ public TraceHandler(final int port) {
+ super(port);
+ connection = new CompletableFuture<>();
+ }
+
+ @Override
+ public void onOpen(final WebSocket conn, final ClientHandshake handshake) {
+ connection.complete(conn);
+ }
+
+ @Override
+ public void onMessage(final WebSocket conn, final String message) { }
+
+ public CompletableFuture getConnection() {
+ return connection;
+ }
+ }
}
diff --git a/tools/kompos/package.json b/tools/kompos/package.json
index b1c8ca5e9..cb65de0b0 100644
--- a/tools/kompos/package.json
+++ b/tools/kompos/package.json
@@ -46,6 +46,6 @@
"compile": "node ./node_modules/typescript/bin/tsc && npm run copylibs",
"lint": "node_modules/tslint/bin/tslint -c tslint.json --project tsconfig.json",
"watch": "node ./node_modules/typescript/bin/tsc -w",
- "test": "node ./node_modules/mocha/bin/mocha -r node-define -t 5000 -u bdd ./out/tests/"
+ "test": "node ./node_modules/mocha/bin/mocha -r node-define -t 10000 -u bdd ./out/tests/"
}
}
diff --git a/tools/kompos/src/vm-connection.ts b/tools/kompos/src/vm-connection.ts
index 8aca06077..bb75fb20d 100644
--- a/tools/kompos/src/vm-connection.ts
+++ b/tools/kompos/src/vm-connection.ts
@@ -6,8 +6,6 @@ import * as WebSocket from "ws";
import {Controller} from "./controller";
import {Activity, Message, Respond, BreakpointData} from "./messages";
-const DBG_PORT = 7977;
-const TRACE_PORT = 7978;
const LOCAL_WS_URL = "ws://localhost";
/**
@@ -35,11 +33,11 @@ export class VmConnection {
return this.socket !== null && this.socket.readyState === WebSocket.OPEN;
}
- private connectTraceDataSocket() {
+ private connectTraceDataSocket(tracePort: number) {
if (!this.useTraceData) { return; }
console.assert(this.traceDataSocket === null || this.traceDataSocket.readyState === WebSocket.CLOSED);
- this.traceDataSocket = new WebSocket(LOCAL_WS_URL + ":" + TRACE_PORT);
+ this.traceDataSocket = new WebSocket(LOCAL_WS_URL + ":" + tracePort);
( this.traceDataSocket).binaryType = "arraybuffer"; // workaround, typescript doesn't recognize this property
const controller = this.controller;
@@ -55,8 +53,15 @@ export class VmConnection {
}
public connect() {
+ $.getJSON("ports.json", data => {
+ console.log(data);
+ this.connectWebSockets(data.dbgPort, data.tracePort);
+ });
+ }
+
+ protected connectWebSockets(dbgPort: number, tracePort: number) {
console.assert(this.socket === null || this.socket.readyState === WebSocket.CLOSED);
- this.socket = new WebSocket(LOCAL_WS_URL + ":" + DBG_PORT);
+ this.socket = new WebSocket(LOCAL_WS_URL + ":" + dbgPort);
const ctrl = this.controller;
this.socket.onopen = () => {
@@ -109,7 +114,7 @@ export class VmConnection {
}
};
- this.connectTraceDataSocket();
+ this.connectTraceDataSocket(tracePort);
}
public disconnect() {
diff --git a/tools/kompos/tests/basic-protocol.ts b/tools/kompos/tests/basic-protocol.ts
index e0aa77c49..f75c263d7 100644
--- a/tools/kompos/tests/basic-protocol.ts
+++ b/tools/kompos/tests/basic-protocol.ts
@@ -60,7 +60,8 @@ describe("Basic Protocol", function() {
conn = new TestConnection();
const ctrl = new ControllerWithInitialBreakpoints([], conn);
let firstSourceCaptured = false;
- sourceP = new Promise((resolve, _reject) => {
+ sourceP = new Promise((resolve, reject) => {
+ conn.fullyConnected.catch(reject);
ctrl.onReceivedSource = (msg: SourceMessage) => {
if (firstSourceCaptured) { return; };
firstSourceCaptured = true;
@@ -255,7 +256,7 @@ describe("Basic Protocol", function() {
before("Start SOMns and Connect", () => {
conn = new TestConnection();
- ctrl = new HandleStoppedAndGetStackTrace([desc.breakpoint], conn);
+ ctrl = new HandleStoppedAndGetStackTrace([desc.breakpoint], conn, conn.fullyConnected);
});
after(closeConnectionAfterSuite);
@@ -276,7 +277,7 @@ describe("Basic Protocol", function() {
const breakpoint = createSectionBreakpointData(PING_PONG_URI, 23, 14, 3,
BT.MSG_SENDER, true);
conn = new TestConnection();
- ctrl = new HandleStoppedAndGetStackTrace([breakpoint], conn, 4);
+ ctrl = new HandleStoppedAndGetStackTrace([breakpoint], conn, conn.fullyConnected, 4);
});
after(closeConnectionAfterSuite);
diff --git a/tools/kompos/tests/csp.ts b/tools/kompos/tests/csp.ts
index 23776dce6..bfd420499 100644
--- a/tools/kompos/tests/csp.ts
+++ b/tools/kompos/tests/csp.ts
@@ -21,7 +21,7 @@ describe("Setting CSP Breakpoints", () => {
const breakpoint = createSectionBreakpointData(CSP_URI, 13, 12, 4,
BreakpointType.CHANNEL_BEFORE_RCV, true);
conn = new TestConnection(["halt"], null, CSP_FILE);
- ctrl = new HandleStoppedAndGetStackTrace([breakpoint], conn);
+ ctrl = new HandleStoppedAndGetStackTrace([breakpoint], conn, conn.fullyConnected);
});
after(closeConnectionAfterSuite);
@@ -39,7 +39,7 @@ describe("Setting CSP Breakpoints", () => {
const breakpoint = createSectionBreakpointData(CSP_URI, 13, 12, 4,
BreakpointType.CHANNEL_AFTER_SEND, true);
conn = new TestConnection(["halt"], null, CSP_FILE);
- ctrl = new HandleStoppedAndGetStackTrace([breakpoint], conn);
+ ctrl = new HandleStoppedAndGetStackTrace([breakpoint], conn, conn.fullyConnected);
});
after(closeConnectionAfterSuite);
@@ -56,7 +56,7 @@ describe("Setting CSP Breakpoints", () => {
const breakpoint = createSectionBreakpointData(CSP_URI, 12, 13, 12,
BreakpointType.CHANNEL_BEFORE_SEND, true);
conn = new TestConnection(["halt"], null, CSP_FILE);
- ctrl = new HandleStoppedAndGetStackTrace([breakpoint], conn);
+ ctrl = new HandleStoppedAndGetStackTrace([breakpoint], conn, conn.fullyConnected);
});
after(closeConnectionAfterSuite);
@@ -74,7 +74,7 @@ describe("Setting CSP Breakpoints", () => {
const breakpoint = createSectionBreakpointData(CSP_URI, 12, 13, 12,
BreakpointType.CHANNEL_AFTER_RCV, true);
conn = new TestConnection(["halt"], null, CSP_FILE);
- ctrl = new HandleStoppedAndGetStackTrace([breakpoint], conn);
+ ctrl = new HandleStoppedAndGetStackTrace([breakpoint], conn, conn.fullyConnected);
});
after(closeConnectionAfterSuite);
diff --git a/tools/kompos/tests/som-integration.ts b/tools/kompos/tests/som-integration.ts
index 55c92bb6e..95f251a36 100644
--- a/tools/kompos/tests/som-integration.ts
+++ b/tools/kompos/tests/som-integration.ts
@@ -55,7 +55,7 @@ describe("Language Debugger Integration", function() {
describe("execute `1 halt` and get suspended event", () => {
before("Start SOMns and Connect", () => {
conn = new TestConnection(["halt"]);
- ctrl = new HandleStoppedAndGetStackTrace([], conn);
+ ctrl = new HandleStoppedAndGetStackTrace([], conn, conn.fullyConnected);
});
after(closeConnectionAfterSuite);
diff --git a/tools/kompos/tests/test-setup.ts b/tools/kompos/tests/test-setup.ts
index f868ebc15..80074d481 100644
--- a/tools/kompos/tests/test-setup.ts
+++ b/tools/kompos/tests/test-setup.ts
@@ -78,24 +78,59 @@ export class TestConnection extends VmConnection {
private initConnection(): Promise {
const promise = new Promise((resolve, reject) => {
+ const msgPortRe = /.*Message Handler:\s+(\d+)/m;
+ const tracePortRe = /.*Trace Handler:\s+(\d+)/m;
this.connectionResolver = resolve;
let connecting = false;
+ let errOut = "";
+ let msgPort = 0;
+ let tracePort = 0;
+
+ this.somProc.on("exit", (code, signal) => {
+ if (code !== 0) {
+ this.somProc.stderr.on("close", () => {
+ this.somProc.on("exit", (_code, _signal) => {
+ reject(new Error("Process exited with code: " + code + " Signal: " + signal + " StdErr: " + errOut));
+ });
+ });
+ }
+ });
- if (PRINT_SOM_OUTPUT) {
- this.somProc.stderr.on("data", (data) => { console.error(data.toString()); });
- }
+ this.somProc.stderr.on("data", data => {
+ const dataStr = data.toString();
+ if (PRINT_SOM_OUTPUT) {
+ console.error(dataStr);
+ }
+ errOut += dataStr;
+ });
this.somProc.stdout.on("data", (data) => {
const dataStr = data.toString();
if (PRINT_SOM_OUTPUT) {
console.log(dataStr);
}
+
+ let m = dataStr.match(msgPortRe);
+ if (m) {
+ msgPort = parseInt(m[1]);
+ }
+ m = dataStr.match(tracePortRe);
+ if (m) {
+ tracePort = parseInt(m[1]);
+ }
+
if (dataStr.includes("Started HTTP Server") && !connecting) {
connecting = true;
- this.connect();
+ console.assert(msgPort > 0 && tracePort > 0);
+ this.connectWebSockets(msgPort, tracePort);
}
if (dataStr.includes("Failed starting WebSocket and/or HTTP Server")) {
- reject(new Error("SOMns failed to starting WebSocket and/or HTTP Server"));
+ this.somProc.stderr.on("close", () => {
+ this.somProc.on("exit", (_code, _signal) => {
+ reject(new Error("SOMns failed to starting WebSocket and/or HTTP Server. StdOut: " + dataStr + " StdErr: " + errOut));
+ });
+ });
+ this.somProc.kill();
}
});
});
@@ -146,23 +181,25 @@ export class HandleStoppedAndGetStackTrace extends ControllerWithInitialBreakpoi
public readonly stoppedActivities: Activity[];
constructor(initialBreakpoints: BreakpointData[], vmConnection: VmConnection,
- numOps: number = 1) {
+ connectionP: Promise, numOps: number = 1) {
super(initialBreakpoints, vmConnection);
this.numOps = numOps;
this.numStopped = 0;
this.stoppedActivities = [];
- this.stackP = new Promise((resolve, _reject) => {
+ this.stackP = new Promise((resolve, reject) => {
this.resolveStackP = resolve;
+ connectionP.catch(reject);
});
if (numOps > 1) {
this.resolveStackPs = [];
this.stackPs = [];
for (let i = 1; i < numOps; i += 1) {
- this.stackPs.push(new Promise((resolve, _reject) => {
+ this.stackPs.push(new Promise((resolve, reject) => {
this.resolveStackPs.push(resolve);
+ connectionP.catch(reject);
}));
}
}