diff --git a/src/workerd/io/io-context.c++ b/src/workerd/io/io-context.c++ index 91af1d52157f..cd6e96eeb232 100644 --- a/src/workerd/io/io-context.c++ +++ b/src/workerd/io/io-context.c++ @@ -121,7 +121,7 @@ IoContext::IoContext(ThreadContext& thread, actor(actorParam), limitEnforcer(kj::mv(limitEnforcerParam)), threadId(getThreadId()), - deleteQueue(kj::atomicRefcounted()), + deleteQueue(kj::atomicRefcounted(), *this), cachePutSerializer(kj::READY_NOW), waitUntilTasks(*this), timeoutManager(kj::heap()), diff --git a/src/workerd/io/io-own.c++ b/src/workerd/io/io-own.c++ index 63405b97dc6a..3e8d34a8a399 100644 --- a/src/workerd/io/io-own.c++ +++ b/src/workerd/io/io-own.c++ @@ -59,6 +59,29 @@ void DeleteQueue::scheduleAction(jsg::Lock& js, kj::Function&& } } +DeleteQueuePtr::~DeleteQueuePtr() noexcept(false) { + auto ptr = get(); + if (ptr != nullptr) { + auto lock = ptr->crossThreadDeleteQueue.lockExclusive(); + // If there are actions still pending in the DeleteQueue, those will end up being + // silently dropped on the floor. This might cause some confusion for users who + // might expect promise continuations to still run. Let's log a warning in that + // case to help users understand what is happening. + KJ_IF_SOME(state, *lock) { + if (state.actions.size() > 0) { + context.logWarning( + "A promise was resolved or rejected from a different request context than " + "the one it was created in. However, the creating context is being destroyed " + "with continuations for that request still pending that can no longer be run " + "safely. This is usally because of the request being canceled or detected as " + "being hung."); + // TODO(soon): Add documentation link to this warning. + } + } + *lock = kj::none; + } +} + void DeleteQueue::checkFarGet(const DeleteQueue* deleteQueue, const std::type_info& type) { IoContext::current().checkFarGet(deleteQueue, type); } diff --git a/src/workerd/io/io-own.h b/src/workerd/io/io-own.h index 538b59923d7b..44348b0d9925 100644 --- a/src/workerd/io/io-own.h +++ b/src/workerd/io/io-own.h @@ -222,18 +222,18 @@ inline ReverseIoOwn DeleteQueue::addObjectReverse( // When the IoContext is destroyed, we need to null out the DeleteQueue. Complicating // matters a bit, we need to cancel all tasks (destroy the TaskSet) before this happens, so -// we can't just do it in IoContext's destrucrtor. As a hack, we customize our pointer +// we can't just do it in IoContext's destructor. As a hack, we customize our pointer // to the delete queue to get the tear-down order right. class DeleteQueuePtr: public kj::Own { public: - DeleteQueuePtr(kj::Own value): kj::Own(kj::mv(value)) {} + DeleteQueuePtr(kj::Own value, IoContext& context) + : kj::Own(kj::mv(value)), + context(context) {} KJ_DISALLOW_COPY_AND_MOVE(DeleteQueuePtr); - ~DeleteQueuePtr() noexcept(false) { - auto ptr = get(); - if (ptr != nullptr) { - *ptr->crossThreadDeleteQueue.lockExclusive() = kj::none; - } - } + ~DeleteQueuePtr() noexcept(false); + +private: + IoContext& context; }; // Owned pointer held by a V8 heap object, pointing to a KJ event loop object. Cannot be