Skip to content

Commit

Permalink
Fixes "promise will never complete" when exceeding memory.
Browse files Browse the repository at this point in the history
  • Loading branch information
dom96 committed Jan 30, 2025
1 parent 8790957 commit 198db4d
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 7 deletions.
33 changes: 26 additions & 7 deletions src/workerd/io/io-context.h
Original file line number Diff line number Diff line change
Expand Up @@ -1257,23 +1257,40 @@ kj::_::ReducePromises<RemoveIoOwn<T>> IoContext::awaitJs(jsg::Lock& js, jsg::Pro
auto paf = kj::newPromiseAndFulfiller<RemoveIoOwn<T>>();
struct RefcountedFulfiller: public Finalizeable, public kj::Refcounted {
kj::Own<kj::PromiseFulfiller<RemoveIoOwn<T>>> fulfiller;
kj::Own<const workerd::Worker::Isolate> isolate;
bool isDone = false;

RefcountedFulfiller(kj::Own<kj::PromiseFulfiller<RemoveIoOwn<T>>> fulfiller)
: fulfiller(kj::mv(fulfiller)) {}
RefcountedFulfiller(kj::Own<const workerd::Worker::Isolate> isolate,
kj::Own<kj::PromiseFulfiller<RemoveIoOwn<T>>> fulfiller)
: fulfiller(kj::mv(fulfiller)),
isolate(kj::mv(isolate)) {}

~RefcountedFulfiller() noexcept(false) {
if (!isDone) {
// The JavaScript resolver was garbage collected, i.e. JavaScript will never resolve
// this promise.
fulfiller->reject(JSG_KJ_EXCEPTION(FAILED, Error, "Promise will never complete."));
auto hasExcessivelyExceededHeapLimit =
isolate->getLimitEnforcer().hasExcessivelyExceededHeapLimit();
if (hasExcessivelyExceededHeapLimit) {
fulfiller->reject(
JSG_KJ_EXCEPTION(OVERLOADED, Error, "jsg.Error: Worker has exceeded memory limit."));
} else {
// The JavaScript resolver was garbage collected, i.e. JavaScript will never resolve
// this promise.
fulfiller->reject(JSG_KJ_EXCEPTION(FAILED, Error, "Promise will never complete."));
}
}
}

private:
kj::Maybe<kj::StringPtr> finalize() override {
if (!isDone) {
fulfiller->reject(JSG_KJ_EXCEPTION(FAILED, Error, "Promise will never complete."));
auto hasExcessivelyExceededHeapLimit =
isolate->getLimitEnforcer().hasExcessivelyExceededHeapLimit();
if (hasExcessivelyExceededHeapLimit) {
fulfiller->reject(
JSG_KJ_EXCEPTION(OVERLOADED, Error, "jsg.Error: Worker has exceeded memory limit."));
} else {
fulfiller->reject(JSG_KJ_EXCEPTION(FAILED, Error, "Promise will never complete."));
}
isDone = true;
return "A hanging Promise was canceled. This happens when the worker runtime is waiting "
"for a Promise from JavaScript to resolve, but has detected that the Promise "
Expand All @@ -1284,7 +1301,9 @@ kj::_::ReducePromises<RemoveIoOwn<T>> IoContext::awaitJs(jsg::Lock& js, jsg::Pro
}
}
};
auto fulfiller = kj::refcounted<RefcountedFulfiller>(kj::mv(paf.fulfiller));
auto& isolate = Worker::Isolate::from(js);
auto fulfiller =
kj::refcounted<RefcountedFulfiller>(kj::atomicAddRef(isolate), kj::mv(paf.fulfiller));

auto errorHandler = [fulfiller = addObject(kj::addRef(*fulfiller))](
jsg::Lock& js, jsg::Value jsExceptionRef) mutable {
Expand Down
2 changes: 2 additions & 0 deletions src/workerd/io/limit-enforcer.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ class IsolateLimitEnforcer: public kj::Refcounted {
virtual size_t getBlobSizeLimit() const {
return 128 * 1024 * 1024; // 128 MB
}

virtual bool hasExcessivelyExceededHeapLimit() const = 0;
};

// Abstract interface that enforces resource limits on a IoContext.
Expand Down
4 changes: 4 additions & 0 deletions src/workerd/server/server.c++
Original file line number Diff line number Diff line change
Expand Up @@ -3184,6 +3184,10 @@ kj::Own<Server::Service> Server::makeWorker(kj::StringPtr name,
// No limit on the number of iterations in workerd
return kj::none;
}

bool hasExcessivelyExceededHeapLimit() const override {
return false;
}
};

auto jsgobserver = kj::atomicRefcounted<JsgIsolateObserver>();
Expand Down

0 comments on commit 198db4d

Please # to comment.