fix: add pong/timeout detection to prevent silent connection hangs
Deploy Dashboard (Dev) / deploy-dev (push) Successful in 2m2s
Deploy Dashboard (Dev) / deploy-dev (push) Successful in 2m2s
- Backend now responds to __ping__ with __pong__ - Frontend detects missing pong within 5s and closes connection - This triggers proper reconnect/auth recovery instead of hanging - Fixes 'keepalive ping timeout' issue where connection appears alive but is dead
This commit is contained in:
@@ -180,6 +180,7 @@ async def ws_endpoint(websocket: WebSocket, host: str = Query("nas"), token: str
|
|||||||
process.stdin.write(data)
|
process.stdin.write(data)
|
||||||
elif "text" in msg and msg["text"]:
|
elif "text" in msg and msg["text"]:
|
||||||
if msg["text"] == "__ping__":
|
if msg["text"] == "__ping__":
|
||||||
|
await websocket.send_text("__pong__")
|
||||||
continue
|
continue
|
||||||
process.stdin.write(msg["text"].encode())
|
process.stdin.write(msg["text"].encode())
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -190,18 +190,24 @@
|
|||||||
|
|
||||||
const proto = location.protocol === "https:" ? "wss:" : "ws:";
|
const proto = location.protocol === "https:" ? "wss:" : "ws:";
|
||||||
let heartbeat = null;
|
let heartbeat = null;
|
||||||
|
let pongTimeout = null;
|
||||||
let reconnectTimer = null;
|
let reconnectTimer = null;
|
||||||
let closedManually = false;
|
let closedManually = false;
|
||||||
let reconnecting = false;
|
let reconnecting = false;
|
||||||
let authRecoveryInFlight = false;
|
let authRecoveryInFlight = false;
|
||||||
let reconnectAttempts = 0;
|
let reconnectAttempts = 0;
|
||||||
const MAX_RECONNECT_DELAY = 30000;
|
const MAX_RECONNECT_DELAY = 30000;
|
||||||
|
const PONG_TIMEOUT = 5000; // 5s to receive pong
|
||||||
|
|
||||||
function clearHeartbeat() {
|
function clearHeartbeat() {
|
||||||
if (heartbeat) {
|
if (heartbeat) {
|
||||||
clearInterval(heartbeat);
|
clearInterval(heartbeat);
|
||||||
heartbeat = null;
|
heartbeat = null;
|
||||||
}
|
}
|
||||||
|
if (pongTimeout) {
|
||||||
|
clearTimeout(pongTimeout);
|
||||||
|
pongTimeout = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleAuthExpired(reason = "Session expired — please log in again") {
|
function handleAuthExpired(reason = "Session expired — please log in again") {
|
||||||
@@ -280,7 +286,15 @@
|
|||||||
sendSize(ws);
|
sendSize(ws);
|
||||||
clearHeartbeat();
|
clearHeartbeat();
|
||||||
heartbeat = setInterval(() => {
|
heartbeat = setInterval(() => {
|
||||||
if (ws.readyState === 1) ws.send("__ping__");
|
if (ws.readyState === 1) {
|
||||||
|
ws.send("__ping__");
|
||||||
|
// Set timeout to detect missing pong
|
||||||
|
pongTimeout = setTimeout(() => {
|
||||||
|
if (ws.readyState === 1) {
|
||||||
|
ws.close(1000, "keepalive ping timeout");
|
||||||
|
}
|
||||||
|
}, PONG_TIMEOUT);
|
||||||
|
}
|
||||||
}, 12000);
|
}, 12000);
|
||||||
const d = tabData.get(tabId);
|
const d = tabData.get(tabId);
|
||||||
if (d) {
|
if (d) {
|
||||||
@@ -293,6 +307,16 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
ws.onmessage = (e) => {
|
ws.onmessage = (e) => {
|
||||||
|
// Check for text pong response
|
||||||
|
if (typeof e.data === 'string' && e.data === '__pong__') {
|
||||||
|
// Clear the pong timeout - connection is alive
|
||||||
|
if (pongTimeout) {
|
||||||
|
clearTimeout(pongTimeout);
|
||||||
|
pongTimeout = null;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const data = new Uint8Array(e.data);
|
const data = new Uint8Array(e.data);
|
||||||
const isPersistenceMessage = tab.persistent
|
const isPersistenceMessage = tab.persistent
|
||||||
&& data.length >= PERSISTENCE_STATUS_PREFIX_BYTES.length
|
&& data.length >= PERSISTENCE_STATUS_PREFIX_BYTES.length
|
||||||
|
|||||||
Reference in New Issue
Block a user