UNPKG

@convex-dev/workpool

Version:

A Convex component for managing async work.

102 lines 4.17 kB
import { internal } from "./_generated/api.js"; import { internalMutation } from "./_generated/server.js"; import { createLogger, DEFAULT_LOG_LEVEL } from "./logging.js"; import { INITIAL_STATE } from "./loop.js"; import { boundScheduledTime, DEFAULT_MAX_PARALLELISM, fromSegment, getCurrentSegment, getNextSegment, SECOND, toSegment, } from "./shared.js"; /** * Called from outside the loop. * Returns the soonest segment to enqueue work for the main loop. */ export async function kickMainLoop(ctx, source, config) { const globals = await getOrUpdateGlobals(ctx, config); const console = createLogger(globals.logLevel); const runStatus = await getOrCreateRunStatus(ctx); const next = getNextSegment(); // Only kick to run now if we're scheduled or idle. if (runStatus.state.kind === "running") { console.debug(`[${source}] main is actively running, so we don't need to kick it`); return next; } // main is scheduled to run later, so we should cancel it and reschedule. if (runStatus.state.kind === "scheduled") { if (source === "enqueue" && runStatus.state.saturated) { console.debug(`[${source}] main is saturated, so we don't need to kick it`); return next; } if (runStatus.state.segment <= toSegment(Date.now() + SECOND)) { console.debug(`[${source}] main is scheduled to run soon enough, so we don't need to kick it`); return next; } console.debug(`[${source}] main is scheduled to run later, so reschedule it to run now`); const scheduled = await ctx.db.system.get(runStatus.state.scheduledId); if (scheduled && scheduled.state.kind === "pending") { await ctx.scheduler.cancel(runStatus.state.scheduledId); } else { console.warn(`[${source}] main is marked as scheduled, but it's status is ${scheduled?.state.kind}`); } } else if (runStatus.state.kind === "idle") { console.debug(`[${source}] main was idle, so run it now`); } await ctx.db.patch(runStatus._id, { state: { kind: "running" } }); const current = getCurrentSegment(); const scheduledTime = boundScheduledTime(fromSegment(current), console); await ctx.scheduler.runAt(scheduledTime, internal.loop.main, { generation: runStatus.state.generation, segment: current, }); return current; } export const forceKick = internalMutation({ args: {}, handler: async (ctx) => { const runStatus = await getOrCreateRunStatus(ctx); await ctx.db.delete(runStatus._id); await kickMainLoop(ctx, "kick"); }, }); async function getOrCreateRunStatus(ctx) { let runStatus = await ctx.db.query("runStatus").unique(); if (!runStatus) { const state = await ctx.db.query("internalState").unique(); const id = await ctx.db.insert("runStatus", { state: { kind: "idle", generation: state?.generation ?? INITIAL_STATE.generation, }, }); runStatus = (await ctx.db.get(id)); if (!state) { await ctx.db.insert("internalState", INITIAL_STATE); } } return runStatus; } async function getOrUpdateGlobals(ctx, config) { const globals = await ctx.db.query("globals").unique(); if (!globals) { const id = await ctx.db.insert("globals", { maxParallelism: config?.maxParallelism ?? DEFAULT_MAX_PARALLELISM, logLevel: config?.logLevel ?? DEFAULT_LOG_LEVEL, }); return (await ctx.db.get(id)); } else if (config) { let updated = false; if (config.maxParallelism && config.maxParallelism !== globals.maxParallelism) { globals.maxParallelism = config.maxParallelism; updated = true; } if (config.logLevel && config.logLevel !== globals.logLevel) { globals.logLevel = config.logLevel; updated = true; } if (updated) { await ctx.db.replace(globals._id, globals); } } return globals; } //# sourceMappingURL=kick.js.map