From c38a2c6616c37398b29f174b74242f8b36ba8895 Mon Sep 17 00:00:00 2001 From: Ben Turner Date: Thu, 11 Feb 2010 12:19:21 -0800 Subject: [PATCH] Bug 545053: Implement IPC hang detection for windows. r=jimm --- ipc/glue/SyncChannel.h | 8 +-- ipc/glue/WindowsMessageLoop.cpp | 106 ++++++++++++++++++++++++++++++-- 2 files changed, 104 insertions(+), 10 deletions(-) diff --git a/ipc/glue/SyncChannel.h b/ipc/glue/SyncChannel.h index fb7281873862..24d765eba319 100644 --- a/ipc/glue/SyncChannel.h +++ b/ipc/glue/SyncChannel.h @@ -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; }; diff --git a/ipc/glue/WindowsMessageLoop.cpp b/ipc/glue/WindowsMessageLoop.cpp index ddc7de96f6b0..8e7285b72221 100644 --- a/ipc/glue/WindowsMessageLoop.cpp +++ b/ipc/glue/WindowsMessageLoop.cpp @@ -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(); 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