67 lines
2.3 KiB
TypeScript
67 lines
2.3 KiB
TypeScript
import "server-only";
|
|
import { after } from "next/server";
|
|
|
|
const workerKey = "__generated_image_archive_worker__";
|
|
type Worker = {
|
|
running: boolean;
|
|
scheduled: boolean;
|
|
requested: boolean;
|
|
retryTimer?: ReturnType<typeof setTimeout>;
|
|
};
|
|
function getWorker(): Worker {
|
|
const root = globalThis as typeof globalThis & { [workerKey]?: Worker };
|
|
return (root[workerKey] ??= { running: false, scheduled: false, requested: false });
|
|
}
|
|
|
|
export function scheduleImageArchiveWorker(deliveryConfirmed = false) {
|
|
const worker = getWorker();
|
|
worker.requested = true;
|
|
if (worker.running || (worker.scheduled && !deliveryConfirmed)) return;
|
|
worker.scheduled = true;
|
|
if (worker.retryTimer) {
|
|
clearTimeout(worker.retryTimer);
|
|
worker.retryTimer = undefined;
|
|
}
|
|
const run = async () => {
|
|
if (worker.running) return;
|
|
worker.scheduled = false;
|
|
worker.running = true;
|
|
let retryNeeded = false;
|
|
try {
|
|
const { runImageArchiveJob } = await import("@/lib/jobs/image-archive-job");
|
|
do {
|
|
worker.requested = false;
|
|
const result = await runImageArchiveJob();
|
|
retryNeeded = result.stats.pendingStore > 0 || result.stats.pendingReplace > 0;
|
|
// Stop when only backoff tasks remain; do not spin while storage is down.
|
|
if (!worker.requested && result.store.total === 0 && result.replace.total === 0) break;
|
|
} while (true);
|
|
} catch (error) {
|
|
retryNeeded = true;
|
|
console.error("[ImageArchiveWorker] background archive failed", error);
|
|
} finally {
|
|
worker.running = false;
|
|
if (worker.requested) scheduleImageArchiveWorker();
|
|
else if (retryNeeded) {
|
|
worker.retryTimer = setTimeout(scheduleImageArchiveWorker, 60_000);
|
|
worker.retryTimer.unref?.();
|
|
}
|
|
}
|
|
};
|
|
// Detached generation tasks may inherit a request that has already closed. Keep
|
|
// a Node-server fallback so archiving never depends on the user leaving a tab open.
|
|
const deliveryFallback = setTimeout(() => {
|
|
if (worker.scheduled && !worker.running) void run();
|
|
}, 1_000);
|
|
deliveryFallback.unref?.();
|
|
try {
|
|
// Request handlers deliver first. Durable task rows also allow cron/restart recovery.
|
|
after(run);
|
|
} catch {
|
|
// Durable canvas tasks can run outside Next's request context on the Node server.
|
|
setImmediate(() => {
|
|
void run();
|
|
});
|
|
}
|
|
}
|