Cron Scheduling¶
Schedule tasks to run automatically using cron expressions, either statically at startup or dynamically at runtime.
Static Cron Schedules¶
Define cron schedules when creating tasks:
const dailyReport = conductor.createTask(
{ name: "daily-report" },
{ cron: "0 9 * * *", name: "morning-report" }, // 9 AM UTC daily
async (event, ctx) => {
ctx.logger.info("Generating daily report");
// Generate report
}
);
Cron expressions are evaluated in UTC, regardless of the timezone of the worker process.
The orchestrator schedules the next execution when an execution starts, before its handler runs, so a failing or retrying execution does not stop the schedule. It does so once per execution: a retried attempt keeps the next execution the first attempt scheduled, and a running execution is never superseded.
When a worker starts, it replaces its queue's static schedules with the ones its tasks declare: a changed expression replaces the pending execution, and removed triggers and tasks are unscheduled. Every orchestrator polling a queue must therefore run the same task set (see One Queue, One Codebase). During a rolling deploy, an old replica that restarts removes the new version's schedules until a new replica starts again.
Multiple Schedules & Event Handling¶
A task can have multiple cron schedules and be invocable. Use event.name to distinguish between triggers:
const notification = conductor.createTask(
{ name: "send-digest" },
[
{ invocable: true },
{ cron: "0 9 * * *", name: "morning" }, // 9 AM
{ cron: "0 17 * * *", name: "evening" }, // 5 PM
],
async (event, ctx) => {
if (event.name === "morning") {
ctx.logger.info("Sending morning digest");
// Morning-specific logic
} else if (event.name === "evening") {
ctx.logger.info("Sending evening digest");
// Evening-specific logic
} else if (event.name === "pgconductor.invoke") {
// Manually invoked
const payload = event.payload;
}
}
);
Each schedule runs independently. The event.name property contains the schedule name for cron triggers or "pgconductor.invoke" for manual invocations.
Dynamic Scheduling¶
Create and manage cron schedules at runtime using ctx.schedule():
const userPreferences = conductor.createTask(
{ name: "apply-preferences" },
{ invocable: true },
async (event, ctx) => {
const { userId, reportTime } = event.payload;
// Schedule personalized report
await ctx.schedule(
{ name: "user-report" },
`user-${userId}`, // Unique schedule name
{ cron: `0 ${reportTime} * * *` }, // Custom time
{ userId } // Payload for scheduled task
);
}
);
Parameters:
await ctx.schedule(
taskRef, // { name: "task-name", queue?: "queue-name" }
scheduleName, // Unique identifier for this schedule
cronOptions, // { cron: "expression" }
payload // Payload passed to task when it runs
);
Every occurrence of a dynamic schedule runs its task like an invocation: event.name is "pgconductor.invoke" and event.payload is the scheduled payload, validated against the task's payload schema. Its priority and group options also apply to every occurrence.
Dynamic schedules are kept separately from static cron triggers: they survive worker restarts and deployments, and a dynamic schedule never replaces a static trigger with the same name. They run until removed with ctx.unschedule().
Unschedule¶
Remove a dynamic schedule:
const cancelReport = conductor.createTask(
{ name: "cancel-user-report" },
{ invocable: true },
async (event, ctx) => {
const { userId } = event.payload;
// Remove the schedule
await ctx.unschedule(
{ name: "user-report" },
`user-${userId}`
);
}
);
This deletes the pending scheduled execution and cancels a running one, which then schedules no further execution.
Schedule Names¶
Schedule names must be unique per task:
// ✅ Good - unique names
await ctx.schedule({ name: "task" }, "schedule-1", { cron: "..." }, {});
await ctx.schedule({ name: "task" }, "schedule-2", { cron: "..." }, {});
// ❌ Bad - duplicate name overwrites
await ctx.schedule({ name: "task" }, "schedule-1", { cron: "0 9 * * *" }, {});
await ctx.schedule({ name: "task" }, "schedule-1", { cron: "0 17 * * *" }, {}); // Overwrites
Overwriting replaces the pending execution of the schedule. One that is already running is not interrupted, but schedules no further execution: the schedule continues with the new cron expression only.
Deduplication¶
Multiple workers with the same cron schedule won't create duplicates:
// Worker 1
const orch1 = Orchestrator.create({
conductor,
tasks: [cronTask],
});
// Worker 2 - same task, same schedule
const orch2 = Orchestrator.create({
conductor,
tasks: [cronTask],
});
// Only one scheduled execution exists in database
What's Next?¶
- Delayed Execution - One-time scheduled tasks
- Task Triggers - Understand trigger types