@@ -535,6 +535,10 @@ kj::Promise<WorkerInterface::CustomEvent::Result> QueueCustomEventImpl::run(
535
535
kj::Maybe<kj::StringPtr > entrypointName,
536
536
Frankenvalue props,
537
537
kj::TaskSet& waitUntilTasks) {
538
+ // This method has three main chunks of logic:
539
+ // 1. Do all necessary setup work. This starts right below this comment.
540
+ // 2. Call into the worker's queue event handler.
541
+ // 3. Wait on the necessary portions of the worker's code to complete.
538
542
incomingRequest->delivered ();
539
543
auto & context = incomingRequest->getContext ();
540
544
@@ -572,9 +576,7 @@ kj::Promise<WorkerInterface::CustomEvent::Result> QueueCustomEventImpl::run(
572
576
};
573
577
auto queueEventHolder = kj::refcounted<QueueEventHolder>();
574
578
575
- // It's a little ugly, but the usage of waitUntil (and finishScheduled) down below are here so
576
- // that users can write queue handlers in the old addEventListener("queue", ...) syntax (where we
577
- // can't just wait on their addEventListener handler to resolve because it can't be async).
579
+ // 2. This is where we call into the worker's queue event handler
578
580
auto runProm = context.run (
579
581
[this , entrypointName = entrypointName, &context, queueEvent = kj::addRef (*queueEventHolder),
580
582
&metrics = incomingRequest->getMetrics (), outcomeObserver = kj::mv (outcomeObserver),
@@ -591,6 +593,13 @@ kj::Promise<WorkerInterface::CustomEvent::Result> QueueCustomEventImpl::run(
591
593
queueEvent->isServiceWorkerHandler = startResp.isServiceWorkerHandler ;
592
594
});
593
595
596
+ // 3. Now that we've (asynchronously) called into the event handler, wait on all necessary async
597
+ // work to complete. This logic is split into two completely separate code paths depending on
598
+ // whether the queueConsumerNoWaitForWaitUntil compatibility flag is enabled.
599
+ // * In the enabled path, the queue event can be considered complete as soon as the event handler
600
+ // returns and the promise that it returns (if any) has resolved.
601
+ // * In the disabled path, the queue event isn't complete until all waitUntil'ed promises resolve.
602
+ // This was how Queues originally worked, but made for a poor user experience.
594
603
auto compatFlags = context.getWorker ().getIsolate ().getApi ().getFeatureFlags ();
595
604
if (compatFlags.getQueueConsumerNoWaitForWaitUntil ()) {
596
605
// The user has opted in to only waiting on their event handler rather than all waitUntil'd
@@ -602,10 +611,11 @@ kj::Promise<WorkerInterface::CustomEvent::Result> QueueCustomEventImpl::run(
602
611
auto outcome = co_await runProm
603
612
.then ([queueEvent = kj::addRef (
604
613
*queueEventHolder)]() mutable -> kj::Promise<EventOutcome> {
605
- // If it returned a promise, wait on the promise.
614
+ // If the queue handler returned a promise, wait on the promise.
606
615
KJ_IF_SOME (handlerProm, queueEvent->exportedHandlerProm ) {
607
616
return handlerProm.then ([]() { return EventOutcome::OK; });
608
617
}
618
+ // If not, we can consider the invocation complete.
609
619
return EventOutcome::OK;
610
620
})
611
621
.catch_ ([](kj::Exception&& e) {
@@ -633,9 +643,11 @@ kj::Promise<WorkerInterface::CustomEvent::Result> QueueCustomEventImpl::run(
633
643
bool completed = result == IoContext_IncomingRequest::FinishScheduledResult::COMPLETED;
634
644
outcome = completed ? context.waitUntilStatus () : EventOutcome::EXCEEDED_CPU;
635
645
} else {
636
- // If we aren't going to wait on the waitUntil tasks via a call to
637
- // incomingRequest->finishScheduled(), we're responsible for calling draing() on the
638
- // incomingRequest to ensure that waitUntil tasks are run in the backgound.
646
+ // We're responsible for calling drain() on the incomingRequest to ensure that waitUntil tasks
647
+ // can continue to run in the backgound for a while even after we return a result to the
648
+ // caller of this event. But this is only needed in this code path because in all other code
649
+ // paths we call incomingRequest->finishScheduled(), which already takes care of waiting on
650
+ // waitUntil tasks.
639
651
waitUntilTasks.add (incomingRequest->drain ().attach (
640
652
kj::mv (incomingRequest), kj::addRef (*queueEventHolder), kj::addRef (*this )));
641
653
}
@@ -654,7 +666,8 @@ kj::Promise<WorkerInterface::CustomEvent::Result> QueueCustomEventImpl::run(
654
666
auto result = co_await incomingRequest->finishScheduled ();
655
667
bool completed = result == IoContext_IncomingRequest::FinishScheduledResult::COMPLETED;
656
668
657
- // Log some debug info if the request timed out or was aborted.
669
+ // Log some debug info if the request timed out or was aborted, to aid in debugging situations
670
+ // where consumer workers appear to get stuck and repeatedly take 15 minutes.
658
671
// In particular, detect whether or not the users queue() handler function completed
659
672
// and include info about other waitUntil tasks that may have caused the request to timeout.
660
673
if (!completed) {
0 commit comments