gecko-dev/dom/media/doctor/DDMediaLogs.cpp

723 строки
25 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
#include "DDMediaLogs.h"
#include "DDLogUtils.h"
#include "mozilla/JSONWriter.h"
#include "mozilla/SharedThreadPool.h"
namespace mozilla {
/* static */ DDMediaLogs::ConstructionResult
DDMediaLogs::New()
{
nsCOMPtr<nsIThread> mThread;
nsresult rv = NS_NewNamedThread("DDMediaLogs",
getter_AddRefs(mThread),
nullptr,
SharedThreadPool::kStackSize);
if (NS_WARN_IF(NS_FAILED(rv))) {
return { rv, nullptr };
}
return { rv, new DDMediaLogs(Move(mThread)) };
}
DDMediaLogs::DDMediaLogs(nsCOMPtr<nsIThread>&& aThread)
: mMediaLogs(1)
, mMutex("DDMediaLogs")
, mThread(Move(aThread))
{
mMediaLogs.SetLength(1);
mMediaLogs[0].mMediaElement = nullptr;
DDL_INFO("DDMediaLogs constructed, processing thread: %p", mThread.get());
}
DDMediaLogs::~DDMediaLogs()
{
// Perform end-of-life processing, ensure the processing thread is shutdown.
Shutdown(/* aPanic = */ false);
}
void
DDMediaLogs::Panic()
{
Shutdown(/* aPanic = */ true);
}
void
DDMediaLogs::Shutdown(bool aPanic)
{
nsCOMPtr<nsIThread> thread;
{
MutexAutoLock lock(mMutex);
thread.swap(mThread);
}
if (!thread) {
// Already shutdown, nothing more to do.
return;
}
DDL_INFO("DDMediaLogs::Shutdown will shutdown thread: %p", thread.get());
// Will block until pending tasks have completed, and thread is dead.
thread->Shutdown();
if (aPanic) {
mMessagesQueue.PopAll([](const DDLogMessage&) {});
MutexAutoLock lock(mMutex);
mLifetimes.Clear();
mMediaLogs.Clear();
mObjectLinks.Clear();
mPendingPromises.Clear();
return;
}
// Final processing is only necessary to output to MOZ_LOG=DDLoggerEnd,
// so there's no point doing any of it if that MOZ_LOG is not enabled.
if (MOZ_LOG_TEST(sDecoderDoctorLoggerEndLog, mozilla::LogLevel::Info)) {
DDL_DEBUG("Perform final DDMediaLogs processing...");
// The processing thread is dead, so we can safely call ProcessLog()
// directly from this thread.
ProcessLog();
for (const DDMediaLog& mediaLog : mMediaLogs) {
if (mediaLog.mMediaElement) {
DDLE_INFO("---");
}
DDLE_INFO("--- Log for HTMLMediaElement[%p] ---", mediaLog.mMediaElement);
for (const DDLogMessage& message : mediaLog.mMessages) {
DDLE_LOG(message.mCategory <= DDLogCategory::_Unlink
? mozilla::LogLevel::Debug
: mozilla::LogLevel::Info,
"%s",
message.Print(mLifetimes).get());
}
DDLE_DEBUG("--- End log for HTMLMediaElement[%p] ---",
mediaLog.mMediaElement);
}
}
}
DDMediaLog&
DDMediaLogs::LogForUnassociatedMessages()
{
MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
return mMediaLogs[0];
}
const DDMediaLog&
DDMediaLogs::LogForUnassociatedMessages() const
{
MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
return mMediaLogs[0];
}
DDMediaLog*
DDMediaLogs::GetLogFor(const dom::HTMLMediaElement* aMediaElement)
{
MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
if (!aMediaElement) {
return &LogForUnassociatedMessages();
}
for (DDMediaLog& log : mMediaLogs) {
if (log.mMediaElement == aMediaElement) {
return &log;
}
}
return nullptr;
}
DDMediaLog&
DDMediaLogs::LogFor(const dom::HTMLMediaElement* aMediaElement)
{
MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
DDMediaLog* log = GetLogFor(aMediaElement);
if (!log) {
log = mMediaLogs.AppendElement();
log->mMediaElement = aMediaElement;
}
return *log;
}
void
DDMediaLogs::SetMediaElement(DDLifetime& aLifetime,
const dom::HTMLMediaElement* aMediaElement)
{
MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
DDMediaLog& log = LogFor(aMediaElement);
// List of lifetimes that are to be linked to aMediaElement.
nsTArray<DDLifetime*> lifetimes;
// We start with the given lifetime.
lifetimes.AppendElement(&aLifetime);
for (size_t i = 0; i < lifetimes.Length(); ++i) {
DDLifetime& lifetime = *lifetimes[i];
// Link the lifetime to aMediaElement.
lifetime.mMediaElement = aMediaElement;
// Classified lifetime's tag is a positive index from the DDMediaLog.
lifetime.mTag = ++log.mLifetimeCount;
DDL_DEBUG(
"%s -> HTMLMediaElement[%p]", lifetime.Printf().get(), aMediaElement);
// Go through the lifetime's existing linked lifetimes, if any is not
// already linked to aMediaElement, add it to the list so it will get
// linked in a later loop.
for (auto& link : mObjectLinks) {
if (lifetime.IsAliveAt(link.mLinkingIndex)) {
if (lifetime.mObject == link.mParent) {
DDLifetime* childLifetime =
mLifetimes.FindLifetime(link.mChild, link.mLinkingIndex);
if (childLifetime && !childLifetime->mMediaElement &&
!lifetimes.Contains(childLifetime)) {
lifetimes.AppendElement(childLifetime);
}
} else if (lifetime.mObject == link.mChild) {
DDLifetime* parentLifetime =
mLifetimes.FindLifetime(link.mParent, link.mLinkingIndex);
if (parentLifetime && !parentLifetime->mMediaElement &&
!lifetimes.Contains(parentLifetime)) {
lifetimes.AppendElement(parentLifetime);
}
}
}
}
}
// Now we need to move yet-unclassified messages related to the just-set
// elements, to the appropriate MediaElement list.
DDMediaLog::LogMessages& messages = log.mMessages;
DDMediaLog::LogMessages& messages0 = LogForUnassociatedMessages().mMessages;
for (size_t i = 0; i < messages0.Length();
/* increment inside the loop */) {
DDLogMessage& message = messages0[i];
bool found = false;
for (const DDLifetime* lifetime : lifetimes) {
if (lifetime->mObject == message.mObject) {
found = true;
break;
}
}
if (found) {
messages.AppendElement(Move(message));
messages0.RemoveElementAt(i);
// No increment, as we've removed this element; next element is now at
// the same index.
} else {
// Not touching this element, increment index to go to next element.
++i;
}
}
}
DDLifetime&
DDMediaLogs::FindOrCreateLifetime(const DDLogObject& aObject,
DDMessageIndex aIndex,
const DDTimeStamp& aTimeStamp)
{
MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
// Try to find lifetime corresponding to message object.
DDLifetime* lifetime = mLifetimes.FindLifetime(aObject, aIndex);
if (!lifetime) {
// No lifetime yet, create one.
lifetime = &(mLifetimes.CreateLifetime(aObject, aIndex, aTimeStamp));
if (MOZ_UNLIKELY(aObject.TypeName() ==
DDLoggedTypeTraits<dom::HTMLMediaElement>::Name())) {
const dom::HTMLMediaElement* mediaElement =
static_cast<const dom::HTMLMediaElement*>(aObject.Pointer());
SetMediaElement(*lifetime, mediaElement);
DDL_DEBUG("%s -> new lifetime: %s with MediaElement %p",
aObject.Printf().get(),
lifetime->Printf().get(),
mediaElement);
} else {
DDL_DEBUG("%s -> new lifetime: %s",
aObject.Printf().get(),
lifetime->Printf().get());
}
}
return *lifetime;
}
void
DDMediaLogs::LinkLifetimes(DDLifetime& aParentLifetime,
const char* aLinkName,
DDLifetime& aChildLifetime,
DDMessageIndex aIndex)
{
MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
mObjectLinks.AppendElement(DDObjectLink{
aParentLifetime.mObject, aChildLifetime.mObject, aLinkName, aIndex });
if (aParentLifetime.mMediaElement) {
if (!aChildLifetime.mMediaElement) {
SetMediaElement(aChildLifetime, aParentLifetime.mMediaElement);
}
} else if (aChildLifetime.mMediaElement) {
if (!aParentLifetime.mMediaElement) {
SetMediaElement(aParentLifetime, aChildLifetime.mMediaElement);
}
}
}
void
DDMediaLogs::UnlinkLifetime(DDLifetime& aLifetime, DDMessageIndex aIndex)
{
MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
for (DDObjectLink& link : mObjectLinks) {
if ((link.mParent == aLifetime.mObject ||
link.mChild == aLifetime.mObject) &&
aLifetime.IsAliveAt(link.mLinkingIndex) && !link.mUnlinkingIndex) {
link.mUnlinkingIndex = Some(aIndex);
}
}
};
void
DDMediaLogs::UnlinkLifetimes(DDLifetime& aParentLifetime,
DDLifetime& aChildLifetime,
DDMessageIndex aIndex)
{
MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
for (DDObjectLink& link : mObjectLinks) {
if ((link.mParent == aParentLifetime.mObject &&
link.mChild == aChildLifetime.mObject) &&
aParentLifetime.IsAliveAt(link.mLinkingIndex) &&
aChildLifetime.IsAliveAt(link.mLinkingIndex) && !link.mUnlinkingIndex) {
link.mUnlinkingIndex = Some(aIndex);
}
}
}
void
DDMediaLogs::DestroyLifetimeLinks(const DDLifetime& aLifetime)
{
mObjectLinks.RemoveElementsBy([&](DDObjectLink& link) {
return (link.mParent == aLifetime.mObject ||
link.mChild == aLifetime.mObject) &&
aLifetime.IsAliveAt(link.mLinkingIndex);
});
}
size_t
DDMediaLogs::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
{
size_t size = aMallocSizeOf(this) +
mLifetimes.SizeOfExcludingThis(aMallocSizeOf) +
mMediaLogs.ShallowSizeOfExcludingThis(aMallocSizeOf) +
mObjectLinks.ShallowSizeOfExcludingThis(aMallocSizeOf) +
mPendingPromises.ShallowSizeOfExcludingThis(aMallocSizeOf);
for (const DDMediaLog& log : mMediaLogs) {
size += log.SizeOfExcludingThis(aMallocSizeOf);
}
return size;
}
void
DDMediaLogs::ProcessBuffer()
{
MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
mMessagesQueue.PopAll([this](const DDLogMessage& message) {
DDL_DEBUG("Processing: %s", message.Print().Data());
// Either this message will carry a new object for which to create a
// lifetime, or we'll find an existing one.
DDLifetime& lifetime =
FindOrCreateLifetime(message.mObject, message.mIndex, message.mTimeStamp);
// Copy the message contents (without the mValid flag) to the
// appropriate MediaLog corresponding to the message's object lifetime.
LogFor(lifetime.mMediaElement)
.mMessages.AppendElement(static_cast<const DDLogMessage&>(message));
switch (message.mCategory) {
case DDLogCategory::_Construction:
// The FindOrCreateLifetime above will have set a construction time,
// so there's nothing more we need to do here.
MOZ_ASSERT(lifetime.mConstructionTimeStamp);
break;
case DDLogCategory::_DerivedConstruction:
// The FindOrCreateLifetime above will have set a construction time.
MOZ_ASSERT(lifetime.mConstructionTimeStamp);
// A derived construction must come with the base object.
MOZ_ASSERT(message.mValue.is<DDLogObject>());
{
const DDLogObject& base = message.mValue.as<DDLogObject>();
DDLifetime& baseLifetime =
FindOrCreateLifetime(base, message.mIndex, message.mTimeStamp);
// FindOrCreateLifetime could have moved `lifetime`.
DDLifetime* lifetime2 =
mLifetimes.FindLifetime(message.mObject, message.mIndex);
MOZ_ASSERT(lifetime2);
// Assume there's no multiple-inheritance (at least for the types
// we're watching.)
if (baseLifetime.mDerivedObject.Pointer()) {
DDL_WARN(
"base '%s' was already derived as '%s', now deriving as '%s'",
baseLifetime.Printf().get(),
baseLifetime.mDerivedObject.Printf().get(),
lifetime2->Printf().get());
}
baseLifetime.mDerivedObject = lifetime2->mObject;
baseLifetime.mDerivedObjectLinkingIndex = message.mIndex;
// Link the base and derived objects, to ensure they go to the same
// log.
LinkLifetimes(*lifetime2, "is-a", baseLifetime, message.mIndex);
}
break;
case DDLogCategory::_Destruction:
lifetime.mDestructionIndex = message.mIndex;
lifetime.mDestructionTimeStamp = message.mTimeStamp;
UnlinkLifetime(lifetime, message.mIndex);
break;
case DDLogCategory::_Link:
MOZ_ASSERT(message.mValue.is<DDLogObject>());
{
const DDLogObject& child = message.mValue.as<DDLogObject>();
DDLifetime& childLifetime =
FindOrCreateLifetime(child, message.mIndex, message.mTimeStamp);
// FindOrCreateLifetime could have moved `lifetime`.
DDLifetime* lifetime2 =
mLifetimes.FindLifetime(message.mObject, message.mIndex);
MOZ_ASSERT(lifetime2);
LinkLifetimes(
*lifetime2, message.mLabel, childLifetime, message.mIndex);
}
break;
case DDLogCategory::_Unlink:
MOZ_ASSERT(message.mValue.is<DDLogObject>());
{
const DDLogObject& child = message.mValue.as<DDLogObject>();
DDLifetime& childLifetime =
FindOrCreateLifetime(child, message.mIndex, message.mTimeStamp);
// FindOrCreateLifetime could have moved `lifetime`.
DDLifetime* lifetime2 =
mLifetimes.FindLifetime(message.mObject, message.mIndex);
MOZ_ASSERT(lifetime2);
UnlinkLifetimes(*lifetime2, childLifetime, message.mIndex);
}
break;
default:
// Anything else: Nothing more to do.
break;
}
});
}
struct StringWriteFunc : public JSONWriteFunc
{
nsCString& mCString;
explicit StringWriteFunc(nsCString& aCString)
: mCString(aCString)
{
}
void Write(const char* aStr) override { mCString.Append(aStr); }
};
void
DDMediaLogs::FulfillPromises()
{
MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
MozPromiseHolder<LogMessagesPromise> promiseHolder;
const dom::HTMLMediaElement* mediaElement = nullptr;
{
// Grab the first pending promise (if any).
// Note that we don't pop it yet, so we don't potentially leave the list
// empty and therefore allow another processing task to be dispatched.
MutexAutoLock lock(mMutex);
if (mPendingPromises.IsEmpty()) {
return;
}
promiseHolder = Move(mPendingPromises[0].mPromiseHolder);
mediaElement = mPendingPromises[0].mMediaElement;
}
for (;;) {
DDMediaLog* log = GetLogFor(mediaElement);
if (!log) {
// No such media element -> Reject this promise.
DDL_INFO("Rejecting promise for HTMLMediaElement[%p] - Cannot find log",
mediaElement);
promiseHolder.Reject(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR, __func__);
// Pop this rejected promise, fetch next one.
MutexAutoLock lock(mMutex);
mPendingPromises.RemoveElementAt(0);
if (mPendingPromises.IsEmpty()) {
break;
}
promiseHolder = Move(mPendingPromises[0].mPromiseHolder);
mediaElement = mPendingPromises[0].mMediaElement;
continue;
}
nsCString json;
JSONWriter jw{ MakeUnique<StringWriteFunc>(json) };
jw.Start();
jw.StartArrayProperty("messages");
for (const DDLogMessage& message : log->mMessages) {
jw.StartObjectElement(JSONWriter::SingleLineStyle);
jw.IntProperty("i", message.mIndex.Value());
jw.DoubleProperty("ts", ToSeconds(message.mTimeStamp));
DDLifetime* lifetime =
mLifetimes.FindLifetime(message.mObject, message.mIndex);
if (lifetime) {
jw.IntProperty("ob", lifetime->mTag);
} else {
jw.StringProperty("ob",
nsPrintfCString(R"("%s[%p]")",
message.mObject.TypeName(),
message.mObject.Pointer())
.get());
}
jw.StringProperty("cat", ToShortString(message.mCategory));
if (message.mLabel && message.mLabel[0] != '\0') {
jw.StringProperty("lbl", message.mLabel);
}
if (!message.mValue.is<DDNoValue>()) {
if (message.mValue.is<DDLogObject>()) {
const DDLogObject& ob2 = message.mValue.as<DDLogObject>();
DDLifetime* lifetime2 = mLifetimes.FindLifetime(ob2, message.mIndex);
if (lifetime2) {
jw.IntProperty("val", lifetime2->mTag);
} else {
ToJSON(message.mValue, jw, "val");
}
} else {
ToJSON(message.mValue, jw, "val");
}
}
jw.EndObject();
}
jw.EndArray();
jw.StartObjectProperty("objects");
mLifetimes.Visit(
mediaElement,
[&](const DDLifetime& lifetime) {
jw.StartObjectProperty(nsPrintfCString("%" PRIi32, lifetime.mTag).get(),
JSONWriter::SingleLineStyle);
jw.IntProperty("tag", lifetime.mTag);
jw.StringProperty("cls", lifetime.mObject.TypeName());
jw.StringProperty(
"ptr", nsPrintfCString("%p", lifetime.mObject.Pointer()).get());
jw.IntProperty("con", lifetime.mConstructionIndex.Value());
jw.DoubleProperty("con_ts", ToSeconds(lifetime.mConstructionTimeStamp));
if (lifetime.mDestructionTimeStamp) {
jw.IntProperty("des", lifetime.mDestructionIndex.Value());
jw.DoubleProperty("des_ts",
ToSeconds(lifetime.mDestructionTimeStamp));
}
if (lifetime.mDerivedObject.Pointer()) {
DDLifetime* derived = mLifetimes.FindLifetime(
lifetime.mDerivedObject, lifetime.mDerivedObjectLinkingIndex);
if (derived) {
jw.IntProperty("drvd", derived->mTag);
}
}
jw.EndObject();
},
// If there were no (new) messages, only give the main HTMLMediaElement
// object (used to identify this log against the correct element.)
log->mMessages.IsEmpty());
jw.EndObject();
jw.End();
DDL_DEBUG("RetrieveMessages(%p) ->\n%s", mediaElement, json.get());
// This log exists (new messages or not) -> Resolve this promise.
DDL_INFO("Resolving promise for HTMLMediaElement[%p] with messages %" PRImi
"-%" PRImi,
mediaElement,
log->mMessages.IsEmpty() ? 0 : log->mMessages[0].mIndex.Value(),
log->mMessages.IsEmpty()
? 0
: log->mMessages[log->mMessages.Length() - 1].mIndex.Value());
promiseHolder.Resolve(Move(json), __func__);
// Remove exported messages.
log->mMessages.Clear();
// Pop this resolved promise, fetch next one.
MutexAutoLock lock(mMutex);
mPendingPromises.RemoveElementAt(0);
if (mPendingPromises.IsEmpty()) {
break;
}
promiseHolder = Move(mPendingPromises[0].mPromiseHolder);
mediaElement = mPendingPromises[0].mMediaElement;
}
}
void
DDMediaLogs::CleanUpLogs()
{
MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
DDTimeStamp now = DDNow();
// Keep up to 30s of unclassified messages (if a message doesn't get
// classified this quickly, it probably never will be.)
static const double sMaxAgeUnclassifiedMessages_s = 30.0;
// Keep "dead" log (video element and dependents were destroyed) for up to
// 2 minutes, in case the user wants to look at it after the facts.
static const double sMaxAgeDeadLog_s = 120.0;
// Keep old messages related to a live video for up to 5 minutes.
static const double sMaxAgeClassifiedMessages_s = 300.0;
for (size_t logIndexPlus1 = mMediaLogs.Length(); logIndexPlus1 != 0;
--logIndexPlus1) {
DDMediaLog& log = mMediaLogs[logIndexPlus1 - 1];
if (log.mMediaElement) {
// Remove logs for which no lifetime still existed some time ago.
bool used = mLifetimes.VisitBreakable(
log.mMediaElement, [&](const DDLifetime& lifetime) {
// Do we still have a lifetime that existed recently enough?
return !lifetime.mDestructionTimeStamp ||
(now - lifetime.mDestructionTimeStamp).ToSeconds() <=
sMaxAgeDeadLog_s;
});
if (!used) {
DDL_INFO("Removed old log for media element %p", log.mMediaElement);
mLifetimes.Visit(log.mMediaElement, [&](const DDLifetime& lifetime) {
DestroyLifetimeLinks(lifetime);
});
mLifetimes.RemoveLifetimesFor(log.mMediaElement);
mMediaLogs.RemoveElementAt(logIndexPlus1 - 1);
continue;
}
}
// Remove old messages.
size_t old = 0;
const size_t len = log.mMessages.Length();
while (old < len && (now - log.mMessages[old].mTimeStamp).ToSeconds() >
(log.mMediaElement ? sMaxAgeClassifiedMessages_s
: sMaxAgeUnclassifiedMessages_s)) {
++old;
}
if (old != 0) {
// We are going to remove `old` messages.
// First, remove associated destroyed lifetimes that are not used after
// these old messages. (We want to keep non-destroyed lifetimes, in
// case they get used later on.)
size_t removedLifetimes = 0;
for (size_t i = 0; i < old; ++i) {
auto RemoveDestroyedUnusedLifetime = [&](DDLifetime* lifetime) {
if (!lifetime->mDestructionTimeStamp) {
// Lifetime is still alive, keep it.
return;
}
bool used = false;
for (size_t after = old; after < len; ++after) {
const DDLogMessage message = log.mMessages[i];
if (!lifetime->IsAliveAt(message.mIndex)) {
// Lifetime is already dead, and not used yet -> kill it.
break;
}
const DDLogObject& ob = message.mObject;
if (lifetime->mObject == ob) {
used = true;
break;
}
if (message.mValue.is<DDLogObject>()) {
if (lifetime->mObject == message.mValue.as<DDLogObject>()) {
used = true;
break;
}
}
}
if (!used) {
DestroyLifetimeLinks(*lifetime);
mLifetimes.RemoveLifetime(lifetime);
++removedLifetimes;
}
};
const DDLogMessage message = log.mMessages[i];
const DDLogObject& ob = message.mObject;
DDLifetime* lifetime1 = mLifetimes.FindLifetime(ob, message.mIndex);
if (lifetime1) {
RemoveDestroyedUnusedLifetime(lifetime1);
}
if (message.mValue.is<DDLogObject>()) {
DDLifetime* lifetime2 = mLifetimes.FindLifetime(
message.mValue.as<DDLogObject>(), message.mIndex);
if (lifetime2) {
RemoveDestroyedUnusedLifetime(lifetime2);
}
}
}
DDL_INFO("Removed %zu messages (#%" PRImi " %f - #%" PRImi
" %f) and %zu lifetimes from log for media element %p",
old,
log.mMessages[0].mIndex.Value(),
ToSeconds(log.mMessages[0].mTimeStamp),
log.mMessages[old - 1].mIndex.Value(),
ToSeconds(log.mMessages[old - 1].mTimeStamp),
removedLifetimes,
log.mMediaElement);
log.mMessages.RemoveElementsAt(0, old);
}
}
}
void
DDMediaLogs::ProcessLog()
{
MOZ_ASSERT(!mThread || mThread.get() == NS_GetCurrentThread());
ProcessBuffer();
FulfillPromises();
CleanUpLogs();
DDL_INFO("DDMediaLog size: %zu", SizeOfIncludingThis(moz_malloc_size_of));
}
nsresult
DDMediaLogs::DispatchProcessLog(const MutexAutoLock& aProofOfLock)
{
if (!mThread) {
return NS_ERROR_SERVICE_NOT_AVAILABLE;
}
return mThread->Dispatch(
NS_NewRunnableFunction("ProcessLog", [this] { ProcessLog(); }),
NS_DISPATCH_NORMAL);
}
nsresult
DDMediaLogs::DispatchProcessLog()
{
MutexAutoLock lock(mMutex);
return DispatchProcessLog(lock);
}
RefPtr<DDMediaLogs::LogMessagesPromise>
DDMediaLogs::RetrieveMessages(const dom::HTMLMediaElement* aMediaElement)
{
MozPromiseHolder<LogMessagesPromise> holder;
RefPtr<LogMessagesPromise> promise = holder.Ensure(__func__);
{
MutexAutoLock lock(mMutex);
// If there were unfulfilled promises, we know processing has already
// been requested.
if (mPendingPromises.IsEmpty()) {
// But if we're the first one, start processing.
nsresult rv = DispatchProcessLog(lock);
if (NS_FAILED(rv)) {
holder.Reject(rv, __func__);
}
}
mPendingPromises.AppendElement(
PendingPromise{ Move(holder), aMediaElement });
}
return promise;
}
} // namespace mozilla