diff --git a/src/qz/ws/PrintSocketClient.java b/src/qz/ws/PrintSocketClient.java index 93d5a19fb..a14a6109f 100644 --- a/src/qz/ws/PrintSocketClient.java +++ b/src/qz/ws/PrintSocketClient.java @@ -38,8 +38,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Locale; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeoutException; +import java.util.concurrent.*; @WebSocket @@ -66,7 +65,11 @@ public void onConnect(Session session) { trayManager.displayInfoMessage("Client connected"); //new connections are unknown until they send a proper certificate - openConnections.put(((InetSocketAddress)session.getRemoteAddress()).getPort(), new SocketConnection(Certificate.UNKNOWN)); + SocketConnection connection = new SocketConnection(Certificate.UNKNOWN); + openConnections.put(((InetSocketAddress)session.getRemoteAddress()).getPort(), connection); + + //Browsers now slow or interrupt JS timers, leading to the websockets keepalive failing. A server-side keepalive ping will qct as a fallback. + connection.startKeepAlive(session, (int)(session.getPolicy().getIdleTimeout().toSeconds() / 2)); } @OnWebSocketClose diff --git a/src/qz/ws/SocketConnection.java b/src/qz/ws/SocketConnection.java index 9e4e1c079..6819406d3 100644 --- a/src/qz/ws/SocketConnection.java +++ b/src/qz/ws/SocketConnection.java @@ -3,6 +3,7 @@ import jssc.SerialPortException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.eclipse.jetty.websocket.api.Session; import qz.auth.Certificate; import qz.communication.*; import qz.printer.status.StatusMonitor; @@ -11,6 +12,10 @@ import java.io.IOException; import java.nio.file.Path; import java.util.HashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; public class SocketConnection { @@ -32,6 +37,9 @@ public class SocketConnection { // DeviceOptions -> open DeviceIO private final HashMap openDevices = new HashMap<>(); + private static final ScheduledExecutorService keepAliveExecutor = Executors.newScheduledThreadPool(1); + private ScheduledFuture keepAlive; + public SocketConnection(Certificate cert) { certificate = cert; @@ -95,6 +103,27 @@ public FileIO getFileListener(Path absolute) { return openFiles.get(absolute); } + public void startKeepAlive(Session session, int seconds) { + if (keepAlive != null && (!keepAlive.isCancelled() || !keepAlive.isDone())) { + stopKeepAlive(); + } + //Sane fallback, this should never come up. + seconds = Math.max(seconds, 15); + keepAlive = keepAliveExecutor.scheduleAtFixedRate(() -> { + try { + session.getRemote().sendPing(null); + } + catch(IOException e) { + log.warn("Websocket keepalive failed. Stopping", e); + this.stopKeepAlive(); + } + }, 0, seconds, TimeUnit.SECONDS); + } + + public void stopKeepAlive() { + keepAlive.cancel(true); + } + public void removeFileListener(Path absolute) { openFiles.remove(absolute); } @@ -147,6 +176,7 @@ public synchronized void disconnect() throws SerialPortException, DeviceExceptio dio.close(); } + stopKeepAlive(); removeAllFileListeners(); stopDeviceListening(); StatusMonitor.stopListening(this);