Bug 1594577 - Record hangs which precede forced shutdowns r=froydnj

In short - if a user forcibly terminates the browser because it seems
to be permanently hung, we currently do not get a change to record the
hang. This is unfortunate, because these likely represent the most
egregious hangs in terms of user frustration. This patch seeks to
address that.

If a hang exceeds 8192ms (the current definition of a "permahang" in
existing BHR terms), then we decide to immediately persist it to disk,
in case we never get a chance to return to the main thread and
submit it. On the next start of the browser, we read the file from
disk on a background thread, and just submit it using the normal
mechanism.

Regarding the handling of the file itself, I tried to do the simplest
thing I could - as far as I can tell there is no standard simple
serialization mechanism available directly to C++ in Gecko, so I just
serialized it by hand. I didn't take any special care with endianness
or anything as I can't think of a situation in which we really care
at all about these files being transferable between architectures. I
directly used PR_Write / PR_Read instead of doing something fancy
like memory mapping the file, because I don't think performance is a
critical concern here and it offers a simple protection against
reading out of bounds.

Differential Revision: https://phabricator.services.mozilla.com/D52566
This commit is contained in:
Doug Thayer
2019-11-14 21:35:42 +00:00
parent a661147654
commit 867e9b8573
8 changed files with 439 additions and 30 deletions

View File

@@ -5792,7 +5792,7 @@ mozilla::ipc::IPCResult ContentParent::RecvBHRThreadHang(
// XXX: We should be able to avoid this potentially expensive copy here by
// moving our deserialized argument.
nsCOMPtr<nsIHangDetails> hangDetails =
new nsHangDetails(HangDetails(aDetails));
new nsHangDetails(HangDetails(aDetails), PersistedToDisk::No);
obs->NotifyObservers(hangDetails, "bhr-thread-hang", nullptr);
}
return IPC_OK();

View File

@@ -269,7 +269,7 @@ mozilla::ipc::IPCResult GPUChild::RecvBHRThreadHang(
// XXX: We should be able to avoid this potentially expensive copy here by
// moving our deserialized argument.
nsCOMPtr<nsIHangDetails> hangDetails =
new nsHangDetails(HangDetails(aDetails));
new nsHangDetails(HangDetails(aDetails), PersistedToDisk::No);
obs->NotifyObservers(hangDetails, "bhr-thread-hang", nullptr);
}
return IPC_OK();

View File

