Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 1.4k
feat: add ttl support at task and config levels#3196
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
424238aaec70595a68fa012d596713e7b4a4d99f68e90e211d8eaf31d0529baa36a9a0caacba8a788c9fb06c42d7f2bd9848670b68e970f0e660b96659c27eFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| "@trigger.dev/sdk": patch | ||
| "@trigger.dev/core": patch | ||
| "trigger.dev": patch | ||
| --- | ||
| Add support for setting TTL (time-to-live) defaults at the task level and globally in trigger.config.ts, with per-trigger overrides still taking precedence |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -73,19 +73,20 @@ export class DefaultQueueManager implements QueueManager { | ||
| ): Promise<QueueProperties> { | ||
| let queueName: string; | ||
| let lockedQueueId: string | undefined; | ||
| let taskTtl: string | null | undefined; | ||
| // Determine queue name based on lockToVersion and provided options | ||
| if (lockedBackgroundWorker) { | ||
| // Task is locked to a specific worker version | ||
| const specifiedQueueName = extractQueueName(request.body.options?.queue); | ||
| if (specifiedQueueName) { | ||
| // A specific queue name is provided | ||
| // A specific queue name is provided, validate it exists for the locked worker | ||
| const specifiedQueue = await this.prisma.taskQueue.findFirst({ | ||
| // Validate it exists for the locked worker | ||
| where: { | ||
| name: specifiedQueueName, | ||
| runtimeEnvironmentId: request.environment.id, | ||
| workers: { some: { id: lockedBackgroundWorker.id } }, // Ensure the queue is associated with any task of the locked worker | ||
| workers: { some: { id: lockedBackgroundWorker.id } }, | ||
| }, | ||
| }); | ||
| @@ -95,11 +96,26 @@ export class DefaultQueueManager implements QueueManager { | ||
| }'.` | ||
| ); | ||
| } | ||
| // Use the validated queue name directly | ||
| queueName = specifiedQueue.name; | ||
| lockedQueueId = specifiedQueue.id; | ||
| // Only fetch task for TTL if caller didn't provide a per-trigger TTL | ||
| if (request.body.options?.ttl === undefined) { | ||
| const lockedTask = await this.prisma.backgroundWorkerTask.findFirst({ | ||
| where: { | ||
| workerId: lockedBackgroundWorker.id, | ||
| runtimeEnvironmentId: request.environment.id, | ||
| slug: request.taskId, | ||
| }, | ||
| select: { ttl: true }, | ||
| }); | ||
| taskTtl = lockedTask?.ttl; | ||
| } | ||
| } else { | ||
| // No specific queue name provided, use the default queue for the task on the locked worker | ||
| // No queue override - fetch task with queue to get both default queue and TTL | ||
| const lockedTask = await this.prisma.backgroundWorkerTask.findFirst({ | ||
| where: { | ||
| workerId: lockedBackgroundWorker.id, | ||
| @@ -118,6 +134,8 @@ export class DefaultQueueManager implements QueueManager { | ||
| ); | ||
| } | ||
| taskTtl = lockedTask.ttl; | ||
| if (!lockedTask.queue) { | ||
| // This case should ideally be prevented by earlier checks or schema constraints, | ||
| // but handle it defensively. | ||
| @@ -131,6 +149,7 @@ export class DefaultQueueManager implements QueueManager { | ||
| }'.` | ||
| ); | ||
| } | ||
| // Use the task's default queue name | ||
| queueName = lockedTask.queue.name; | ||
| lockedQueueId = lockedTask.queue.id; | ||
| @@ -145,7 +164,9 @@ export class DefaultQueueManager implements QueueManager { | ||
| } | ||
| // Get queue name using the helper for non-locked case (handles provided name or finds default) | ||
| queueName = await this.getQueueName(request); | ||
| const taskInfo = await this.getTaskQueueInfo(request); | ||
| queueName = taskInfo.queueName; | ||
| taskTtl = taskInfo.taskTtl; | ||
| } | ||
| // Sanitize the final determined queue name once | ||
| @@ -161,21 +182,27 @@ export class DefaultQueueManager implements QueueManager { | ||
| return { | ||
| queueName, | ||
| lockedQueueId, | ||
| taskTtl, | ||
| }; | ||
| } | ||
| async getQueueName(request: TriggerTaskRequest): Promise<string> { | ||
| private async getTaskQueueInfo( | ||
| request: TriggerTaskRequest | ||
| ): Promise<{ queueName: string; taskTtl?: string | null }> { | ||
| const { taskId, environment, body } = request; | ||
| const { queue } = body.options ?? {}; | ||
| // Use extractQueueName to handle double-wrapped queue objects | ||
| const queueName = extractQueueName(queue); | ||
| if (queueName) { | ||
| return queueName; | ||
| } | ||
| const overriddenQueueName = extractQueueName(queue); | ||
| const defaultQueueName = `task/${taskId}`; | ||
| // When caller provides both a queue override and a per-trigger TTL, | ||
| // we don't need any DB queries - the per-trigger TTL takes precedence | ||
| if (overriddenQueueName && body.options?.ttl !== undefined) { | ||
| return { queueName: overriddenQueueName, taskTtl: undefined }; | ||
| } | ||
nicktrn marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| // Find the current worker for the environment | ||
| const worker = await findCurrentWorkerFromEnvironment(environment, this.prisma); | ||
| @@ -185,7 +212,21 @@ export class DefaultQueueManager implements QueueManager { | ||
| environmentId: environment.id, | ||
| }); | ||
| return defaultQueueName; | ||
| return { queueName: overriddenQueueName ?? defaultQueueName, taskTtl: undefined }; | ||
| } | ||
| // When queue is overridden, we only need TTL from the task (no queue join needed) | ||
| if (overriddenQueueName) { | ||
| const task = await this.prisma.backgroundWorkerTask.findFirst({ | ||
| where: { | ||
| workerId: worker.id, | ||
| runtimeEnvironmentId: environment.id, | ||
| slug: taskId, | ||
| }, | ||
| select: { ttl: true }, | ||
| }); | ||
| return { queueName: overriddenQueueName, taskTtl: task?.ttl }; | ||
| } | ||
| const task = await this.prisma.backgroundWorkerTask.findFirst({ | ||
| @@ -205,7 +246,7 @@ export class DefaultQueueManager implements QueueManager { | ||
| environmentId: environment.id, | ||
| }); | ||
| return defaultQueueName; | ||
| return { queueName: defaultQueueName, taskTtl: undefined }; | ||
| } | ||
| if (!task.queue) { | ||
| @@ -215,10 +256,10 @@ export class DefaultQueueManager implements QueueManager { | ||
| queueConfig: task.queueConfig, | ||
| }); | ||
| return defaultQueueName; | ||
| return { queueName: defaultQueueName, taskTtl: task.ttl }; | ||
| } | ||
| return task.queue.name ?? defaultQueueName; | ||
| return { queueName: task.queue.name ?? defaultQueueName, taskTtl: task.ttl }; | ||
| } | ||
devin-ai-integration[bot] marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| async validateQueueLimits( | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| -- AlterTable | ||
| ALTER TABLE "public"."BackgroundWorkerTask" ADD COLUMN IF NOT EXISTS "ttl" TEXT; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -132,6 +132,20 @@ if (typeof config.maxDuration === "number") { | ||
| }); | ||
| } | ||
| // If the config has a TTL, we need to apply it to all tasks that don't have a TTL | ||
| if (config.ttl !== undefined) { | ||
| tasks = tasks.map((task) => { | ||
| if (task.ttl === undefined) { | ||
| return { | ||
| ...task, | ||
| ttl: config.ttl, | ||
| } satisfies TaskManifest; | ||
| } | ||
| return task; | ||
| }); | ||
| } | ||
nicktrn marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| // If the config has a machine preset, we need to apply it to all tasks that don't have a machine preset | ||
| if (typeof config.machine === "string") { | ||
| tasks = tasks.map((task) => { | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -13,6 +13,7 @@ export const TaskResource = z.object({ | ||
| triggerSource: z.string().optional(), | ||
| schedule: ScheduleMetadata.optional(), | ||
| maxDuration: z.number().optional(), | ||
| ttl: z.string().or(z.number().nonnegative().int()).optional(), | ||
nicktrn marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| // JSONSchema type - using z.unknown() for runtime validation to accept JSONSchema7 | ||
| payloadSchema: z.unknown().optional(), | ||
| }); | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.