Bug 545053: Implement IPC hang detection for windows. r=jimm

This commit is contained in:
Ben Turner 2010-02-11 12:19:21 -08:00
parent 1ce25062ef
commit 7f56845cb9
2 changed files with 104 additions and 10 deletions

View File

@ -40,10 +40,6 @@
#ifndef ipc_glue_SyncChannel_h
#define ipc_glue_SyncChannel_h 1
#include "base/basictypes.h"
#include "prinrval.h"
#include "mozilla/ipc/AsyncChannel.h"
namespace mozilla {
@ -155,10 +151,10 @@ protected:
static bool sIsPumpingMessages;
int32 mTimeoutMs;
private:
bool EventOccurred();
int32 mTimeoutMs;
};

View File

@ -517,6 +517,45 @@ Init()
"Running on different threads!");
}
// This timeout stuff assumes a sane value of mTimeoutMs (less than the overflow
// value for GetTickCount(), which is something like 50 days). It uses the
// cheapest (and least accurate) method supported by Windows 2000.
struct TimeoutData
{
DWORD startTicks;
DWORD targetTicks;
};
void
InitTimeoutData(TimeoutData* aData,
int32 aTimeoutMs)
{
aData->startTicks = GetTickCount();
if (!aData->startTicks) {
// How unlikely is this!
aData->startTicks++;
}
aData->targetTicks = aData->startTicks + aTimeoutMs;
}
bool
TimeoutHasExpired(const TimeoutData& aData)
{
if (!aData.startTicks) {
return false;
}
DWORD now = GetTickCount();
if (aData.targetTicks < aData.startTicks) {
// Overflow
return now < aData.startTicks && now >= aData.targetTicks;
}
return now >= aData.targetTicks;
}
} // anonymous namespace
bool
@ -609,12 +648,26 @@ SyncChannel::WaitForNotify()
MutexAutoUnlock unlock(mMutex);
bool retval = true;
if (++gEventLoopDepth == 1) {
NS_ASSERTION(!gNeuteredWindows, "Should only set this once!");
gNeuteredWindows = new nsAutoTArray<HWND, 20>();
NS_ASSERTION(gNeuteredWindows, "Out of memory!");
}
UINT_PTR timerId = NULL;
TimeoutData timeoutData = { 0 };
if (mTimeoutMs != kNoTimeout) {
InitTimeoutData(&timeoutData, mTimeoutMs);
// We only do this to ensure that we won't get stuck in
// MsgWaitForMultipleObjects below.
timerId = SetTimer(NULL, 0, mTimeoutMs, NULL);
NS_ASSERTION(timerId, "SetTimer failed!");
}
// Setup deferred processing of native events while we wait for a response.
NS_ASSERTION(!SyncChannel::IsPumpingMessages(),
"Shouldn't be pumping already!");
@ -630,7 +683,7 @@ SyncChannel::WaitForNotify()
{
MutexAutoLock lock(mMutex);
if (!Connected()) {
return true;
break;
}
}
@ -647,6 +700,12 @@ SyncChannel::WaitForNotify()
break;
}
if (TimeoutHasExpired(timeoutData)) {
// A timeout was specified and we've passed it. Break out.
retval = false;
break;
}
// The only way to know on which thread the message was delivered is to
// use some logic on the return values of GetQueueStatus and PeekMessage.
// PeekMessage will return false if there are no "queued" messages, but it
@ -702,9 +761,13 @@ SyncChannel::WaitForNotify()
// a "nonqueued" message.
ScheduleDeferredMessageRun();
if (timerId) {
KillTimer(NULL, timerId);
}
SyncChannel::SetIsPumpingMessages(false);
return true;
return retval;
}
bool
@ -712,11 +775,18 @@ RPCChannel::WaitForNotify()
{
mMutex.AssertCurrentThreadOwns();
if (!StackDepth() && !mBlockedOnParent) {
// There is currently no way to recover from this condition.
NS_RUNTIMEABORT("StackDepth() is 0 in call to RPCChannel::WaitForNotify!");
}
// Initialize global objects used in deferred messaging.
Init();
MutexAutoUnlock unlock(mMutex);
bool retval = true;
// IsSpinLoopActive indicates modal UI is being displayed in a plugin. Drop
// down into the spin loop until all modal loops end. If SpinInternalEventLoop
// returns true, the out-call response we were waiting on arrived, or we
@ -737,6 +807,18 @@ RPCChannel::WaitForNotify()
NS_ASSERTION(gNeuteredWindows, "Out of memory!");
}
UINT_PTR timerId = NULL;
TimeoutData timeoutData = { 0 };
if (mTimeoutMs != kNoTimeout) {
InitTimeoutData(&timeoutData, mTimeoutMs);
// We only do this to ensure that we won't get stuck in
// MsgWaitForMultipleObjects below.
timerId = SetTimer(NULL, 0, mTimeoutMs, NULL);
NS_ASSERTION(timerId, "SetTimer failed!");
}
// Setup deferred processing of native events while we wait for a response.
NS_ASSERTION(!SyncChannel::IsPumpingMessages(),
"Shouldn't be pumping already!");
@ -752,8 +834,9 @@ RPCChannel::WaitForNotify()
// Don't get wrapped up in here if the child connection dies.
{
MutexAutoLock lock(mMutex);
if (!Connected())
if (!Connected()) {
break;
}
}
DWORD result = MsgWaitForMultipleObjects(0, NULL, FALSE, INFINITE,
@ -763,6 +846,12 @@ RPCChannel::WaitForNotify()
break;
}
if (TimeoutHasExpired(timeoutData)) {
// A timeout was specified and we've passed it. Break out.
retval = false;
break;
}
// See SyncChannel's WaitForNotify for details.
bool haveSentMessagesPending =
(HIWORD(GetQueueStatus(QS_SENDMESSAGE)) & QS_SENDMESSAGE) != 0;
@ -779,6 +868,11 @@ RPCChannel::WaitForNotify()
UnhookWindowsHookEx(windowHook);
windowHook = NULL;
if (timerId) {
KillTimer(NULL, timerId);
timerId = NULL;
}
// Used by widget to assert on incoming native events.
SyncChannel::SetIsPumpingMessages(false);
@ -832,9 +926,13 @@ RPCChannel::WaitForNotify()
// a "nonqueued" message.
ScheduleDeferredMessageRun();
if (timerId) {
KillTimer(NULL, timerId);
}
SyncChannel::SetIsPumpingMessages(false);
return true;
return retval;
}
void