Bug fix: Topology WebSocket timeout if there are no events to transmit
Apparently, if there are no topology-related events to transmit to
the client, the WebSocket will automatically timeout after several minutes.
The solution is to use the WebSocket embedded PING-PONG mechanism.
Implementation-wise, if there are no events to transmit for 30 seconds,
ONOS will transmit a PING message. If three PING messages are transmitted,
and there is no PONG, then the WebSocket is closed. The total
timeout time is approximately 30x(3+1) = 120 seconds.
Note that the PONG message is automatically transmitted by the Web browsers,
so no code change is required to the ONOS GUI.
This fixes bug ONOS-1932
Also, updated websocket-api artifact in pom.xml from 1.0 to 1.1
Change-Id: I474e04e81a6f613774bb987f17a6a0b5d3afa99d
diff --git a/src/main/java/net/onrc/onos/apps/websocket/TopologyWebSocket.java b/src/main/java/net/onrc/onos/apps/websocket/TopologyWebSocket.java
index 9d9ed6f..fe365d9 100644
--- a/src/main/java/net/onrc/onos/apps/websocket/TopologyWebSocket.java
+++ b/src/main/java/net/onrc/onos/apps/websocket/TopologyWebSocket.java
@@ -6,8 +6,10 @@
import java.io.IOException;
import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
import javax.websocket.CloseReason;
import javax.websocket.EndpointConfig;
@@ -58,6 +60,10 @@
new LinkedBlockingQueue<>();
private Session socketSession;
private boolean isOpen = false;
+ // Ping-related state
+ private static final int PING_INTERVAL_SEC = 30; // Ping every 30 secs
+ private static final int MAX_MISSING_PING = 3;
+ private int missingPing = 0; // Pings to the client without a pong
/**
* Shutdown the socket.
@@ -76,7 +82,7 @@
*/
@Override
public void topologyEvents(TopologyEvents topologyEvents) {
- // The topologyEvents object is a deep copy so we can add it as-is
+ // The topologyEvents object is immutable, so we can add it as-is
this.topologyEventsQueue.add(topologyEvents);
}
@@ -89,18 +95,39 @@
ObjectMapper mapper = new ObjectMapper();
//
- // The main loop for sending events to the clients
+ // The main loop for sending events to the clients.
+ //
+ // If there are no events, we send periodic PING messages to discover
+ // unreachable clients.
//
while (this.isOpen && (!this.isInterrupted())) {
String eventsJson = null;
try {
- TopologyEvents events = topologyEventsQueue.take();
- eventsJson = mapper.writeValueAsString(events);
- if (eventsJson != null) {
+ TopologyEvents events =
+ topologyEventsQueue.poll(PING_INTERVAL_SEC,
+ TimeUnit.SECONDS);
+ if (events != null) {
+ // Send the event
+ eventsJson = mapper.writeValueAsString(events);
socketSession.getBasicRemote().sendText(eventsJson);
+ continue;
+ }
+ // Send a PING message
+ missingPing++;
+ if (missingPing > MAX_MISSING_PING) {
+ // Timeout
+ log.debug("WebSocket session timeout");
+ shutdown();
+ } else {
+ String msg = "PING(TopologyWebsocket)";
+ ByteBuffer pingBuffer =
+ ByteBuffer.wrap(msg.getBytes(StandardCharsets.UTF_8));
+ socketSession.getBasicRemote().sendPing(pingBuffer);
}
} catch (IOException e) {
log.debug("Exception sending TopologyWebSocket events: ", e);
+ } catch (InterruptedException e) {
+ log.debug("TopologyWebSocket interrupted while waiting: ", e);
} catch (Exception exception) {
log.debug("Exception processing TopologyWebSocket events: ",
exception);
@@ -174,8 +201,9 @@
*/
@OnMessage
public void onPongMessage(Session session, PongMessage msg) {
- log.debug("WebSocket Pong message received: {}",
- msg.getApplicationData());
+ log.trace("WebSocket Pong message received for session: {}",
+ session.getId());
+ missingPing = 0;
}
/**