Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add waitForBookmark to DurableObjectStorage #2923

Merged
merged 1 commit into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/workerd/api/actor-state.c++
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,10 @@ kj::Promise<kj::String> DurableObjectStorage::onNextSessionRestoreBookmark(kj::S
return cache->onNextSessionRestoreBookmark(bookmark);
}

kj::Promise<void> DurableObjectStorage::waitForBookmark(kj::String bookmark) {
return cache->waitForBookmark(bookmark);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this safely be called multiple times from javascript? As it is here, every call on this will result in a new JavaScript promise backed by a kj::Promise... specifically, if I do..

const p1 = whatever.waitForBookmark('...');
const p2 = whatever.waitForBookmark('...');

Then p1 !== p2

Should this always return the same JS promise when the bookmark id is the same and is already being waited on?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The internal PR waits on the same forked promise, so in production this will wait on the same thing. p1 will still be !== p2, is that a problem?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a problem just something to be aware of. Creating multiple promises that wait on the same thing can add performance cost.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is fine as-is. Don't think people are likely to call it multiple times in a row like that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But for @justin-mp's understanding: One problem with waiting on multiple forks of the KJ promise is that each one's resolution will lead to a separate entering of the isolate, requiring taking the isolate lock, setting up CPU time limits, etc., which is relatively expensive. If we somehow imported just one instance of the KJ promise to JS, and then had all the waiters wait on that shared JS promise, then the isolate only needs to be entered once.

But this is an optimization which can easily be left for later.

}

ActorCacheOps& DurableObjectTransaction::getCache(OpName op) {
JSG_REQUIRE(!rolledBack, Error, kj::str("Cannot ", op, " on rolled back transaction"));
auto& result = *JSG_REQUIRE_NONNULL(cacheTxn, Error,
Expand Down
9 changes: 9 additions & 0 deletions src/workerd/api/actor-state.h
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,14 @@ class DurableObjectStorage: public jsg::Object, public DurableObjectStorageOpera
// by calling state.abort() or by throwing from a blockConcurrencyWhile() callback.
kj::Promise<kj::String> onNextSessionRestoreBookmark(kj::String bookmark);

// Wait until the database has been updated to the state represented by `bookmark`.
//
// `waitForBookmark` is useful synchronizing requests across replicas of the same database. On
// primary databases, `waitForBookmark` will resolve immediately. On replica databases,
// `waitForBookmark` will resolve when the replica has been updated to a point at or after
// `bookmark`.
kj::Promise<void> waitForBookmark(kj::String bookmark);

JSG_RESOURCE_TYPE(DurableObjectStorage, CompatibilityFlags::Reader flags) {
JSG_METHOD(get);
JSG_METHOD(list);
Expand All @@ -245,6 +253,7 @@ class DurableObjectStorage: public jsg::Object, public DurableObjectStorageOpera
JSG_METHOD(getCurrentBookmark);
JSG_METHOD(getBookmarkForTime);
JSG_METHOD(onNextSessionRestoreBookmark);
JSG_METHOD(waitForBookmark);

JSG_TS_OVERRIDE({
get<T = unknown>(key: string, options?: DurableObjectGetOptions): Promise<T | undefined>;
Expand Down
5 changes: 5 additions & 0 deletions src/workerd/io/actor-cache.c++
Original file line number Diff line number Diff line change
Expand Up @@ -3357,4 +3357,9 @@ kj::Promise<kj::String> ActorCacheInterface::onNextSessionRestoreBookmark(kj::St
Error, "This Durable Object's storage back-end does not implement point-in-time recovery.");
}

kj::Promise<void> ActorCacheInterface::waitForBookmark(kj::StringPtr bookmark) {
JSG_FAIL_REQUIRE(
Error, "This Durable Object's storage back-end does not implement point-in-time recovery.");
}

} // namespace workerd
1 change: 1 addition & 0 deletions src/workerd/io/actor-cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ class ActorCacheInterface: public ActorCacheOps {
virtual kj::Promise<kj::String> getCurrentBookmark();
virtual kj::Promise<kj::String> getBookmarkForTime(kj::Date timestamp);
virtual kj::Promise<kj::String> onNextSessionRestoreBookmark(kj::StringPtr bookmark);
virtual kj::Promise<void> waitForBookmark(kj::StringPtr bookmark);
};

// An in-memory caching layer on top of ActorStorage.Stage RPC interface.
Expand Down