Implement Durable Object alarms in workerd#605
Conversation
189e535 to
29980e0
Compare
6e89938 to
9f93337
Compare
0d1b894 to
f920788
Compare
|
I implemented the rest of what's missing and added a basic test -- with no infrastructure for more complex tests in workerd that can perform process restarts it'll be hard to test all of the behavior |
f920788 to
b5eb6b8
Compare
f21f3bc to
28a7aef
Compare
| let alarm = await this.state.storage.getAlarm(); | ||
| if (alarm != null) { | ||
| throw new Error(`alarm time not cleared when handler ends. ${alarm}`); | ||
| } |
There was a problem hiding this comment.
Suggestion: you can use
assert.ok(alarm == null, `alarm time not cleared when handler ends. ${alarm}`);
to simplify these checks.
|
|
||
| const time = Date.now() + 100; | ||
| await this.state.storage.setAlarm(time); | ||
| assert.equal(await this.state.storage.getAlarm(), time); |
There was a problem hiding this comment.
| assert.equal(await this.state.storage.getAlarm(), time); | |
| assert.strictEqual(await this.state.storage.getAlarm(), time); |
| let obj = env.ns.get(id); | ||
| let res = await obj.fetch("http://foo/test"); | ||
| let text = await res.text(); | ||
| assert.equal(text, "OK"); |
There was a problem hiding this comment.
| assert.equal(text, "OK"); | |
| assert.strictEqual(text, "OK"); |
84f6152 to
a97cb8a
Compare
src/workerd/server/workerd.capnp
Outdated
| # Extensions provide capabilities to all workers. Extensions are usually prepared separately | ||
| # and are late-linked with the app using this config field. | ||
|
|
||
| alarmScheduler @4 :AlarmConfig; |
There was a problem hiding this comment.
Hmm, do we really want this to be top-level config?
It feels confusing that I could configure a durable object namespace that stores to disk, but get in-memory-only alarms. Vice-versa is even more confusing I think.
Also, this means that if someone is serving multiple DO namespaces from the same workerd instance, but later decides that they want to split them up, they can't really do that -- they'll lose their alarms.
Could we instead create a separate alarms database per DO namespace? If the namespace is in-memory, use in-memory handling. If the DO namespace is on-disk, then we store the alarms on-disk in the same directory. I'd call the file something like metadata.sqlite or namespace.sqlite so that if we find there's per-namespace stuff other than alarms we want to put in there in the future, we can.
|
Sorry for the late review here -- as you know I'm also trying to meet a dev week deadline so it's been tough keeping up. I'm really happy you implemented this! This was sorely-missing functionality. I think, though, that we really need to have a separate alarms database for each DO namespace, per my comment above. Having a global alarms database severely limits the ability to combine and split workerd configs, and means that part of a namespace's data is stored separately from the namespace. If there's not time to resolve this for dev week, I think as a compromise we could remove the configuration and say for now that alarms are always configured for in-memory use only. That should be OK for local testing purposes. It might even be desirable -- it could be confusing if you start up your test instance and suddenly a bunch of alarms from a past session are delivered? |
The desired behavior here is persistent alarms -- the idea was that wrangler/miniflare would delete the alarms db at the beginning/end of the session, so your alarms would persist across code updates, but you wouldn't have alarms from previous sessions hanging around. in-memory alarms wouldn't work for this. |
|
OK, well... how do we proceed here? I really think it's important that alarms are stored along with the namespace data and not centralized. It also makes the config simpler (no new config is actually needed, I think). We could name the config property in a way that indicates it's a temporary solution that will change soon, and shouldn't be used in production... |
We could name the config property in a way that indicates it's a temporary solution that will change soon, and shouldn't be used in production... @mrbbot has said he's OK with in-memory for the short term -- I think I'll rip out the ability to config the alarm scheduler to be backed by a |
a639681 to
f3a11f9
Compare
Workerd now supports running alarms, storing them either in-memory or in a local sqlite database to persist across workerd process restarts.
f3a11f9 to
4d3c606
Compare
In a Durable Object, the type of `ctx.id` is supposed to be of type `DurableObjectId`, and in production, it is. Way way back in #605, a bug was introduced such that `ctx.id` became a string for Durable Objects in workerd. But `ctx.id` is only supposed to be a string for "ephemeral objects" (aka colo-local actors). Somehow, nobody ever noticed? Probably because the only thing most people would do with it anyway is stringify it, which JavaScript is really good at doing automatically. Anyway, this fixes the bug, restoring the proper type. (Also this tidies up ownership of the key strings in the actor map. The key is now owned by the ActorContainer.)
In a Durable Object, the type of `ctx.id` is supposed to be of type `DurableObjectId`, and in production, it is. Way way back in #605, a bug was introduced such that `ctx.id` became a string for Durable Objects in workerd. But `ctx.id` is only supposed to be a string for "ephemeral objects" (aka colo-local actors). Somehow, nobody ever noticed? Probably because the only thing most people would do with it anyway is stringify it, which JavaScript is really good at doing automatically. Anyway, this fixes the bug, restoring the proper type. (Also this tidies up ownership of the key strings in the actor map. The key is now owned by the ActorContainer.)
| Worker::Lock lock(*service.worker, asyncLock); | ||
| auto newActor = kj::refcounted<Worker::Actor>( | ||
| *service.worker, nullptr, kj::mv(id), true, kj::mv(makeActorCache), | ||
| *service.worker, nullptr, kj::str(id), true, kj::mv(makeActorCache), |
There was a problem hiding this comment.
Prior to this change, id was a Worker::Actor::Id, but this downgraded it to a string. Oops. This is visible in ctx.id in JavaScript having the wrong type (a string, rather than a DO ID), but it seems nobody ever noticed. Fixed in #4041.
In a Durable Object, the type of `ctx.id` is supposed to be of type `DurableObjectId`, and in production, it is. Way way back in #605, a bug was introduced such that `ctx.id` became a string for Durable Objects in workerd. But `ctx.id` is only supposed to be a string for "ephemeral objects" (aka colo-local actors). Somehow, nobody ever noticed? Probably because the only thing most people would do with it anyway is stringify it, which JavaScript is really good at doing automatically. Anyway, this fixes the bug, restoring the proper type. (Also this tidies up ownership of the key strings in the actor map. The key is now owned by the ActorContainer.)
In a Durable Object, the type of `ctx.id` is supposed to be of type `DurableObjectId`, and in production, it is. Way way back in #605, a bug was introduced such that `ctx.id` became a string for Durable Objects in workerd. But `ctx.id` is only supposed to be a string for "ephemeral objects" (aka colo-local actors). Somehow, nobody ever noticed? Probably because the only thing most people would do with it anyway is stringify it, which JavaScript is really good at doing automatically. Anyway, this fixes the bug, restoring the proper type. (Also this tidies up ownership of the key strings in the actor map. The key is now owned by the ActorContainer.)
Previously, all alarms were stored in a single global in-memory SQLite database, meaning they were lost on process restart. This made it impossible to test alarm resiliency scenarios. Move alarm scheduler ownership from Server into ActorNamespace, so each DO namespace gets its own AlarmScheduler backed by metadata.sqlite in the namespace's storage directory. On-disk namespaces get persistent alarms; in-memory namespaces get in-memory alarms. No new configuration is needed -- alarm storage follows the existing durableObjectStorage setting. Ref: #605 (comment) Co-authored-by: Cursor <cursoragent@cursor.com>
Previously, all alarms were stored in a single global in-memory SQLite database, meaning they were lost on process restart. This made it impossible to test alarm resiliency scenarios. Move alarm scheduler ownership from Server into ActorNamespace, so each DO namespace gets its own AlarmScheduler backed by metadata.sqlite in the namespace's storage directory. On-disk namespaces get persistent alarms; in-memory namespaces get in-memory alarms. No new configuration is needed -- alarm storage follows the existing durableObjectStorage setting. Ref: #605 (comment) Co-authored-by: Cursor <cursoragent@cursor.com>
Previously, all alarms were stored in a single global in-memory SQLite database, meaning they were lost on process restart. This made it impossible to test alarm resiliency scenarios. Move alarm scheduler ownership from Server into ActorNamespace, so each DO namespace gets its own AlarmScheduler backed by metadata.sqlite in the namespace's storage directory. On-disk namespaces get persistent alarms; in-memory namespaces get in-memory alarms. No new configuration is needed -- alarm storage follows the existing durableObjectStorage setting. Ref: #605 (comment) Co-authored-by: Cursor <cursoragent@cursor.com>
Previously, all alarms were stored in a single global in-memory SQLite database, meaning they were lost on process restart. This made it impossible to test alarm resiliency scenarios. Move alarm scheduler ownership from Server into ActorNamespace, so each DO namespace gets its own AlarmScheduler backed by metadata.sqlite in the namespace's storage directory. On-disk namespaces get persistent alarms; in-memory namespaces get in-memory alarms. No new configuration is needed -- alarm storage follows the existing durableObjectStorage setting. Ref: #605 (comment) Co-authored-by: Cursor <cursoragent@cursor.com>
Workerd now supports running alarms, storing them either in-memory or in a local sqlite database to persist across workerd process restarts.
Still need to hook up the alarms API to the scheduler, but wanted to put this up earlier than later.