/src/mozilla-central/dom/media/systemservices/MediaUtils.h
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set sw=2 ts=8 et ft=cpp : */ |
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 file, |
5 | | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #ifndef mozilla_MediaUtils_h |
8 | | #define mozilla_MediaUtils_h |
9 | | |
10 | | #include "mozilla/Assertions.h" |
11 | | #include "mozilla/Monitor.h" |
12 | | #include "mozilla/MozPromise.h" |
13 | | #include "mozilla/RefPtr.h" |
14 | | #include "mozilla/SharedThreadPool.h" |
15 | | #include "mozilla/TaskQueue.h" |
16 | | #include "mozilla/UniquePtr.h" |
17 | | #include "nsCOMPtr.h" |
18 | | #include "nsIAsyncShutdown.h" |
19 | | #include "nsISupportsImpl.h" |
20 | | #include "nsThreadUtils.h" |
21 | | |
22 | | class nsIEventTarget; |
23 | | |
24 | | namespace mozilla { |
25 | | namespace media { |
26 | | |
27 | | /* |
28 | | * media::Pledge - A promise-like pattern for c++ that takes lambda functions. |
29 | | * |
30 | | * Asynchronous APIs that proxy to another thread or to the chrome process and |
31 | | * back may find it useful to return a pledge to callers who then use |
32 | | * pledge.Then(func) to specify a lambda function to be invoked with the result |
33 | | * later back on this same thread. |
34 | | * |
35 | | * Callers will enjoy that lambdas allow "capturing" of local variables, much |
36 | | * like closures in JavaScript (safely by-copy by default). |
37 | | * |
38 | | * Callers will also enjoy that they do not need to be thread-safe (their code |
39 | | * runs on the same thread after all). |
40 | | * |
41 | | * Advantageously, pledges are non-threadsafe by design (because locking and |
42 | | * event queues are redundant). This means none of the lambdas you pass in, |
43 | | * or variables you lambda-capture into them, need be threasafe or support |
44 | | * threadsafe refcounting. After all, they'll run later on the same thread. |
45 | | * |
46 | | * RefPtr<media::Pledge<Foo>> p = GetFooAsynchronously(); // returns a pledge |
47 | | * p->Then([](const Foo& foo) { |
48 | | * // use foo here (same thread. Need not be thread-safe!) |
49 | | * }); |
50 | | * |
51 | | * See media::CoatCheck below for an example of GetFooAsynchronously(). |
52 | | */ |
53 | | |
54 | | class PledgeBase |
55 | | { |
56 | | public: |
57 | | NS_INLINE_DECL_REFCOUNTING(PledgeBase); |
58 | | protected: |
59 | | virtual ~PledgeBase() {}; |
60 | | }; |
61 | | |
62 | | template<typename ValueType, typename ErrorType = nsresult> |
63 | | class Pledge : public PledgeBase |
64 | | { |
65 | | // TODO: Remove workaround once mozilla allows std::function from <functional> |
66 | | // wo/std::function support, do template + virtual trick to accept lambdas |
67 | | class FunctorsBase |
68 | | { |
69 | | public: |
70 | 0 | FunctorsBase() {} |
71 | | virtual void Succeed(ValueType& result) = 0; |
72 | | virtual void Fail(ErrorType& error) = 0; |
73 | 0 | virtual ~FunctorsBase() {}; |
74 | | }; |
75 | | |
76 | | public: |
77 | | explicit Pledge() : mDone(false), mRejected(false) {} |
78 | | Pledge(const Pledge& aOther) = delete; |
79 | | Pledge& operator = (const Pledge&) = delete; |
80 | | |
81 | | template<typename OnSuccessType> |
82 | | void Then(OnSuccessType&& aOnSuccess) |
83 | | { |
84 | | Then(std::forward<OnSuccessType>(aOnSuccess), [](ErrorType&){}); |
85 | | } |
86 | | |
87 | | template<typename OnSuccessType, typename OnFailureType> |
88 | | void Then(OnSuccessType&& aOnSuccess, OnFailureType&& aOnFailure) |
89 | 0 | { |
90 | 0 | class Functors : public FunctorsBase |
91 | 0 | { |
92 | 0 | public: |
93 | 0 | Functors(OnSuccessType&& aOnSuccessRef, OnFailureType&& aOnFailureRef) |
94 | 0 | : mOnSuccess(std::move(aOnSuccessRef)), mOnFailure(std::move(aOnFailureRef)) {} |
95 | 0 |
|
96 | 0 | void Succeed(ValueType& result) |
97 | 0 | { |
98 | 0 | mOnSuccess(result); |
99 | 0 | } |
100 | 0 | void Fail(ErrorType& error) |
101 | 0 | { |
102 | 0 | mOnFailure(error); |
103 | 0 | }; |
104 | 0 |
|
105 | 0 | OnSuccessType mOnSuccess; |
106 | 0 | OnFailureType mOnFailure; |
107 | 0 | }; |
108 | 0 | mFunctors = MakeUnique<Functors>(std::forward<OnSuccessType>(aOnSuccess), |
109 | 0 | std::forward<OnFailureType>(aOnFailure)); |
110 | 0 | if (mDone) { |
111 | 0 | if (!mRejected) { |
112 | 0 | mFunctors->Succeed(mValue); |
113 | 0 | } else { |
114 | 0 | mFunctors->Fail(mError); |
115 | 0 | } |
116 | 0 | } |
117 | 0 | } |
118 | | |
119 | | void Resolve(const ValueType& aValue) |
120 | 0 | { |
121 | 0 | mValue = aValue; |
122 | 0 | Resolve(); |
123 | 0 | } |
124 | | |
125 | | void Reject(ErrorType rv) |
126 | | { |
127 | | if (!mDone) { |
128 | | mDone = mRejected = true; |
129 | | mError = rv; |
130 | | if (mFunctors) { |
131 | | mFunctors->Fail(mError); |
132 | | } |
133 | | } |
134 | | } |
135 | | |
136 | | protected: |
137 | | void Resolve() |
138 | 0 | { |
139 | 0 | if (!mDone) { |
140 | 0 | mDone = true; |
141 | 0 | MOZ_ASSERT(!mRejected); |
142 | 0 | if (mFunctors) { |
143 | 0 | mFunctors->Succeed(mValue); |
144 | 0 | } |
145 | 0 | } |
146 | 0 | } |
147 | | |
148 | | ValueType mValue; |
149 | | private: |
150 | | ~Pledge() {}; |
151 | | bool mDone; |
152 | | bool mRejected; |
153 | | ErrorType mError; |
154 | | UniquePtr<FunctorsBase> mFunctors; |
155 | | }; |
156 | | |
157 | | /* media::NewRunnableFrom() - Create a Runnable from a lambda. |
158 | | * |
159 | | * Passing variables (closures) to an async function is clunky with Runnable: |
160 | | * |
161 | | * void Foo() |
162 | | * { |
163 | | * class FooRunnable : public Runnable |
164 | | * { |
165 | | * public: |
166 | | * FooRunnable(const Bar &aBar) : mBar(aBar) {} |
167 | | * NS_IMETHOD Run() override |
168 | | * { |
169 | | * // Use mBar |
170 | | * } |
171 | | * private: |
172 | | * RefPtr<Bar> mBar; |
173 | | * }; |
174 | | * |
175 | | * RefPtr<Bar> bar = new Bar(); |
176 | | * NS_DispatchToMainThread(new FooRunnable(bar); |
177 | | * } |
178 | | * |
179 | | * It's worse with more variables. Lambdas have a leg up with variable capture: |
180 | | * |
181 | | * void Foo() |
182 | | * { |
183 | | * RefPtr<Bar> bar = new Bar(); |
184 | | * NS_DispatchToMainThread(media::NewRunnableFrom([bar]() mutable { |
185 | | * // use bar |
186 | | * })); |
187 | | * } |
188 | | * |
189 | | * Capture is by-copy by default, so the nsRefPtr 'bar' is safely copied for |
190 | | * access on the other thread (threadsafe refcounting in bar is assumed). |
191 | | * |
192 | | * The 'mutable' keyword is only needed for non-const access to bar. |
193 | | */ |
194 | | |
195 | | template<typename OnRunType> |
196 | | class LambdaRunnable : public Runnable |
197 | | { |
198 | | public: |
199 | | explicit LambdaRunnable(OnRunType&& aOnRun) |
200 | | : Runnable("media::LambdaRunnable") |
201 | | , mOnRun(std::move(aOnRun)) |
202 | 0 | { |
203 | 0 | } |
204 | | |
205 | | private: |
206 | | NS_IMETHODIMP |
207 | | Run() override |
208 | 0 | { |
209 | 0 | return mOnRun(); |
210 | 0 | } |
211 | | OnRunType mOnRun; |
212 | | }; |
213 | | |
214 | | template<typename OnRunType> |
215 | | already_AddRefed<LambdaRunnable<OnRunType>> |
216 | | NewRunnableFrom(OnRunType&& aOnRun) |
217 | 0 | { |
218 | 0 | typedef LambdaRunnable<OnRunType> LambdaType; |
219 | 0 | RefPtr<LambdaType> lambda = new LambdaType(std::forward<OnRunType>(aOnRun)); |
220 | 0 | return lambda.forget(); |
221 | 0 | } |
222 | | |
223 | | /* media::CoatCheck - There and back again. Park an object in exchange for an id. |
224 | | * |
225 | | * A common problem with calling asynchronous functions that do work on other |
226 | | * threads or processes is how to pass in a heap object for use once the |
227 | | * function completes, without requiring that object to have threadsafe |
228 | | * refcounting, contain mutexes, be marshaled, or leak if things fail |
229 | | * (or worse, intermittent use-after-free because of lifetime issues). |
230 | | * |
231 | | * One solution is to set up a coat-check on the caller side, park your object |
232 | | * in exchange for an id, and send the id. Common in IPC, but equally useful |
233 | | * for same-process thread-hops, because by never leaving the thread there's |
234 | | * no need for objects to be threadsafe or use threadsafe refcounting. E.g. |
235 | | * |
236 | | * class FooDoer |
237 | | * { |
238 | | * CoatCheck<Foo> mOutstandingFoos; |
239 | | * |
240 | | * public: |
241 | | * void DoFoo() |
242 | | * { |
243 | | * RefPtr<Foo> foo = new Foo(); |
244 | | * uint32_t requestId = mOutstandingFoos.Append(*foo); |
245 | | * sChild->SendFoo(requestId); |
246 | | * } |
247 | | * |
248 | | * void RecvFooResponse(uint32_t requestId) |
249 | | * { |
250 | | * RefPtr<Foo> foo = mOutstandingFoos.Remove(requestId); |
251 | | * if (foo) { |
252 | | * // use foo |
253 | | * } |
254 | | * } |
255 | | * }; |
256 | | * |
257 | | * If you read media::Pledge earlier, here's how this is useful for pledges: |
258 | | * |
259 | | * class FooGetter |
260 | | * { |
261 | | * CoatCheck<Pledge<Foo>> mOutstandingPledges; |
262 | | * |
263 | | * public: |
264 | | * already_addRefed<Pledge<Foo>> GetFooAsynchronously() |
265 | | * { |
266 | | * RefPtr<Pledge<Foo>> p = new Pledge<Foo>(); |
267 | | * uint32_t requestId = mOutstandingPledges.Append(*p); |
268 | | * sChild->SendFoo(requestId); |
269 | | * return p.forget(); |
270 | | * } |
271 | | * |
272 | | * void RecvFooResponse(uint32_t requestId, const Foo& fooResult) |
273 | | * { |
274 | | * RefPtr<Foo> p = mOutstandingPledges.Remove(requestId); |
275 | | * if (p) { |
276 | | * p->Resolve(fooResult); |
277 | | * } |
278 | | * } |
279 | | * }; |
280 | | * |
281 | | * This helper is currently optimized for very small sets (i.e. not optimized). |
282 | | * It is also not thread-safe as the whole point is to stay on the same thread. |
283 | | */ |
284 | | |
285 | | template<class T> |
286 | | class CoatCheck |
287 | | { |
288 | | public: |
289 | | typedef std::pair<uint32_t, RefPtr<T>> Element; |
290 | | |
291 | | uint32_t Append(T& t) |
292 | | { |
293 | | uint32_t id = GetNextId(); |
294 | | mElements.AppendElement(Element(id, RefPtr<T>(&t))); |
295 | | return id; |
296 | | } |
297 | | |
298 | | already_AddRefed<T> Remove(uint32_t aId) |
299 | | { |
300 | | for (auto& element : mElements) { |
301 | | if (element.first == aId) { |
302 | | RefPtr<T> ref; |
303 | | ref.swap(element.second); |
304 | | mElements.RemoveElement(element); |
305 | | return ref.forget(); |
306 | | } |
307 | | } |
308 | | MOZ_ASSERT_UNREACHABLE("Received id with no matching parked object!"); |
309 | | return nullptr; |
310 | | } |
311 | | |
312 | | private: |
313 | | static uint32_t GetNextId() |
314 | | { |
315 | | static uint32_t counter = 0; |
316 | | return ++counter; |
317 | | }; |
318 | | AutoTArray<Element, 3> mElements; |
319 | | }; |
320 | | |
321 | | /* media::Refcountable - Add threadsafe ref-counting to something that isn't. |
322 | | * |
323 | | * Often, reference counting is the most practical way to share an object with |
324 | | * another thread without imposing lifetime restrictions, even if there's |
325 | | * otherwise no concurrent access happening on the object. For instance, an |
326 | | * algorithm on another thread may find it more expedient to modify a passed-in |
327 | | * object, rather than pass expensive copies back and forth. |
328 | | * |
329 | | * Lists in particular often aren't ref-countable, yet are expensive to copy, |
330 | | * e.g. nsTArray<RefPtr<Foo>>. Refcountable can be used to make such objects |
331 | | * (or owning smart-pointers to such objects) refcountable. |
332 | | * |
333 | | * Technical limitation: A template specialization is needed for types that take |
334 | | * a constructor. Please add below (UniquePtr covers a lot of ground though). |
335 | | */ |
336 | | |
337 | | class RefcountableBase |
338 | | { |
339 | | public: |
340 | | NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RefcountableBase) |
341 | | protected: |
342 | | virtual ~RefcountableBase() {} |
343 | | }; |
344 | | |
345 | | template<typename T> |
346 | | class Refcountable : public T, public RefcountableBase |
347 | | { |
348 | | public: |
349 | | NS_METHOD_(MozExternalRefCountType) AddRef() |
350 | | { |
351 | | return RefcountableBase::AddRef(); |
352 | | } |
353 | | |
354 | | NS_METHOD_(MozExternalRefCountType) Release() |
355 | | { |
356 | | return RefcountableBase::Release(); |
357 | | } |
358 | | |
359 | | private: |
360 | | ~Refcountable<T>() {} |
361 | | }; |
362 | | |
363 | | template<typename T> |
364 | | class Refcountable<UniquePtr<T>> : public UniquePtr<T> |
365 | | { |
366 | | public: |
367 | | explicit Refcountable<UniquePtr<T>>(T* aPtr) : UniquePtr<T>(aPtr) {} |
368 | | NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Refcountable<T>) |
369 | | private: |
370 | | ~Refcountable<UniquePtr<T>>() {} |
371 | | }; |
372 | | |
373 | | /* Async shutdown helpers |
374 | | */ |
375 | | |
376 | | already_AddRefed<nsIAsyncShutdownClient> |
377 | | GetShutdownBarrier(); |
378 | | |
379 | | class ShutdownBlocker : public nsIAsyncShutdownBlocker |
380 | | { |
381 | | public: |
382 | | ShutdownBlocker(const nsString& aName) : mName(aName) {} |
383 | | |
384 | | NS_IMETHOD |
385 | | BlockShutdown(nsIAsyncShutdownClient* aProfileBeforeChange) override = 0; |
386 | | |
387 | | NS_IMETHOD GetName(nsAString& aName) override |
388 | | { |
389 | | aName = mName; |
390 | | return NS_OK; |
391 | | } |
392 | | |
393 | | NS_IMETHOD GetState(nsIPropertyBag**) override |
394 | | { |
395 | | return NS_OK; |
396 | | } |
397 | | |
398 | | NS_DECL_ISUPPORTS |
399 | | protected: |
400 | | virtual ~ShutdownBlocker() {} |
401 | | private: |
402 | | const nsString mName; |
403 | | }; |
404 | | |
405 | | class ShutdownTicket final |
406 | | { |
407 | | public: |
408 | | explicit ShutdownTicket(nsIAsyncShutdownBlocker* aBlocker) : mBlocker(aBlocker) {} |
409 | | NS_INLINE_DECL_REFCOUNTING(ShutdownTicket) |
410 | | private: |
411 | | ~ShutdownTicket() |
412 | | { |
413 | | nsCOMPtr<nsIAsyncShutdownClient> barrier = GetShutdownBarrier(); |
414 | | barrier->RemoveBlocker(mBlocker); |
415 | | } |
416 | | |
417 | | nsCOMPtr<nsIAsyncShutdownBlocker> mBlocker; |
418 | | }; |
419 | | |
420 | | /** |
421 | | * Await convenience methods to block until the promise has been resolved or |
422 | | * rejected. The Resolve/Reject functions, while called on a different thread, |
423 | | * would be running just as on the current thread thanks to the memory barrier |
424 | | * provided by the monitor. |
425 | | * For now Await can only be used with an exclusive MozPromise if passed a |
426 | | * Resolve/Reject function. |
427 | | * Await() can *NOT* be called from a task queue/nsISerialEventTarget used for |
428 | | * resolving/rejecting aPromise, otherwise things will deadlock. |
429 | | */ |
430 | | template<typename ResolveValueType, |
431 | | typename RejectValueType, |
432 | | typename ResolveFunction, |
433 | | typename RejectFunction> |
434 | | void |
435 | | Await( |
436 | | already_AddRefed<nsIEventTarget> aPool, |
437 | | RefPtr<MozPromise<ResolveValueType, RejectValueType, true>> aPromise, |
438 | | ResolveFunction&& aResolveFunction, |
439 | | RejectFunction&& aRejectFunction) |
440 | | { |
441 | | RefPtr<TaskQueue> taskQueue = |
442 | | new TaskQueue(std::move(aPool), "MozPromiseAwait"); |
443 | | Monitor mon(__func__); |
444 | | bool done = false; |
445 | | |
446 | | aPromise->Then(taskQueue, |
447 | | __func__, |
448 | | [&](ResolveValueType&& aResolveValue) { |
449 | | MonitorAutoLock lock(mon); |
450 | | aResolveFunction(std::forward<ResolveValueType>(aResolveValue)); |
451 | | done = true; |
452 | | mon.Notify(); |
453 | | }, |
454 | | [&](RejectValueType&& aRejectValue) { |
455 | | MonitorAutoLock lock(mon); |
456 | | aRejectFunction(std::forward<RejectValueType>(aRejectValue)); |
457 | | done = true; |
458 | | mon.Notify(); |
459 | | }); |
460 | | |
461 | | MonitorAutoLock lock(mon); |
462 | | while (!done) { |
463 | | mon.Wait(); |
464 | | } |
465 | | } |
466 | | |
467 | | template<typename ResolveValueType, typename RejectValueType, bool Excl> |
468 | | typename MozPromise<ResolveValueType, RejectValueType, Excl>:: |
469 | | ResolveOrRejectValue |
470 | | Await(already_AddRefed<nsIEventTarget> aPool, |
471 | | RefPtr<MozPromise<ResolveValueType, RejectValueType, Excl>> aPromise) |
472 | | { |
473 | | RefPtr<TaskQueue> taskQueue = |
474 | | new TaskQueue(std::move(aPool), "MozPromiseAwait"); |
475 | | Monitor mon(__func__); |
476 | | bool done = false; |
477 | | |
478 | | typename MozPromise<ResolveValueType, RejectValueType, Excl>::ResolveOrRejectValue val; |
479 | | aPromise->Then(taskQueue, |
480 | | __func__, |
481 | | [&](ResolveValueType aResolveValue) { |
482 | | val.SetResolve(std::move(aResolveValue)); |
483 | | MonitorAutoLock lock(mon); |
484 | | done = true; |
485 | | mon.Notify(); |
486 | | }, |
487 | | [&](RejectValueType aRejectValue) { |
488 | | val.SetReject(std::move(aRejectValue)); |
489 | | MonitorAutoLock lock(mon); |
490 | | done = true; |
491 | | mon.Notify(); |
492 | | }); |
493 | | |
494 | | MonitorAutoLock lock(mon); |
495 | | while (!done) { |
496 | | mon.Wait(); |
497 | | } |
498 | | |
499 | | return val; |
500 | | } |
501 | | |
502 | | /** |
503 | | * Similar to Await, takes an array of promises of the same type. |
504 | | * MozPromise::All is used to handle the resolution/rejection of the promises. |
505 | | */ |
506 | | template<typename ResolveValueType, |
507 | | typename RejectValueType, |
508 | | typename ResolveFunction, |
509 | | typename RejectFunction> |
510 | | void |
511 | | AwaitAll(already_AddRefed<nsIEventTarget> aPool, |
512 | | nsTArray<RefPtr<MozPromise<ResolveValueType, RejectValueType, true>>>& |
513 | | aPromises, |
514 | | ResolveFunction&& aResolveFunction, |
515 | | RejectFunction&& aRejectFunction) |
516 | | { |
517 | | typedef MozPromise<ResolveValueType, RejectValueType, true> Promise; |
518 | | RefPtr<nsIEventTarget> pool = aPool; |
519 | | RefPtr<TaskQueue> taskQueue = |
520 | | new TaskQueue(do_AddRef(pool), "MozPromiseAwaitAll"); |
521 | | RefPtr<typename Promise::AllPromiseType> p = |
522 | | Promise::All(taskQueue, aPromises); |
523 | | Await( |
524 | | pool.forget(), p, std::move(aResolveFunction), std::move(aRejectFunction)); |
525 | | } |
526 | | |
527 | | // Note: only works with exclusive MozPromise, as Promise::All would attempt |
528 | | // to perform copy of nsTArrays which are disallowed. |
529 | | template<typename ResolveValueType, typename RejectValueType> |
530 | | typename MozPromise<ResolveValueType, |
531 | | RejectValueType, |
532 | | true>::AllPromiseType::ResolveOrRejectValue |
533 | | AwaitAll(already_AddRefed<nsIEventTarget> aPool, |
534 | | nsTArray<RefPtr<MozPromise<ResolveValueType, RejectValueType, true>>>& |
535 | | aPromises) |
536 | | { |
537 | | typedef MozPromise<ResolveValueType, RejectValueType, true> Promise; |
538 | | RefPtr<nsIEventTarget> pool = aPool; |
539 | | RefPtr<TaskQueue> taskQueue = |
540 | | new TaskQueue(do_AddRef(pool), "MozPromiseAwaitAll"); |
541 | | RefPtr<typename Promise::AllPromiseType> p = |
542 | | Promise::All(taskQueue, aPromises); |
543 | | return Await(pool.forget(), p); |
544 | | } |
545 | | |
546 | | } // namespace media |
547 | | } // namespace mozilla |
548 | | |
549 | | #endif // mozilla_MediaUtils_h |