Skip to content

Commit 0efe8ae

Browse files
authored
Merge pull request #372 from cloudflare/bcaimano/alarm-in-ctor
Allow setAlarm to be invoked in Durable Object constructors
2 parents f8043a8 + edfecbb commit 0efe8ae

File tree

3 files changed

+37
-10
lines changed

3 files changed

+37
-10
lines changed

src/workerd/api/actor-state.c++

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -231,11 +231,8 @@ jsg::Promise<jsg::Value> DurableObjectStorageOperations::getOne(
231231

232232
jsg::Promise<kj::Maybe<double>> DurableObjectStorageOperations::getAlarm(
233233
jsg::Optional<GetAlarmOptions> maybeOptions, v8::Isolate* isolate) {
234-
235-
if (!IoContext::current().getActorOrThrow().hasAlarmHandler()) {
236-
return jsg::resolvedPromise<kj::Maybe<double>>(isolate, nullptr);
237-
}
238-
234+
// Even if we do not have an alarm handler, we might once have had one. It's fine to return
235+
// whatever a previous alarm setting or a falsy result.
239236
auto options = configureOptions(maybeOptions.map([](auto& o) {
240237
return GetOptions {
241238
.allowConcurrency = o.allowConcurrency,
@@ -400,8 +397,11 @@ jsg::Promise<void> DurableObjectStorageOperations::setAlarm(kj::Date scheduledTi
400397
JSG_REQUIRE(scheduledTime > kj::origin<kj::Date>(), TypeError,
401398
"setAlarm() cannot be called with an alarm time <= 0");
402399

403-
JSG_REQUIRE(IoContext::current().getActorOrThrow().hasAlarmHandler(), TypeError,
404-
"Your Durable Object class must have an alarm() handler in order to call setAlarm()");
400+
// This doesn't check if we have an alarm handler per say. It checks if we have an initialized
401+
// (post-ctor) JS durable object with an alarm handler. Notably, this means this won't throw if
402+
// `setAlarm` is invoked in the DO ctor even if the DO class does not have an alarm handler. This
403+
// is better than throwing even if we do have an alarm handler.
404+
IoContext::current().getActorOrThrow().assertCanSetAlarm();
405405

406406
auto options = configureOptions(maybeOptions.map([](auto& o) {
407407
return PutOptions {
@@ -470,6 +470,8 @@ kj::OneOf<jsg::Promise<bool>, jsg::Promise<int>> DurableObjectStorageOperations:
470470

471471
jsg::Promise<void> DurableObjectStorageOperations::deleteAlarm(
472472
jsg::Optional<SetAlarmOptions> maybeOptions, v8::Isolate* isolate) {
473+
// Even if we do not have an alarm handler, we might once have had one. It's fine to remove that
474+
// alarm or noop on the absence of one.
473475
auto options = configureOptions(maybeOptions.map([](auto& o) {
474476
return PutOptions {
475477
.allowConcurrency = o.allowConcurrency,

src/workerd/io/worker.c++

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2821,8 +2821,33 @@ kj::Maybe<jsg::Ref<api::DurableObjectStorage>>
28212821
});
28222822
}
28232823

2824-
bool Worker::Actor::hasAlarmHandler() {
2825-
return getHandler().map([](auto& h) { return h.alarm != nullptr; }).orDefault(false);
2824+
void Worker::Actor::assertCanSetAlarm() {
2825+
KJ_SWITCH_ONEOF(impl->classInstance) {
2826+
KJ_CASE_ONEOF(_, Impl::NoClass) {
2827+
// Once upon a time, we allowed actors without classes. Let's make a nicer message if we
2828+
// we somehow see a classless actor attempt to run an alarm in the wild.
2829+
JSG_FAIL_REQUIRE(TypeError,
2830+
"Your Durable Object must be class-based in order to call setAlarm()");
2831+
}
2832+
KJ_CASE_ONEOF(_, DurableObjectConstructor*) {
2833+
KJ_FAIL_ASSERT("setAlarm() invoked before Durable Object ctor");
2834+
}
2835+
KJ_CASE_ONEOF(_, Impl::Initializing) {
2836+
// We don't explicitly know if we have an alarm handler or not, so just let it happen. We'll
2837+
// handle it when we go to run the alarm.
2838+
return;
2839+
}
2840+
KJ_CASE_ONEOF(handler, api::ExportedHandler) {
2841+
JSG_REQUIRE(handler.alarm != nullptr, TypeError,
2842+
"Your Durable Object class must have an alarm() handler in order to call setAlarm()");
2843+
return;
2844+
}
2845+
KJ_CASE_ONEOF(exception, kj::Exception) {
2846+
// We've failed in the ctor, might as well just throw that exception for now.
2847+
kj::throwFatalException(kj::cp(exception));
2848+
}
2849+
}
2850+
KJ_UNREACHABLE;
28262851
}
28272852

28282853
kj::Promise<void> Worker::Actor::makeAlarmTaskForPreview(kj::Date scheduledTime) {

src/workerd/io/worker.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -703,7 +703,7 @@ class Worker::Actor final: public kj::Refcounted {
703703

704704
const Worker& getWorker() { return *worker; }
705705

706-
bool hasAlarmHandler();
706+
void assertCanSetAlarm();
707707
kj::Promise<void> makeAlarmTaskForPreview(kj::Date scheduledTime);
708708

709709
kj::Promise<WorkerInterface::AlarmResult> dedupAlarm(

0 commit comments

Comments
 (0)