Keep HTTP responsive during confluence replay.
CI / Test (push) Canceled after 0s
CI / Build and push (push) Canceled after 0s

Boot replay was a tight CPU loop that wedged the Unraid backend, so login hit the 8s client timeout. Yield every few as-of dates, shorten the boot ledger fill, and make /health a cheap liveness check.
This commit is contained in:
Investor Flow Build
2026-09-09 10:16:36 -04:00
parent 7426103d73
commit 0b901fc190
3 changed files with 12 additions and 3 deletions
@@ -301,7 +301,12 @@ export async function runConfluenceReplay(
const weeklyFull = await provider.resolveAsOf(symbol, '1wk', '9999-12-31'); const weeklyFull = await provider.resolveAsOf(symbol, '1wk', '9999-12-31');
const benchFull = await provider.resolveAsOf(BENCHMARK_SYMBOL, '1d', '9999-12-31'); const benchFull = await provider.resolveAsOf(BENCHMARK_SYMBOL, '1d', '9999-12-31');
let asOfN = 0;
for (const asOf of slice) { for (const asOf of slice) {
asOfN += 1;
if (asOfN % 4 === 0) {
await new Promise<void>((r) => setImmediate(r));
}
const dailyCut = daily.candles.filter((c) => (c.ts ?? '').slice(0, 10) <= asOf); const dailyCut = daily.candles.filter((c) => (c.ts ?? '').slice(0, 10) <= asOf);
const weeklyCut = weeklyFull.candles.filter((c) => (c.ts ?? '').slice(0, 10) <= asOf); const weeklyCut = weeklyFull.candles.filter((c) => (c.ts ?? '').slice(0, 10) <= asOf);
const benchCut = benchFull.candles.filter((c) => (c.ts ?? '').slice(0, 10) <= asOf); const benchCut = benchFull.candles.filter((c) => (c.ts ?? '').slice(0, 10) <= asOf);
@@ -391,6 +396,7 @@ export async function fillLearningLedger(
complete: batch.complete, complete: batch.complete,
}; };
if (batch.evaluationsStored === 0) break; if (batch.evaluationsStored === 0) break;
await new Promise<void>((r) => setImmediate(r));
} }
return { ...last, ledger: await learningLedgerStatus(db, cache) }; return { ...last, ledger: await learningLedgerStatus(db, cache) };
} }
+4 -2
View File
@@ -264,7 +264,7 @@ async function confluenceTick(label: string): Promise<void> {
} = await import('./confluence/confluenceEngine.ts'); } = await import('./confluence/confluenceEngine.ts');
const { CONFLUENCE_LEARNING_UNIVERSE } = await import('./confluence/confluenceSeed.ts'); const { CONFLUENCE_LEARNING_UNIVERSE } = await import('./confluence/confluenceSeed.ts');
const replay = label === 'boot' const replay = label === 'boot'
? await fillLearningLedger(database, cache, 75_000) ? await fillLearningLedger(database, cache, 12_000)
: await runConfluenceReplay(database, cache, { : await runConfluenceReplay(database, cache, {
symbols: CONFLUENCE_LEARNING_UNIVERSE, symbols: CONFLUENCE_LEARNING_UNIVERSE,
symbolsPerTick: LEARNING_REPLAY_SYMBOLS, symbolsPerTick: LEARNING_REPLAY_SYMBOLS,
@@ -331,7 +331,9 @@ const server = createServer(async (req, res) => {
const url = new URL(req.url ?? '/', `http://localhost:${PORT}`); const url = new URL(req.url ?? '/', `http://localhost:${PORT}`);
if (url.pathname === '/health') { if (url.pathname === '/health') {
res.writeHead(200, { 'content-type': 'application/json' }); res.writeHead(200, { 'content-type': 'application/json' });
res.end(JSON.stringify({ ok: true, queue: queue.health() })); // Cheap liveness only. queue.health() is a full SQLite scan and must not
// ride the docker healthcheck / login path.
res.end(JSON.stringify({ ok: true }));
return; return;
} }
if (url.pathname.startsWith('/api/trpc')) { if (url.pathname.startsWith('/api/trpc')) {
+2 -1
View File
@@ -1008,7 +1008,8 @@ export const api = {
}, },
auth: { auth: {
signup: (email: string, password: string) => trpcMutate<{ userId: string }>("auth.signup", { email, password }), signup: (email: string, password: string) => trpcMutate<{ userId: string }>("auth.signup", { email, password }),
login: (email: string, password: string, totp?: string) => trpcMutate<{ userId: string }>("auth.login", { email, password, ...(totp ? { totp } : {}) }), login: (email: string, password: string, totp?: string) =>
trpcMutate<{ userId: string }>("auth.login", { email, password, ...(totp ? { totp } : {}) }, 20_000),
logout: () => trpcMutate<{ ok: boolean }>("auth.logout", {}), logout: () => trpcMutate<{ ok: boolean }>("auth.logout", {}),
changePassword: (current: string, next: string) => changePassword: (current: string, next: string) =>
trpcMutate<{ ok: boolean }>("auth.changePassword", { current, next }), trpcMutate<{ ok: boolean }>("auth.changePassword", { current, next }),