Durable Objects 的 ctx.abort() 怎麼避免 alarm 重試?用 retryAlarm: false
Cloudflare Durable Object 的 ctx.abort() 會立即重設目前的 object instance。過去只要在 alarm 執行期間呼叫它,被中斷的 alarm 預設會在 Durable Object 重設後再次重試;這對避免無關 request 意外取消排程很安全,但對「清理成功後不要再跑一次」的工作就可能造成重複執行。
現在要明確停止被中斷的 alarm,可以在 ctx.abort() 傳入 { retryAlarm: false }。最小修正如下:
import { DurableObject } from "cloudflare:workers";
export class CleanupTask extends DurableObject { async alarm(): Promise<void> { await this.ctx.storage.deleteAll(); this.ctx.abort("Cleanup complete", { retryAlarm: false }); }}重點不是把每一個 ctx.abort() 都加上選項,而是把「這一次 abort 是否應該讓 alarm 再試一次」變成每個呼叫點都能說清楚的決策。
ctx.abort() 的預設行為是什麼
Cloudflare 文件目前的規則可以整理成三種情況:
| 呼叫方式 | 被中斷的 alarm | 適合的情境 |
|---|---|---|
ctx.abort("reason") | Durable Object reset 後預設重試 | 無關 request abort,但不希望它永久取消既有 alarm |
ctx.abort("reason", { retryAlarm: true }) | 明確保留重試 | 你希望把重試意圖直接寫在程式碼上 |
ctx.abort("reason", { retryAlarm: false }) | 不重試這次被中斷的 alarm | 清理完成、使用者取消工作,或該 alarm 不應再執行 |
retryAlarm 控制的是「被這次 abort 中斷的 alarm 要不要重試」,不是把 alarm 本身改成一次性,也不是關閉 Durable Object 的所有後續 alarm。之後若程式再次呼叫 setAlarm(),仍要依那次排程的邏輯處理。
另外,ctx.abort() 產生的錯誤不能由 application code catch;它會立即重設 object,runtime 會記錄傳入的訊息。因此不要把它當成可以在 try/catch 裡攔截、再繼續執行的普通例外。
哪些 abort 路徑應該設定 retryAlarm: false
alarm 自己完成不可重複的工作
例如清空儲存資料、寫入一次性的完成標記,然後希望 instance reset 後不再重跑:
export class CleanupTask extends DurableObject { async alarm(): Promise<void> { const pending = await this.ctx.storage.get("pending");
if (!pending) { this.ctx.abort("Nothing to clean", { retryAlarm: false }); }
await this.ctx.storage.deleteAll(); this.ctx.abort("Cleanup complete", { retryAlarm: false }); }}這裡的 deleteAll() 在 abort 前完成;retryAlarm: false 則避免同一個 alarm 因為 reset 再次執行。若清理中途真的可能安全重做,則應先設計好 idempotency,再決定是否保留預設重試。
使用者取消或管理 request 明確要終止 alarm
alarm 可以和同一個 Durable Object 的其他 request 並行執行。如果取消 endpoint 的業務語意是「不要讓正在跑的 alarm 再回來」,該 request 的 abort 也要傳入選項:
export class CleanupTask extends DurableObject { async cancel(): Promise<void> { this.ctx.abort("Canceled by operator", { retryAlarm: false }); }}不要只修改 alarm() 內的呼叫點。Cloudflare 明確指出,另一個 request 在 alarm 執行時呼叫 ctx.abort(),重試行為仍由那個 request 傳入的 retryAlarm 控制。
不確定是否應該停止時,保留預設值並補測試
預設重試的目的,是避免一個無關 request 永久取消 alarm。如果 abort 只是為了重設卡住的 instance,而不是取消排程,直接改成 false 可能讓真正需要完成的工作消失。這種呼叫點應保留預設行為,或明確寫 { retryAlarm: true },並在程式碼旁邊註明原因。
升級 Wrangler 並找出所有呼叫點
本機開發需要 Wrangler 4.126.0 或更新版本才能使用 retryAlarm。先查目前 CLI 和 package 來源:
pnpm exec wrangler --versionpnpm list wrangler --depth 0pnpm why wranglerrg -n 'ctx\.abort|async alarm|alarm\(' src若版本低於最低要求,先在專案的 dependency policy 允許範圍內更新,再重跑 lockfile、型別檢查和本機 Durable Object 測試。不要只在其中一個 handler 加選項後就結束;用搜尋結果建立一份 abort path 清單,逐一標記「保留重試」或「停止重試」。
如何驗證 alarm 沒有不必要地重跑
測試的重點是觀察狀態轉移,不是只看 ctx.abort() 的 log:
- 準備一筆只應清理一次的 storage 資料,並安排 alarm。
- 在 alarm 完成清理後呼叫
ctx.abort("...", { retryAlarm: false })。 - 重新請求同一個 Durable Object,確認清理結果維持完成狀態,沒有第二次副作用。
- 另測一條普通 request 呼叫
ctx.abort()的路徑,確認你預期的 alarm retry 行為仍存在。 - 用另一個 request 在 alarm 執行時呼叫 abort,驗證該 request 的
retryAlarm設定,而不是只測 alarm handler。
部署後若需要觀察流量和版本,可搭配 Durable Objects Deployments 分辨設定與實際流量;儲存量的圖表則要注意 namespace 與單一 object 的範圍不同,可參考 Durable Objects SQLite 儲存用量檢查。
常見問題
Q: 加上 retryAlarm: false 後,Durable Object 還能再設定 alarm 嗎?
A: 可以。這個選項只決定被該次 ctx.abort() 中斷的 alarm 是否重試,不是永久停用該 Durable Object 的 alarm API。後續由程式排程的新 alarm 仍按新的設定執行。
Q: 所有 ctx.abort() 都應該改成 retryAlarm: false 嗎?
A: 不應該。預設重試可以避免無關 request 讓正在執行的 alarm 永久消失。只有業務語意確定要取消該次工作時才設 false;其餘呼叫點保留預設或明確使用 true。
Q: ctx.abort() 可以用 try/catch 接住再回傳 JSON 嗎?
A: 不行。Cloudflare 文件將它定義為立即 reset,application code 無法 catch 這個錯誤。需要回傳取消結果時,應在呼叫 abort 前先完成可持久化的狀態更新,並讓下一次 request 讀取該狀態。
參考資料:
Cloudflare Changelog:Prevent Durable Object alarm retries when using
ctx.abort()
回報錯字、失效連結,或告訴我你想看的延伸主題。