perf(email): make library prewarm idle and bounded (#5925)

* perf(email): make library prewarm idle and bounded

* fix(email): preserve idle prewarm and prioritize foreground

* fix(email): retry interrupted idle prewarm safely
This commit is contained in:
RaresKeY
2026-08-10 19:14:10 +01:00
committed by GitHub
parent 8f2f483725
commit 96aca52094
2 changed files with 711 additions and 132 deletions
+268 -103
View File
@@ -1793,11 +1793,18 @@ function _rememberedEmailAccountId() {
// results and __scheduled__ are deliberately not cached. // results and __scheduled__ are deliberately not cached.
const _libListCache = new Map(); const _libListCache = new Map();
const _LIB_CACHE_MAX = 24; const _LIB_CACHE_MAX = 24;
const _LIB_INITIAL_PAGE_SIZE = 100;
const _LIB_SESSION_CACHE_PREFIX = 'odysseus.email.list.'; const _LIB_SESSION_CACHE_PREFIX = 'odysseus.email.list.';
const _LIB_SESSION_CACHE_TTL_MS = 10 * 60 * 1000; const _LIB_SESSION_CACHE_TTL_MS = 10 * 60 * 1000;
const _LIB_LAST_ACCOUNT_KEY = 'odysseus.email.lastAccountId'; const _LIB_LAST_ACCOUNT_KEY = 'odysseus.email.lastAccountId';
let _libPrewarmTimer = null; const _LIB_PREWARM_COOLDOWN_MS = 5 * 60 * 1000;
let _libPrewarmDelayTimer = null;
let _libPrewarmIdleHandle = null;
let _libPrewarmPromise = null; let _libPrewarmPromise = null;
let _libPrewarmResolve = null;
let _libPrewarmAbortController = null;
let _libPrewarmDetachPriorityListeners = null;
let _libPrewarmGeneration = 0;
let _libLastPrewarmAt = 0; let _libLastPrewarmAt = 0;
let _libUnreadPrewarmKey = ''; let _libUnreadPrewarmKey = '';
let _libUnreadPrewarmAt = 0; let _libUnreadPrewarmAt = 0;
@@ -2108,66 +2115,244 @@ function _isChatInteractionBusy() {
} }
} }
function _loadEmailsWhenChatIdle({ delay = 50, retries = 180, options = {} } = {}) { function _canRunEmailPrewarm() {
const run = () => { if (state._libOpen || state._libLoading || _libSearchInFlight) return false;
if (!state._libOpen || !document.getElementById('email-lib-modal')) return; if (document.visibilityState && document.visibilityState !== 'visible') return false;
if (_isChatInteractionBusy() && retries > 0) { return !_isChatInteractionBusy();
setTimeout(() => _loadEmailsWhenChatIdle({ delay: 1000, retries: retries - 1, options }), 1000); }
function _isEmailPrewarmTemporarilyBlocked() {
if (state._libOpen || state._libLoading || _libSearchInFlight) return false;
if (document.visibilityState && document.visibilityState !== 'visible') return false;
return _isChatInteractionBusy();
}
function _isEmailPrewarmCurrent(generation, signal) {
return generation === _libPrewarmGeneration
&& !signal?.aborted
&& _canRunEmailPrewarm();
}
function _settleEmailPrewarm(generation, value = false) {
if (generation !== _libPrewarmGeneration) return;
const resolve = _libPrewarmResolve;
const detachPriorityListeners = _libPrewarmDetachPriorityListeners;
_libPrewarmDelayTimer = null;
_libPrewarmIdleHandle = null;
_libPrewarmPromise = null;
_libPrewarmResolve = null;
_libPrewarmAbortController = null;
_libPrewarmDetachPriorityListeners = null;
detachPriorityListeners?.();
resolve?.(value);
}
function _cancelEmailPrewarm() {
const resolve = _libPrewarmResolve;
const detachPriorityListeners = _libPrewarmDetachPriorityListeners;
_libPrewarmGeneration += 1;
if (_libPrewarmDelayTimer !== null) {
clearTimeout(_libPrewarmDelayTimer);
}
if (_libPrewarmIdleHandle !== null && typeof window.cancelIdleCallback === 'function') {
try { window.cancelIdleCallback(_libPrewarmIdleHandle); } catch (_) {}
}
try { _libPrewarmAbortController?.abort(); } catch (_) {}
_libPrewarmDelayTimer = null;
_libPrewarmIdleHandle = null;
_libPrewarmPromise = null;
_libPrewarmResolve = null;
_libPrewarmAbortController = null;
_libPrewarmDetachPriorityListeners = null;
detachPriorityListeners?.();
resolve?.(false);
}
function _scheduleEmailPrewarm(task, { delay = 0 } = {}) {
if (_libPrewarmPromise) return _libPrewarmPromise;
// Do not disguise a timer as idle work. Browsers without the genuine idle
// callback simply skip this optional optimization and load on demand.
if (typeof window.requestIdleCallback !== 'function') return Promise.resolve(false);
const generation = ++_libPrewarmGeneration;
_libPrewarmPromise = new Promise(resolve => { _libPrewarmResolve = resolve; });
const promise = _libPrewarmPromise;
let attemptPending = false;
let retryRequested = false;
function clearScheduledAttempt() {
if (_libPrewarmDelayTimer !== null) clearTimeout(_libPrewarmDelayTimer);
if (_libPrewarmIdleHandle !== null && typeof window.cancelIdleCallback === 'function') {
try { window.cancelIdleCallback(_libPrewarmIdleHandle); } catch (_) {}
}
_libPrewarmDelayTimer = null;
_libPrewarmIdleHandle = null;
}
function scheduleIdleRetry(delay = 500) {
if (generation !== _libPrewarmGeneration) return;
retryRequested = true;
if (attemptPending || _libPrewarmDelayTimer !== null || _libPrewarmIdleHandle !== null) return;
if (document.visibilityState && document.visibilityState !== 'visible') return;
_libPrewarmDelayTimer = setTimeout(requestIdle, Math.max(50, Number(delay) || 500));
}
function handlePriorityChange() {
if (generation !== _libPrewarmGeneration) return;
if (_canRunEmailPrewarm()) {
scheduleIdleRetry(50);
return; return;
} }
_loadEmails(options);
const priorityBlocked = _isChatInteractionBusy()
|| (document.visibilityState && document.visibilityState !== 'visible');
if (!priorityBlocked) return;
retryRequested = true;
clearScheduledAttempt();
const controller = _libPrewarmAbortController;
_libPrewarmAbortController = null;
try { controller?.abort(); } catch (_) {}
// A hidden page waits for visibilitychange. Chat priority also retains the
// timer fallback for busy-until windows whose final transition has no event.
if (!document.visibilityState || document.visibilityState === 'visible') {
scheduleIdleRetry();
}
}
window.addEventListener('odysseus:chat-busy-change', handlePriorityChange);
document.addEventListener('visibilitychange', handlePriorityChange);
_libPrewarmDetachPriorityListeners = () => {
window.removeEventListener('odysseus:chat-busy-change', handlePriorityChange);
document.removeEventListener('visibilitychange', handlePriorityChange);
}; };
setTimeout(run, Math.max(0, Number(delay) || 0));
function requestIdle() {
if (generation !== _libPrewarmGeneration) return;
_libPrewarmDelayTimer = null;
try {
_libPrewarmIdleHandle = window.requestIdleCallback((deadline) => {
if (generation !== _libPrewarmGeneration) return;
_libPrewarmIdleHandle = null;
const hasIdleBudget = Boolean(
deadline
&& !deadline.didTimeout
&& typeof deadline.timeRemaining === 'function'
&& deadline.timeRemaining() > 0
);
if (!_canRunEmailPrewarm()) {
if (_isEmailPrewarmTemporarilyBlocked()) {
scheduleIdleRetry();
} else {
_settleEmailPrewarm(generation, false);
}
return;
}
if (!hasIdleBudget) {
scheduleIdleRetry();
return;
}
if (generation !== _libPrewarmGeneration) {
_settleEmailPrewarm(generation, false);
return;
}
const controller = new AbortController();
_libPrewarmAbortController = controller;
attemptPending = true;
retryRequested = false;
Promise.resolve()
.then(() => task({ signal: controller.signal, generation }))
.then(value => {
if (controller !== _libPrewarmAbortController || controller.signal.aborted) return;
_settleEmailPrewarm(generation, Boolean(value));
})
.catch(() => {
if (controller !== _libPrewarmAbortController || controller.signal.aborted) return;
_settleEmailPrewarm(generation, false);
})
.finally(() => {
attemptPending = false;
if (generation !== _libPrewarmGeneration) return;
if (retryRequested) scheduleIdleRetry();
});
});
} catch (_) {
_settleEmailPrewarm(generation, false);
}
}
const wait = Math.max(0, Number(delay) || 0);
if (wait > 0) _libPrewarmDelayTimer = setTimeout(requestIdle, wait);
else requestIdle();
return promise;
} }
export function prewarmEmailLibrary({ delay = 2500 } = {}) { export function prewarmEmailLibrary({ delay = 2500 } = {}) {
if (_libPrewarmTimer || _libPrewarmPromise) return; if (_libPrewarmPromise) return _libPrewarmPromise;
const elapsed = Date.now() - _libLastPrewarmAt; const elapsed = Date.now() - _libLastPrewarmAt;
if (elapsed >= 0 && elapsed < 5 * 60 * 1000) return; if (elapsed >= 0 && elapsed < _LIB_PREWARM_COOLDOWN_MS) return Promise.resolve(false);
_libPrewarmTimer = setTimeout(() => { return _scheduleEmailPrewarm(_prewarmEmailViews, { delay });
_libPrewarmTimer = null;
_libPrewarmPromise = _prewarmEmailViews()
.catch(() => {})
.finally(() => { _libPrewarmPromise = null; });
}, Math.max(0, Number(delay) || 0));
} }
async function _ensureEmailAccountsForPrewarm() { function _chooseEmailPrewarmAccountId(accounts) {
const enabled = Array.isArray(accounts) ? accounts.filter(a => a && a.enabled !== false) : [];
const remembered = _rememberedEmailAccountId();
const current = String(state._libAccountId || '').trim();
const chosen = enabled.find(a => String(a.id || '') === remembered)
|| enabled.find(a => String(a.id || '') === current)
|| enabled.find(a => a.is_default)
|| enabled[0]
|| null;
return String(chosen?.id || '').trim();
}
async function _ensureEmailAccountsForPrewarm({ signal, generation } = {}) {
if (!_isEmailPrewarmCurrent(generation, signal)) return null;
const accountsFresh = _libAccountsLoadedAt && (Date.now() - _libAccountsLoadedAt) < _LIB_ACCOUNTS_TTL_MS; const accountsFresh = _libAccountsLoadedAt && (Date.now() - _libAccountsLoadedAt) < _LIB_ACCOUNTS_TTL_MS;
if (Array.isArray(state._libAccounts) && state._libAccounts.length && accountsFresh) { if (!(Array.isArray(state._libAccounts) && state._libAccounts.length && accountsFresh)) {
if (!state._libAccountId) {
const def = state._libAccounts.find(a => a.is_default) || state._libAccounts[0];
state._libAccountId = def?.id || null;
_publishActiveAccount();
}
return;
}
try { try {
const accountsRes = await fetch(`${API_BASE}/api/email/accounts`, { credentials: 'same-origin' }); const accountsRes = await fetch(`${API_BASE}/api/email/accounts`, {
if (!accountsRes.ok) return; credentials: 'same-origin',
signal,
});
if (!_isEmailPrewarmCurrent(generation, signal)) return null;
if (accountsRes.ok) {
const accountsData = await accountsRes.json().catch(() => ({})); const accountsData = await accountsRes.json().catch(() => ({}));
if (!_isEmailPrewarmCurrent(generation, signal)) return null;
if (Array.isArray(accountsData.accounts)) { if (Array.isArray(accountsData.accounts)) {
state._libAccounts = accountsData.accounts; state._libAccounts = accountsData.accounts;
_libAccountsLoadedAt = Date.now(); _libAccountsLoadedAt = Date.now();
if (!state._libAccountId && state._libAccounts.length) { }
const def = state._libAccounts.find(a => a.is_default) || state._libAccounts[0]; }
state._libAccountId = def?.id || null; } catch (err) {
if (err?.name === 'AbortError') return null;
}
}
const accountId = _chooseEmailPrewarmAccountId(state._libAccounts);
if (!_isEmailPrewarmCurrent(generation, signal)) return null;
if (!accountId) return null;
if (accountId && state._libAccountId !== accountId) {
state._libAccountId = accountId;
_publishActiveAccount(); _publishActiveAccount();
} }
} return accountId;
} catch (_) {}
} }
export async function prewarmUnreadEmails({ limit = 8, maxUid = 0 } = {}) { export function prewarmUnreadEmails({ limit = 8, maxUid = 0 } = {}) {
if (state._libOpen) return; return _scheduleEmailPrewarm(
await _ensureEmailAccountsForPrewarm(); context => _prewarmUnreadEmailsNow({ limit, maxUid }, context),
if (state._libOpen) return; { delay: 0 }
const accountId = state._libAccountId || ''; );
}
async function _prewarmUnreadEmailsNow({ limit = 8, maxUid = 0 } = {}, { signal, generation } = {}) {
if (!_isEmailPrewarmCurrent(generation, signal)) return false;
const accountId = await _ensureEmailAccountsForPrewarm({ signal, generation });
if (accountId === null || !_isEmailPrewarmCurrent(generation, signal)) return false;
const n = Math.max(1, Math.min(20, Number(limit) || 8)); const n = Math.max(1, Math.min(20, Number(limit) || 8));
const key = `${accountId}|${maxUid || 0}|${n}`; const key = `${accountId}|${maxUid || 0}|${n}`;
if (_libUnreadPrewarmKey === key && (Date.now() - _libUnreadPrewarmAt) < 60 * 1000) return; if (_libUnreadPrewarmKey === key && (Date.now() - _libUnreadPrewarmAt) < 60 * 1000) return true;
_libUnreadPrewarmKey = key;
_libUnreadPrewarmAt = Date.now();
try { try {
const folder = 'INBOX'; const folder = 'INBOX';
const res = await fetch(emailApiUrl('/api/email/list', { const res = await fetch(emailApiUrl('/api/email/list', {
@@ -2176,94 +2361,73 @@ export async function prewarmUnreadEmails({ limit = 8, maxUid = 0 } = {}) {
offset: 0, offset: 0,
filter: 'unread', filter: 'unread',
account_id: accountId || undefined, account_id: accountId || undefined,
}), { credentials: 'same-origin' }); }), {
if (state._libOpen) return; credentials: 'same-origin',
if (!res.ok) return; signal,
});
if (!_isEmailPrewarmCurrent(generation, signal) || !res.ok) return false;
const data = await res.json().catch(() => null); const data = await res.json().catch(() => null);
if (!data || data.error || !Array.isArray(data.emails) || !data.emails.length) return; if (!_isEmailPrewarmCurrent(generation, signal)) return false;
if (!data || data.error || !Array.isArray(data.emails) || !data.emails.length) return false;
const sync = data.sync || {}; const sync = data.sync || {};
_libCachePut(_libCacheKeyFor(accountId, folder, 'unread', false), { _libCachePut(_libCacheKeyFor(accountId, folder, 'unread', false), {
emails: data.emails, emails: data.emails,
total: data.total || data.emails.length, total: data.total || data.emails.length,
sync, sync,
}); });
} catch (_) {} _libUnreadPrewarmKey = key;
_libUnreadPrewarmAt = Date.now();
return true;
} catch (_) {
return false;
}
} }
function _sleep(ms) { async function _prewarmEmailViews({ signal, generation } = {}) {
return new Promise(resolve => setTimeout(resolve, ms)); if (!_isEmailPrewarmCurrent(generation, signal)) return false;
}
async function _prewarmEmailViews() {
if (state._libOpen) return;
_libLastPrewarmAt = Date.now();
_setEmailSyncStatus({ warming: true }); _setEmailSyncStatus({ warming: true });
const folder = 'INBOX'; const folder = 'INBOX';
const filter = 'all'; const filter = 'all';
// The accounts request is cheap and warms the account strip for first open.
// Then folder/list requests warm both the client cache and the backend
// IMAP/read caches. Failure stays silent: no configured mail should not nag.
try { try {
const accountsRes = await fetch(`${API_BASE}/api/email/accounts`, { credentials: 'same-origin' }); const accountId = await _ensureEmailAccountsForPrewarm({ signal, generation });
if (accountsRes.ok) { if (accountId === null || !_isEmailPrewarmCurrent(generation, signal)) return false;
const accountsData = await accountsRes.json().catch(() => ({}));
if (Array.isArray(accountsData.accounts)) {
state._libAccounts = accountsData.accounts;
_libAccountsLoadedAt = Date.now();
}
}
} catch (_) {}
const accounts = Array.isArray(state._libAccounts) ? state._libAccounts.filter(a => a && a.enabled !== false) : [];
const preferred = state._libAccountId
|| (accounts.find(a => a.is_default)?.id)
|| (accounts[0]?.id)
|| '';
if (!state._libAccountId && preferred) {
state._libAccountId = preferred;
_publishActiveAccount();
}
const orderedAccountIds = [
preferred,
...accounts.map(a => a.id).filter(id => id && id !== preferred),
].filter((id, idx, arr) => arr.indexOf(id) === idx);
if (!orderedAccountIds.length) orderedAccountIds.push('');
try {
for (const accountId of orderedAccountIds.slice(0, 4)) {
if (state._libOpen) return;
const ck = _libCacheKeyFor(accountId, folder, filter, false); const ck = _libCacheKeyFor(accountId, folder, filter, false);
if (_libCacheGet(ck)) continue; if (_libCacheGet(ck)) {
await fetch(emailApiUrl('/api/email/folders', { account_id: accountId || undefined }), { credentials: 'same-origin' }).catch(() => null); _libLastPrewarmAt = Date.now();
await fetch(emailApiUrl('/api/email/unread-state', { folder, account_id: accountId || undefined }), { credentials: 'same-origin' }).catch(() => null); return true;
}
// One optional first-page request only. Folder metadata, unread state, and
// other accounts remain demand-driven so startup cannot fan out into IMAP.
const res = await fetch(emailApiUrl('/api/email/list', { const res = await fetch(emailApiUrl('/api/email/list', {
folder, folder,
limit: 100, limit: _LIB_INITIAL_PAGE_SIZE,
offset: 0, offset: 0,
filter, filter,
account_id: accountId || undefined, account_id: accountId || undefined,
}), { }), {
credentials: 'same-origin', credentials: 'same-origin',
signal,
}); });
if (res.ok) { if (!_isEmailPrewarmCurrent(generation, signal) || !res.ok) return false;
const data = await res.json().catch(() => null); const data = await res.json().catch(() => null);
if (data && !data.error) { if (!_isEmailPrewarmCurrent(generation, signal)) return false;
if (!data || data.error || !Array.isArray(data.emails)) return false;
const sync = data.sync || {}; const sync = data.sync || {};
_libCachePut(ck, { _libCachePut(ck, {
emails: data.emails || [], emails: data.emails,
total: data.total || 0, total: data.total || 0,
sync, sync,
}); });
_libLastPrewarmAt = Date.now();
_setEmailSyncStatus({ _setEmailSyncStatus({
updatedAt: sync.updated_at || new Date().toISOString(), updatedAt: sync.updated_at || new Date().toISOString(),
source: sync.source || '', source: sync.source || '',
warming: true, warming: true,
}); });
} return true;
} } catch (_) {
await _sleep(900); return false;
}
} finally { } finally {
_setEmailSyncStatus({ warming: false }); _setEmailSyncStatus({ warming: false });
} }
@@ -2342,10 +2506,10 @@ export function initEmailLibrary(config) {
export function isOpen() { return state._libOpen; } export function isOpen() { return state._libOpen; }
export function openEmailLibrary(opts = {}) { export function openEmailLibrary(opts = {}) {
if (_libPrewarmTimer) { // Foreground email always wins: cancel a delayed/idle callback and abort the
clearTimeout(_libPrewarmTimer); // one optional request if it has already started. Generation checks make a
_libPrewarmTimer = null; // non-abortable response harmless if it races this transition.
} _cancelEmailPrewarm();
// Force-clean any stale state from previous attempts // Force-clean any stale state from previous attempts
const existing = document.getElementById('email-lib-modal'); const existing = document.getElementById('email-lib-modal');
if (existing) existing.remove(); if (existing) existing.remove();
@@ -2977,7 +3141,7 @@ export function openEmailLibrary(opts = {}) {
} }
const fastAccountAtOpen = state._libAccountId || ''; const fastAccountAtOpen = state._libAccountId || '';
if (fastAccountAtOpen) { if (fastAccountAtOpen) {
_loadEmailsWhenChatIdle({ delay: 0 }); _loadEmails({ useCache: true });
} }
// If we already know the previous/default account, paint that inbox first // If we already know the previous/default account, paint that inbox first
// from the durable index and validate accounts in parallel. Cold refreshes // from the durable index and validate accounts in parallel. Cold refreshes
@@ -2987,7 +3151,7 @@ export function openEmailLibrary(opts = {}) {
_loadFolders(); _loadFolders();
_loadEmailReminderBellVisibility(); _loadEmailReminderBellVisibility();
if (!fastAccountAtOpen || fastAccountAtOpen !== (state._libAccountId || '')) { if (!fastAccountAtOpen || fastAccountAtOpen !== (state._libAccountId || '')) {
_loadEmailsWhenChatIdle(); _loadEmails({ useCache: true });
} }
})(); })();
} }
@@ -3172,6 +3336,7 @@ export async function openEmailLibrarySettings() {
} }
export function closeEmailLibrary() { export function closeEmailLibrary() {
_cancelEmailPrewarm();
const modal = document.getElementById('email-lib-modal'); const modal = document.getElementById('email-lib-modal');
if (modal) modal.remove(); if (modal) modal.remove();
if (_libSyncTicker) { if (_libSyncTicker) {
@@ -4605,7 +4770,7 @@ async function _loadEmails({ force = false, useCache = true } = {}) {
const ctrl = new AbortController(); const ctrl = new AbortController();
const timer = setTimeout(() => ctrl.abort(), 450); const timer = setTimeout(() => ctrl.abort(), 450);
try { try {
const fastRes = await fetch(`${API_BASE}/api/email/list?folder=${encodeURIComponent(folderAtStart)}${accountQS}&limit=100&offset=${offsetAtStart}&filter=${filterAtStart}${attQS}&cached_only=1`, { const fastRes = await fetch(`${API_BASE}/api/email/list?folder=${encodeURIComponent(folderAtStart)}${accountQS}&limit=${_LIB_INITIAL_PAGE_SIZE}&offset=${offsetAtStart}&filter=${filterAtStart}${attQS}&cached_only=1`, {
signal: ctrl.signal, signal: ctrl.signal,
}); });
const fastData = await fastRes.json().catch(() => null); const fastData = await fastRes.json().catch(() => null);
@@ -4632,7 +4797,7 @@ async function _loadEmails({ force = false, useCache = true } = {}) {
// opens omit it so rapid close/reopen returns instantly; the // opens omit it so rapid close/reopen returns instantly; the
// Refresh button passes `force: true` to add it back. // Refresh button passes `force: true` to add it back.
const buster = force ? `&_=${Date.now()}` : ''; const buster = force ? `&_=${Date.now()}` : '';
const res = await fetch(`${API_BASE}/api/email/list?folder=${encodeURIComponent(folderAtStart)}${accountQS}&limit=100&offset=${offsetAtStart}&filter=${filterAtStart}${attQS}${buster}`); const res = await fetch(`${API_BASE}/api/email/list?folder=${encodeURIComponent(folderAtStart)}${accountQS}&limit=${_LIB_INITIAL_PAGE_SIZE}&offset=${offsetAtStart}&filter=${filterAtStart}${attQS}${buster}`);
const data = await res.json(); const data = await res.json();
if (seq !== _libLoadSeq || accountAtStart !== (state._libAccountId || '')) return; if (seq !== _libLoadSeq || accountAtStart !== (state._libAccountId || '')) return;
if (data.error) throw new Error(data.error); if (data.error) throw new Error(data.error);
+414
View File
@@ -0,0 +1,414 @@
import json
from pathlib import Path
import shutil
import subprocess
import pytest
_REPO = Path(__file__).resolve().parents[1]
_EMAIL_LIBRARY = _REPO / "static" / "js" / "emailLibrary.js"
def _source() -> str:
return _EMAIL_LIBRARY.read_text(encoding="utf-8")
def _function_source(name: str) -> str:
"""Return one top-level JS function using balanced braces."""
text = _source()
markers = (f"function {name}", f"async function {name}", f"export function {name}", f"export async function {name}")
starts = [text.find(marker) for marker in markers]
starts = [start for start in starts if start >= 0]
assert starts, f"missing function {name}"
start = min(starts)
paren = text.index("(", start)
paren_depth = 0
quote = None
escaped = False
for index in range(paren, len(text)):
char = text[index]
if quote:
if escaped:
escaped = False
elif char == "\\":
escaped = True
elif char == quote:
quote = None
continue
if char in ("'", '"', "`"):
quote = char
elif char == "(":
paren_depth += 1
elif char == ")":
paren_depth -= 1
if paren_depth == 0:
brace = text.index("{", index)
break
else:
raise AssertionError(f"unterminated signature {name}")
depth = 0
quote = None
escaped = False
template_depth = 0
for index in range(brace, len(text)):
char = text[index]
if quote:
if escaped:
escaped = False
elif char == "\\":
escaped = True
elif char == quote and template_depth == 0:
quote = None
elif quote == "`" and char == "$" and index + 1 < len(text) and text[index + 1] == "{":
template_depth += 1
elif quote == "`" and char == "}" and template_depth:
template_depth -= 1
continue
if char in ("'", '"', "`"):
quote = char
elif char == "{":
depth += 1
elif char == "}":
depth -= 1
if depth == 0:
return text[start:index + 1]
raise AssertionError(f"unterminated function {name}")
def _run_scheduler_scenario(scenario: str):
node = shutil.which("node")
if not node:
pytest.skip("node not on PATH")
functions = "\n".join(
_function_source(name)
for name in (
"_isChatInteractionBusy",
"_canRunEmailPrewarm",
"_isEmailPrewarmTemporarilyBlocked",
"_settleEmailPrewarm",
"_cancelEmailPrewarm",
"_scheduleEmailPrewarm",
)
)
script = f"""
let now = 0;
Date.now = () => now;
const state = {{ _libOpen: false, _libLoading: false }};
let _libSearchInFlight = false;
let _libPrewarmDelayTimer = null;
let _libPrewarmIdleHandle = null;
let _libPrewarmPromise = null;
let _libPrewarmResolve = null;
let _libPrewarmAbortController = null;
let _libPrewarmDetachPriorityListeners = null;
let _libPrewarmGeneration = 0;
let nextHandle = 1;
const timers = new Map();
const idleCallbacks = new Map();
let idleRequestCount = 0;
function eventTarget(target) {{
const listeners = new Map();
target.addEventListener = (type, callback) => {{
if (!listeners.has(type)) listeners.set(type, new Set());
listeners.get(type).add(callback);
}};
target.removeEventListener = (type, callback) => listeners.get(type)?.delete(callback);
target.dispatchEvent = (event) => {{
for (const callback of [...(listeners.get(event.type) || [])]) callback(event);
}};
target.listenerCount = (type) => listeners.get(type)?.size || 0;
return target;
}}
const document = eventTarget({{ visibilityState: 'visible' }});
const window = {{
__odysseusChatBusy: false,
__odysseusChatBusyUntil: 0,
requestIdleCallback(callback) {{
const handle = nextHandle++;
idleRequestCount += 1;
idleCallbacks.set(handle, callback);
return handle;
}},
cancelIdleCallback(handle) {{ idleCallbacks.delete(handle); }},
}};
eventTarget(window);
function setTimeout(callback, delay) {{
const handle = nextHandle++;
timers.set(handle, {{ callback, at: now + Number(delay || 0) }});
return handle;
}}
function clearTimeout(handle) {{ timers.delete(handle); }}
async function flushMicrotasks() {{
for (let i = 0; i < 6; i += 1) await Promise.resolve();
}}
async function advanceTo(target) {{
while (true) {{
const pending = [...timers.entries()]
.filter(([, timer]) => timer.at <= target)
.sort((a, b) => a[1].at - b[1].at)[0];
if (!pending) break;
const [handle, timer] = pending;
timers.delete(handle);
now = timer.at;
timer.callback();
await flushMicrotasks();
}}
now = target;
await flushMicrotasks();
}}
async function fireNextIdle(budget = 5) {{
const pending = idleCallbacks.entries().next().value;
if (!pending) throw new Error('no idle callback pending');
const [handle, callback] = pending;
idleCallbacks.delete(handle);
callback({{ didTimeout: false, timeRemaining: () => budget }});
await flushMicrotasks();
}}
{functions}
{scenario}
"""
proc = subprocess.run(
[node, "--input-type=module"],
input=script,
capture_output=True,
text=True,
cwd=str(_REPO),
timeout=30,
)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout.strip())
def test_prewarm_is_genuine_idle_only_and_single_flight():
scheduler = _function_source("_scheduleEmailPrewarm")
assert "if (_libPrewarmPromise) return _libPrewarmPromise;" in scheduler
assert "typeof window.requestIdleCallback !== 'function'" in scheduler
assert "return Promise.resolve(false);" in scheduler
assert "window.requestIdleCallback((deadline)" in scheduler
assert "!deadline.didTimeout" in scheduler
assert "deadline.timeRemaining() > 0" in scheduler
idle_callback = scheduler.index("window.requestIdleCallback((deadline)")
assert "Promise.resolve()" in scheduler
task_start = scheduler.index("task({ signal: controller.signal, generation })")
assert idle_callback < task_start, "network work must only be reachable from the idle callback"
def test_temporary_chat_priority_retries_one_single_flight_until_idle():
out = _run_scheduler_scenario("""
window.__odysseusChatBusyUntil = 10000;
let taskCalls = 0;
const task = async () => { taskCalls += 1; return true; };
const first = _scheduleEmailPrewarm(task, { delay: 1800 });
const joined = _scheduleEmailPrewarm(task, { delay: 0 });
const samePromise = first === joined;
await advanceTo(1800);
await fireNextIdle(7);
const callsWhileBusy = taskCalls;
while (now < 10300) {
await advanceTo(now + 500);
await fireNextIdle(7);
}
const result = await first;
console.log(JSON.stringify({
result, samePromise, callsWhileBusy, taskCalls, idleRequestCount,
timers: timers.size, idleCallbacks: idleCallbacks.size,
}));
""")
assert out == {
"result": True,
"samePromise": True,
"callsWhileBusy": 0,
"taskCalls": 1,
"idleRequestCount": 18,
"timers": 0,
"idleCallbacks": 0,
}
def test_cancelled_prewarm_cannot_issue_a_delayed_duplicate():
out = _run_scheduler_scenario("""
let taskCalls = 0;
const pending = _scheduleEmailPrewarm(async () => { taskCalls += 1; return true; }, { delay: 1800 });
await advanceTo(1400);
_cancelEmailPrewarm();
await advanceTo(12000);
const result = await pending;
console.log(JSON.stringify({
result, taskCalls, idleRequestCount,
timers: timers.size, idleCallbacks: idleCallbacks.size,
}));
""")
assert out == {
"result": False,
"taskCalls": 0,
"idleRequestCount": 0,
"timers": 0,
"idleCallbacks": 0,
}
@pytest.mark.parametrize("transition", ["busy", "hidden"])
def test_active_prewarm_is_aborted_and_retried_once_after_priority_transition(transition):
block = (
"window.__odysseusChatBusy = true; "
"window.dispatchEvent({ type: 'odysseus:chat-busy-change' });"
if transition == "busy"
else "document.visibilityState = 'hidden'; document.dispatchEvent({ type: 'visibilitychange' });"
)
unblock = (
"window.__odysseusChatBusy = false; window.__odysseusChatBusyUntil = now; "
"window.dispatchEvent({ type: 'odysseus:chat-busy-change' });"
if transition == "busy"
else "document.visibilityState = 'visible'; document.dispatchEvent({ type: 'visibilitychange' });"
)
out = _run_scheduler_scenario(f"""
let taskCalls = 0;
let firstSignal = null;
let finishFirst;
const firstAttempt = new Promise(resolve => {{ finishFirst = resolve; }});
const pending = _scheduleEmailPrewarm(async ({{ signal }}) => {{
taskCalls += 1;
if (taskCalls === 1) {{ firstSignal = signal; return firstAttempt; }}
return true;
}});
await fireNextIdle(7);
{block}
const aborted = firstSignal.aborted;
{unblock}
const callsBeforeLateResult = taskCalls;
finishFirst(true);
await flushMicrotasks();
const stillPendingAfterLateResult = _libPrewarmPromise === pending;
await advanceTo(now + 500);
await fireNextIdle(7);
const result = await pending;
console.log(JSON.stringify({{
result, aborted, callsBeforeLateResult, taskCalls,
stillPendingAfterLateResult,
timers: timers.size, idleCallbacks: idleCallbacks.size,
chatListeners: window.listenerCount('odysseus:chat-busy-change'),
visibilityListeners: document.listenerCount('visibilitychange'),
}}));
""")
assert out == {
"result": True,
"aborted": True,
"callsBeforeLateResult": 1,
"taskCalls": 2,
"stillPendingAfterLateResult": True,
"timers": 0,
"idleCallbacks": 0,
"chatListeners": 0,
"visibilityListeners": 0,
}
def test_prewarm_skips_hidden_and_foreground_work():
guard = _function_source("_canRunEmailPrewarm")
assert "state._libOpen" in guard
assert "state._libLoading" in guard
assert "_libSearchInFlight" in guard
assert "document.visibilityState !== 'visible'" in guard
assert "!_isChatInteractionBusy()" in guard
def test_prewarm_selects_only_last_used_or_default_account():
chooser = _function_source("_chooseEmailPrewarmAccountId")
prewarm = _function_source("_prewarmEmailViews")
assert "_rememberedEmailAccountId()" in chooser
assert "a.enabled !== false" in chooser
assert "a.is_default" in chooser
assert "enabled[0]" in chooser
assert "for (" not in prewarm
assert "orderedAccountIds" not in prewarm
assert "slice(0, 4)" not in prewarm
assert "/api/email/folders" not in prewarm
assert "/api/email/unread-state" not in prewarm
assert prewarm.count("/api/email/list") == 1
def test_prewarm_account_chooser_rejects_disabled_or_empty_authoritative_inventory():
node = shutil.which("node")
if not node:
pytest.skip("node not on PATH")
chooser = _function_source("_chooseEmailPrewarmAccountId")
script = f"""
const state = {{ _libAccountId: 'disabled-current' }};
function _rememberedEmailAccountId() {{ return 'disabled-remembered'; }}
{chooser}
const onlyDisabled = _chooseEmailPrewarmAccountId([
{{ id: 'disabled-remembered', enabled: false, is_default: true }},
{{ id: 'disabled-current', enabled: false }},
]);
const empty = _chooseEmailPrewarmAccountId([]);
const mixed = _chooseEmailPrewarmAccountId([
{{ id: 'disabled-remembered', enabled: false, is_default: true }},
{{ id: 'enabled-default', enabled: true, is_default: true }},
]);
console.log(JSON.stringify({{ onlyDisabled, empty, mixed }}));
"""
proc = subprocess.run(
[node, "--input-type=module"],
input=script,
capture_output=True,
text=True,
cwd=str(_REPO),
timeout=30,
)
assert proc.returncode == 0, proc.stderr
assert json.loads(proc.stdout.strip()) == {
"onlyDisabled": "",
"empty": "",
"mixed": "enabled-default",
}
ensure_accounts = _function_source("_ensureEmailAccountsForPrewarm")
assert "if (!accountId) return null;" in ensure_accounts
assert ensure_accounts.index("if (!accountId) return null;") < ensure_accounts.index("_publishActiveAccount();")
def test_prewarm_is_bounded_to_the_interactive_initial_page_size():
text = _source()
prewarm = _function_source("_prewarmEmailViews")
assert "const _LIB_INITIAL_PAGE_SIZE = 100;" in text
assert "limit: _LIB_INITIAL_PAGE_SIZE" in prewarm
assert text.count("limit=${_LIB_INITIAL_PAGE_SIZE}&offset=${offsetAtStart}") == 2
assert "limit: 100" not in prewarm
def test_open_cancels_scheduled_or_inflight_prewarm_first():
text = _source()
cancel = _function_source("_cancelEmailPrewarm")
open_library = _function_source("openEmailLibrary")
assert "clearTimeout(_libPrewarmDelayTimer)" in cancel
assert "window.cancelIdleCallback(_libPrewarmIdleHandle)" in cancel
assert "_libPrewarmAbortController?.abort()" in cancel
assert "_libPrewarmGeneration += 1" in cancel
assert open_library.index("_cancelEmailPrewarm();") < open_library.index("state._libOpen = true;")
assert "_loadEmailsWhenChatIdle" not in text
assert text.count("_loadEmails({ useCache: true });") >= 2
def test_close_cancels_pending_prewarm_cleanup():
close_library = _function_source("closeEmailLibrary")
assert close_library.index("_cancelEmailPrewarm();") < close_library.index("state._libOpen = false;")
def test_unread_warm_joins_the_same_idle_single_flight_gate():
unread_entry = _function_source("prewarmUnreadEmails")
unread_work = _function_source("_prewarmUnreadEmailsNow")
assert "_scheduleEmailPrewarm(" in unread_entry
assert "fetch(" not in unread_entry
assert "_ensureEmailAccountsForPrewarm({ signal, generation })" in unread_work
assert "signal" in unread_work
assert "Math.min(20" in unread_work