Bug 1357829 - Part 2: Use profiler_suspend_sample_thread in the background hang monitor, r=froydnj
This patch uses the profiler_suspend_sample_thread method which was added in part 1. With this patch, we no longer manually run code to pause the target thread, instead using the profiler's provided code to do so. In addition, we no longer manually walk the stack to collect native stack frames, instead relying on the profiler's cross-platform stack walking logic. This helps remove some of the code from ThreadStackHelper which was redundant with the profiler. Much of the pseudostack code in ThreadStackHelper is also redundant, and should hopefully be eliminated in a follow-up. MozReview-Commit-ID: 4RjLHt6inH9
This commit is contained in:
@@ -9,9 +9,6 @@
|
||||
#include "nsJSPrincipals.h"
|
||||
#include "nsScriptSecurityManager.h"
|
||||
#include "jsfriendapi.h"
|
||||
#ifdef MOZ_THREADSTACKHELPER_NATIVE
|
||||
#include "shared-libraries.h"
|
||||
#endif
|
||||
#ifdef MOZ_THREADSTACKHELPER_PSEUDO
|
||||
#include "PseudoStack.h"
|
||||
#endif
|
||||
@@ -65,91 +62,20 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_THREADSTACKHELPER_NATIVE
|
||||
#if defined(MOZ_THREADSTACKHELPER_X86) || \
|
||||
defined(MOZ_THREADSTACKHELPER_X64)
|
||||
// On these architectures, the stack grows downwards (toward lower addresses).
|
||||
#define MOZ_THREADSTACKHELPER_STACK_GROWS_DOWN
|
||||
#else
|
||||
#error "Unsupported architecture"
|
||||
#endif
|
||||
#endif // MOZ_THREADSTACKHELPER_NATIVE
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
void
|
||||
ThreadStackHelper::Startup()
|
||||
{
|
||||
#if defined(XP_LINUX)
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (!sInitialized) {
|
||||
// TODO: centralize signal number allocation
|
||||
sFillStackSignum = SIGRTMIN + 4;
|
||||
if (sFillStackSignum > SIGRTMAX) {
|
||||
// Leave uninitialized
|
||||
MOZ_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
struct sigaction sigact = {};
|
||||
sigact.sa_sigaction = FillStackHandler;
|
||||
sigemptyset(&sigact.sa_mask);
|
||||
sigact.sa_flags = SA_SIGINFO | SA_RESTART;
|
||||
MOZ_ALWAYS_TRUE(!::sigaction(sFillStackSignum, &sigact, nullptr));
|
||||
}
|
||||
sInitialized++;
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
ThreadStackHelper::Shutdown()
|
||||
{
|
||||
#if defined(XP_LINUX)
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (sInitialized == 1) {
|
||||
struct sigaction sigact = {};
|
||||
sigact.sa_handler = SIG_DFL;
|
||||
MOZ_ALWAYS_TRUE(!::sigaction(sFillStackSignum, &sigact, nullptr));
|
||||
}
|
||||
sInitialized--;
|
||||
#endif
|
||||
}
|
||||
|
||||
ThreadStackHelper::ThreadStackHelper()
|
||||
: mStackToFill(nullptr)
|
||||
#ifdef MOZ_THREADSTACKHELPER_PSEUDO
|
||||
: mStackToFill(nullptr)
|
||||
, mPseudoStack(profiler_get_pseudo_stack())
|
||||
, mMaxStackSize(Stack::sMaxInlineStorage)
|
||||
, mMaxBufferSize(512)
|
||||
#endif
|
||||
{
|
||||
#if defined(XP_LINUX)
|
||||
MOZ_ALWAYS_TRUE(!::sem_init(&mSem, 0, 0));
|
||||
mThreadID = ::syscall(SYS_gettid);
|
||||
#elif defined(XP_WIN)
|
||||
mInitialized = !!::DuplicateHandle(
|
||||
::GetCurrentProcess(), ::GetCurrentThread(),
|
||||
::GetCurrentProcess(), &mThreadID,
|
||||
THREAD_SUSPEND_RESUME
|
||||
#ifdef MOZ_THREADSTACKHELPER_NATIVE
|
||||
| THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION
|
||||
, mNativeStackToFill(nullptr)
|
||||
#endif
|
||||
, FALSE, 0);
|
||||
mStackTop = profiler_get_stack_top();
|
||||
MOZ_ASSERT(mInitialized);
|
||||
#elif defined(XP_MACOSX)
|
||||
mThreadID = mach_thread_self();
|
||||
#endif
|
||||
}
|
||||
|
||||
ThreadStackHelper::~ThreadStackHelper()
|
||||
{
|
||||
#if defined(XP_LINUX)
|
||||
MOZ_ALWAYS_TRUE(!::sem_destroy(&mSem));
|
||||
#elif defined(XP_WIN)
|
||||
if (mInitialized) {
|
||||
MOZ_ALWAYS_TRUE(!!::CloseHandle(mThreadID));
|
||||
}
|
||||
#endif
|
||||
mThreadId = profiler_current_thread_id();
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -170,138 +96,10 @@ ThreadStackHelper::GetPseudoStack(Stack& aStack)
|
||||
GetStacksInternal(&aStack, nullptr);
|
||||
}
|
||||
|
||||
void
|
||||
ThreadStackHelper::GetStacksInternal(Stack* aStack, NativeStack* aNativeStack)
|
||||
{
|
||||
// Always run PrepareStackBuffer first to clear aStack
|
||||
if (aStack && !PrepareStackBuffer(*aStack)) {
|
||||
// Skip and return empty aStack
|
||||
return;
|
||||
}
|
||||
|
||||
ScopedSetPtr<Stack> stackPtr(mStackToFill, aStack);
|
||||
|
||||
#if defined(XP_LINUX)
|
||||
if (!sInitialized) {
|
||||
MOZ_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
if (aStack) {
|
||||
siginfo_t uinfo = {};
|
||||
uinfo.si_signo = sFillStackSignum;
|
||||
uinfo.si_code = SI_QUEUE;
|
||||
uinfo.si_pid = getpid();
|
||||
uinfo.si_uid = getuid();
|
||||
uinfo.si_value.sival_ptr = this;
|
||||
if (::syscall(SYS_rt_tgsigqueueinfo, uinfo.si_pid,
|
||||
mThreadID, sFillStackSignum, &uinfo)) {
|
||||
// rt_tgsigqueueinfo was added in Linux 2.6.31.
|
||||
// Could have failed because the syscall did not exist.
|
||||
return;
|
||||
}
|
||||
MOZ_ALWAYS_TRUE(!::sem_wait(&mSem));
|
||||
}
|
||||
|
||||
#elif defined(XP_WIN)
|
||||
if (!mInitialized) {
|
||||
MOZ_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE: We can only perform frame pointer stack walking on non win64
|
||||
// platforms, because Win64 always omits frame pointers. We don't want to use
|
||||
// MozStackWalk here, so we just skip collecting stacks entirely.
|
||||
#ifndef MOZ_THREADSTACKHELPER_X64
|
||||
if (aNativeStack) {
|
||||
aNativeStack->reserve(Telemetry::HangStack::sMaxNativeFrames);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (::SuspendThread(mThreadID) == DWORD(-1)) {
|
||||
MOZ_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// SuspendThread is asynchronous, so the thread may still be running. Use
|
||||
// GetThreadContext to ensure it's really suspended.
|
||||
// See https://blogs.msdn.microsoft.com/oldnewthing/20150205-00/?p=44743.
|
||||
CONTEXT context;
|
||||
memset(&context, 0, sizeof(context));
|
||||
context.ContextFlags = CONTEXT_CONTROL;
|
||||
if (::GetThreadContext(mThreadID, &context)) {
|
||||
if (aStack) {
|
||||
FillStackBuffer();
|
||||
}
|
||||
|
||||
#ifndef MOZ_THREADSTACKHELPER_X64
|
||||
if (aNativeStack) {
|
||||
auto callback = [](uint32_t, void* aPC, void*, void* aClosure) {
|
||||
NativeStack* stack = static_cast<NativeStack*>(aClosure);
|
||||
stack->push_back(reinterpret_cast<uintptr_t>(aPC));
|
||||
};
|
||||
|
||||
// Now we need to get our frame pointer, our stack pointer, and our stack
|
||||
// top. Rather than registering and storing the stack tops ourselves, we use
|
||||
// the gecko profiler to look it up.
|
||||
void** framePointer = reinterpret_cast<void**>(context.Ebp);
|
||||
void** stackPointer = reinterpret_cast<void**>(context.Esp);
|
||||
|
||||
MOZ_ASSERT(mStackTop, "The thread should be registered by the profiler");
|
||||
|
||||
// Double check that the values we pulled for the thread make sense before
|
||||
// walking the stack.
|
||||
if (mStackTop && framePointer >= stackPointer && framePointer < mStackTop) {
|
||||
// NOTE: In bug 1346415 this was changed to use FramePointerStackWalk.
|
||||
// This was done because lowering the background hang timer threshold
|
||||
// would cause it to fire on infra early during the boot process, causing
|
||||
// a deadlock in MozStackWalk when the target thread was holding the
|
||||
// windows-internal lock on the function table, as it would be suspended
|
||||
// before we tried to grab the lock to walk its stack.
|
||||
//
|
||||
// FramePointerStackWalk is implemented entirely in userspace and thus
|
||||
// doesn't have the same issues with deadlocking. Unfortunately as 64-bit
|
||||
// windows is not guaranteed to have frame pointers, the stack walking
|
||||
// code is only enabled on 32-bit windows builds (bug 1357829).
|
||||
FramePointerStackWalk(callback, /* skipFrames */ 0,
|
||||
/* maxFrames */ Telemetry::HangStack::sMaxNativeFrames,
|
||||
reinterpret_cast<void*>(aNativeStack), framePointer,
|
||||
mStackTop);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_TRUE(::ResumeThread(mThreadID) != DWORD(-1));
|
||||
|
||||
#elif defined(XP_MACOSX)
|
||||
# if defined(MOZ_VALGRIND) && defined(RUNNING_ON_VALGRIND)
|
||||
if (RUNNING_ON_VALGRIND) {
|
||||
/* thread_suspend and thread_resume sometimes hang runs on Valgrind,
|
||||
for unknown reasons. So, just avoid them. See bug 1100911. */
|
||||
return;
|
||||
}
|
||||
# endif
|
||||
|
||||
if (aStack) {
|
||||
if (::thread_suspend(mThreadID) != KERN_SUCCESS) {
|
||||
MOZ_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
|
||||
FillStackBuffer();
|
||||
|
||||
MOZ_ALWAYS_TRUE(::thread_resume(mThreadID) == KERN_SUCCESS);
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
ThreadStackHelper::GetNativeStack(NativeStack& aNativeStack)
|
||||
{
|
||||
#ifdef MOZ_THREADSTACKHELPER_NATIVE
|
||||
GetStacksInternal(nullptr, &aNativeStack);
|
||||
#endif // MOZ_THREADSTACKHELPER_NATIVE
|
||||
}
|
||||
|
||||
void
|
||||
@@ -310,22 +108,47 @@ ThreadStackHelper::GetPseudoAndNativeStack(Stack& aStack, NativeStack& aNativeSt
|
||||
GetStacksInternal(&aStack, &aNativeStack);
|
||||
}
|
||||
|
||||
#ifdef XP_LINUX
|
||||
|
||||
int ThreadStackHelper::sInitialized;
|
||||
int ThreadStackHelper::sFillStackSignum;
|
||||
|
||||
void
|
||||
ThreadStackHelper::FillStackHandler(int aSignal, siginfo_t* aInfo,
|
||||
void* aContext)
|
||||
ThreadStackHelper::GetStacksInternal(Stack* aStack, NativeStack* aNativeStack)
|
||||
{
|
||||
ThreadStackHelper* const helper =
|
||||
reinterpret_cast<ThreadStackHelper*>(aInfo->si_value.sival_ptr);
|
||||
helper->FillStackBuffer();
|
||||
::sem_post(&helper->mSem);
|
||||
}
|
||||
#if defined(MOZ_THREADSTACKHELPER_PSEUDO) || defined(MOZ_THREADSTACKHELPER_NATIVE)
|
||||
// Always run PrepareStackBuffer first to clear aStack
|
||||
if (aStack && !PrepareStackBuffer(*aStack)) {
|
||||
// Skip and return empty aStack
|
||||
return;
|
||||
}
|
||||
|
||||
#endif // XP_LINUX
|
||||
// Prepare the native stack
|
||||
if (aNativeStack) {
|
||||
aNativeStack->clear();
|
||||
aNativeStack->reserve(Telemetry::HangStack::sMaxNativeFrames);
|
||||
}
|
||||
|
||||
#ifdef MOZ_THREADSTACKHELPER_PSEUDO
|
||||
ScopedSetPtr<Stack> stackPtr(mStackToFill, aStack);
|
||||
#endif
|
||||
#ifdef MOZ_THREADSTACKHELPER_NATIVE
|
||||
ScopedSetPtr<NativeStack> nativeStackPtr(mNativeStackToFill, aNativeStack);
|
||||
#endif
|
||||
|
||||
auto callback = [&, this] (void** aPCs, size_t aCount) {
|
||||
FillStackBuffer();
|
||||
|
||||
#ifdef MOZ_THREADSTACKHELPER_NATIVE
|
||||
if (mNativeStackToFill) {
|
||||
while (aCount-- &&
|
||||
mNativeStackToFill->size() < mNativeStackToFill->capacity()) {
|
||||
mNativeStackToFill->push_back(reinterpret_cast<uintptr_t>(aPCs[aCount]));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
profiler_suspend_and_sample_thread(mThreadId,
|
||||
callback,
|
||||
/* aSampleNative = */ !!aNativeStack);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool
|
||||
ThreadStackHelper::PrepareStackBuffer(Stack& aStack)
|
||||
@@ -484,9 +307,9 @@ ThreadStackHelper::AppendJSEntry(const volatile js::ProfileEntry* aEntry,
|
||||
void
|
||||
ThreadStackHelper::FillStackBuffer()
|
||||
{
|
||||
#ifdef MOZ_THREADSTACKHELPER_PSEUDO
|
||||
MOZ_ASSERT(mStackToFill->empty());
|
||||
|
||||
#ifdef MOZ_THREADSTACKHELPER_PSEUDO
|
||||
size_t reservedSize = mStackToFill->capacity();
|
||||
size_t reservedBufferSize = mStackToFill->AvailableBufferSize();
|
||||
intptr_t availableBufferSize = intptr_t(reservedBufferSize);
|
||||
|
||||
Reference in New Issue
Block a user