@@ -6,6 +6,11 @@
ChromeUtils.import("resource://gre/modules/Services.jsm", this);
ChromeUtils.defineModuleGetter(
this,
"OS",
"resource://gre/modules/osfile.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"TelemetryController",
@@ -35,6 +40,7 @@ BHRTelemetryService.prototype = Object.freeze({
modules: [],
hangs: [],
};
this.clearPermahangFile = false;
},
recordHang({
@@ -46,6 +52,7 @@ BHRTelemetryService.prototype = Object.freeze({
remoteType,
modules,
annotations,
wasPersisted,
}) {
if (!Services.telemetry.canRecordExtended) {
return;
@@ -99,6 +106,10 @@ BHRTelemetryService.prototype = Object.freeze({
stack,
});
if (wasPersisted) {
this.clearPermahangFile = true;
}
// If we have collected enough hangs, we can submit the hangs we have
// collected to telemetry.
if (this.payload.hangs.length > this.TRANSMIT_HANG_COUNT) {
@@ -107,6 +118,13 @@ BHRTelemetryService.prototype = Object.freeze({
},
submit() {
if (this.clearPermahangFile) {
OS.File.remove(
OS.Path.join(OS.Constants.Path.profileDir, "last_permahang.bin"),
{ ignoreAbsent: true }
);
}
if (!Services.telemetry.canRecordExtended) {
return;
}

View File

@@ -20,6 +20,7 @@
#include "prinrval.h"
#include "prthread.h"
#include "ThreadStackHelper.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsIObserverService.h"
#include "nsIObserver.h"
#include "mozilla/Services.h"
@@ -104,6 +105,10 @@ class BackgroundHangManager : public nsIObserver {
// Unwinding and reporting of hangs is despatched to this thread.
nsCOMPtr<nsIThread> mHangProcessingThread;
// Used for recording a permahang in case we don't ever make it back to
// the main thread to record/send it.
nsCOMPtr<nsIFile> mPermahangFile;
// Allows us to watch CPU usage and annotate hangs when the system is
// under high external load.
CPUUsageWatcher mCPUUsageWatcher;
@@ -131,13 +136,31 @@ NS_IMPL_ISUPPORTS(BackgroundHangManager, nsIObserver)
NS_IMETHODIMP
BackgroundHangManager::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData) {
NS_ENSURE_TRUE(!strcmp(aTopic, "profile-after-change"), NS_ERROR_UNEXPECTED);
BackgroundHangMonitor::DisableOnBeta();
if (!strcmp(aTopic, "browser-delayed-startup-finished")) {
MonitorAutoLock autoLock(mLock);
nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
getter_AddRefs(mPermahangFile));
if (NS_SUCCEEDED(rv)) {
mPermahangFile->AppendNative(NS_LITERAL_CSTRING("last_permahang.bin"));
} else {
mPermahangFile = nullptr;
}
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
MOZ_ASSERT(observerService);
observerService->RemoveObserver(this, "profile-after-change");
if (mHangProcessingThread && mPermahangFile) {
nsCOMPtr<nsIRunnable> submitRunnable =
new SubmitPersistedPermahangRunnable(mPermahangFile);
mHangProcessingThread->Dispatch(submitRunnable.forget());
}
} else if (!strcmp(aTopic, "profile-after-change")) {
BackgroundHangMonitor::DisableOnBeta();
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
MOZ_ASSERT(observerService);
observerService->RemoveObserver(BackgroundHangManager::sInstance,
"profile-after-change");
} else {
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
@@ -216,7 +239,8 @@ class BackgroundHangThread : public LinkedListElement<BackgroundHangThread> {
// Report a hang; aManager->mLock IS locked. The hang will be processed
// off-main-thread, and will then be submitted back.
void ReportHang(TimeDuration aHangTime);
void ReportHang(TimeDuration aHangTime,
PersistedToDisk aPersistedToDisk = PersistedToDisk::No);
// Report a permanent hang; aManager->mLock IS locked
void ReportPermaHang();
// Called by BackgroundHangMonitor::NotifyActivity
@@ -468,7 +492,8 @@ BackgroundHangThread::~BackgroundHangThread() {
}
}
void BackgroundHangThread::ReportHang(TimeDuration aHangTime) {
void BackgroundHangThread::ReportHang(TimeDuration aHangTime,
PersistedToDisk aPersistedToDisk) {
// Recovered from a hang; called on the monitor thread
// mManager->mLock IS locked
@@ -478,17 +503,24 @@ void BackgroundHangThread::ReportHang(TimeDuration aHangTime) {
VoidString(), mThreadName, mRunnableName, std::move(mHangStack),
std::move(mAnnotations));
PersistedToDisk persistedToDisk = aPersistedToDisk;
if (aPersistedToDisk == PersistedToDisk::Yes && XRE_IsParentProcess()) {
auto res = WriteHangDetailsToFile(hangDetails, mManager->mPermahangFile);
persistedToDisk = res.isOk() ? PersistedToDisk::Yes : PersistedToDisk::No;
}
// If the hang processing thread exists, we can process the native stack
// on it. Otherwise, we are unable to report a native stack, so we just
// report without one.
if (mManager->mHangProcessingThread) {
nsCOMPtr<nsIRunnable> processHangStackRunnable =
new ProcessHangStackRunnable(std::move(hangDetails));
new ProcessHangStackRunnable(std::move(hangDetails), persistedToDisk);
mManager->mHangProcessingThread->Dispatch(
processHangStackRunnable.forget());
} else {
NS_WARNING("Unable to report native stack without a BHR processing thread");
RefPtr<nsHangDetails> hd = new nsHangDetails(std::move(hangDetails));
RefPtr<nsHangDetails> hd =
new nsHangDetails(std::move(hangDetails), persistedToDisk);
hd->Submit();
}
@@ -509,13 +541,11 @@ void BackgroundHangThread::ReportPermaHang() {
// Permanently hanged; called on the monitor thread
// mManager->mLock IS locked
// NOTE: We used to capture a native stack in this situation if one had not
// already been captured, but with the new ReportHang design that is less
// practical.
//
// We currently don't look at hang reports outside of nightly, and already
// collect native stacks eagerly on nightly, so this should be OK.
ReportHang(mMaxTimeout);
// The significance of a permahang is that it's likely that we won't ever
// recover and be allowed to submit this hang. On the parent thread, we
// compensate for this by writing the hang details to disk on this thread,
// and in our next session we'll try to read those details
ReportHang(mMaxTimeout, PersistedToDisk::Yes);
}
MOZ_ALWAYS_INLINE void BackgroundHangThread::Update() {
@@ -613,17 +643,16 @@ void BackgroundHangMonitor::Startup() {
return;
}
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
MOZ_ASSERT(observerService);
if (!strcmp(MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL), "beta")) {
if (XRE_IsParentProcess()) { // cached ClientID hasn't been read yet
BackgroundHangThread::Startup();
BackgroundHangManager::sInstance = new BackgroundHangManager();
Unused << NS_WARN_IF(
BackgroundHangManager::sInstance->mCPUUsageWatcher.Init().isErr());
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
MOZ_ASSERT(observerService);
observerService->AddObserver(BackgroundHangManager::sInstance,
"profile-after-change", false);
return;
@@ -636,6 +665,10 @@ void BackgroundHangMonitor::Startup() {
BackgroundHangManager::sInstance = new BackgroundHangManager();
Unused << NS_WARN_IF(
BackgroundHangManager::sInstance->mCPUUsageWatcher.Init().isErr());
if (XRE_IsParentProcess()) {
observerService->AddObserver(BackgroundHangManager::sInstance,
"browser-delayed-startup-finished", false);
}
#endif
}
@@ -648,6 +681,11 @@ void BackgroundHangMonitor::Shutdown() {
MOZ_ASSERT(BackgroundHangManager::sInstance, "Not initialized");
BackgroundHangManager::sInstance->mCPUUsageWatcher.Uninit();
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
MOZ_ASSERT(observerService);
observerService->RemoveObserver(BackgroundHangManager::sInstance,
"browser-delayed-startup-finished");
/* Scope our lock inside Shutdown() because the sInstance object can
be destroyed as soon as we set sInstance to nullptr below, and
we don't want to hold the lock when it's being destroyed. */

View File

@@ -12,13 +12,22 @@
#include "mozilla/dom/ContentParent.h" // For RemoteTypePrefix
#include "mozilla/Unused.h"
#include "mozilla/GfxMessageUtils.h" // For ParamTraits<GeckoProcessType>
#include "mozilla/ResultExtensions.h"
#ifdef MOZ_GECKO_PROFILER
# include "shared-libraries.h"
#endif
static const char MAGIC[] = "permahangsavev1";
namespace mozilla {
NS_IMETHODIMP
nsHangDetails::GetWasPersisted(bool* aWasPersisted) {
*aWasPersisted = mPersistedToDisk == PersistedToDisk::Yes;
return NS_OK;
}
NS_IMETHODIMP
nsHangDetails::GetDuration(double* aDuration) {
*aDuration = mDetails.duration().ToMilliseconds();
@@ -376,14 +385,319 @@ void ReadModuleInformation(HangStack& stack) {
#endif
}
Result<Ok, nsresult> WriteUint(PRFileDesc* aFile, const CheckedUint32& aInt) {
if (!aInt.isValid()) {
MOZ_ASSERT_UNREACHABLE("Integer value out of bounds.");
return Err(NS_ERROR_UNEXPECTED);
}
int32_t value = aInt.value();
if (PR_Write(aFile, (void*)&value, sizeof(value)) != sizeof(value)) {
return Err(NS_ERROR_FAILURE);
}
return Ok();
}
Result<uint32_t, nsresult> ReadUint(PRFileDesc* aFile) {
int32_t value;
if (PR_Read(aFile, (void*)&value, sizeof(value)) != sizeof(value)) {
return Err(NS_ERROR_FAILURE);
}
return value;
}
Result<Ok, nsresult> WriteCString(PRFileDesc* aFile, const char* aString) {
size_t length = strlen(aString);
MOZ_TRY(WriteUint(aFile, CheckedUint32(length)));
if (PR_Write(aFile, (void*)aString, length) != length) {
return Err(NS_ERROR_FAILURE);
}
return Ok();
}
template <typename CharT>
Result<Ok, nsresult> WriteTString(PRFileDesc* aFile,
const nsTString<CharT>& aString) {
MOZ_TRY(WriteUint(aFile, CheckedUint32(aString.Length())));
size_t size = aString.Length() * sizeof(CharT);
if (PR_Write(aFile, (void*)aString.get(), size) != size) {
return Err(NS_ERROR_FAILURE);
}
return Ok();
}
template <typename CharT>
Result<nsTString<CharT>, nsresult> ReadTString(PRFileDesc* aFile) {
uint32_t length;
MOZ_TRY_VAR(length, ReadUint(aFile));
nsTString<CharT> result;
CharT buffer[512];
size_t bufferLength = sizeof(buffer) / sizeof(CharT);
while (length != 0) {
size_t toRead = std::min(bufferLength, size_t(length));
size_t toReadSize = toRead * sizeof(CharT);
if (PR_Read(aFile, (void*)buffer, toReadSize) != toReadSize) {
return Err(NS_ERROR_FAILURE);
}
if (!result.Append(buffer, toRead, mozilla::fallible)) {
return Err(NS_ERROR_FAILURE);
}
if (length > bufferLength) {
length -= bufferLength;
} else {
length = 0;
}
}
return result;
}
Result<Ok, nsresult> WriteEntry(PRFileDesc* aFile, const HangStack& aStack,
const HangEntry& aEntry) {
MOZ_TRY(WriteUint(aFile, uint32_t(aEntry.type())));
switch (aEntry.type()) {
case HangEntry::TnsCString: {
MOZ_TRY(WriteTString(aFile, aEntry.get_nsCString()));
break;
}
case HangEntry::THangEntryBufOffset: {
uint32_t offset = aEntry.get_HangEntryBufOffset().index();
if (NS_WARN_IF(aStack.strbuffer().IsEmpty() ||
offset >= aStack.strbuffer().Length())) {
MOZ_ASSERT_UNREACHABLE("Corrupted offset data");
return Err(NS_ERROR_FAILURE);
}
if (aStack.strbuffer().LastElement() != '\0') {
MOZ_ASSERT_UNREACHABLE("Corrupted strbuffer data");
return Err(NS_ERROR_FAILURE);
}
const char* start = (const char*)aStack.strbuffer().Elements() + offset;
MOZ_TRY(WriteCString(aFile, start));
break;
}
case HangEntry::THangEntryModOffset: {
const HangEntryModOffset& mo = aEntry.get_HangEntryModOffset();
MOZ_TRY(WriteUint(aFile, CheckedUint32(mo.module())));
MOZ_TRY(WriteUint(aFile, CheckedUint32(mo.offset())));
break;
}
case HangEntry::THangEntryProgCounter:
case HangEntry::THangEntryContent:
case HangEntry::THangEntryJit:
case HangEntry::THangEntryWasm:
case HangEntry::THangEntryChromeScript:
case HangEntry::THangEntrySuppressed: {
break;
}
default:
MOZ_CRASH("Unsupported HangEntry type?");
}
return Ok();
}
Result<Ok, nsresult> ReadEntry(PRFileDesc* aFile, HangStack& aStack) {
uint32_t type;
MOZ_TRY_VAR(type, ReadUint(aFile));
HangEntry::Type entryType = HangEntry::Type(type);
switch (entryType) {
case HangEntry::TnsCString:
case HangEntry::THangEntryBufOffset: {
nsCString str;
MOZ_TRY_VAR(str, ReadTString<char>(aFile));
aStack.stack().AppendElement(std::move(str));
break;
}
case HangEntry::THangEntryModOffset: {
uint32_t module;
MOZ_TRY_VAR(module, ReadUint(aFile));
uint32_t offset;
MOZ_TRY_VAR(offset, ReadUint(aFile));
aStack.stack().AppendElement(HangEntryModOffset(module, offset));
break;
}
case HangEntry::THangEntryProgCounter: {
aStack.stack().AppendElement(HangEntryProgCounter());
break;
}
case HangEntry::THangEntryContent: {
aStack.stack().AppendElement(HangEntryContent());
break;
}
case HangEntry::THangEntryJit: {
aStack.stack().AppendElement(HangEntryJit());
break;
}
case HangEntry::THangEntryWasm: {
aStack.stack().AppendElement(HangEntryWasm());
break;
}
case HangEntry::THangEntryChromeScript: {
aStack.stack().AppendElement(HangEntryChromeScript());
break;
}
case HangEntry::THangEntrySuppressed: {
aStack.stack().AppendElement(HangEntrySuppressed());
break;
}
default:
MOZ_CRASH("Unsupported HangEntry type?");
}
return Ok();
}
Result<HangDetails, nsresult> ReadHangDetailsFromFile(nsIFile* aFile) {
AutoFDClose fd;
nsresult rv = aFile->OpenNSPRFileDesc(PR_RDONLY, 0644, &fd.rwget());
if (NS_FAILED(rv)) {
return Err(rv);
}
uint8_t magicBuffer[sizeof(MAGIC)];
if (PR_Read(fd, (void*)magicBuffer, sizeof(MAGIC)) != sizeof(MAGIC)) {
return Err(NS_ERROR_FAILURE);
}
if (memcmp(magicBuffer, MAGIC, sizeof(MAGIC)) != 0) {
return Err(NS_ERROR_FAILURE);
}
HangDetails result;
uint32_t duration;
MOZ_TRY_VAR(duration, ReadUint(fd));
result.duration() = TimeDuration::FromMilliseconds(double(duration));
MOZ_TRY_VAR(result.threadName(), ReadTString<char>(fd));
MOZ_TRY_VAR(result.runnableName(), ReadTString<char>(fd));
MOZ_TRY_VAR(result.process(), ReadTString<char>(fd));
MOZ_TRY_VAR(result.remoteType(), ReadTString<char16_t>(fd));
uint32_t numAnnotations;
MOZ_TRY_VAR(numAnnotations, ReadUint(fd));
auto& annotations = result.annotations();
// Add a "Unrecovered" annotation so we can know when processing this that
// the hang persisted until the process was closed.
if (!annotations.SetCapacity(numAnnotations + 1, mozilla::fallible)) {
return Err(NS_ERROR_FAILURE);
}
annotations.AppendElement(HangAnnotation(NS_LITERAL_STRING("Unrecovered"),
NS_LITERAL_STRING("true")));
for (size_t i = 0; i < numAnnotations; ++i) {
HangAnnotation annot;
MOZ_TRY_VAR(annot.name(), ReadTString<char16_t>(fd));
MOZ_TRY_VAR(annot.value(), ReadTString<char16_t>(fd));
annotations.AppendElement(std::move(annot));
}
auto& stack = result.stack();
uint32_t numFrames;
MOZ_TRY_VAR(numFrames, ReadUint(fd));
if (!stack.stack().SetCapacity(numFrames, mozilla::fallible)) {
return Err(NS_ERROR_FAILURE);
}
for (size_t i = 0; i < numFrames; ++i) {
MOZ_TRY(ReadEntry(fd, stack));
}
uint32_t numModules;
MOZ_TRY_VAR(numModules, ReadUint(fd));
auto& modules = stack.modules();
if (!annotations.SetCapacity(numModules, mozilla::fallible)) {
return Err(NS_ERROR_FAILURE);
}
for (size_t i = 0; i < numModules; ++i) {
HangModule module;
MOZ_TRY_VAR(module.name(), ReadTString<char16_t>(fd));
MOZ_TRY_VAR(module.breakpadId(), ReadTString<char>(fd));
modules.AppendElement(std::move(module));
}
return result;
}
Result<Ok, nsresult> WriteHangDetailsToFile(HangDetails& aDetails,
nsIFile* aFile) {
AutoFDClose fd;
nsresult rv = aFile->OpenNSPRFileDesc(
PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0644, &fd.rwget());
if (NS_FAILED(rv)) {
return Err(rv);
}
if (PR_Write(fd, (void*)MAGIC, sizeof(MAGIC)) != sizeof(MAGIC)) {
return Err(NS_ERROR_FAILURE);
}
double duration = aDetails.duration().ToMilliseconds();
if (duration > double(MaxValue<uint32_t>::value)) {
// Something has gone terribly wrong if we've hung for more than 2^32 ms.
return Err(NS_ERROR_FAILURE);
}
MOZ_TRY(WriteUint(fd, uint32_t(duration)));
MOZ_TRY(WriteTString(fd, aDetails.threadName()));
MOZ_TRY(WriteTString(fd, aDetails.runnableName()));
MOZ_TRY(WriteTString(fd, aDetails.process()));
MOZ_TRY(WriteTString(fd, aDetails.remoteType()));
MOZ_TRY(WriteUint(fd, CheckedUint32(aDetails.annotations().Length())));
for (auto& annot : aDetails.annotations()) {
MOZ_TRY(WriteTString(fd, annot.name()));
MOZ_TRY(WriteTString(fd, annot.value()));
}
auto& stack = aDetails.stack();
ReadModuleInformation(stack);
MOZ_TRY(WriteUint(fd, CheckedUint32(stack.stack().Length())));
for (auto& entry : stack.stack()) {
MOZ_TRY(WriteEntry(fd, stack, entry));
}
auto& modules = stack.modules();
MOZ_TRY(WriteUint(fd, CheckedUint32(modules.Length())));
for (auto& module : modules) {
MOZ_TRY(WriteTString(fd, module.name()));
MOZ_TRY(WriteTString(fd, module.breakpadId()));
}
return Ok();
}
NS_IMETHODIMP
ProcessHangStackRunnable::Run() {
// NOTE: Reading module information can take a long time, which is why we do
// it off-main-thread.
ReadModuleInformation(mHangDetails.stack());
if (mHangDetails.stack().modules().IsEmpty()) {
ReadModuleInformation(mHangDetails.stack());
}
RefPtr<nsHangDetails> hangDetails =
new nsHangDetails(std::move(mHangDetails));
new nsHangDetails(std::move(mHangDetails), mPersistedToDisk);
hangDetails->Submit();
return NS_OK;
}
NS_IMETHODIMP
SubmitPersistedPermahangRunnable::Run() {
auto hangDetailsResult = ReadHangDetailsFromFile(mPermahangFile);
if (hangDetailsResult.isErr()) {
// If we somehow failed in trying to deserialize the hang file, go ahead
// and delete it to prevent future runs from having to go through the
// same thing. If we succeeded, however, the file should be cleaned up
// once the hang is submitted.
Unused << mPermahangFile->Remove(false);
return hangDetailsResult.unwrapErr();
}
RefPtr<nsHangDetails> hangDetails =
new nsHangDetails(hangDetailsResult.unwrap(), PersistedToDisk::Yes);
hangDetails->Submit();
return NS_OK;

View File

@@ -10,6 +10,7 @@
#include "ipc/IPCMessageUtils.h"
#include "mozilla/ProcessedStack.h"
#include "mozilla/RefPtr.h"
#include "mozilla/Result.h"
#include "mozilla/Move.h"
#include "mozilla/HangTypes.h"
#include "mozilla/HangAnnotations.h"
@@ -19,6 +20,11 @@
namespace mozilla {
enum class PersistedToDisk {
No,
Yes,
};
/**
* HangDetails is the concrete implementaion of nsIHangDetails, and contains the
* infromation which we want to expose to observers of the bhr-thread-hang
@@ -29,8 +35,9 @@ class nsHangDetails : public nsIHangDetails {
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIHANGDETAILS
explicit nsHangDetails(HangDetails&& aDetails)
: mDetails(std::move(aDetails)) {}
explicit nsHangDetails(HangDetails&& aDetails,
PersistedToDisk aPersistedToDisk)
: mDetails(std::move(aDetails)), mPersistedToDisk(aPersistedToDisk) {}
// Submit these HangDetails to the main thread. This will dispatch a runnable
// to the main thread which will fire off the bhr-thread-hang observer
@@ -41,8 +48,12 @@ class nsHangDetails : public nsIHangDetails {
virtual ~nsHangDetails() {}
HangDetails mDetails;
PersistedToDisk mPersistedToDisk;
};
Result<Ok, nsresult> WriteHangDetailsToFile(HangDetails& aDetails,
nsIFile* aFile);
/**
* This runnable is run on the StreamTransportService threadpool in order to
* process the stack off main thread before submitting it to the main thread as
@@ -53,14 +64,34 @@ class nsHangDetails : public nsIHangDetails {
*/
class ProcessHangStackRunnable final : public Runnable {
public:
explicit ProcessHangStackRunnable(HangDetails&& aHangDetails)
explicit ProcessHangStackRunnable(HangDetails&& aHangDetails,
PersistedToDisk aPersistedToDisk)
: Runnable("ProcessHangStackRunnable"),
mHangDetails(std::move(aHangDetails)) {}
mHangDetails(std::move(aHangDetails)),
mPersistedToDisk(aPersistedToDisk) {}
NS_IMETHOD Run() override;
private:
HangDetails mHangDetails;
PersistedToDisk mPersistedToDisk;
};
/**
* This runnable handles checking whether our last session wrote a permahang to
* disk which we were unable to submit through telemetry. If so, we read the
* permahang out and try again to submit it.
*/
class SubmitPersistedPermahangRunnable final : public Runnable {
public:
explicit SubmitPersistedPermahangRunnable(nsIFile* aPermahangFile)
: Runnable("SubmitPersistedPermahangRunnable"),
mPermahangFile(aPermahangFile) {}
NS_IMETHOD Run() override;
private:
nsCOMPtr<nsIFile> mPermahangFile;
};
} // namespace mozilla

View File

@@ -20,6 +20,12 @@ class HangDetails;
[scriptable, uuid(23d63fff-38d6-4003-9c57-2c90aca1180a)]
interface nsIHangDetails : nsISupports
{
/**
* The hang was persisted to disk as a permahang, so we can clear the
* permahang file once we submit this.
*/
readonly attribute bool wasPersisted;
/**
* The detected duration of the hang in milliseconds.
*/

View File

@@ -153,3 +153,5 @@ The following annotations are currently present in tree:
+-----------------+-------------------------------------------------+
| HangUIDontShow | "true" if the hang UI was not shown |
+-----------------+-------------------------------------------------+
| Unrecovered | "true" if the hang persisted until process exit |
+-----------------+-------------------------------------------------+