Skip to content

Commit 931c85f

Browse files
committed
Merge remote-tracking branch 'origin/main' into fix/testcontainers
2 parents aecde2e + 43a4139 commit 931c85f

File tree

45 files changed

+774
-145
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+774
-145
lines changed

.changeset/pre.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,14 @@
2525
"green-lions-relate",
2626
"hip-cups-wave",
2727
"honest-files-decide",
28+
"itchy-games-sort",
2829
"late-chairs-ring",
2930
"moody-squids-count",
3031
"nice-colts-boil",
3132
"polite-impalas-care",
3233
"polite-lies-fix",
34+
"real-rats-drop",
35+
"red-chairs-begin",
3336
"red-wasps-cover",
3437
"shiny-kiwis-beam",
3538
"smart-coins-hammer",
@@ -42,6 +45,7 @@
4245
"tricky-houses-invite",
4346
"two-tigers-dream",
4447
"weak-jobs-hide",
45-
"wet-deers-think"
48+
"wet-deers-think",
49+
"wet-steaks-reflect"
4650
]
4751
}

.changeset/real-rats-drop.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@trigger.dev/sdk": patch
3+
---
4+
5+
Add onCancel lifecycle hook

apps/webapp/app/components/runs/v3/RunIcon.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ export function RunIcon({ name, className, spanName }: TaskIconProps) {
9797
case "task-hook-onResume":
9898
case "task-hook-onComplete":
9999
case "task-hook-cleanup":
100+
case "task-hook-onCancel":
100101
return <FunctionIcon className={cn(className, "text-text-dimmed")} />;
101102
case "task-hook-onFailure":
102103
case "task-hook-catchError":

apps/webapp/app/v3/services/cancelTaskRun.server.ts

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -47,29 +47,6 @@ export class CancelTaskRunService extends BaseService {
4747
tx: this._prisma,
4848
});
4949

50-
const inProgressEvents = await eventRepository.queryIncompleteEvents(
51-
getTaskEventStoreTableForRun(taskRun),
52-
{
53-
runId: taskRun.friendlyId,
54-
},
55-
taskRun.createdAt,
56-
taskRun.completedAt ?? undefined
57-
);
58-
59-
logger.debug("Cancelling in-progress events", {
60-
inProgressEvents: inProgressEvents.map((event) => event.id),
61-
});
62-
63-
await Promise.all(
64-
inProgressEvents.map((event) => {
65-
return eventRepository.cancelEvent(
66-
event,
67-
options?.cancelledAt ?? new Date(),
68-
options?.reason ?? "Run cancelled"
69-
);
70-
})
71-
);
72-
7350
return {
7451
id: result.run.id,
7552
};

