Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 <map>
#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<Bar> mBar;
* };
*
* RefPtr<Bar> 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> 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 <typename OnRunType>
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 <typename OnRunType>
already_AddRefed<LambdaRunnable<OnRunType>> NewRunnableFrom(
OnRunType&& aOnRun) {
typedef LambdaRunnable<OnRunType> LambdaType;
RefPtr<LambdaType> lambda = new LambdaType(std::forward<OnRunType>(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<RefPtr<Foo>>. 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 <typename T>
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 <typename T>
class Refcountable<UniquePtr<T>> : public UniquePtr<T>,
public RefcountableBase {
public:
explicit Refcountable(T* aPtr) : UniquePtr<T>(aPtr) {}
};
template <>
class Refcountable<bool> : 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<nsIAsyncShutdownClient> GetShutdownBarrier();
// Like GetShutdownBarrier but will release assert that the result is not null.
nsCOMPtr<nsIAsyncShutdownClient> 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<bool, bool, false>;
/**
* 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<ShutdownBlockingTicket> 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 <typename ResolveValueType, typename RejectValueType,
typename ResolveFunction, typename RejectFunction>
void Await(already_AddRefed<nsIEventTarget> aPool,
RefPtr<MozPromise<ResolveValueType, RejectValueType, true>> aPromise,
ResolveFunction&& aResolveFunction,
RejectFunction&& aRejectFunction) {
RefPtr<TaskQueue> 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<ResolveValueType>(aResolveValue));
done = true;
mon.Notify();
},
[&](RejectValueType&& aRejectValue) {
MonitorAutoLock lock(mon);
aRejectFunction(std::forward<RejectValueType>(aRejectValue));
done = true;
mon.Notify();
});
MonitorAutoLock lock(mon);
while (!done) {
mon.Wait();
}
}
template <typename ResolveValueType, typename RejectValueType, bool Excl>
typename MozPromise<ResolveValueType, RejectValueType,
Excl>::ResolveOrRejectValue
Await(already_AddRefed<nsIEventTarget> aPool,
RefPtr<MozPromise<ResolveValueType, RejectValueType, Excl>> aPromise) {
RefPtr<TaskQueue> taskQueue =
TaskQueue::Create(std::move(aPool), "MozPromiseAwait");
Monitor mon MOZ_UNANNOTATED(__func__);
bool done = false;
typename MozPromise<ResolveValueType, RejectValueType,
Excl>::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 <typename ResolveValueType, typename RejectValueType,
typename ResolveFunction, typename RejectFunction>
void AwaitAll(
already_AddRefed<nsIEventTarget> aPool,
nsTArray<RefPtr<MozPromise<ResolveValueType, RejectValueType, true>>>&
aPromises,
ResolveFunction&& aResolveFunction, RejectFunction&& aRejectFunction) {
typedef MozPromise<ResolveValueType, RejectValueType, true> Promise;
RefPtr<nsIEventTarget> pool = aPool;
RefPtr<TaskQueue> taskQueue =
TaskQueue::Create(do_AddRef(pool), "MozPromiseAwaitAll");
RefPtr<typename Promise::AllPromiseType> 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 ResolveValueType, typename RejectValueType>
typename MozPromise<ResolveValueType, RejectValueType,
true>::AllPromiseType::ResolveOrRejectValue
AwaitAll(already_AddRefed<nsIEventTarget> aPool,
nsTArray<RefPtr<MozPromise<ResolveValueType, RejectValueType, true>>>&
aPromises) {
typedef MozPromise<ResolveValueType, RejectValueType, true> Promise;
RefPtr<nsIEventTarget> pool = aPool;
RefPtr<TaskQueue> taskQueue =
TaskQueue::Create(do_AddRef(pool), "MozPromiseAwaitAll");
RefPtr<typename Promise::AllPromiseType> p =
Promise::All(taskQueue, aPromises);
return Await(pool.forget(), p);
}
} // namespace mozilla::media
#endif // mozilla_MediaUtils_h