fix(compute): migrate compute backend initialization to async and update consumers

Refactor compute backend initialization to use asynchronous loading and promise caching, replacing previous synchronous singleton pattern. Update all consumers—including TTS segment alignment and PDF layout parsing jobs—to await compute backend resolution before invoking methods. This change improves compatibility with dynamic imports and future-proofs backend selection logic.
This commit is contained in:
Richard R 2026-05-19 19:38:36 -06:00
parent e52b754106
commit 01cb95d8e7
3 changed files with 22 additions and 12 deletions

View file

@ -150,6 +150,11 @@ async function deleteEntryIfUnused(userId: string, segmentEntryId: string): Prom
export async function POST(request: NextRequest) { export async function POST(request: NextRequest) {
let didCreateDeviceIdCookie = false; let didCreateDeviceIdCookie = false;
let deviceIdToSet: string | null = null; let deviceIdToSet: string | null = null;
let computeBackendPromise: ReturnType<typeof getCompute> | null = null;
const getComputeBackend = async () => {
if (!computeBackendPromise) computeBackendPromise = getCompute();
return computeBackendPromise;
};
try { try {
if (!isS3Configured()) return s3NotConfiguredResponse(); if (!isS3Configured()) return s3NotConfiguredResponse();
@ -372,7 +377,8 @@ export async function POST(request: NextRequest) {
// previously unavailable, retry alignment using the current segment text. // previously unavailable, retry alignment using the current segment text.
if (!alignment) { if (!alignment) {
try { try {
const aligned = (await getCompute().alignWords({ const computeBackend = await getComputeBackend();
const aligned = (await computeBackend.alignWords({
audioObjectKey: existing.audioKey, audioObjectKey: existing.audioKey,
text: segment.text, text: segment.text,
})).alignments; })).alignments;
@ -523,7 +529,8 @@ export async function POST(request: NextRequest) {
let alignment: TTSSegmentManifestItem['alignment'] = null; let alignment: TTSSegmentManifestItem['alignment'] = null;
try { try {
const whisperBytes = Uint8Array.from(persistedBuffer); const whisperBytes = Uint8Array.from(persistedBuffer);
const aligned = (await getCompute().alignWords({ const computeBackend = await getComputeBackend();
const aligned = (await computeBackend.alignWords({
audioBuffer: whisperBytes.buffer, audioBuffer: whisperBytes.buffer,
audioObjectKey: audioKey, audioObjectKey: audioKey,
text: segment.text, text: segment.text,

View file

@ -2,21 +2,23 @@ import type { ComputeBackend, ComputeMode } from '@/lib/server/compute/types';
import { isComputeModeAvailable, readComputeMode } from '@/lib/server/compute/mode'; import { isComputeModeAvailable, readComputeMode } from '@/lib/server/compute/mode';
import { WorkerComputeBackend } from '@/lib/server/compute/worker'; import { WorkerComputeBackend } from '@/lib/server/compute/worker';
let backend: ComputeBackend | null = null; let backendPromise: Promise<ComputeBackend> | null = null;
function createBackend(): ComputeBackend { async function createBackend(): Promise<ComputeBackend> {
const mode: ComputeMode = readComputeMode(); const mode: ComputeMode = readComputeMode();
if (mode === 'worker') return new WorkerComputeBackend(); if (mode === 'worker') return new WorkerComputeBackend();
// Intentionally lazy-load local compute to avoid tracing heavy ONNX const { LocalComputeBackend } = await import('@/lib/server/compute/local');
// dependencies unless the backend is actually local.
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { LocalComputeBackend } = require('@/lib/server/compute/local') as typeof import('@/lib/server/compute/local');
return new LocalComputeBackend(); return new LocalComputeBackend();
} }
export function getCompute(): ComputeBackend { export async function getCompute(): Promise<ComputeBackend> {
if (!backend) backend = createBackend(); if (!backendPromise) {
return backend; backendPromise = createBackend().catch((error) => {
backendPromise = null;
throw error;
});
}
return backendPromise;
} }
export function isComputeAvailable(): boolean { export function isComputeAvailable(): boolean {

View file

@ -28,7 +28,8 @@ export async function parsePdfJob(input: ParsePdfJobInput): Promise<void> {
.set({ parseStatus: 'running' }) .set({ parseStatus: 'running' })
.where(and(eq(documents.id, input.documentId), eq(documents.userId, input.userId))); .where(and(eq(documents.id, input.documentId), eq(documents.userId, input.userId)));
const parsed = await getCompute().parsePdfLayout({ const compute = await getCompute();
const parsed = await compute.parsePdfLayout({
documentId: input.documentId, documentId: input.documentId,
namespace: input.namespace, namespace: input.namespace,
documentObjectKey: documentKey(input.documentId, input.namespace), documentObjectKey: documentKey(input.documentId, input.namespace),