docs/upgrade-to-v4.mdx

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,107 @@ tasks.onComplete(({ ctx, result }) => {
170170
});
171171
```
172172

173+
### onCancel
174+
175+
<Note>Available in v4.0.0-beta.12 and later.</Note>
176+
177+
You can now define an `onCancel` hook that is called when a run is cancelled. This is useful if you want to clean up any resources that were allocated for the run.
178+
179+
```ts
180+
tasks.onCancel(({ ctx, signal }) => {
181+
console.log("Run cancelled", signal);
182+
});
183+
```
184+
185+
You can use the `onCancel` hook along with the `signal` passed into the run function to interrupt a call to an external service, for example using the [streamText](https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text) function from the AI SDK:
186+
187+
```ts
188+
import { logger, tasks, schemaTask } from "@trigger.dev/sdk";
189+
import { streamText } from "ai";
190+
import { z } from "zod";
191+
192+
export const interruptibleChat = schemaTask({
193+
id: "interruptible-chat",
194+
description: "Chat with the AI",
195+
schema: z.object({
196+
prompt: z.string().describe("The prompt to chat with the AI"),
197+
}),
198+
run: async ({ prompt }, { signal }) => {
199+
const chunks: TextStreamPart<{}>[] = [];
200+
201+
// 👇 This is a global onCancel hook, but it's inside of the run function
202+
tasks.onCancel(async () => {
203+
// We have access to the chunks here, and can save them to the database
204+
await saveChunksToDatabase(chunks);
205+
});
206+
207+
try {
208+
const result = streamText({
209+
model: getModel(),
210+
prompt,
211+
experimental_telemetry: {
212+
isEnabled: true,
213+
},
214+
tools: {},
215+
abortSignal: signal, // 👈 Pass the signal to the streamText function, which aborts with the run is cancelled
216+
onChunk: ({ chunk }) => {
217+
chunks.push(chunk);
218+
},
219+
});
220+
221+
const textParts = [];
222+
223+
for await (const part of result.textStream) {
224+
textParts.push(part);
225+
}
226+
227+
return textParts.join("");
228+
} catch (error) {
229+
if (error instanceof Error && error.name === "AbortError") {
230+
// streamText will throw an AbortError if the signal is aborted, so we can handle it here
231+
} else {
232+
throw error;
233+
}
234+
}
235+
},
236+
});
237+
```
238+
239+
The `onCancel` hook can optionally wait for the `run` function to finish, and access the output of the run:
240+
241+
```ts
242+
import { logger, task } from "@trigger.dev/sdk";
243+
import { setTimeout } from "node:timers/promises";
244+
245+
export const cancelExampleTask = task({
246+
id: "cancel-example",
247+
// Signal will be aborted when the task is cancelled 👇
248+
run: async (payload: { message: string }, { signal }) => {
249+
try {
250+
// We pass the signal to setTimeout to abort the timeout if the task is cancelled
251+
await setTimeout(10_000, undefined, { signal });
252+
} catch (error) {
253+
// Ignore the abort error
254+
}
255+
256+
// Do some more work here
257+
258+
return {
259+
message: "Hello, world!",
260+
};
261+
},
262+
onCancel: async ({ runPromise }) => {
263+
// You can await the runPromise to get the output of the task
264+
const output = await runPromise;
265+
},
266+
});
267+
```
268+
269+
<Note>
270+
You will have up to 30 seconds to complete the `runPromise` in the `onCancel` hook. After that
271+
point the process will be killed.
272+
</Note>
273+
173274
### Improved middleware and locals
174275

175276
Our task middleware system is now much more useful. Previously it only ran "around" the `run` function, but now we've hoisted it to the top level and it now runs before/after all the other hooks.
@@ -704,7 +805,7 @@ export const myTask = task({
704805
id: "my-task",
705806
onStart: ({ payload, ctx }) => {},
706807
// The run function still uses separate parameters
707-
run: async ( payload, { ctx }) => {},
808+
run: async (payload, { ctx }) => {},
708809
});
709810
```
710811

