Bug 1185639 - Allow deferred message processing to happen between consecutive IPC message dispatches. r=jimm

This commit is contained in:
Aaron Klotz 2015-07-21 01:21:51 -06:00
parent d0be04cef9
commit f56584d2b0
5 changed files with 168 additions and 82 deletions

View File

@ -853,6 +853,7 @@ MessageChannel::Send(Message* aMsg, Message* aReply)
#ifdef OS_WIN #ifdef OS_WIN
SyncStackFrame frame(this, false); SyncStackFrame frame(this, false);
NeuteredWindowRegion neuteredRgn(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION);
#endif #endif
CxxStackFrame f(*this, OUT_MESSAGE, msg); CxxStackFrame f(*this, OUT_MESSAGE, msg);
@ -994,6 +995,7 @@ MessageChannel::Call(Message* aMsg, Message* aReply)
#ifdef OS_WIN #ifdef OS_WIN
SyncStackFrame frame(this, true); SyncStackFrame frame(this, true);
NeuteredWindowRegion neuteredRgn(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION);
#endif #endif
// This must come before MonitorAutoLock, as its destructor acquires the // This must come before MonitorAutoLock, as its destructor acquires the
@ -1032,6 +1034,12 @@ MessageChannel::Call(Message* aMsg, Message* aReply)
return false; return false;
} }
#ifdef OS_WIN
/* We should pump messages at this point to ensure that the IPC peer
does not become deadlocked on a pending inter-thread SendMessage() */
neuteredRgn.PumpOnce();
#endif
// Now might be the time to process a message deferred because of race // Now might be the time to process a message deferred because of race
// resolution. // resolution.
MaybeUndeferIncall(); MaybeUndeferIncall();
@ -1148,6 +1156,7 @@ MessageChannel::WaitForIncomingMessage()
{ {
#ifdef OS_WIN #ifdef OS_WIN
SyncStackFrame frame(this, true); SyncStackFrame frame(this, true);
NeuteredWindowRegion neuteredRgn(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION);
#endif #endif
{ // Scope for lock { // Scope for lock

View File

@ -15,6 +15,9 @@
#include "mozilla/Monitor.h" #include "mozilla/Monitor.h"
#include "mozilla/Vector.h" #include "mozilla/Vector.h"
#include "mozilla/WeakPtr.h" #include "mozilla/WeakPtr.h"
#if defined(OS_WIN)
#include "mozilla/ipc/Neutering.h"
#endif // defined(OS_WIN)
#include "mozilla/ipc/Transport.h" #include "mozilla/ipc/Transport.h"
#include "MessageLink.h" #include "MessageLink.h"
#include "nsAutoPtr.h" #include "nsAutoPtr.h"

64
ipc/glue/Neutering.h Normal file
View File

@ -0,0 +1,64 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef mozilla_ipc_Neutering_h
#define mozilla_ipc_Neutering_h
#include "mozilla/GuardObjects.h"
/**
* This header declares RAII wrappers for Window neutering. See
* WindowsMessageLoop.cpp for more details.
*/
namespace mozilla {
namespace ipc {
/**
* This class is a RAII wrapper around Window neutering. As long as a
* NeuteredWindowRegion object is instantiated, Win32 windows belonging to the
* current thread will be neutered. It is safe to nest multiple instances of
* this class.
*/
class MOZ_STACK_CLASS NeuteredWindowRegion
{
public:
explicit NeuteredWindowRegion(bool aDoNeuter MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
~NeuteredWindowRegion();
/**
* This function clears any backlog of nonqueued messages that are pending for
* the current thread.
*/
void PumpOnce();
private:
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
bool mNeuteredByThis;
};
/**
* This class is analagous to MutexAutoUnlock for Mutex; it is an RAII class
* that is to be instantiated within a NeuteredWindowRegion, thus temporarily
* disabling neutering for the remainder of its enclosing block.
* @see NeuteredWindowRegion
*/
class MOZ_STACK_CLASS DeneuteredWindowRegion
{
public:
DeneuteredWindowRegion(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
~DeneuteredWindowRegion();
private:
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
bool mReneuter;
};
} // namespace ipc
} // namespace mozilla
#endif // mozilla_ipc_Neutering_h

View File

@ -8,6 +8,7 @@
#include "mozilla/DebugOnly.h" #include "mozilla/DebugOnly.h"
#include "WindowsMessageLoop.h" #include "WindowsMessageLoop.h"
#include "Neutering.h"
#include "MessageChannel.h" #include "MessageChannel.h"
#include "nsAutoPtr.h" #include "nsAutoPtr.h"
@ -862,6 +863,81 @@ IsTimeoutExpired(PRIntervalTime aStart, PRIntervalTime aTimeout)
(aTimeout <= (PR_IntervalNow() - aStart)); (aTimeout <= (PR_IntervalNow() - aStart));
} }
static HHOOK gWindowHook;
static inline void
StartNeutering()
{
MOZ_ASSERT(gUIThreadId);
MOZ_ASSERT(!gWindowHook);
NS_ASSERTION(!MessageChannel::IsPumpingMessages(),
"Shouldn't be pumping already!");
MessageChannel::SetIsPumpingMessages(true);
gWindowHook = ::SetWindowsHookEx(WH_CALLWNDPROC, CallWindowProcedureHook,
nullptr, gUIThreadId);
NS_ASSERTION(gWindowHook, "Failed to set hook!");
}
static void
StopNeutering()
{
MOZ_ASSERT(MessageChannel::IsPumpingMessages());
::UnhookWindowsHookEx(gWindowHook);
gWindowHook = NULL;
::UnhookNeuteredWindows();
// Before returning we need to set a hook to run any deferred messages that
// we received during the IPC call. The hook will unset itself as soon as
// someone else calls GetMessage, PeekMessage, or runs code that generates
// a "nonqueued" message.
::ScheduleDeferredMessageRun();
MessageChannel::SetIsPumpingMessages(false);
}
NeuteredWindowRegion::NeuteredWindowRegion(bool aDoNeuter MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
: mNeuteredByThis(!gWindowHook)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
if (aDoNeuter && mNeuteredByThis) {
StartNeutering();
}
}
NeuteredWindowRegion::~NeuteredWindowRegion()
{
if (gWindowHook && mNeuteredByThis) {
StopNeutering();
}
}
void
NeuteredWindowRegion::PumpOnce()
{
MSG msg = {0};
// Pump any COM messages so that we don't hang due to STA marshaling.
if (gCOMWindow && ::PeekMessageW(&msg, gCOMWindow, 0, 0, PM_REMOVE)) {
::TranslateMessage(&msg);
::DispatchMessageW(&msg);
}
// Expunge any nonqueued messages on the current thread.
::PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE);
}
DeneuteredWindowRegion::DeneuteredWindowRegion(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
: mReneuter(gWindowHook != NULL)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
if (mReneuter) {
StopNeutering();
}
}
DeneuteredWindowRegion::~DeneuteredWindowRegion()
{
if (mReneuter) {
StartNeutering();
}
}
bool bool
MessageChannel::WaitForSyncNotify() MessageChannel::WaitForSyncNotify()
{ {
@ -916,15 +992,6 @@ MessageChannel::WaitForSyncNotify()
NS_ASSERTION(timerId, "SetTimer failed!"); NS_ASSERTION(timerId, "SetTimer failed!");
} }
// Setup deferred processing of native events while we wait for a response.
NS_ASSERTION(!MessageChannel::IsPumpingMessages(),
"Shouldn't be pumping already!");
MessageChannel::SetIsPumpingMessages(true);
HHOOK windowHook = SetWindowsHookEx(WH_CALLWNDPROC, CallWindowProcedureHook,
nullptr, gUIThreadId);
NS_ASSERTION(windowHook, "Failed to set hook!");
{ {
while (1) { while (1) {
MSG msg = { 0 }; MSG msg = { 0 };
@ -998,25 +1065,11 @@ MessageChannel::WaitForSyncNotify()
} }
} }
// Unhook the neutered window procedure hook.
UnhookWindowsHookEx(windowHook);
// Unhook any neutered windows procedures so messages can be delivered
// normally.
UnhookNeuteredWindows();
// Before returning we need to set a hook to run any deferred messages that
// we received during the IPC call. The hook will unset itself as soon as
// someone else calls GetMessage, PeekMessage, or runs code that generates
// a "nonqueued" message.
ScheduleDeferredMessageRun();
if (timerId) { if (timerId) {
KillTimer(nullptr, timerId); KillTimer(nullptr, timerId);
timerId = 0;
} }
MessageChannel::SetIsPumpingMessages(false);
return WaitResponse(timedout); return WaitResponse(timedout);
} }
@ -1050,57 +1103,29 @@ MessageChannel::WaitForInterruptNotify()
UINT_PTR timerId = 0; UINT_PTR timerId = 0;
TimeoutData timeoutData = { 0 }; TimeoutData timeoutData = { 0 };
// windowHook is used as a flag variable for the loop below: if it is set // gWindowHook is used as a flag variable for the loop below: if it is set
// and we start to spin a nested event loop, we need to clear the hook and // and we start to spin a nested event loop, we need to clear the hook and
// process deferred/pending messages. // process deferred/pending messages.
// If windowHook is nullptr, MessageChannel::IsPumpingMessages should be false.
HHOOK windowHook = nullptr;
while (1) { while (1) {
NS_ASSERTION((!!windowHook) == MessageChannel::IsPumpingMessages(), NS_ASSERTION((!!gWindowHook) == MessageChannel::IsPumpingMessages(),
"windowHook out of sync with reality"); "gWindowHook out of sync with reality");
if (mTopFrame->mSpinNestedEvents) { if (mTopFrame->mSpinNestedEvents) {
if (windowHook) { if (gWindowHook && timerId) {
UnhookWindowsHookEx(windowHook);
windowHook = nullptr;
if (timerId) {
KillTimer(nullptr, timerId); KillTimer(nullptr, timerId);
timerId = 0; timerId = 0;
} }
DeneuteredWindowRegion deneuteredRgn;
// Used by widget to assert on incoming native events
MessageChannel::SetIsPumpingMessages(false);
// Unhook any neutered windows procedures so messages can be delievered
// normally.
UnhookNeuteredWindows();
// Send all deferred "nonqueued" message to the intended receiver.
// We're dropping into SpinInternalEventLoop so we should be fairly
// certain these will get delivered soohn.
ScheduleDeferredMessageRun();
}
SpinInternalEventLoop(); SpinInternalEventLoop();
ResetEvent(mEvent); ResetEvent(mEvent);
return true; return true;
} }
if (!windowHook) { if (mTimeoutMs != kNoTimeout && !timerId) {
MessageChannel::SetIsPumpingMessages(true);
windowHook = SetWindowsHookEx(WH_CALLWNDPROC, CallWindowProcedureHook,
nullptr, gUIThreadId);
NS_ASSERTION(windowHook, "Failed to set hook!");
NS_ASSERTION(!timerId, "Timer already initialized?");
if (mTimeoutMs != kNoTimeout) {
InitTimeoutData(&timeoutData, mTimeoutMs); InitTimeoutData(&timeoutData, mTimeoutMs);
timerId = SetTimer(nullptr, 0, mTimeoutMs, nullptr); timerId = SetTimer(nullptr, 0, mTimeoutMs, nullptr);
NS_ASSERTION(timerId, "SetTimer failed!"); NS_ASSERTION(timerId, "SetTimer failed!");
} }
}
MSG msg = { 0 }; MSG msg = { 0 };
@ -1151,26 +1176,10 @@ MessageChannel::WaitForInterruptNotify()
} }
} }
if (windowHook) {
// Unhook the neutered window procedure hook.
UnhookWindowsHookEx(windowHook);
// Unhook any neutered windows procedures so messages can be delivered
// normally.
UnhookNeuteredWindows();
// Before returning we need to set a hook to run any deferred messages that
// we received during the IPC call. The hook will unset itself as soon as
// someone else calls GetMessage, PeekMessage, or runs code that generates
// a "nonqueued" message.
ScheduleDeferredMessageRun();
if (timerId) { if (timerId) {
KillTimer(nullptr, timerId); KillTimer(nullptr, timerId);
timerId = 0;
} }
}
MessageChannel::SetIsPumpingMessages(false);
return WaitResponse(timedout); return WaitResponse(timedout);
} }

View File

@ -25,6 +25,7 @@ EXPORTS.mozilla.ipc += [
'IOThreadChild.h', 'IOThreadChild.h',
'MessageChannel.h', 'MessageChannel.h',
'MessageLink.h', 'MessageLink.h',
'Neutering.h',
'ProcessChild.h', 'ProcessChild.h',
'ProtocolUtils.h', 'ProtocolUtils.h',
'ScopedXREEmbed.h', 'ScopedXREEmbed.h',