diff options
Diffstat (limited to 'dom/promise/PromiseWorkerProxy.h')
-rw-r--r-- | dom/promise/PromiseWorkerProxy.h | 219 |
1 files changed, 219 insertions, 0 deletions
diff --git a/dom/promise/PromiseWorkerProxy.h b/dom/promise/PromiseWorkerProxy.h new file mode 100644 index 0000000000..026fdd091f --- /dev/null +++ b/dom/promise/PromiseWorkerProxy.h @@ -0,0 +1,219 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_PromiseWorkerProxy_h +#define mozilla_dom_PromiseWorkerProxy_h + +#include <cstdint> +#include "js/TypeDecls.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Mutex.h" +#include "mozilla/RefPtr.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/PromiseNativeHandler.h" +#include "mozilla/dom/StructuredCloneHolder.h" +#include "nsISupports.h" + +struct JSStructuredCloneReader; +struct JSStructuredCloneWriter; + +namespace JS { +class CloneDataPolicy; +} // namespace JS + +namespace mozilla::dom { + +class ThreadSafeWorkerRef; +class WorkerPrivate; + +// A proxy to (eventually) mirror a resolved/rejected Promise's result from the +// main thread to a Promise on the worker thread. +// +// How to use: +// +// 1. Create a Promise on the worker thread and return it to the content +// script: +// +// RefPtr<Promise> promise = +// Promise::Create(workerPrivate->GlobalScope(), aRv); +// if (aRv.Failed()) { +// return nullptr; +// } +// +// 2. Create a PromiseWorkerProxy wrapping the Promise. If this fails, the +// worker is shutting down and you should fail the original call. This is +// only likely to happen in (Gecko-specific) worker onclose handlers. +// +// RefPtr<PromiseWorkerProxy> proxy = +// PromiseWorkerProxy::Create(workerPrivate, promise); +// if (!proxy) { +// // You may also reject the Promise with an AbortError or similar. +// return nullptr; +// } +// +// 3. Dispatch a runnable to the main thread, with a reference to the proxy to +// perform the main thread operation. PromiseWorkerProxy is thread-safe +// refcounted. +// +// 4. Return the worker thread promise to the JS caller: +// +// return promise.forget(); +// +// 5. In your main thread runnable Run(), obtain a Promise on +// the main thread and call its AppendNativeHandler(PromiseNativeHandler*) +// to bind the PromiseWorkerProxy created at #2. +// +// 4. Then the Promise results returned by ResolvedCallback/RejectedCallback +// will be dispatched as a WorkerRunnable to the worker thread to +// resolve/reject the Promise created at #1. +// +// PromiseWorkerProxy can also be used in situations where there is no main +// thread Promise, or where special handling is required on the worker thread +// for promise resolution. Create a PromiseWorkerProxy as in steps 1 to 3 +// above. When the main thread is ready to resolve the worker thread promise: +// +// 1. Acquire the mutex before attempting to access the worker private. +// +// AssertIsOnMainThread(); +// MutexAutoLock lock(proxy->Lock()); +// if (proxy->CleanedUp()) { +// // Worker has already shut down, can't access worker private. +// return; +// } +// +// 2. Dispatch a runnable to the worker. Use GetWorkerPrivate() to acquire the +// worker. +// +// RefPtr<FinishTaskWorkerRunnable> runnable = +// new FinishTaskWorkerRunnable(proxy->GetWorkerPrivate(), proxy, +// result); +// if (!r->Dispatch()) { +// // Worker is alive but not Running any more, so the Promise can't +// // be resolved, give up. The proxy will get Release()d at some +// // point. +// +// // Usually do nothing, but you may want to log the fact. +// } +// +// 3. In the WorkerRunnable's WorkerRun() use GetWorkerPromise() to access the +// Promise and resolve/reject it. Then call CleanUp(). +// +// bool +// WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override +// { +// aWorkerPrivate->AssertIsOnWorkerThread(); +// RefPtr<Promise> promise = mProxy->GetWorkerPromise(); +// promise->MaybeResolve(mResult); +// mProxy->CleanUp(); +// } +// +// Note: If a PromiseWorkerProxy is not cleaned up by a WorkerRunnable - this +// can happen if the main thread Promise is never fulfilled - it will +// stay alive till the worker reaches a Canceling state, even if all external +// references to it are dropped. + +class PromiseWorkerProxy : public PromiseNativeHandler, + public StructuredCloneHolderBase, + public SingleWriterLockOwner { + friend class PromiseWorkerProxyRunnable; + + NS_DECL_THREADSAFE_ISUPPORTS + + public: + typedef JSObject* (*ReadCallbackOp)(JSContext* aCx, + JSStructuredCloneReader* aReader, + const PromiseWorkerProxy* aProxy, + uint32_t aTag, uint32_t aData); + typedef bool (*WriteCallbackOp)(JSContext* aCx, + JSStructuredCloneWriter* aWorker, + PromiseWorkerProxy* aProxy, + JS::Handle<JSObject*> aObj); + + bool OnWritingThread() const override; + + struct PromiseWorkerProxyStructuredCloneCallbacks { + ReadCallbackOp Read; + WriteCallbackOp Write; + }; + + static already_AddRefed<PromiseWorkerProxy> Create( + WorkerPrivate* aWorkerPrivate, Promise* aWorkerPromise, + const PromiseWorkerProxyStructuredCloneCallbacks* aCallbacks = nullptr); + + // Main thread callers must hold Lock() and check CleanUp() before calling + // this. Worker thread callers, this will assert that the proxy has not been + // cleaned up. + WorkerPrivate* GetWorkerPrivate() const MOZ_NO_THREAD_SAFETY_ANALYSIS; + + // This should only be used within WorkerRunnable::WorkerRun() running on the + // worker thread! If this method is called after CleanUp(), return nullptr. + Promise* GetWorkerPromise() const; + + // Worker thread only. Calling this invalidates several assumptions, so be + // sure this is the last thing you do. + // 1. WorkerPrivate() will no longer return a valid worker. + // 2. GetWorkerPromise() will return null! + void CleanUp(); + + Mutex& Lock() MOZ_RETURN_CAPABILITY(mCleanUpLock) { return mCleanUpLock; } + + bool CleanedUp() const MOZ_REQUIRES(mCleanUpLock) { + mCleanUpLock.AssertCurrentThreadOwns(); + return mCleanedUp; + } + + // StructuredCloneHolderBase + + JSObject* CustomReadHandler(JSContext* aCx, JSStructuredCloneReader* aReader, + const JS::CloneDataPolicy& aCloneDataPolicy, + uint32_t aTag, uint32_t aIndex) override; + + bool CustomWriteHandler(JSContext* aCx, JSStructuredCloneWriter* aWriter, + JS::Handle<JSObject*> aObj, + bool* aSameProcessScopeRequired) override; + + protected: + virtual void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, + ErrorResult& aRv) override; + + virtual void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, + ErrorResult& aRv) override; + + private: + explicit PromiseWorkerProxy( + Promise* aWorkerPromise, + const PromiseWorkerProxyStructuredCloneCallbacks* aCallbacks = nullptr); + + virtual ~PromiseWorkerProxy(); + + // Function pointer for calling Promise::{ResolveInternal,RejectInternal}. + typedef void (Promise::*RunCallbackFunc)(JSContext*, JS::Handle<JS::Value>); + + void RunCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, + RunCallbackFunc aFunc); + + // Any thread with appropriate checks. + RefPtr<ThreadSafeWorkerRef> mWorkerRef; + + // Worker thread only. + RefPtr<Promise> mWorkerPromise; + + // Modified on the worker thread. + // It is ok to *read* this without a lock on the worker. + // Main thread must always acquire a lock. + bool mCleanedUp MOZ_GUARDED_BY( + mCleanUpLock); // To specify if the cleanUp() has been done. + + const PromiseWorkerProxyStructuredCloneCallbacks* mCallbacks; + + // Ensure the worker and the main thread won't race to access |mCleanedUp|. + // Should be a MutexSingleWriter, but that causes a lot of issues when you + // expose the lock via Lock(). + Mutex mCleanUpLock; +}; +} // namespace mozilla::dom + +#endif // mozilla_dom_PromiseWorkerProxy_h |