Fix cron overlap skipping by persisting executionJobId#21
Fix cron overlap skipping by persisting executionJobId#21leventov wants to merge 1 commit intoget-convex:mainfrom
Conversation
📝 WalkthroughWalkthroughThis change enhances a scheduling system by adding an optional parameter to the Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/component/public.ts (2)
296-315: Consider consolidating the twoscheduleNextRuncalls for readability.The early
returninside theelseblock means line 315 is only reachable whenstillRunningis true, which readers must mentally verify. MovingscheduleNextRuninside each branch eliminates the implicit fall-through and makes the two paths self-contained:♻️ Proposed refactor
if (stillRunning) { console.log(`Cron ${cronJob._id} still running, skipping this run.`); + await scheduleNextRun(ctx, id, new Date(schedulerJob.scheduledTime), cronJob.schedule); } else { console.log(`Running cron ${cronJob._id}.`); const executionJobId = await ctx.scheduler.runAfter( 0, cronJob.functionHandle as FunctionHandle<"mutation" | "action">, cronJob.args, ); await scheduleNextRun( ctx, id, new Date(schedulerJob.scheduledTime), cronJob.schedule, { executionJobId }, ); - return; } - - await scheduleNextRun(ctx, id, new Date(schedulerJob.scheduledTime), cronJob.schedule); },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/component/public.ts` around lines 296 - 315, The two calls to scheduleNextRun are split between the else branch (after creating executionJobId) and after the if/else, making control flow implicit; refactor so each branch calls scheduleNextRun explicitly: in the stillRunning branch call await scheduleNextRun(ctx, id, new Date(schedulerJob.scheduledTime), cronJob.schedule) and in the running branch call await scheduleNextRun(ctx, id, new Date(schedulerJob.scheduledTime), cronJob.schedule, { executionJobId }) immediately after obtaining executionJobId from ctx.scheduler.runAfter; keep use of cronJob._id, stillRunning, ctx.scheduler.runAfter, executionJobId, and scheduleNextRun unchanged.
128-136:Partial<...>type permits silent field removal viadb.patch.
Partial<Pick<Doc<"crons">, "executionJobId">>allows callers to pass{ executionJobId: undefined }. In Convex, passing{ a: undefined }todb.patchremoves the field from the document, while passing{}does not change it — so that variant would silently wipeexecutionJobIdrather than being a no-op.No current call site does this, but the type signature permits it. Use a non-optional field type in the patch object to close the gap:
♻️ Proposed fix: tighten the type to eliminate the footgun
async function scheduleNextRun( ctx: MutationCtx, id: Id<"crons">, lastScheduled: Date, schedule: Schedule, - extraPatch?: Partial<Pick<Doc<"crons">, "executionJobId">>, + executionJobId?: Id<"_scheduled_functions">, ) { const nextRun = calculateNextRun(lastScheduled, schedule); const schedulerJobId = await ctx.scheduler.runAt( nextRun, internal.public.rescheduler, { id }, ); - await ctx.db.patch(id, { schedulerJobId, ...extraPatch }); + await ctx.db.patch(id, { schedulerJobId, ...(executionJobId !== undefined && { executionJobId }) }); }And update the call site in
rescheduler:- await scheduleNextRun( - ctx, - id, - new Date(schedulerJob.scheduledTime), - cronJob.schedule, - { executionJobId }, - ); + await scheduleNextRun(ctx, id, new Date(schedulerJob.scheduledTime), cronJob.schedule, executionJobId);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/component/public.ts` around lines 128 - 136, The extraPatch parameter type currently allows callers to pass { executionJobId: undefined } which Convex interprets as deleting the field; replace the optional Partial<Pick<Doc<"crons">, "executionJobId">> with an explicit optional property that forbids undefined values (e.g. extraPatch?: { executionJobId?: Doc<"crons">["executionJobId"] }), update any call sites such as the rescheduler invocation to pass either a concrete executionJobId value or omit the property, and ensure the ctx.db.patch(id, { schedulerJobId, ...extraPatch }) call only receives defined executionJobId values so fields are not accidentally removed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/component/public.ts`:
- Around line 296-315: The two calls to scheduleNextRun are split between the
else branch (after creating executionJobId) and after the if/else, making
control flow implicit; refactor so each branch calls scheduleNextRun explicitly:
in the stillRunning branch call await scheduleNextRun(ctx, id, new
Date(schedulerJob.scheduledTime), cronJob.schedule) and in the running branch
call await scheduleNextRun(ctx, id, new Date(schedulerJob.scheduledTime),
cronJob.schedule, { executionJobId }) immediately after obtaining executionJobId
from ctx.scheduler.runAfter; keep use of cronJob._id, stillRunning,
ctx.scheduler.runAfter, executionJobId, and scheduleNextRun unchanged.
- Around line 128-136: The extraPatch parameter type currently allows callers to
pass { executionJobId: undefined } which Convex interprets as deleting the
field; replace the optional Partial<Pick<Doc<"crons">, "executionJobId">> with
an explicit optional property that forbids undefined values (e.g. extraPatch?: {
executionJobId?: Doc<"crons">["executionJobId"] }), update any call sites such
as the rescheduler invocation to pass either a concrete executionJobId value or
omit the property, and ensure the ctx.db.patch(id, { schedulerJobId,
...extraPatch }) call only receives defined executionJobId values so fields are
not accidentally removed.
Problem
reschedulertries to prevent overlapping cron executions by checkingcron.executionJobIdagainst_scheduled_functions(skip if the previous execution is stillpending/inProgress). However, the component never persisted the job id returned fromctx.scheduler.runAfter(...), soexecutionJobIdstayed unset and the “still running, skipping” logic nevertriggered.
Change
runAfter.executionJobId(insrc/component/public.ts), alongside scheduling the nextschedulerJobId.Notes
delalready attempts to cancelexecutionJobId; this makes that cancellation effective too.Summary by CodeRabbit