fix: IREN/ASTS/IRE stuck-pending + corridor feature
Fix stuck adapter_queue jobs (ASTS/IRE/IREN stuck pending forever): 1. fetchSpec early-return paths (cooldown checks) now update job status to 'backoff' with last_attempt set and 30s backoff_until, instead of returning without any status change. Prevents jobs from being re-processed every drain cycle indefinitely. 2. Wrap adapter.fetchOne() in 30s Promise.race timeout. A hung HTTP request no longer blocks the entire per-source promise chain forever, preventing all subsequent jobs for that source. Also includes corridor/confluence feature, tiered quote schedules, cache improvements, and HoldingsBookView refinements.
This commit is contained in:
+22
@@ -8,6 +8,7 @@ import { db as defaultDb } from '../db/client.ts';
|
||||
import {
|
||||
CANDLE_FRESH_MS,
|
||||
quoteTtlMs,
|
||||
tieredQuoteTtlMs,
|
||||
SYMBOL_META_INCOMPLETE_TTL_MS,
|
||||
} from '../queue/sourceRatePolicy.ts';
|
||||
import { KvReadCache } from './LruCache.ts';
|
||||
@@ -190,6 +191,13 @@ export function needsQuoteRefresh(d: DatabaseSync, symbol: string, now = Date.no
|
||||
return tsAgeMs(row.observed_at, now) > quoteTtlMs(new Date(now));
|
||||
}
|
||||
|
||||
/** Tier-aware quote freshness check. Portfolio (T0) gets the tightest TTL. */
|
||||
export function needsTieredQuoteRefresh(d: DatabaseSync, symbol: string, tier: number, now = Date.now()): boolean {
|
||||
const row = d.prepare('SELECT observed_at FROM quotes WHERE symbol=?').get(symbol) as { observed_at: string } | undefined;
|
||||
if (!row?.observed_at) return true;
|
||||
return tsAgeMs(row.observed_at, now) > tieredQuoteTtlMs(tier, new Date(now));
|
||||
}
|
||||
|
||||
/** True when symbol meta missing, incomplete (no name), or past weekly TTL. */
|
||||
export function needsSymbolMetaRefresh(d: DatabaseSync, symbol: string, now = Date.now()): boolean {
|
||||
const row = d.prepare('SELECT name, sector, updated_at FROM symbols WHERE symbol=?').get(symbol) as
|
||||
@@ -617,6 +625,8 @@ export interface CacheRepository {
|
||||
* Safe to call on every Market Outlook / ticker context load.
|
||||
*/
|
||||
ensureInDemand(symbol: string, tickerKind: TickerKind): Promise<void>;
|
||||
/** Bump symbol to watched tier (2) on page view. Decays back after 10 min. */
|
||||
bumpToWatched(symbol: string, tickerKind: TickerKind): Promise<void>;
|
||||
/** Permanent system pin (rotation universe, SPY, VIX) — survives unsubscribe. */
|
||||
pinSystemSymbol(symbol: string, tickerKind: TickerKind): Promise<void>;
|
||||
demandSet(): Promise<string[]>;
|
||||
@@ -735,6 +745,18 @@ export class CacheRepositoryImpl implements CacheRepository {
|
||||
await this.queueIfNeeded(symbol);
|
||||
}
|
||||
|
||||
/** Bump a symbol to watched tier (2) on page view. The periodic tier
|
||||
* recompute decays it back to background after ~10 min of inactivity. */
|
||||
async bumpToWatched(symbol: string, tickerKind: TickerKind): Promise<void> {
|
||||
this.ensureDemandRow(symbol, tickerKind);
|
||||
const now = new Date().toISOString();
|
||||
// Only lower tier (raise priority) — never raise tier above current.
|
||||
this._db.prepare(
|
||||
"UPDATE symbol_demand SET tier = MIN(tier, 2), last_viewed_at = ?, in_demand = 1 WHERE symbol=?",
|
||||
).run(now, symbol);
|
||||
await this.queueIfNeeded(symbol);
|
||||
}
|
||||
|
||||
async pinSystemSymbol(symbol: string, tickerKind: TickerKind): Promise<void> {
|
||||
this.ensureDemandRow(symbol, tickerKind);
|
||||
this._db.prepare(
|
||||
|
||||
Reference in New Issue
Block a user