From fbb6f484ce183796d70e40a2a091c918805a6a1f Mon Sep 17 00:00:00 2001 From: Richard R Date: Thu, 21 May 2026 16:15:34 -0600 Subject: [PATCH] compute: centralize timeout config in compute-core and use in worker --- compute/core/package.json | 3 +- compute/core/src/runtime/timeout-config.ts | 39 ++++++++++++++++++++++ compute/worker/src/server.ts | 12 ++++--- 3 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 compute/core/src/runtime/timeout-config.ts diff --git a/compute/core/package.json b/compute/core/package.json index 9141f07..af7968f 100644 --- a/compute/core/package.json +++ b/compute/core/package.json @@ -14,6 +14,7 @@ "exports": { ".": "./src/index.ts", "./contracts": "./src/contracts.ts", - "./local-runtime": "./src/local-runtime.ts" + "./local-runtime": "./src/local-runtime.ts", + "./runtime/timeout-config": "./src/runtime/timeout-config.ts" } } diff --git a/compute/core/src/runtime/timeout-config.ts b/compute/core/src/runtime/timeout-config.ts new file mode 100644 index 0000000..00b1027 --- /dev/null +++ b/compute/core/src/runtime/timeout-config.ts @@ -0,0 +1,39 @@ +const DEFAULT_COMPUTE_WHISPER_TIMEOUT_MS = 30_000; +const DEFAULT_COMPUTE_PDF_TIMEOUT_MS = 300_000; +const DEFAULT_COMPUTE_PDF_HARD_CAP_MS = 24 * 60 * 60 * 1000; +const DEFAULT_WORKER_WAIT_BUFFER_MS = 15_000; +const DEFAULT_WORKER_WAIT_MIN_MS = 60_000; + +export type ComputeTimeoutConfig = { + whisperTimeoutMs: number; + pdfTimeoutMs: number; + pdfHardCapMs: number; +}; + +export type ComputeOperationKind = 'whisper_align' | 'pdf_layout'; + +function readPositiveIntEnv(name: string, fallback: number): number { + const raw = process.env[name]?.trim(); + if (!raw) return fallback; + const parsed = Number(raw); + if (!Number.isFinite(parsed) || parsed <= 0) return fallback; + return Math.floor(parsed); +} + +let timeoutConfigCache: ComputeTimeoutConfig | null = null; + +export function getComputeTimeoutConfig(): ComputeTimeoutConfig { + if (timeoutConfigCache) return timeoutConfigCache; + timeoutConfigCache = { + whisperTimeoutMs: readPositiveIntEnv('COMPUTE_WHISPER_TIMEOUT_MS', DEFAULT_COMPUTE_WHISPER_TIMEOUT_MS), + pdfTimeoutMs: readPositiveIntEnv('COMPUTE_PDF_TIMEOUT_MS', DEFAULT_COMPUTE_PDF_TIMEOUT_MS), + pdfHardCapMs: DEFAULT_COMPUTE_PDF_HARD_CAP_MS, + }; + return timeoutConfigCache; +} + +export function getWorkerClientWaitTimeoutMs(kind: ComputeOperationKind): number { + const config = getComputeTimeoutConfig(); + const timeoutMs = kind === 'pdf_layout' ? config.pdfTimeoutMs : config.whisperTimeoutMs; + return Math.max(DEFAULT_WORKER_WAIT_MIN_MS, timeoutMs + DEFAULT_WORKER_WAIT_BUFFER_MS); +} diff --git a/compute/worker/src/server.ts b/compute/worker/src/server.ts index 8085c95..17c2c55 100644 --- a/compute/worker/src/server.ts +++ b/compute/worker/src/server.ts @@ -27,6 +27,7 @@ import { runPdfLayoutFromPdfBuffer, runWhisperAlignmentFromAudioBuffer, } from '@openreader/compute-core/local-runtime'; +import { getComputeTimeoutConfig } from '@openreader/compute-core/runtime/timeout-config'; import { type PdfLayoutJobRequest, type PdfLayoutJobResult, @@ -56,7 +57,6 @@ const RUNNING_HEARTBEAT_MS = 5000; const DOCUMENT_ID_REGEX = /^[a-f0-9]{64}$/i; const SAFE_NAMESPACE_REGEX = /^[a-zA-Z0-9._-]{1,128}$/; const WHISPER_MAX_DELIVER = 1; -const PDF_LAYOUT_HARD_CAP_MS = 24 * 60 * 60 * 1000; const NATS_API_TIMEOUT_MS = 60_000; interface QueuedJob { @@ -472,10 +472,12 @@ async function main(): Promise { const host = process.env.COMPUTE_WORKER_HOST?.trim() || '0.0.0.0'; const workerToken = requireEnv('COMPUTE_WORKER_TOKEN'); const natsUrl = requireEnv('NATS_URL'); + const timeoutConfig = getComputeTimeoutConfig(); const jobConcurrency = readIntEnv('COMPUTE_JOB_CONCURRENCY', 1); - const whisperTimeoutMs = readIntEnv('COMPUTE_WHISPER_TIMEOUT_MS', 30_000); - const pdfTimeoutMs = readIntEnv('COMPUTE_PDF_TIMEOUT_MS', 5 * 60_000); + const whisperTimeoutMs = timeoutConfig.whisperTimeoutMs; + const pdfTimeoutMs = timeoutConfig.pdfTimeoutMs; + const pdfHardCapMs = timeoutConfig.pdfHardCapMs; const pdfAttempts = readIntEnv('COMPUTE_PDF_JOB_ATTEMPTS', 1); const prewarmModels = parseBoolEnv('COMPUTE_PREWARM_MODELS', true); const jobsStreamMaxBytes = readIntEnv('COMPUTE_JOBS_STREAM_MAX_BYTES', 256 * 1024 * 1024); @@ -560,7 +562,7 @@ async function main(): Promise { availableCpuCores: getAvailableCpuCores(), onnxThreadsPerJob: getOnnxThreadsPerJob(jobConcurrency), natsApiTimeoutMs: NATS_API_TIMEOUT_MS, - pdfLayoutHardCapMs: PDF_LAYOUT_HARD_CAP_MS, + pdfLayoutHardCapMs: pdfHardCapMs, }, 'compute runtime config'); const opIndexCodec = createJsonCodec(); @@ -914,7 +916,7 @@ async function main(): Promise { const computeStartedAt = Date.now(); const result = await withIdleTimeoutAndHardCap({ idleTimeoutMs: Math.max(pdfTimeoutMs, 1_000), - hardCapMs: PDF_LAYOUT_HARD_CAP_MS, + hardCapMs: pdfHardCapMs, label: 'pdf layout job', run: async (touchProgress) => runPdfLayoutFromPdfBuffer({ documentId: parsed.documentId,