mirror of
https://github.com/screentinker/screentinker.git
synced 2026-08-13 22:03:13 -06:00
feat(tizen): harden FIX B with an application-level liveness watchdog
Replace the resume-only hide-duration heuristic as the AUTHORITATIVE half-open detector with a real server-silence watchdog, so the .wgt self-heals a dead-but-connected socket from ANY cause (network drop, NAT idle timeout, transport death while foregrounded), not just resume. - Central receive-path liveness: markAlive() refreshes lastServerMsgAt on EVERY inbound server message — app events via socket.onAny, and the server's ~15s engine ping via socket.io 'ping' (both client-only signals the server already sends; no server change; heartbeats get no ack). - Watchdog (10s cadence): if socket.connected && authenticated && silent > 35s (2+ missed pings, under engine.io's own ~45s close), treat as half-open and reconnect via the teardown-first connect() -> exactly one socket, #118 re-registers once. - #148 discipline: fires ONLY while socket.connected===true (the state socket.io can't see), so it never races socket.io's own down-socket auto-reconnect; connect() resets liveness so the watchdog and the resume fast-path can't double-fire. Resume path kept as the fast suspend path. - Timers cleared on exit. Client-only; keep-awake+lifecycle remain the leading flap candidate, NOT a confirmed cause.
This commit is contained in:
parent
78c71e00ab
commit
dcd3a05a7e
|
|
@ -134,6 +134,41 @@
|
|||
}
|
||||
if (typeof window !== 'undefined') window.__stResumeDecision = resumeDecision; // test hook (inert in prod)
|
||||
|
||||
// FIX B (hardened) — application-level LIVENESS WATCHDOG. The resume path above only fires on
|
||||
// visibilitychange, so a socket that goes half-open with NO visibility event (network drop, NAT
|
||||
// idle timeout, transport death while foregrounded) would never be caught: socket.connected stays
|
||||
// true on a dead socket and socket.io won't reconnect. The watchdog watches for server SILENCE.
|
||||
// The server sends an engine ping every ~15s (config.pingInterval) AND app events, so a healthy
|
||||
// socket refreshes lastServerMsgAt at least every ~15s (markAlive is wired into a central receive
|
||||
// path in connect(): socket.onAny + socket.io 'ping'). If the socket goes quiet past the liveness
|
||||
// window while we still believe we're connected + authenticated, it is half-open -> clean
|
||||
// teardown-before-reopen via connect() (exactly one socket; #118 re-registers once).
|
||||
//
|
||||
// Double-connect discipline: the watchdog fires ONLY while socket.connected===true (the half-open
|
||||
// state socket.io cannot see) — socket.io's own auto-reconnect only runs when socket.connected is
|
||||
// false, so the two never overlap. connect() is teardown-first, and it resets lastServerMsgAt, so
|
||||
// the watchdog and the resume fast-path can't double-fire a second reconnect. Client-only: uses
|
||||
// signals the server already sends; no server change.
|
||||
var lastServerMsgAt = 0;
|
||||
var LIVENESS_TIMEOUT_MS = 35000; // ~2+ missed 15s server pings; below engine.io's own ~45s close
|
||||
var watchdogTimer = null;
|
||||
function markAlive() { lastServerMsgAt = Date.now(); } // central receive-path hook (see connect())
|
||||
// Pure, unit-testable: reconnect only for a connected+authenticated socket gone silent past the window.
|
||||
function watchdogShouldReconnect(hasSocket, connected, authed, silentMs) {
|
||||
return !!(hasSocket && connected && authed && silentMs > LIVENESS_TIMEOUT_MS);
|
||||
}
|
||||
function startWatchdog() {
|
||||
stopWatchdog();
|
||||
watchdogTimer = setInterval(function () {
|
||||
var silentMs = lastServerMsgAt ? (Date.now() - lastServerMsgAt) : 0;
|
||||
if (watchdogShouldReconnect(!!socket, !!(socket && socket.connected), authenticated, silentMs)) {
|
||||
connect(); // half-open backstop: teardown-first -> one socket, #118 re-registers once
|
||||
}
|
||||
}, 10000);
|
||||
}
|
||||
function stopWatchdog() { if (watchdogTimer) { clearInterval(watchdogTimer); watchdogTimer = null; } }
|
||||
if (typeof window !== 'undefined') window.__stWatchdogShouldReconnect = watchdogShouldReconnect; // test hook (inert in prod)
|
||||
|
||||
// ---- networking ----
|
||||
var socket = null;
|
||||
var deviceId = get(LS.id);
|
||||
|
|
@ -184,6 +219,15 @@
|
|||
timeout: 10000
|
||||
});
|
||||
|
||||
// FIX B (hardened): central receive-path liveness. A fresh socket is assumed alive; then EVERY
|
||||
// inbound server message refreshes lastServerMsgAt — app events via onAny, and the engine ping
|
||||
// (~15s) via the manager 'ping'. This resets liveness so the watchdog / resume fast-path can't
|
||||
// double-fire, and feeds the watchdog's server-silence detection. (io() returns a fresh socket
|
||||
// per connect — verified — so these listeners don't accumulate.)
|
||||
lastServerMsgAt = Date.now();
|
||||
socket.onAny(markAlive);
|
||||
socket.io.on('ping', markAlive);
|
||||
|
||||
socket.on('connect', function () {
|
||||
// #118: a brand-new socket is not authenticated until device:registered. Reset the
|
||||
// flag and kill any heartbeat carried over from the previous socket, so a beat can't
|
||||
|
|
@ -530,7 +574,7 @@
|
|||
document.addEventListener('keydown', function (e) {
|
||||
if (e.keyCode === 10009) { // Samsung RETURN / BACK
|
||||
if (!elSetup.classList.contains('hidden')) {
|
||||
stopKeepAwake(); // FIX A: clear the interval cleanly before the app exits
|
||||
stopKeepAwake(); stopWatchdog(); // FIX A/B: clear timers cleanly before the app exits
|
||||
try { tizen.application.getCurrentApplication().exit(); } catch (x) {}
|
||||
} else {
|
||||
if (socket) { try { socket.disconnect(); } catch (x) {} }
|
||||
|
|
@ -546,7 +590,8 @@
|
|||
// fully provisioned device (has a saved device_id + token) goes straight to
|
||||
// playback; otherwise show the setup screen and ask for / confirm the server.
|
||||
startKeepAwake(); // FIX A: assert + re-assert keep-awake on an interval
|
||||
document.addEventListener('visibilitychange', onVisibility); // FIX B: handle suspend/resume
|
||||
document.addEventListener('visibilitychange', onVisibility); // FIX B: suspend/resume fast-path
|
||||
startWatchdog(); // FIX B (hardened): server-silence liveness backstop
|
||||
if (serverUrl && deviceId && deviceToken) {
|
||||
show(elStage); connect(); // paired — reconnect to playback
|
||||
} else if (serverUrl) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue