diff --git a/src/content/docs/agents/api-reference/schedule-tasks.mdx b/src/content/docs/agents/api-reference/schedule-tasks.mdx index 0174d45792a..bfb6079a95a 100644 --- a/src/content/docs/agents/api-reference/schedule-tasks.mdx +++ b/src/content/docs/agents/api-reference/schedule-tasks.mdx @@ -11,6 +11,12 @@ An Agent can schedule tasks to be run in the future by calling `this.schedule(wh Scheduled tasks can do anything a request or message from a user can: make requests, query databases, send emails, read+write state: scheduled tasks can invoke any regular method on your Agent. +:::note[Calling destroy() in scheduled tasks] + +You can safely call `this.destroy()` within a scheduled task callback. The Agent will properly handle cleanup and eviction, skipping any remaining database updates after the destroy flag is set. + +::: + ### Scheduling tasks You can call `this.schedule` within any method on an Agent, and schedule tens-of-thousands of tasks per individual Agent: @@ -48,6 +54,9 @@ You can schedule tasks in multiple ways: ```ts +// schedule a task to run immediately +let task = await this.schedule(0, "someTask", { message: "hello" }); + // schedule a task to run in 10 seconds let task = await this.schedule(10, "someTask", { message: "hello" }); @@ -74,6 +83,12 @@ Each task is mapped to a row in the Agent's underlying [SQLite database](/durabl ::: +:::note[Destroying an Agent from a scheduled task] + +You can safely call `this.destroy()` from within a scheduled task callback. The Agent SDK handles this scenario by setting an internal flag to prevent database updates after destruction and deferring the context abort to allow the alarm handler to complete cleanly. This prevents errors that would otherwise occur when the alarm handler attempts to update the schedule table after storage has been cleared. + +::: + ### Managing scheduled tasks You can get, cancel and filter across scheduled tasks within an Agent using the scheduling API: @@ -104,3 +119,40 @@ let tasks = this.getSchedules({ ``` + +### Lifecycle management with scheduled tasks + +When working with scheduled tasks, you can safely call `this.destroy()` from within a scheduled callback to terminate the Agent instance after completing a task. The Agent SDK properly handles the cleanup process to ensure scheduled tasks complete successfully before the Agent is evicted. + + + +```ts +export class SchedulingAgent extends Agent { + async onRequest(request) { + // Schedule a self-destructing task + await this.schedule(60, "cleanupAndDestroy"); + return new Response("Agent will self-destruct in 60 seconds"); + } + + async cleanupAndDestroy(data) { + // Perform final cleanup operations + await this.sendEmail({ + to: "admin@example.com", + subject: "Task completed", + body: "Agent task finished, shutting down" + }); + + // Safely destroy the Agent instance + // The destroy operation will complete after this callback finishes + await this.destroy(); + } +} +``` + + + +:::note + +When `destroy()` is called from within a scheduled task, the Agent SDK defers the destruction to ensure the scheduled callback completes successfully. The Agent instance will be evicted immediately after the callback finishes executing. + +::: diff --git a/src/content/docs/agents/concepts/agent-class.mdx b/src/content/docs/agents/concepts/agent-class.mdx index 86f130d5f47..f69d419eee9 100644 --- a/src/content/docs/agents/concepts/agent-class.mdx +++ b/src/content/docs/agents/concepts/agent-class.mdx @@ -430,7 +430,11 @@ class MyAgent extends Agent { ### `this.destroy` -`this.destroy()` drops all tables, deletes alarms, clears storage, and aborts the context. To ensure that the Durable Object is fully evicted, `this.ctx.abort()` is called, which throws an uncatchable error that will show up in your logs (read more about it in [abort()](/durable-objects/api/state/#abort)). +`this.destroy()` drops all tables, deletes alarms, clears storage, and aborts the context. To ensure that the Durable Object is fully evicted, `this.ctx.abort()` is called asynchronously using `setTimeout()` to allow any currently executing handlers (like scheduled tasks) to complete their cleanup operations before the context is aborted. + +This means `this.ctx.abort()` throws an uncatchable error that will show up in your logs, but it does so after yielding to the event loop (read more about it in [abort()](/durable-objects/api/state/#abort)). + +The `destroy()` method can be safely called within scheduled tasks. When called from within a schedule callback, the Agent sets an internal flag to skip any remaining database updates, and yields `ctx.abort()` to the event loop to ensure the alarm handler completes cleanly before the Agent is evicted. ```ts class MyAgent extends Agent { @@ -443,9 +447,26 @@ class MyAgent extends Agent { // This wipes everything! await this.destroy(); } + + async selfDestruct() { + // Safe to call from within a scheduled task + await this.schedule(60, "destroyAfterDelay", {}); + } + + async destroyAfterDelay() { + // This will safely destroy the Agent even when + // called from within the alarm handler + await this.destroy(); + } } ``` +:::note[Using destroy() in scheduled tasks] + +You can safely call `this.destroy()` from within a scheduled task callback. The Agent SDK sets an internal flag to prevent database updates after destruction and defers the context abort to ensure the alarm handler completes cleanly. + +::: + ### Routing The `Agent` class re-exports PartyKit's [addressing helpers](#addressing) as `getAgentByName` and `routeAgentRequest`.