Repository navigation
[Bug] Alarms execute before @Persist properties are hydrated on cold start #121
Description
Describe the bug
When an Actor wakes up from hibernation/eviction (triggered by an alarm scheduled far enough in the future), the alarm callback executes before the @Persist properties have been hydrated from storage.
If the alarm callback relies on any @Persist property, it reads null/undefined, leading to silent failures on tasks like longer interval cron jobs.
While debugging this, I noticed it might be related to the initialization order of blockConcurrencyWhile locks on cold start. It seems like this.alarms = new Alarms(...) runs and schedules the alarms before the Actor constructor queues the lock to _initializePersistedProperties().
To Reproduce
Steps to reproduce the behavior:
- Create a basic Hono worker and Actor that schedules a task far enough in the future to force a Durable Object eviction (for example, 2 min):
- Commands used:
pnpm create hono@latest my-actor --template cloudflare-workers
cd my-actor
pnpm add @cloudflare/actors- Add this to
wrangler.jsonc:
{
"migrations": [
{
"new_sqlite_classes": ["MyActor"],
"tag": "v1"
}
],
"durable_objects": {
"bindings": [
{
"class_name": "MyActor",
"name": "MyActor"
}
]
}
}import { Actor, Persist } from "@cloudflare/actors";
import { Hono } from "hono";
import { logger } from "hono/logger";
export class MyActor extends Actor<CloudflareBindings> {
@Persist
myConfig: string | null = null;
async scheduleTask() {
this.myConfig = "hydrated_value";
// Schedule an alarm 2 minutes in the future (long enough to force DO eviction)
await this.alarms.schedule(120, "myAlarmCallback");
console.log("Scheduled!");
}
async myAlarmCallback() {
// BUG: When the DO wakes up, this logs "null" instead of "hydrated_value"
console.log("Alarm fired. Config is:", this.myConfig);
}
}
const app = new Hono<{ Bindings: CloudflareBindings }>();
app.use(logger());
app.get("/", async (c) => {
const exampleActor = MyActor.get("example");
const date = new Date();
console.log(`Current time: ${date.toLocaleTimeString()}`);
await exampleActor.scheduleTask();
date.setMinutes(date.getMinutes() + 2);
console.log(`Check back at: ${date.toLocaleTimeString()}`);
return c.text(`Schedule started`);
});
export default app;- Trigger the / route using wrangler dev.
- Wait 2 minutes for the alarm to trigger (ensure the DO goes idle and gets evicted).
- Check logs: The logs will print Alarm fired. Config is:
nullinstead ofhydrated_value.
Expected behavior
When the alarm triggers a callback on a sleeping Actor, the @Persist properties should be fully hydrated from the SQLite table before the user's callback function is invoked. In the example hydrated_value should have been printed.
Screenshots: N/A
Information:
- Actor Version: 0.0.1-beta.6
- Wrangler: 4.4.0 (I've tried deleting the
.wranglerfolder and updating wrangler to latest version(4.86.0) but faced the same issue) - Node.js: v24.15.0
- Typescript: 6.0.3
Additional context
Currently, my only workaround is to manually query this.sql for the _actor_persist table inside the alarm handler to get the data I need.
Activity