File
Blob: src/workerd/io/io-own.c++
| 1 | #include "io-own.h" |
| 2 | |
| 3 | #include "io-context.h" |
| 4 | |
| 5 | #include <workerd/jsg/util.h> |
| 6 | |
| 7 | namespace workerd { |
| 8 | |
| 9 | void DeleteQueue::scheduleDeletion(OwnedObject* object) const { |
| 10 | if (IoContext::hasCurrent() && IoContext::current().deleteQueue.queue.get() == this) { |
| 11 | // Deletion from same thread. No need to enqueue. |
| 12 | kj::AllowAsyncDestructorsScope scope; |
| 13 | OwnedObjectList::unlink(*object); |
| 14 | } else { |
| 15 | auto lock = crossThreadDeleteQueue.lockExclusive(); |
| 16 | KJ_IF_SOME(state, *lock) { |
| 17 | state.queue.add(object); |
| 18 | } |
| 19 | } |
| 20 | } |
| 21 | |
| 22 | void DeleteQueue::scheduleAction(jsg::Lock& js, kj::Function<void(jsg::Lock&)>&& action) const { |
| 23 | { |
| 24 | auto lock = crossThreadDeleteQueue.lockExclusive(); |
| 25 | KJ_IF_SOME(state, *lock) { |
| 26 | state.actions.add(kj::mv(action)); |
| 27 | KJ_REQUIRE_NONNULL(state.crossThreadFulfiller)->fulfill(); |
| 28 | return; |
| 29 | } |
| 30 | } |
| 31 | |
| 32 | // The queue was deleted, likely because the IoContext was destroyed and the |
| 33 | // DeleteQueuePtr was invalidated. We are going to emit a warning and drop the |
| 34 | // actions on the floor without scheduling them. |
| 35 | KJ_IF_SOME(ioContext, IoContext::tryCurrent()) { |
| 36 | // We are creating an error here just so we can include the JavaScript stack |
| 37 | // with the warning if it exists. We are not going to throw this error. |
| 38 | auto err = v8::Exception::Error( |
| 39 | js.str("A promise was resolved or rejected from a different request context than " |
| 40 | "the one it was created in. However, the creating request has already been " |
| 41 | "completed or canceled. Continuations for that request are unlikely to " |
| 42 | "run safely and have been canceled. If this behavior breaks your worker, " |
| 43 | "consider setting the `no_handle_cross_request_promise_resolution` " |
| 44 | "compatibility flag for your worker."_kj)) |
| 45 | .As<v8::Object>(); |
| 46 | // TODO(soon): Add documentation link to this warning. |
| 47 | // Changing the name property to "Warning" will make the serialize stack start with |
| 48 | // "Warning: " rather that "Error: " |
| 49 | jsg::check(err->Set(js.v8Context(), js.str("name"_kj), js.str("Warning"_kj))); |
| 50 | auto stack = jsg::check(err->Get(js.v8Context(), js.str("stack"_kj))); |
| 51 | |
| 52 | // Safe to mutate here since we have the exclusive lock on the queue above. |
| 53 | ioContext.logWarning(kj::str(stack)); |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | void DeleteQueue::checkFarGet(const DeleteQueue& deleteQueue, const std::type_info& type) { |
| 58 | IoContext::current().checkFarGet(deleteQueue, type); |
| 59 | } |
| 60 | |
| 61 | void DeleteQueue::checkWeakGet(workerd::WeakRef<IoContext>& weak) { |
| 62 | JSG_REQUIRE(weak.isValid(), Error, |
| 63 | "Couldn't complete operation because the execution context has ended."); |
| 64 | } |
| 65 | |
| 66 | kj::Promise<void> DeleteQueue::resetCrossThreadSignal() const { |
| 67 | auto lock = crossThreadDeleteQueue.lockExclusive(); |
| 68 | KJ_IF_SOME(state, *lock) { |
| 69 | KJ_IF_SOME(fulfiller, state.crossThreadFulfiller) { |
| 70 | // We should only reset the signal if it has been fulfilled. |
| 71 | KJ_ASSERT(!fulfiller->isWaiting()); |
| 72 | } |
| 73 | auto paf = kj::newPromiseAndCrossThreadFulfiller<void>(); |
| 74 | state.crossThreadFulfiller = kj::mv(paf.fulfiller); |
| 75 | return kj::mv(paf.promise); |
| 76 | } else { |
| 77 | return kj::NEVER_DONE; |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | OwnedObjectList::~OwnedObjectList() noexcept(false) { |
| 82 | while (head != kj::none) { |
| 83 | // We want to have the same order of operations as the recursive destructor here. Without this |
| 84 | // optimization, `~SpecificOwnedObject<T>` is invoked first, then `~OwnedObject()` which |
| 85 | // destructs the next node which continues the process. The key takeaway is that we destroy each |
| 86 | // node's `T` before we move onto the next node. This duplicates that behavior by unlinking |
| 87 | // forward through the list, which hopefully should keep our stack size low no matter how many |
| 88 | // `OwnedObject` instances we have. |
| 89 | unlink(*KJ_ASSERT_NONNULL(head)); |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | void OwnedObjectList::unlink(OwnedObject& object) { |
| 94 | KJ_IF_SOME(next, object.next) { |
| 95 | next.get()->prev = object.prev; |
| 96 | } |
| 97 | *object.prev = kj::mv(object.next); |
| 98 | } |
| 99 | |
| 100 | void OwnedObjectList::link(kj::Own<OwnedObject> object) { |
| 101 | object->next = kj::mv(head); |
| 102 | KJ_IF_SOME(next, object->next) { |
| 103 | next.get()->prev = &object->next; |
| 104 | } |
| 105 | object->prev = &head; |
| 106 | head = kj::mv(object); |
| 107 | } |
| 108 | |
| 109 | void IoCrossContextExecutor::execute(jsg::Lock& js, kj::Function<void(jsg::Lock&)>&& func) { |
| 110 | deleteQueue->scheduleAction(js, kj::mv(func)); |
| 111 | } |
| 112 | |
| 113 | } // namespace workerd |