UNPKG

@convex-dev/workpool

Version:

A Convex component for managing async work.

82 lines 3.16 kB
import { v } from "convex/values"; import { internalMutation } from "./_generated/server.js"; import { kickMainLoop } from "./kick.js"; import { createLogger } from "./logging.js"; import { vResultValidator } from "./shared.js"; import { recordCompleted } from "./stats.js"; export const completeArgs = v.object({ jobs: v.array(v.object({ runResult: vResultValidator, workId: v.id("work"), attempt: v.number(), })), }); export async function completeHandler(ctx, args) { const globals = await ctx.db.query("globals").unique(); const console = createLogger(globals?.logLevel); const pendingCompletions = []; await Promise.all(args.jobs.map(async (job) => { const work = await ctx.db.get(job.workId); if (!work) { console.warn(`[complete] ${job.workId} is done, but its work is gone`); return; } if (work.attempts !== job.attempt) { console.warn(`[complete] ${job.workId} mismatched attempt number`); return; } work.attempts++; await ctx.db.patch(work._id, { attempts: work.attempts }); const pendingCompletion = await ctx.db .query("pendingCompletion") .withIndex("workId", (q) => q.eq("workId", job.workId)) .unique(); if (pendingCompletion) { console.warn(`[complete] ${job.workId} already in pendingCompletion`); return; } const maxAttempts = work.retryBehavior?.maxAttempts; const retry = job.runResult.kind === "failed" && !!maxAttempts && work.attempts < maxAttempts; if (!retry) { if (work.onComplete) { try { const handle = work.onComplete.fnHandle; await ctx.runMutation(handle, { workId: work._id, context: work.onComplete.context, result: job.runResult, }); console.debug(`[complete] onComplete for ${job.workId} completed`); } catch (e) { console.error(`[complete] error running onComplete for ${job.workId}`, e); // TODO: store failures in a table for later debugging } } recordCompleted(console, work, job.runResult.kind); // This is the terminating state for work. await ctx.db.delete(job.workId); } if (job.runResult.kind !== "canceled") { pendingCompletions.push({ runResult: job.runResult, workId: job.workId, retry, }); } })); if (pendingCompletions.length > 0) { const segment = await kickMainLoop(ctx, "complete"); await Promise.all(pendingCompletions.map((completion) => ctx.db.insert("pendingCompletion", { ...completion, segment, }))); } } export const complete = internalMutation({ args: completeArgs, handler: completeHandler, }); //# sourceMappingURL=complete.js.map