diff --git a/docs/snippets/paused-execution-free.mdx b/docs/snippets/paused-execution-free.mdx index e58678fe95..7a9483d451 100644 --- a/docs/snippets/paused-execution-free.mdx +++ b/docs/snippets/paused-execution-free.mdx @@ -1,4 +1,4 @@ - - In the Trigger.dev Cloud we automatically pause execution of tasks when they are waiting for - longer than a few seconds. You are not charged when execution is paused. - +In the Trigger.dev Cloud we automatically pause execution of tasks when they are waiting for +longer than a few seconds. + +When triggering and waiting for subtasks, the parent is checkpointed and while waiting does not count towards compute usage. When waiting for a time period (`wait.for` or `wait.until`), if the wait is longer than 5 seconds we checkpoint and it does not count towards compute usage. \ No newline at end of file diff --git a/packages/trigger-sdk/src/v3/wait.ts b/packages/trigger-sdk/src/v3/wait.ts index fe5c16e93b..4768a9e7ee 100644 --- a/packages/trigger-sdk/src/v3/wait.ts +++ b/packages/trigger-sdk/src/v3/wait.ts @@ -369,6 +369,14 @@ export class WaitpointTimeoutError extends Error { } } +const DURATION_WAIT_CHARGE_THRESHOLD_MS = 5000; + +function printWaitBelowThreshold() { + console.warn( + `Waits of ${DURATION_WAIT_CHARGE_THRESHOLD_MS / 1000}s or less count towards compute usage.` + ); +} + export const wait = { for: async (options: WaitForOptions) => { const ctx = taskContext.ctx; @@ -380,6 +388,36 @@ export const wait = { const start = Date.now(); const durationInMs = calculateDurationInMs(options); + + if (durationInMs <= DURATION_WAIT_CHARGE_THRESHOLD_MS) { + return tracer.startActiveSpan( + `wait.for()`, + async (span) => { + if (durationInMs <= 0) { + return; + } + + printWaitBelowThreshold(); + + await new Promise((resolve) => setTimeout(resolve, durationInMs)); + }, + { + attributes: { + [SemanticInternalAttributes.STYLE_ICON]: "wait", + ...accessoryAttributes({ + items: [ + { + text: nameForWaitOptions(options), + variant: "normal", + }, + ], + style: "codepath", + }), + }, + } + ); + } + const date = new Date(start + durationInMs); const result = await apiClient.waitForDuration(ctx.run.id, { date: date, @@ -417,6 +455,46 @@ export const wait = { throw new Error("wait.forToken can only be used from inside a task.run()"); } + // Calculate duration in ms + const durationInMs = options.date.getTime() - Date.now(); + + if (durationInMs <= DURATION_WAIT_CHARGE_THRESHOLD_MS) { + return tracer.startActiveSpan( + `wait.for()`, + async (span) => { + if (durationInMs === 0) { + return; + } + + if (durationInMs < 0) { + if (options.throwIfInThePast) { + throw new Error("Date is in the past"); + } + + return; + } + + printWaitBelowThreshold(); + + await new Promise((resolve) => setTimeout(resolve, durationInMs)); + }, + { + attributes: { + [SemanticInternalAttributes.STYLE_ICON]: "wait", + ...accessoryAttributes({ + items: [ + { + text: options.date.toISOString(), + variant: "normal", + }, + ], + style: "codepath", + }), + }, + } + ); + } + const apiClient = apiClientManager.clientOrThrow(); const result = await apiClient.waitForDuration(ctx.run.id, {