@@ -760,4 +861,4 @@ const batchHandle = await tasks.batchTrigger([
760861
// Now you need to call runs.list()
761862
const runs = await batchHandle.runs.list();
762863
console.log(runs);
763-
```
864+
```

packages/build/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# @trigger.dev/build
22

3+
## 4.0.0-v4-beta.12
4+
5+
### Patch Changes
6+
7+
- Updated dependencies:
8+
- `@trigger.dev/core@4.0.0-v4-beta.12`
9+
310
## 4.0.0-v4-beta.11
411

512
### Patch Changes

packages/build/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@trigger.dev/build",
3-
"version": "4.0.0-v4-beta.11",
3+
"version": "4.0.0-v4-beta.12",
44
"description": "trigger.dev build extensions",
55
"license": "MIT",
66
"publishConfig": {
@@ -69,7 +69,7 @@
6969
"check-exports": "attw --pack ."
7070
},
7171
"dependencies": {
72-
"@trigger.dev/core": "workspace:4.0.0-v4-beta.11",
72+
"@trigger.dev/core": "workspace:4.0.0-v4-beta.12",
7373
"pkg-types": "^1.1.3",
7474
"tinyglobby": "^0.2.2",
7575
"tsconfck": "3.1.3"

packages/cli-v3/CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# trigger.dev
22

3+
## 4.0.0-v4-beta.12
4+
5+
### Patch Changes
6+
7+
- Display clickable links in Cursor terminal ([#1998](https://github.com/triggerdotdev/trigger.dev/pull/1998))
8+
- Added AI assistance link when you have build errors ([#1925](https://github.com/triggerdotdev/trigger.dev/pull/1925))
9+
- If you pass a directory when calling deploy we validate it exists and give helpful hints ([#2013](https://github.com/triggerdotdev/trigger.dev/pull/2013))
10+
- Updated dependencies:
11+
- `@trigger.dev/build@4.0.0-v4-beta.12`
12+
- `@trigger.dev/core@4.0.0-v4-beta.12`
13+
314
## 4.0.0-v4-beta.11
415

516
### Patch Changes

packages/cli-v3/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "trigger.dev",
3-
"version": "4.0.0-v4-beta.11",
3+
"version": "4.0.0-v4-beta.12",
44
"description": "A Command-Line Interface for Trigger.dev (v3) projects",
55
"type": "module",
66
"license": "MIT",
@@ -89,8 +89,8 @@
8989
"@opentelemetry/sdk-trace-base": "1.25.1",
9090
"@opentelemetry/sdk-trace-node": "1.25.1",
9191
"@opentelemetry/semantic-conventions": "1.25.1",
92-
"@trigger.dev/build": "workspace:4.0.0-v4-beta.11",
93-
"@trigger.dev/core": "workspace:4.0.0-v4-beta.11",
92+
"@trigger.dev/build": "workspace:4.0.0-v4-beta.12",
93+
"@trigger.dev/core": "workspace:4.0.0-v4-beta.12",
9494
"ansi-escapes": "^7.0.0",
9595
"c12": "^1.11.1",
9696
"chalk": "^5.2.0",

packages/cli-v3/src/entryPoints/dev-run-worker.ts

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
TaskRunExecution,
2424
timeout,
2525
TriggerConfig,
26+
UsageMeasurement,
2627
waitUntil,
2728
WorkerManifest,
2829
WorkerToExecutorMessageCatalog,
@@ -232,7 +233,10 @@ async function bootstrap() {
232233

233234
let _execution: TaskRunExecution | undefined;
234235
let _isRunning = false;
236+
let _isCancelled = false;
235237
let _tracingSDK: TracingSDK | undefined;
238+
let _executionMeasurement: UsageMeasurement | undefined;
239+
const cancelController = new AbortController();
236240

237241
const zodIpc = new ZodIpcConnection({
238242
listenSchema: WorkerToExecutorMessageCatalog,
@@ -403,18 +407,17 @@ const zodIpc = new ZodIpcConnection({
403407
getNumberEnvVar("TRIGGER_RUN_METADATA_FLUSH_INTERVAL", 1000)
404408
);
405409

406-
const measurement = usage.start();
410+
_executionMeasurement = usage.start();
407411

408-
// This lives outside of the executor because this will eventually be moved to the controller level
409-
const signal = execution.run.maxDuration
410-
? timeout.abortAfterTimeout(execution.run.maxDuration)
411-
: undefined;
412+
const timeoutController = timeout.abortAfterTimeout(execution.run.maxDuration);
413+
414+
const signal = AbortSignal.any([cancelController.signal, timeoutController.signal]);
412415

413416
const { result } = await executor.execute(execution, metadata, traceContext, signal);
414417

415-
const usageSample = usage.stop(measurement);
418+
if (_isRunning && !_isCancelled) {
419+
const usageSample = usage.stop(_executionMeasurement);
416420

417-
if (_isRunning) {
418421
return sender.send("TASK_RUN_COMPLETED", {
419422
execution,
420423
result: {
@@ -458,7 +461,16 @@ const zodIpc = new ZodIpcConnection({
458461
WAIT_COMPLETED_NOTIFICATION: async () => {
459462
await managedWorkerRuntime.completeWaitpoints([]);
460463
},
461-
FLUSH: async ({ timeoutInMs }, sender) => {
464+
CANCEL: async ({ timeoutInMs }) => {
465+
_isCancelled = true;
466+
cancelController.abort("run cancelled");
467+
await callCancelHooks(timeoutInMs);
468+
if (_executionMeasurement) {
469+
usage.stop(_executionMeasurement);
470+
}
471+
await flushAll(timeoutInMs);
472+
},
473+
FLUSH: async ({ timeoutInMs }) => {
462474
await flushAll(timeoutInMs);
463475
},
464476
WAITPOINT_CREATED: async ({ wait, waitpoint }) => {
@@ -470,6 +482,18 @@ const zodIpc = new ZodIpcConnection({
470482
},
471483
});
472484

485+
async function callCancelHooks(timeoutInMs: number = 10_000) {
486+
const now = performance.now();
487+
488+
try {
489+
await Promise.race([lifecycleHooks.callOnCancelHookListeners(), setTimeout(timeoutInMs)]);
490+
} finally {
491+
const duration = performance.now() - now;
492+
493+
log(`Called cancel hooks in ${duration}ms`);
494+
}
495+
}
496+
473497
async function flushAll(timeoutInMs: number = 10_000) {
474498
const now = performance.now();
475499

0 commit comments

Comments
 (0)