summaryrefslogtreecommitdiffstats
path: root/dom/promise/PromiseWorkerProxy.h
blob: 026fdd091fbf0bf333abedfb6f9ba61c920731c2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
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