/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set sw=2 ts=8 et ft=cpp : */ /* 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 "nsThreadUtils.h" #include "nsIAsyncShutdown.h" #include "mozilla/UniquePtr.h" namespace mozilla { namespace media { /* * media::Pledge - A promise-like pattern for c++ that takes lambda functions. * * Asynchronous APIs that proxy to another thread or to the chrome process and * back may find it useful to return a pledge to callers who then use * pledge.Then(func) to specify a lambda function to be invoked with the result * later back on this same thread. * * Callers will enjoy that lambdas allow "capturing" of local variables, much * like closures in JavaScript (safely by-copy by default). * * Callers will also enjoy that they do not need to be thread-safe (their code * runs on the same thread after all). * * Advantageously, pledges are non-threadsafe by design (because locking and * event queues are redundant). This means none of the lambdas you pass in, * or variables you lambda-capture into them, need be threasafe or support * threadsafe refcounting. After all, they'll run later on the same thread. * * RefPtr> p = GetFooAsynchronously(); // returns a pledge * p->Then([](const Foo& foo) { * // use foo here (same thread. Need not be thread-safe!) * }); * * See media::CoatCheck below for an example of GetFooAsynchronously(). */ class PledgeBase { public: NS_INLINE_DECL_REFCOUNTING(PledgeBase); protected: virtual ~PledgeBase() {}; }; template class Pledge : public PledgeBase { // TODO: Remove workaround once mozilla allows std::function from // wo/std::function support, do template + virtual trick to accept lambdas class FunctorsBase { public: FunctorsBase() {} virtual void Succeed(ValueType& result) = 0; virtual void Fail(ErrorType& error) = 0; virtual ~FunctorsBase() {}; }; public: explicit Pledge() : mDone(false), mRejected(false) {} Pledge(const Pledge& aOther) = delete; Pledge& operator = (const Pledge&) = delete; template void Then(OnSuccessType&& aOnSuccess) { Then(Forward(aOnSuccess), [](ErrorType&){}); } template void Then(OnSuccessType&& aOnSuccess, OnFailureType&& aOnFailure) { class Functors : public FunctorsBase { public: Functors(OnSuccessType&& aOnSuccessRef, OnFailureType&& aOnFailureRef) : mOnSuccess(Move(aOnSuccessRef)), mOnFailure(Move(aOnFailureRef)) {} void Succeed(ValueType& result) { mOnSuccess(result); } void Fail(ErrorType& error) { mOnFailure(error); }; OnSuccessType mOnSuccess; OnFailureType mOnFailure; }; mFunctors = MakeUnique(Forward(aOnSuccess), Forward(aOnFailure)); if (mDone) { if (!mRejected) { mFunctors->Succeed(mValue); } else { mFunctors->Fail(mError); } } } void Resolve(const ValueType& aValue) { mValue = aValue; Resolve(); } void Reject(ErrorType rv) { if (!mDone) { mDone = mRejected = true; mError = rv; if (mFunctors) { mFunctors->Fail(mError); } } } protected: void Resolve() { if (!mDone) { mDone = true; MOZ_ASSERT(!mRejected); if (mFunctors) { mFunctors->Succeed(mValue); } } } ValueType mValue; private: ~Pledge() {}; bool mDone; bool mRejected; ErrorType mError; UniquePtr mFunctors; }; /* 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) : mOnRun(Move(aOnRun)) {} private: NS_IMETHODIMP Run() override { return mOnRun(); } OnRunType mOnRun; }; template already_AddRefed> NewRunnableFrom(OnRunType&& aOnRun) { typedef LambdaRunnable LambdaType; RefPtr lambda = new LambdaType(Forward(aOnRun)); return lambda.forget(); } /* media::CoatCheck - There and back again. Park an object in exchange for an id. * * A common problem with calling asynchronous functions that do work on other * threads or processes is how to pass in a heap object for use once the * function completes, without requiring that object to have threadsafe * refcounting, contain mutexes, be marshaled, or leak if things fail * (or worse, intermittent use-after-free because of lifetime issues). * * One solution is to set up a coat-check on the caller side, park your object * in exchange for an id, and send the id. Common in IPC, but equally useful * for same-process thread-hops, because by never leaving the thread there's * no need for objects to be threadsafe or use threadsafe refcounting. E.g. * * class FooDoer * { * CoatCheck mOutstandingFoos; * * public: * void DoFoo() * { * RefPtr foo = new Foo(); * uint32_t requestId = mOutstandingFoos.Append(*foo); * sChild->SendFoo(requestId); * } * * void RecvFooResponse(uint32_t requestId) * { * RefPtr foo = mOutstandingFoos.Remove(requestId); * if (foo) { * // use foo * } * } * }; * * If you read media::Pledge earlier, here's how this is useful for pledges: * * class FooGetter * { * CoatCheck> mOutstandingPledges; * * public: * already_addRefed> GetFooAsynchronously() * { * RefPtr> p = new Pledge(); * uint32_t requestId = mOutstandingPledges.Append(*p); * sChild->SendFoo(requestId); * return p.forget(); * } * * void RecvFooResponse(uint32_t requestId, const Foo& fooResult) * { * RefPtr p = mOutstandingPledges.Remove(requestId); * if (p) { * p->Resolve(fooResult); * } * } * }; * * This helper is currently optimized for very small sets (i.e. not optimized). * It is also not thread-safe as the whole point is to stay on the same thread. */ template class CoatCheck { public: typedef std::pair> Element; uint32_t Append(T& t) { uint32_t id = GetNextId(); mElements.AppendElement(Element(id, RefPtr(&t))); return id; } already_AddRefed Remove(uint32_t aId) { for (auto& element : mElements) { if (element.first == aId) { RefPtr ref; ref.swap(element.second); mElements.RemoveElement(element); return ref.forget(); } } MOZ_ASSERT_UNREACHABLE("Received id with no matching parked object!"); return nullptr; } private: static uint32_t GetNextId() { static uint32_t counter = 0; return ++counter; }; AutoTArray mElements; }; /* 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). */ template class Refcountable : public T { public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Refcountable) private: ~Refcountable() {} }; template class Refcountable> : public UniquePtr { public: explicit Refcountable>(T* aPtr) : UniquePtr(aPtr) {} NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Refcountable) private: ~Refcountable>() {} }; /* media::ShutdownBlocker - Async shutdown helper. */ class ShutdownBlocker : public nsIAsyncShutdownBlocker { public: ShutdownBlocker(const nsString& 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_ISUPPORTS protected: virtual ~ShutdownBlocker() {} private: const nsString mName; }; } // namespace media } // namespace mozilla #endif // mozilla_MediaUtils_h