Skip to content

Commit 8b89221

Browse files
committed
Exceptions from fetch handlers should wait for output gate.
Prior to this, if an application queued a storage write, then threw an exception, and the storage write failed later on, the exception might nevertheless be received by the caller. If, for some reason, an application were confirming writes by throwing exceptions, this could result in confirming a write that didn't actually make it to disk. Of course, surely no app does that. The bigger problem is that this could screw with the `durableObjectReset` flag on exceptions thrown after storage breakages. Say a storage write fails, and we respond by simultaneously throwing an exception from the storage API to the application, as well as breaking the output gate with the same exception. The version of the exception thrown at the application could propagate out of the fetch handler before the output gate had a chance to propagate its version. However, the version that throws *through* the application would lose the `broken.` annotation, as this by design does not transit through application code. The version of the exception that broke the output gate keeps this annotation, so is a better one to throw to the client. By properly waiting for the output gate here, we can make sure the preferred exception is propagated.
1 parent af0c339 commit 8b89221

File tree

1 file changed

+6
-1
lines changed

1 file changed

+6
-1
lines changed

src/workerd/io/worker-entrypoint.c++

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,12 @@ kj::Promise<void> WorkerEntrypoint::request(
175175
loggedExceptionEarlier = true;
176176
context.logUncaughtExceptionAsync(UncaughtExceptionSource::REQUEST_HANDLER,
177177
kj::cp(exception));
178-
return kj::mv(exception);
178+
179+
// Do not allow the exception to escape the isolate without waiting for the output gate to
180+
// open. Note that in the success path, this is taken care of in `FetchEvent::respondWith()`.
181+
return context.waitForOutputLocks()
182+
.then([exception = kj::mv(exception)]() mutable
183+
-> kj::Promise<void> { return kj::mv(exception); });
179184
}).attach(kj::defer([this,incomingRequest = kj::mv(incomingRequest),&context]() mutable {
180185
// The request has been canceled, but allow it to continue executing in the background.
181186
if (context.isFailOpen()) {

0 commit comments

Comments
 (0)