File
Blob: src/workerd/io/io-own.h
| 1 | #pragma once |
| 2 | |
| 3 | #include <workerd/util/weak-refs.h> |
| 4 | |
| 5 | #include <kj/async.h> |
| 6 | #include <kj/common.h> |
| 7 | #include <kj/function.h> |
| 8 | #include <kj/mutex.h> |
| 9 | #include <kj/refcount.h> |
| 10 | #include <kj/vector.h> |
| 11 | |
| 12 | #include <typeinfo> |
| 13 | |
| 14 | namespace workerd { |
| 15 | namespace jsg { |
| 16 | class Lock; |
| 17 | } |
| 18 | |
| 19 | class IoContext; |
| 20 | |
| 21 | template <typename T> |
| 22 | class IoOwn; |
| 23 | template <typename T> |
| 24 | class IoPtr; |
| 25 | template <typename T> |
| 26 | class ReverseIoOwn; |
| 27 | |
| 28 | template <typename T> |
| 29 | struct RemoveIoOwn_ { |
| 30 | using Type = T; |
| 31 | static constexpr bool is = false; |
| 32 | }; |
| 33 | template <typename T> |
| 34 | struct RemoveIoOwn_<IoOwn<T>> { |
| 35 | using Type = T; |
| 36 | static constexpr bool is = true; |
| 37 | }; |
| 38 | |
| 39 | template <typename T> |
| 40 | constexpr bool isIoOwn() { |
| 41 | return RemoveIoOwn_<T>::is; |
| 42 | } |
| 43 | template <typename T> |
| 44 | using RemoveIoOwn = RemoveIoOwn_<T>::Type; |
| 45 | |
| 46 | struct OwnedObject { |
| 47 | kj::Maybe<kj::Own<OwnedObject>> next; |
| 48 | kj::Maybe<kj::Own<OwnedObject>>* prev; |
| 49 | }; |
| 50 | |
| 51 | template <typename T> |
| 52 | struct SpecificOwnedObject: public OwnedObject { |
| 53 | SpecificOwnedObject(kj::Own<T> ptr): ptr(kj::mv(ptr)) {} |
| 54 | kj::Own<T> ptr; |
| 55 | }; |
| 56 | |
| 57 | class OwnedObjectList { |
| 58 | public: |
| 59 | OwnedObjectList() = default; |
| 60 | KJ_DISALLOW_COPY_AND_MOVE(OwnedObjectList); |
| 61 | ~OwnedObjectList() noexcept(false); |
| 62 | |
| 63 | void link(kj::Own<OwnedObject> object); |
| 64 | static void unlink(OwnedObject& object); |
| 65 | |
| 66 | private: |
| 67 | kj::Maybe<kj::Own<OwnedObject>> head; |
| 68 | }; |
| 69 | |
| 70 | // Object which receives possibly-cross-thread deletions of owned objects. |
| 71 | class DeleteQueue: public kj::AtomicRefcounted { |
| 72 | public: |
| 73 | DeleteQueue(): crossThreadDeleteQueue(State{kj::Vector<OwnedObject*>()}) {} |
| 74 | |
| 75 | void scheduleDeletion(OwnedObject* object) const; |
| 76 | void scheduleAction(jsg::Lock& js, kj::Function<void(jsg::Lock&)>&& action) const; |
| 77 | |
| 78 | struct State { |
| 79 | kj::Vector<OwnedObject*> queue; |
| 80 | // Actions that some other IoContext has requested be executed in this IoContext. When |
| 81 | // adding an action to this list, crossThreadFulfiller should be fulfilled, signaling the |
| 82 | // target IoContext to wake up and run actions. After draining the actions queue, the target |
| 83 | // IoContext should replace crossThreadFulfiller with a new one which will wake it up again. |
| 84 | // |
| 85 | // In particular, these actions are used to implement cross-context promise resolution. |
| 86 | // |
| 87 | // Keep in mind the IoContext could be destroyed before the cross-thread signal runs, in |
| 88 | // which case the actions will never run. |
| 89 | kj::Vector<kj::Function<void(jsg::Lock&)>> actions; |
| 90 | kj::Maybe<kj::Own<kj::CrossThreadPromiseFulfiller<void>>> crossThreadFulfiller; |
| 91 | }; |
| 92 | |
| 93 | // Pointers from IoOwns that were dropped in other threads, and therefore should be deleted |
| 94 | // whenever the IoContext gets around to it. The maybe is changed to kj::none when the |
| 95 | // IoContext goes away, at which point all OwnedObjects have already been deleted so |
| 96 | // cross-thread deletions can just be ignored. |
| 97 | kj::MutexGuarded<kj::Maybe<State>> crossThreadDeleteQueue; |
| 98 | |
| 99 | // Implements the corresponding methods of IoContext and ActorContext. |
| 100 | template <typename T> |
| 101 | IoOwn<T> addObject(kj::Own<T> obj, OwnedObjectList& ownedObjects) const; |
| 102 | |
| 103 | template <typename T> |
| 104 | ReverseIoOwn<T> addObjectReverse(kj::Own<workerd::WeakRef<IoContext>> weakRef, |
| 105 | kj::Own<T> obj, |
| 106 | OwnedObjectList& ownedObjects) const; |
| 107 | |
| 108 | static void checkFarGet(const DeleteQueue& deleteQueue, const std::type_info& type); |
| 109 | static void checkWeakGet(workerd::WeakRef<IoContext>& weak); |
| 110 | |
| 111 | private: |
| 112 | template <typename T> |
| 113 | SpecificOwnedObject<T>* addObjectImpl(kj::Own<T> obj, OwnedObjectList& ownedObjects) const; |
| 114 | |
| 115 | kj::Promise<void> resetCrossThreadSignal() const; |
| 116 | |
| 117 | friend class IoContext; |
| 118 | }; |
| 119 | |
| 120 | // Object which can push actions into a specific DeleteQueue then signal its |
| 121 | // owning IoContext to wake up to process the queue. This is a bit of a hack of |
| 122 | // the DeleteQueue concept that allows us to use the same queue for more than |
| 123 | // just deletions. |
| 124 | class IoCrossContextExecutor { |
| 125 | public: |
| 126 | IoCrossContextExecutor(kj::Arc<DeleteQueue> deleteQueue): deleteQueue(kj::mv(deleteQueue)) {} |
| 127 | |
| 128 | // Tries to execute the specified action to the owning IoContext. |
| 129 | // The target IoContext will be signaled to run the action as soon as it is able. |
| 130 | void execute(jsg::Lock& js, kj::Function<void(jsg::Lock&)>&& action); |
| 131 | |
| 132 | private: |
| 133 | friend class IoContext; |
| 134 | friend class DeleteQueue; |
| 135 | |
| 136 | kj::Arc<DeleteQueue> deleteQueue; |
| 137 | }; |
| 138 | |
| 139 | template <typename T> |
| 140 | inline SpecificOwnedObject<T>* DeleteQueue::addObjectImpl( |
| 141 | kj::Own<T> obj, OwnedObjectList& ownedObjects) const { |
| 142 | // HACK: We need an Own<OwnedObject>, but we actually need to allocate it as the subclass |
| 143 | // SpecificOwnedObject<T>. OwnedObject is not polymorphic, which means kj::Own will refuse |
| 144 | // to upcast kj::Own<SpecificOwnedObject<T>> to kj::Own<OwnedObject> since it can't guarantee |
| 145 | // the disposers are compatible. However, since we're only using single inheritance here, the |
| 146 | // disposers *are* compatible (the numeric value of pointers to SpecificOwnedObject<T> and |
| 147 | // its parent OwnedObject are equal). So, instead of forcing OwnedObject to be polymorphic |
| 148 | // (which would have forced a bunch of useless vtables and vtable pointers)... I'm manually |
| 149 | // constructing the kj::Own<> using a disposer that I know is compatible. |
| 150 | // TODO(cleanup): Can KJ be made to support this use case? |
| 151 | kj::Own<OwnedObject> ownedObject(new SpecificOwnedObject<T>(kj::mv(obj)), |
| 152 | kj::_::HeapDisposer<SpecificOwnedObject<T>>::instance); |
| 153 | |
| 154 | auto result = static_cast<SpecificOwnedObject<T>*>(ownedObject.get()); |
| 155 | ownedObjects.link(kj::mv(ownedObject)); |
| 156 | return result; |
| 157 | } |
| 158 | |
| 159 | template <typename T> |
| 160 | inline IoOwn<T> DeleteQueue::addObject(kj::Own<T> obj, OwnedObjectList& ownedObjects) const { |
| 161 | return IoOwn<T>(addRefToThis(), addObjectImpl(kj::mv(obj), ownedObjects)); |
| 162 | } |
| 163 | |
| 164 | template <typename T> |
| 165 | inline ReverseIoOwn<T> DeleteQueue::addObjectReverse(kj::Own<workerd::WeakRef<IoContext>> weakRef, |
| 166 | kj::Own<T> obj, |
| 167 | OwnedObjectList& ownedObjects) const { |
| 168 | return ReverseIoOwn<T>(kj::mv(weakRef), addObjectImpl(kj::mv(obj), ownedObjects)); |
| 169 | } |
| 170 | |
| 171 | // When the IoContext is destroyed, we need to null out the DeleteQueue. Complicating |
| 172 | // matters a bit, we need to cancel all tasks (destroy the TaskSet) before this happens, so |
| 173 | // we can't just do it in IoContext's destructor. As a hack, we customize our pointer |
| 174 | // to the delete queue to get the tear-down order right. |
| 175 | class DeleteQueuePtr { |
| 176 | public: |
| 177 | DeleteQueuePtr(kj::Arc<DeleteQueue> queue): queue(kj::mv(queue)) {} |
| 178 | KJ_DISALLOW_COPY_AND_MOVE(DeleteQueuePtr); |
| 179 | ~DeleteQueuePtr() noexcept(false) { |
| 180 | auto ptr = queue.get(); |
| 181 | if (ptr != nullptr) { |
| 182 | auto lock = ptr->crossThreadDeleteQueue.lockExclusive(); |
| 183 | KJ_IF_SOME(state, *lock) { |
| 184 | // The delete queue state may include a kj::CrossThreadPromiseFulfiller that |
| 185 | // needs to be destroyed. To do so, we need to allow async destructors here. |
| 186 | // We only want to destroy the crossThreadFulfiller in this scope tho, not |
| 187 | // everything that may be in the queue. |
| 188 | kj::AllowAsyncDestructorsScope scope; |
| 189 | state.crossThreadFulfiller = kj::none; |
| 190 | } |
| 191 | *lock = kj::none; |
| 192 | } |
| 193 | } |
| 194 | kj::Arc<DeleteQueue> queue; |
| 195 | }; |
| 196 | |
| 197 | // Owned pointer held by a V8 heap object, pointing to a KJ event loop object. Cannot be |
| 198 | // dereferenced unless the isolate is executing on the appropriate event loop thread. |
| 199 | template <typename T> |
| 200 | class IoOwn { |
| 201 | |
| 202 | public: |
| 203 | IoOwn(IoOwn&& other) noexcept; |
| 204 | IoOwn(decltype(nullptr)): item(nullptr) {} |
| 205 | ~IoOwn() noexcept(false); |
| 206 | KJ_DISALLOW_COPY(IoOwn); |
| 207 | |
| 208 | T* operator->(); |
| 209 | T& operator*() { |
| 210 | return *operator->(); |
| 211 | } |
| 212 | operator kj::Own<T>() &&; |
| 213 | IoOwn& operator=(IoOwn&& other); |
| 214 | IoOwn& operator=(decltype(nullptr)); |
| 215 | |
| 216 | // Releases this object from the IoOwn, but instead of deleting it, attaches it to the |
| 217 | // IoContext (or ActorContext) such that it won't be destroyed until that context is torn |
| 218 | // down. |
| 219 | // |
| 220 | // This may need to be used in cases where an application could directly observe the destruction |
| 221 | // of this object. If that's the case, then the object cannot be destroyed during GC, as this |
| 222 | // would let the application observe GC, which might enable side channels. So, the destructor |
| 223 | // of the owning object must manually call `deferGcToContext()` to pass all such objects away |
| 224 | // to their respective contexts. |
| 225 | // |
| 226 | // Since this is expected to be called during GC, it is safe to call from a thread other than |
| 227 | // the one that owns the IoContext. |
| 228 | void deferGcToContext() &&; |
| 229 | |
| 230 | private: |
| 231 | friend class IoContext; |
| 232 | friend class DeleteQueue; |
| 233 | |
| 234 | kj::Arc<DeleteQueue> deleteQueue; |
| 235 | SpecificOwnedObject<T>* item; |
| 236 | |
| 237 | IoOwn(kj::Arc<DeleteQueue> deleteQueue, SpecificOwnedObject<T>* item) |
| 238 | : deleteQueue(kj::mv(deleteQueue)), |
| 239 | item(item) {} |
| 240 | }; |
| 241 | |
| 242 | // Reference held by a V8 heap object, pointing to a KJ event loop object. Cannot be |
| 243 | // dereferenced unless the isolate is executing on the appropriate event loop thread. |
| 244 | template <typename T> |
| 245 | class IoPtr { |
| 246 | public: |
| 247 | IoPtr(const IoPtr& other): deleteQueue(other.deleteQueue.addRef()), ptr(other.ptr) {} |
| 248 | IoPtr(IoPtr&& other) = default; |
| 249 | |
| 250 | T* operator->(); |
| 251 | T& operator*() { |
| 252 | return *operator->(); |
| 253 | } |
| 254 | IoPtr& operator=(decltype(nullptr)); |
| 255 | |
| 256 | private: |
| 257 | friend class IoContext; |
| 258 | friend class DeleteQueue; |
| 259 | |
| 260 | kj::Arc<DeleteQueue> deleteQueue; |
| 261 | T* ptr; |
| 262 | |
| 263 | IoPtr(kj::Arc<DeleteQueue> deleteQueue, T* ptr): deleteQueue(kj::mv(deleteQueue)), ptr(ptr) {} |
| 264 | }; |
| 265 | |
| 266 | // Owned pointer held by a KJ I/O object living in the same thread as an IoContext. The underlying |
| 267 | // object is destroyed when the ReverseIoOwn is dropped OR when the IoContext is destroyed, |
| 268 | // whichever comes first. Accessing the ReverseIoOwn after the IoContext is destroyed will throw. |
| 269 | // |
| 270 | // Use this when you have a KJ I/O object that could outlive an IoContext, but wants to hold onto |
| 271 | // some information that itself should not outlive the IoContext. In particular, if a KJ I/O object |
| 272 | // wants to hold JS handles (`jsg::JsRef`), this is normally safe as long as the handles do not |
| 273 | // outlive the isolate they point into. But if the holder could outlive the IoContext, then it |
| 274 | // could also outlive the isolate. In that case, the handles should be wrapped in an object held |
| 275 | // using `ReverseIoOwn`. |
| 276 | template <typename T> |
| 277 | class ReverseIoOwn { |
| 278 | public: |
| 279 | ReverseIoOwn(ReverseIoOwn&& other) noexcept; |
| 280 | ReverseIoOwn(decltype(nullptr)): item(nullptr) {} |
| 281 | ~ReverseIoOwn() noexcept(false); |
| 282 | KJ_DISALLOW_COPY(ReverseIoOwn); |
| 283 | |
| 284 | T* operator->(); |
| 285 | T& operator*() { |
| 286 | return *operator->(); |
| 287 | } |
| 288 | operator kj::Own<T>() &&; |
| 289 | ReverseIoOwn& operator=(ReverseIoOwn&& other); |
| 290 | ReverseIoOwn& operator=(decltype(nullptr)); |
| 291 | |
| 292 | // Try to get the underlying object if safe to dereference. |
| 293 | // Returns kj::none if the IoContext has been destroyed or if this is null. |
| 294 | // This is a safe alternative to operator->() that won't throw or crash. |
| 295 | kj::Maybe<T&> tryGet() { |
| 296 | if (item != nullptr && weakRef->isValid()) { |
| 297 | return *item->ptr.get(); |
| 298 | } |
| 299 | return kj::none; |
| 300 | } |
| 301 | |
| 302 | private: |
| 303 | friend class IoContext; |
| 304 | friend class DeleteQueue; |
| 305 | |
| 306 | kj::Own<workerd::WeakRef<IoContext>> weakRef; |
| 307 | SpecificOwnedObject<T>* item; |
| 308 | |
| 309 | ReverseIoOwn(kj::Own<workerd::WeakRef<IoContext>> weakRef, SpecificOwnedObject<T>* item) |
| 310 | : weakRef(kj::mv(weakRef)), |
| 311 | item(item) {} |
| 312 | }; |
| 313 | |
| 314 | template <typename T> |
| 315 | IoOwn<T>::IoOwn(IoOwn&& other) noexcept: deleteQueue(kj::mv(other.deleteQueue)), |
| 316 | item(other.item) { |
| 317 | other.item = nullptr; |
| 318 | } |
| 319 | |
| 320 | template <typename T> |
| 321 | IoOwn<T>::~IoOwn() noexcept(false) { |
| 322 | if (item != nullptr) { |
| 323 | deleteQueue->scheduleDeletion(item); |
| 324 | } |
| 325 | } |
| 326 | |
| 327 | template <typename T> |
| 328 | IoOwn<T>& IoOwn<T>::operator=(IoOwn<T>&& other) { |
| 329 | if (item != nullptr) { |
| 330 | deleteQueue->scheduleDeletion(item); |
| 331 | } |
| 332 | deleteQueue = kj::mv(other.deleteQueue); |
| 333 | item = other.item; |
| 334 | other.item = nullptr; |
| 335 | return *this; |
| 336 | } |
| 337 | |
| 338 | template <typename T> |
| 339 | IoOwn<T>& IoOwn<T>::operator=(decltype(nullptr)) { |
| 340 | if (item != nullptr) { |
| 341 | deleteQueue->scheduleDeletion(item); |
| 342 | } |
| 343 | deleteQueue = nullptr; |
| 344 | item = nullptr; |
| 345 | return *this; |
| 346 | } |
| 347 | |
| 348 | template <typename T> |
| 349 | void IoOwn<T>::deferGcToContext() && { |
| 350 | // Turns out, if we simply *don't* enqueue the item for deletion, we get the behavior we want. |
| 351 | // So we can just null out the pointers here... |
| 352 | item = nullptr; |
| 353 | deleteQueue = nullptr; |
| 354 | } |
| 355 | |
| 356 | template <typename T> |
| 357 | IoPtr<T>& IoPtr<T>::operator=(decltype(nullptr)) { |
| 358 | deleteQueue = nullptr; |
| 359 | ptr = nullptr; |
| 360 | return *this; |
| 361 | } |
| 362 | |
| 363 | template <typename T> |
| 364 | inline T* IoOwn<T>::operator->() { |
| 365 | DeleteQueue::checkFarGet(*deleteQueue.get(), typeid(T)); |
| 366 | return item->ptr; |
| 367 | } |
| 368 | |
| 369 | template <typename T> |
| 370 | inline IoOwn<T>::operator kj::Own<T>() && { |
| 371 | DeleteQueue::checkFarGet(*deleteQueue.get(), typeid(T)); |
| 372 | auto result = kj::mv(item->ptr); |
| 373 | OwnedObjectList::unlink(*item); |
| 374 | item = nullptr; |
| 375 | deleteQueue = nullptr; // not needed anymore, might as well drop the refcount |
| 376 | return result; |
| 377 | } |
| 378 | |
| 379 | template <typename T> |
| 380 | inline T* IoPtr<T>::operator->() { |
| 381 | DeleteQueue::checkFarGet(*deleteQueue.get(), typeid(T)); |
| 382 | return ptr; |
| 383 | } |
| 384 | |
| 385 | template <typename T> |
| 386 | ReverseIoOwn<T>::ReverseIoOwn(ReverseIoOwn&& other) noexcept |
| 387 | : weakRef(kj::mv(other.weakRef)), |
| 388 | item(other.item) { |
| 389 | other.item = nullptr; |
| 390 | } |
| 391 | |
| 392 | template <typename T> |
| 393 | ReverseIoOwn<T>::~ReverseIoOwn() noexcept(false) { |
| 394 | if (item != nullptr && weakRef->isValid()) { |
| 395 | OwnedObjectList::unlink(*item); |
| 396 | } |
| 397 | } |
| 398 | |
| 399 | template <typename T> |
| 400 | ReverseIoOwn<T>& ReverseIoOwn<T>::operator=(ReverseIoOwn<T>&& other) { |
| 401 | if (item != nullptr) { |
| 402 | OwnedObjectList::unlink(*item); |
| 403 | } |
| 404 | weakRef = kj::mv(other.weakRef); |
| 405 | item = other.item; |
| 406 | other.item = nullptr; |
| 407 | return *this; |
| 408 | } |
| 409 | |
| 410 | template <typename T> |
| 411 | ReverseIoOwn<T>& ReverseIoOwn<T>::operator=(decltype(nullptr)) { |
| 412 | if (item != nullptr) { |
| 413 | OwnedObjectList::unlink(*item); |
| 414 | } |
| 415 | weakRef = nullptr; |
| 416 | item = nullptr; |
| 417 | return *this; |
| 418 | } |
| 419 | |
| 420 | template <typename T> |
| 421 | inline T* ReverseIoOwn<T>::operator->() { |
| 422 | DeleteQueue::checkWeakGet(*weakRef); |
| 423 | return item->ptr; |
| 424 | } |
| 425 | |
| 426 | template <typename T> |
| 427 | inline ReverseIoOwn<T>::operator kj::Own<T>() && { |
| 428 | DeleteQueue::checkWeakGet(*weakRef); |
| 429 | auto result = kj::mv(item->ptr); |
| 430 | OwnedObjectList::unlink(*item); |
| 431 | item = nullptr; |
| 432 | weakRef = nullptr; // not needed anymore, might as well drop the refcount |
| 433 | return result; |
| 434 | } |
| 435 | |
| 436 | } // namespace workerd |