PromiseWorkerProxy.h (8324B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef mozilla_dom_PromiseWorkerProxy_h 8 #define mozilla_dom_PromiseWorkerProxy_h 9 10 #include <cstdint> 11 12 #include "js/TypeDecls.h" 13 #include "mozilla/AlreadyAddRefed.h" 14 #include "mozilla/Mutex.h" 15 #include "mozilla/RefPtr.h" 16 #include "mozilla/dom/Promise.h" 17 #include "mozilla/dom/PromiseNativeHandler.h" 18 #include "mozilla/dom/StructuredCloneHolder.h" 19 #include "nsISupports.h" 20 21 struct JSStructuredCloneReader; 22 struct JSStructuredCloneWriter; 23 24 namespace JS { 25 class CloneDataPolicy; 26 } // namespace JS 27 28 namespace mozilla::dom { 29 30 class ThreadSafeWorkerRef; 31 class WorkerPrivate; 32 33 // A proxy to (eventually) mirror a resolved/rejected Promise's result from the 34 // main thread to a Promise on the worker thread. 35 // 36 // How to use: 37 // 38 // 1. Create a Promise on the worker thread and return it to the content 39 // script: 40 // 41 // RefPtr<Promise> promise = 42 // Promise::Create(workerPrivate->GlobalScope(), aRv); 43 // if (aRv.Failed()) { 44 // return nullptr; 45 // } 46 // 47 // 2. Create a PromiseWorkerProxy wrapping the Promise. If this fails, the 48 // worker is shutting down and you should fail the original call. This is 49 // only likely to happen in (Gecko-specific) worker onclose handlers. 50 // 51 // RefPtr<PromiseWorkerProxy> proxy = 52 // PromiseWorkerProxy::Create(workerPrivate, promise); 53 // if (!proxy) { 54 // // You may also reject the Promise with an AbortError or similar. 55 // return nullptr; 56 // } 57 // 58 // 3. Dispatch a runnable to the main thread, with a reference to the proxy to 59 // perform the main thread operation. PromiseWorkerProxy is thread-safe 60 // refcounted. 61 // 62 // 4. Return the worker thread promise to the JS caller: 63 // 64 // return promise.forget(); 65 // 66 // 5. In your main thread runnable Run(), obtain a Promise on 67 // the main thread and call its AppendNativeHandler(PromiseNativeHandler*) 68 // to bind the PromiseWorkerProxy created at #2. 69 // 70 // 4. Then the Promise results returned by ResolvedCallback/RejectedCallback 71 // will be dispatched as a WorkerRunnable to the worker thread to 72 // resolve/reject the Promise created at #1. 73 // 74 // PromiseWorkerProxy can also be used in situations where there is no main 75 // thread Promise, or where special handling is required on the worker thread 76 // for promise resolution. Create a PromiseWorkerProxy as in steps 1 to 3 77 // above. When the main thread is ready to resolve the worker thread promise: 78 // 79 // 1. Acquire the mutex before attempting to access the worker private. 80 // 81 // AssertIsOnMainThread(); 82 // MutexAutoLock lock(proxy->Lock()); 83 // if (proxy->CleanedUp()) { 84 // // Worker has already shut down, can't access worker private. 85 // return; 86 // } 87 // 88 // 2. Dispatch a runnable to the worker. Use GetWorkerPrivate() to acquire the 89 // worker. 90 // 91 // RefPtr<FinishTaskWorkerRunnable> runnable = 92 // new FinishTaskWorkerRunnable(proxy->GetWorkerPrivate(), proxy, 93 // result); 94 // if (!r->Dispatch()) { 95 // // Worker is alive but not Running any more, so the Promise can't 96 // // be resolved, give up. The proxy will get Release()d at some 97 // // point. 98 // 99 // // Usually do nothing, but you may want to log the fact. 100 // } 101 // 102 // 3. In the WorkerRunnable's WorkerRun() use GetWorkerPromise() to access the 103 // Promise and resolve/reject it. Then call CleanUp(). 104 // 105 // bool 106 // WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override 107 // { 108 // aWorkerPrivate->AssertIsOnWorkerThread(); 109 // RefPtr<Promise> promise = mProxy->GetWorkerPromise(); 110 // promise->MaybeResolve(mResult); 111 // mProxy->CleanUp(); 112 // } 113 // 114 // Note: If a PromiseWorkerProxy is not cleaned up by a WorkerRunnable - this 115 // can happen if the main thread Promise is never fulfilled - it will 116 // stay alive till the worker reaches a Canceling state, even if all external 117 // references to it are dropped. 118 119 class PromiseWorkerProxy : public PromiseNativeHandler, 120 public StructuredCloneHolderBase { 121 friend class PromiseWorkerProxyRunnable; 122 123 NS_DECL_THREADSAFE_ISUPPORTS 124 125 public: 126 typedef JSObject* (*ReadCallbackOp)(JSContext* aCx, 127 JSStructuredCloneReader* aReader, 128 const PromiseWorkerProxy* aProxy, 129 uint32_t aTag, uint32_t aData); 130 typedef bool (*WriteCallbackOp)(JSContext* aCx, 131 JSStructuredCloneWriter* aWorker, 132 PromiseWorkerProxy* aProxy, 133 JS::Handle<JSObject*> aObj); 134 135 struct PromiseWorkerProxyStructuredCloneCallbacks { 136 ReadCallbackOp Read; 137 WriteCallbackOp Write; 138 }; 139 140 static already_AddRefed<PromiseWorkerProxy> Create( 141 WorkerPrivate* aWorkerPrivate, Promise* aWorkerPromise, 142 const PromiseWorkerProxyStructuredCloneCallbacks* aCallbacks = nullptr); 143 144 // Main thread callers must hold Lock() and check CleanUp() before calling 145 // this. Worker thread callers, this will assert that the proxy has not been 146 // cleaned up. 147 WorkerPrivate* GetWorkerPrivate() const MOZ_NO_THREAD_SAFETY_ANALYSIS; 148 149 // This should only be used within WorkerRunnable::WorkerRun() running on the 150 // worker thread! If this method is called after CleanUp(), return nullptr. 151 Promise* GetWorkerPromise() const; 152 153 // Worker thread only. Calling this invalidates several assumptions, so be 154 // sure this is the last thing you do. 155 // 1. WorkerPrivate() will no longer return a valid worker. 156 // 2. GetWorkerPromise() will return null! 157 void CleanUp(); 158 159 Mutex& Lock() MOZ_RETURN_CAPABILITY(mCleanUpLock) { return mCleanUpLock; } 160 161 bool CleanedUp() const MOZ_REQUIRES(mCleanUpLock) { 162 mCleanUpLock.AssertCurrentThreadOwns(); 163 return mCleanedUp; 164 } 165 166 // StructuredCloneHolderBase 167 168 JSObject* CustomReadHandler(JSContext* aCx, JSStructuredCloneReader* aReader, 169 const JS::CloneDataPolicy& aCloneDataPolicy, 170 uint32_t aTag, uint32_t aIndex) override; 171 172 bool CustomWriteHandler(JSContext* aCx, JSStructuredCloneWriter* aWriter, 173 JS::Handle<JSObject*> aObj, 174 bool* aSameProcessScopeRequired) override; 175 176 protected: 177 virtual void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, 178 ErrorResult& aRv) override; 179 180 virtual void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, 181 ErrorResult& aRv) override; 182 183 private: 184 explicit PromiseWorkerProxy( 185 Promise* aWorkerPromise, 186 const PromiseWorkerProxyStructuredCloneCallbacks* aCallbacks = nullptr); 187 188 virtual ~PromiseWorkerProxy(); 189 190 // Function pointer for calling Promise::{ResolveInternal,RejectInternal}. 191 typedef void (Promise::*RunCallbackFunc)(JSContext*, JS::Handle<JS::Value>); 192 193 void RunCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, 194 RunCallbackFunc aFunc); 195 196 // Any thread with appropriate checks. 197 RefPtr<ThreadSafeWorkerRef> mWorkerRef; 198 199 // Worker thread only. 200 RefPtr<Promise> mWorkerPromise; 201 202 // Modified on the worker thread. 203 // It is ok to *read* this without a lock on the worker. 204 // Main thread must always acquire a lock. 205 bool mCleanedUp MOZ_GUARDED_BY( 206 mCleanUpLock); // To specify if the cleanUp() has been done. 207 208 const PromiseWorkerProxyStructuredCloneCallbacks* mCallbacks; 209 210 // Ensure the worker and the main thread won't race to access |mCleanedUp|. 211 // This could perhaps be a type similar to `EventTargetAndLockCapability` 212 // guarding `mCleanedUp` and `mWorkerRef`, however doing so naively could 213 // inflate the size of this object. 214 Mutex mCleanUpLock; 215 }; 216 } // namespace mozilla::dom 217 218 #endif // mozilla_dom_PromiseWorkerProxy_h