From c108cd42f0e313ff333762606eb3720ccf40b182 Mon Sep 17 00:00:00 2001 From: coji Date: Mon, 16 Mar 2026 22:31:25 +0900 Subject: [PATCH] docs: document synchronous event listener behavior (#126) Add warning about synchronous execution in worker hot path, best practices for fast listeners, and clarify error handling semantics. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/durably/docs/llms.md | 2 +- website/api/events.md | 32 +++++++++++++++++++++++++++++++- website/public/llms.txt | 2 +- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/packages/durably/docs/llms.md b/packages/durably/docs/llms.md index 527b8240..5e0d2593 100644 --- a/packages/durably/docs/llms.md +++ b/packages/durably/docs/llms.md @@ -275,7 +275,7 @@ For automatic cleanup, use the `retainRuns` option (see Core Concepts). Cleanup ## Events -Subscribe to job execution events: +Subscribe to job execution events. **Listeners run synchronously** in the worker's hot path — keep them fast and non-blocking. Use fire-and-forget (`void asyncFn()`) for expensive work. ```ts // Run lifecycle events diff --git a/website/api/events.md b/website/api/events.md index 5b99891b..2514db3a 100644 --- a/website/api/events.md +++ b/website/api/events.md @@ -263,9 +263,39 @@ durably.on('worker:error', (event) => { }) ``` +## Synchronous Execution + +::: warning Listeners run synchronously +Event listeners are called **synchronously** in the worker's hot path. A slow listener will block job execution and lease renewal until it returns. Keep listeners fast and non-blocking. +::: + +**Do:** + +```ts +// Fast: queue work for later +durably.on('run:complete', (e) => { + void sendToAnalytics(e) // fire-and-forget async +}) + +// Fast: simple logging +durably.on('run:fail', (e) => { + console.error(`[${e.jobName}] Failed: ${e.error}`) +}) +``` + +**Don't:** + +```ts +// Slow: synchronous heavy computation blocks the worker +durably.on('run:complete', (e) => { + const report = generateExpensiveReport(e) // ❌ blocks polling + fs.writeFileSync('report.json', JSON.stringify(report)) +}) +``` + ## Error Handling -Exceptions in event listeners don't affect run execution. To catch listener errors: +Exceptions thrown in event listeners are caught and forwarded to the error handler — they do not crash the worker or abort the current run. However, because listeners run synchronously, an exception still interrupts subsequent listeners for the same event. Use `onError` to catch these: ```ts durably.onError((error, event) => { diff --git a/website/public/llms.txt b/website/public/llms.txt index 553cf38e..835f3e49 100644 --- a/website/public/llms.txt +++ b/website/public/llms.txt @@ -275,7 +275,7 @@ For automatic cleanup, use the `retainRuns` option (see Core Concepts). Cleanup ## Events -Subscribe to job execution events: +Subscribe to job execution events. **Listeners run synchronously** in the worker's hot path — keep them fast and non-blocking. Use fire-and-forget (`void asyncFn()`) for expensive work. ```ts // Run lifecycle events