Skip to content
File

Blob: src/workerd/io/io-own.h

cpp437 lines
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 
14namespace workerd {
15namespace jsg {
16class Lock;
17}
18 
19class IoContext;
20 
21template <typename T>
22class IoOwn;
23template <typename T>
24class IoPtr;
25template <typename T>
26class ReverseIoOwn;
27 
28template <typename T>
29struct RemoveIoOwn_ {
30 using Type = T;
31 static constexpr bool is = false;
32};
33template <typename T>
34struct RemoveIoOwn_<IoOwn<T>> {
35 using Type = T;
36 static constexpr bool is = true;
37};
38 
39template <typename T>
40constexpr bool isIoOwn() {
41 return RemoveIoOwn_<T>::is;
42}
43template <typename T>
44using RemoveIoOwn = RemoveIoOwn_<T>::Type;
45 
46struct OwnedObject {
47 kj::Maybe<kj::Own<OwnedObject>> next;
48 kj::Maybe<kj::Own<OwnedObject>>* prev;
49};
50 
51template <typename T>
52struct SpecificOwnedObject: public OwnedObject {
53 SpecificOwnedObject(kj::Own<T> ptr): ptr(kj::mv(ptr)) {}
54 kj::Own<T> ptr;
55};
56 
57class 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.
71class 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.
124class 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 
139template <typename T>
140inline 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 
159template <typename T>
160inline IoOwn<T> DeleteQueue::addObject(kj::Own<T> obj, OwnedObjectList& ownedObjects) const {
161 return IoOwn<T>(addRefToThis(), addObjectImpl(kj::mv(obj), ownedObjects));
162}
163 
164template <typename T>
165inline 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.
175class 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.
199template <typename T>
200class 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.
244template <typename T>
245class 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`.
276template <typename T>
277class 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 
314template <typename T>
315IoOwn<T>::IoOwn(IoOwn&& other) noexcept: deleteQueue(kj::mv(other.deleteQueue)),
316 item(other.item) {
317 other.item = nullptr;
318}
319 
320template <typename T>
321IoOwn<T>::~IoOwn() noexcept(false) {
322 if (item != nullptr) {
323 deleteQueue->scheduleDeletion(item);
324 }
325}
326 
327template <typename T>
328IoOwn<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 
338template <typename T>
339IoOwn<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 
348template <typename T>
349void 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 
356template <typename T>
357IoPtr<T>& IoPtr<T>::operator=(decltype(nullptr)) {
358 deleteQueue = nullptr;
359 ptr = nullptr;
360 return *this;
361}
362 
363template <typename T>
364inline T* IoOwn<T>::operator->() {
365 DeleteQueue::checkFarGet(*deleteQueue.get(), typeid(T));
366 return item->ptr;
367}
368 
369template <typename T>
370inline 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 
379template <typename T>
380inline T* IoPtr<T>::operator->() {
381 DeleteQueue::checkFarGet(*deleteQueue.get(), typeid(T));
382 return ptr;
383}
384 
385template <typename T>
386ReverseIoOwn<T>::ReverseIoOwn(ReverseIoOwn&& other) noexcept
387 : weakRef(kj::mv(other.weakRef)),
388 item(other.item) {
389 other.item = nullptr;
390}
391 
392template <typename T>
393ReverseIoOwn<T>::~ReverseIoOwn() noexcept(false) {
394 if (item != nullptr && weakRef->isValid()) {
395 OwnedObjectList::unlink(*item);
396 }
397}
398 
399template <typename T>
400ReverseIoOwn<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 
410template <typename T>
411ReverseIoOwn<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 
420template <typename T>
421inline T* ReverseIoOwn<T>::operator->() {
422 DeleteQueue::checkWeakGet(*weakRef);
423 return item->ptr;
424}
425 
426template <typename T>
427inline 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