/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=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_MediaUtils_h #define mozilla_MediaUtils_h #include #include "mozilla/Assertions.h" #include "mozilla/Monitor.h" #include "mozilla/MozPromise.h" #include "mozilla/Mutex.h" #include "mozilla/RefPtr.h" #include "mozilla/SharedThreadPool.h" #include "mozilla/TaskQueue.h" #include "mozilla/UniquePtr.h" #include "MediaEventSource.h" #include "nsCOMPtr.h" #include "nsIAsyncShutdown.h" #include "nsISupportsImpl.h" #include "nsProxyRelease.h" #include "nsThreadUtils.h" class nsIEventTarget; namespace mozilla::media { /* media::NewRunnableFrom() - Create a Runnable from a lambda. * * Passing variables (closures) to an async function is clunky with Runnable: * * void Foo() * { * class FooRunnable : public Runnable * { * public: * FooRunnable(const Bar &aBar) : mBar(aBar) {} * NS_IMETHOD Run() override * { * // Use mBar * } * private: * RefPtr mBar; * }; * * RefPtr bar = new Bar(); * NS_DispatchToMainThread(new FooRunnable(bar); * } * * It's worse with more variables. Lambdas have a leg up with variable capture: * * void Foo() * { * RefPtr bar = new Bar(); * NS_DispatchToMainThread(media::NewRunnableFrom([bar]() mutable { * // use bar * })); * } * * Capture is by-copy by default, so the nsRefPtr 'bar' is safely copied for * access on the other thread (threadsafe refcounting in bar is assumed). * * The 'mutable' keyword is only needed for non-const access to bar. */ template class LambdaRunnable : public Runnable { public: explicit LambdaRunnable(OnRunType&& aOnRun) : Runnable("media::LambdaRunnable"), mOnRun(std::move(aOnRun)) {} private: NS_IMETHODIMP Run() override { return mOnRun(); } OnRunType mOnRun; }; template already_AddRefed> NewRunnableFrom( OnRunType&& aOnRun) { typedef LambdaRunnable LambdaType; RefPtr lambda = new LambdaType(std::forward(aOnRun)); return lambda.forget(); } /* media::Refcountable - Add threadsafe ref-counting to something that isn't. * * Often, reference counting is the most practical way to share an object with * another thread without imposing lifetime restrictions, even if there's * otherwise no concurrent access happening on the object. For instance, an * algorithm on another thread may find it more expedient to modify a passed-in * object, rather than pass expensive copies back and forth. * * Lists in particular often aren't ref-countable, yet are expensive to copy, * e.g. nsTArray>. Refcountable can be used to make such objects * (or owning smart-pointers to such objects) refcountable. * * Technical limitation: A template specialization is needed for types that take * a constructor. Please add below (UniquePtr covers a lot of ground though). */ class RefcountableBase { public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RefcountableBase) protected: virtual ~RefcountableBase() = default; }; template class Refcountable : public T, public RefcountableBase { public: Refcountable& operator=(T&& aOther) { T::operator=(std::move(aOther)); return *this; } Refcountable& operator=(T& aOther) { T::operator=(aOther); return *this; } }; template class Refcountable> : public UniquePtr, public RefcountableBase { public: explicit Refcountable(T* aPtr) : UniquePtr(aPtr) {} }; template <> class Refcountable : public RefcountableBase { public: explicit Refcountable(bool aValue) : mValue(aValue) {} Refcountable& operator=(bool aOther) { mValue = aOther; return *this; } Refcountable& operator=(const Refcountable& aOther) { mValue = aOther.mValue; return *this; } explicit operator bool() const { return mValue; } private: bool mValue; }; /* * Async shutdown helpers */ nsCOMPtr GetShutdownBarrier(); // Like GetShutdownBarrier but will release assert that the result is not null. nsCOMPtr MustGetShutdownBarrier(); class ShutdownBlocker : public nsIAsyncShutdownBlocker { public: ShutdownBlocker(const nsAString& aName) : mName(aName) {} NS_IMETHOD BlockShutdown(nsIAsyncShutdownClient* aProfileBeforeChange) override = 0; NS_IMETHOD GetName(nsAString& aName) override { aName = mName; return NS_OK; } NS_IMETHOD GetState(nsIPropertyBag**) override { return NS_OK; } NS_DECL_THREADSAFE_ISUPPORTS protected: virtual ~ShutdownBlocker() = default; private: const nsString mName; }; /** * A convenience class representing a "ticket" that keeps the process from * shutting down until it is destructed. It does this by blocking * xpcom-will-shutdown. Constructed and destroyed on any thread. */ class ShutdownBlockingTicket { public: using ShutdownMozPromise = MozPromise; /** * Construct with an arbitrary name, __FILE__ and __LINE__. * Note that __FILE__ needs to be made wide, typically through * NS_LITERAL_STRING_FROM_CSTRING(__FILE__). * Returns nullptr if we are too far in the shutdown sequence to add a * blocker. Any thread. */ static UniquePtr Create(const nsAString& aName, const nsAString& aFileName, int32_t aLineNr); virtual ~ShutdownBlockingTicket() = default; /** * MozPromise that gets resolved upon xpcom-will-shutdown. * Should the ticket get destroyed before the MozPromise has been resolved, * the MozPromise will get rejected. */ virtual ShutdownMozPromise* ShutdownPromise() = 0; }; /** * Await convenience methods to block until the promise has been resolved or * rejected. The Resolve/Reject functions, while called on a different thread, * would be running just as on the current thread thanks to the memory barrier * provided by the monitor. * For now Await can only be used with an exclusive MozPromise if passed a * Resolve/Reject function. * Await() can *NOT* be called from a task queue/nsISerialEventTarget used for * resolving/rejecting aPromise, otherwise things will deadlock. */ template void Await(already_AddRefed aPool, RefPtr> aPromise, ResolveFunction&& aResolveFunction, RejectFunction&& aRejectFunction) { RefPtr taskQueue = TaskQueue::Create(std::move(aPool), "MozPromiseAwait"); Monitor mon MOZ_UNANNOTATED(__func__); bool done = false; aPromise->Then( taskQueue, __func__, [&](ResolveValueType&& aResolveValue) { MonitorAutoLock lock(mon); aResolveFunction(std::forward(aResolveValue)); done = true; mon.Notify(); }, [&](RejectValueType&& aRejectValue) { MonitorAutoLock lock(mon); aRejectFunction(std::forward(aRejectValue)); done = true; mon.Notify(); }); MonitorAutoLock lock(mon); while (!done) { mon.Wait(); } } template typename MozPromise::ResolveOrRejectValue Await(already_AddRefed aPool, RefPtr> aPromise) { RefPtr taskQueue = TaskQueue::Create(std::move(aPool), "MozPromiseAwait"); Monitor mon MOZ_UNANNOTATED(__func__); bool done = false; typename MozPromise::ResolveOrRejectValue val; aPromise->Then( taskQueue, __func__, [&](ResolveValueType aResolveValue) { val.SetResolve(std::move(aResolveValue)); MonitorAutoLock lock(mon); done = true; mon.Notify(); }, [&](RejectValueType aRejectValue) { val.SetReject(std::move(aRejectValue)); MonitorAutoLock lock(mon); done = true; mon.Notify(); }); MonitorAutoLock lock(mon); while (!done) { mon.Wait(); } return val; } /** * Similar to Await, takes an array of promises of the same type. * MozPromise::All is used to handle the resolution/rejection of the promises. */ template void AwaitAll( already_AddRefed aPool, nsTArray>>& aPromises, ResolveFunction&& aResolveFunction, RejectFunction&& aRejectFunction) { typedef MozPromise Promise; RefPtr pool = aPool; RefPtr taskQueue = TaskQueue::Create(do_AddRef(pool), "MozPromiseAwaitAll"); RefPtr p = Promise::All(taskQueue, aPromises); Await(pool.forget(), p, std::move(aResolveFunction), std::move(aRejectFunction)); } // Note: only works with exclusive MozPromise, as Promise::All would attempt // to perform copy of nsTArrays which are disallowed. template typename MozPromise::AllPromiseType::ResolveOrRejectValue AwaitAll(already_AddRefed aPool, nsTArray>>& aPromises) { typedef MozPromise Promise; RefPtr pool = aPool; RefPtr taskQueue = TaskQueue::Create(do_AddRef(pool), "MozPromiseAwaitAll"); RefPtr p = Promise::All(taskQueue, aPromises); return Await(pool.forget(), p); } } // namespace mozilla::media #endif // mozilla_MediaUtils_h