Cache Keepalive: skip + disarm if the idle timer fired >5s late (sleep/throttle guard)

d82870e0eac66ac100238311f8ea2dbdbcfa78b0

permissionBRICK <permissionBRICK@gmail.com>

1 files changed, +24 -1Ignore whitespace
public/scripts/extensions/keepalive/index.js+24 -1
@@ -37,6 +37,11 @@ const NONE_KEY = '__none__';
37// user message can pre-empt the keepalive.37// user message can pre-empt the keepalive.
38const EXPIRED_GRACE_MS = 3000;38const EXPIRED_GRACE_MS = 3000;
3939
40// If the idle timer fires more than this many ms later than it was scheduled to, assume the
41// machine slept / the tab was throttled — far more wall-clock time passed than the interval,
42// so the cache almost certainly lapsed. Skip the ping and go dormant rather than guess.
43const MAX_TIMER_DRIFT_MS = 5000;
44
40const defaultSettings = {45const defaultSettings = {
41 enabled: false,46 enabled: false,
42 timeoutSeconds: 295,47 timeoutSeconds: 295,
@@ -52,6 +57,9 @@ let keepaliveActive = false;
52// history already present) we have no way to know whether anything is still cached57// history already present) we have no way to know whether anything is still cached
53// server-side — so pinging before then would be pointless. Resets on page reload.58// server-side — so pinging before then would be pointless. Resets on page reload.
54let armed = false;59let armed = false;
60// Wall-clock time (Date.now() + delay) the pending idle timer is expected to fire, or null when
61// none is scheduled. Used to detect a timer that fired far too late (sleep/throttle).
62let scheduledFireTime = null;
55// Per-profile "time since last message", keyed by connection profile id (or NONE_KEY).63// Per-profile "time since last message", keyed by connection profile id (or NONE_KEY).
56// In-memory only: on reload every profile is treated as freshly active.64// In-memory only: on reload every profile is treated as freshly active.
57const lastActivity = {};65const lastActivity = {};
@@ -102,12 +110,15 @@ function clearIdleTimer() {
102function scheduleIdleTimer() {110function scheduleIdleTimer() {
103 clearIdleTimer();111 clearIdleTimer();
104 if (!armed || !isKeepaliveEnabledForActive()) {112 if (!armed || !isKeepaliveEnabledForActive()) {
113 scheduledFireTime = null;
105 return;114 return;
106 }115 }
107 const timeoutMs = Math.max(60, Number(extension_settings[MODULE].timeoutSeconds) || 295) * 1000;116 const timeoutMs = Math.max(60, Number(extension_settings[MODULE].timeoutSeconds) || 295) * 1000;
108 const last = lastActivity[getActiveProfileKey()] ?? Date.now();117 const last = lastActivity[getActiveProfileKey()] ?? Date.now();
109 const remaining = timeoutMs - (Date.now() - last);118 const remaining = timeoutMs - (Date.now() - last);
110 idleTimer = setTimeout(fireKeepalive, remaining > 0 ? remaining : EXPIRED_GRACE_MS);119 const delay = remaining > 0 ? remaining : EXPIRED_GRACE_MS;
120 scheduledFireTime = Date.now() + delay;
121 idleTimer = setTimeout(fireKeepalive, delay);
111}122}
112123
113/**124/**
@@ -151,6 +162,18 @@ async function fireKeepalive() {
151 return;162 return;
152 }163 }
153164
165 // If the timer fired much later than scheduled (machine sleep, background-tab throttling,
166 // etc.), far more wall-clock time has elapsed than the idle interval, so the cache has
167 // almost certainly expired. Don't ping on a stale assumption — go dormant until the next
168 // real message re-arms keepalive, exactly like a freshly opened tab.
169 if (scheduledFireTime !== null && (Date.now() - scheduledFireTime) > MAX_TIMER_DRIFT_MS) {
170 console.debug('[Keepalive] Idle timer fired', Date.now() - scheduledFireTime, 'ms late; skipping and disarming until next activity.');
171 armed = false;
172 clearIdleTimer();
173 scheduledFireTime = null;
174 return;
175 }
176
154 // Never start while any generation is in progress, or while a previous177 // Never start while any generation is in progress, or while a previous
155 // keepalive is still running. Reschedule and bail.178 // keepalive is still running. Reschedule and bail.
156 if (keepaliveActive || isGenerating() || streamingProcessor) {179 if (keepaliveActive || isGenerating() || streamingProcessor) {