Skip to content

Commit b72cacc

Browse files
authored
feat(debounce): add maxDelay option to limit total debounce time (#2984)
1 parent 1ccb8c1 commit b72cacc

File tree

8 files changed

+562
-9
lines changed

8 files changed

+562
-9
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
"@trigger.dev/core": patch
3+
"@trigger.dev/sdk": patch
4+
---
5+
6+
Add `maxDelay` option to debounce feature. This allows setting a maximum time limit for how long a debounced run can be delayed, ensuring execution happens within a specified window even with continuous triggers.
7+
8+
```typescript
9+
await myTask.trigger(payload, {
10+
debounce: {
11+
key: "my-key",
12+
delay: "5s",
13+
maxDelay: "30m", // Execute within 30 minutes regardless of continuous triggers
14+
},
15+
});
16+
```

internal-packages/run-engine/src/engine/systems/debounceSystem.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import {
66
type Result,
77
} from "@internal/redis";
88
import { startSpan } from "@internal/tracing";
9-
import { parseNaturalLanguageDuration } from "@trigger.dev/core/v3/isomorphic";
9+
import {
10+
parseNaturalLanguageDuration,
11+
parseNaturalLanguageDurationInMs,
12+
} from "@trigger.dev/core/v3/isomorphic";
1013
import { PrismaClientOrTransaction, TaskRun, Waitpoint } from "@trigger.dev/database";
1114
import { nanoid } from "nanoid";
1215
import { SystemResources } from "./systems.js";
@@ -17,6 +20,12 @@ export type DebounceOptions = {
1720
key: string;
1821
delay: string;
1922
mode?: "leading" | "trailing";
23+
/**
24+
* Maximum total delay before the run must execute, regardless of subsequent triggers.
25+
* This prevents indefinite delays when continuous triggers keep pushing the execution time.
26+
* If not specified, falls back to the server's maxDebounceDurationMs config.
27+
*/
28+
maxDelay?: string;
2029
/** When mode: "trailing", these fields will be used to update the existing run */
2130
updateData?: {
2231
payload: string;
@@ -521,8 +530,22 @@ return 0
521530
}
522531

523532
// Check if max debounce duration would be exceeded
533+
// Use per-trigger maxDelay if provided, otherwise use global config
534+
let maxDurationMs = this.maxDebounceDurationMs;
535+
if (debounce.maxDelay) {
536+
const parsedMaxDelay = parseNaturalLanguageDurationInMs(debounce.maxDelay);
537+
if (parsedMaxDelay !== undefined) {
538+
maxDurationMs = parsedMaxDelay;
539+
} else {
540+
this.$.logger.warn("handleExistingRun: invalid maxDelay duration, using global config", {
541+
maxDelay: debounce.maxDelay,
542+
fallbackMs: this.maxDebounceDurationMs,
543+
});
544+
}
545+
}
546+
524547
const runCreatedAt = existingRun.createdAt;
525-
const maxDelayUntil = new Date(runCreatedAt.getTime() + this.maxDebounceDurationMs);
548+
const maxDelayUntil = new Date(runCreatedAt.getTime() + maxDurationMs);
526549

527550
if (newDelayUntil > maxDelayUntil) {
528551
this.$.logger.debug("handleExistingRun: max debounce duration would be exceeded", {
@@ -531,7 +554,8 @@ return 0
531554
runCreatedAt,
532555
newDelayUntil,
533556
maxDelayUntil,
534-
maxDebounceDurationMs: this.maxDebounceDurationMs,
557+
maxDurationMs,
558+
maxDelayProvided: debounce.maxDelay,
535559
});
536560
// Clean up Redis key since this debounce window is closed
537561
await this.redis.del(redisKey);

0 commit comments

Comments
 (0)