diff options
Diffstat (limited to 'base/message_pump_win.cc')
-rw-r--r-- | base/message_pump_win.cc | 523 |
1 files changed, 523 insertions, 0 deletions
diff --git a/base/message_pump_win.cc b/base/message_pump_win.cc new file mode 100644 index 0000000..b1809be --- /dev/null +++ b/base/message_pump_win.cc @@ -0,0 +1,523 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "base/message_pump_win.h" + +#include "base/histogram.h" +#include "base/win_util.h" + +namespace base { + +static const wchar_t kWndClass[] = L"Chrome_MessagePumpWindow"; + +// Message sent to get an additional time slice for pumping (processing) another +// task (a series of such messages creates a continuous task pump). +static const int kMsgHaveWork = WM_USER + 1; + +#ifndef NDEBUG +// Force exercise of polling model. +static const int kMaxWaitObjects = 8; +#else +static const int kMaxWaitObjects = MAXIMUM_WAIT_OBJECTS; +#endif + +//----------------------------------------------------------------------------- +// MessagePumpWin public: + +MessagePumpWin::MessagePumpWin() : have_work_(0), state_(NULL) { + InitMessageWnd(); +} + +MessagePumpWin::~MessagePumpWin() { + DestroyWindow(message_hwnd_); +} + +void MessagePumpWin::WatchObject(HANDLE object, Watcher* watcher) { + DCHECK(object); + DCHECK_NE(object, INVALID_HANDLE_VALUE); + + std::vector<HANDLE>::iterator it = + find(objects_.begin(), objects_.end(), object); + if (watcher) { + if (it == objects_.end()) { + static size_t warning_multiple = 1; + if (objects_.size() >= warning_multiple * MAXIMUM_WAIT_OBJECTS / 2) { + LOG(INFO) << "More than " << warning_multiple * MAXIMUM_WAIT_OBJECTS / 2 + << " objects being watched"; + // This DCHECK() is an artificial limitation, meant to warn us if we + // start creating too many objects. It can safely be raised to a higher + // level, and the program is designed to handle much larger values. + // Before raising this limit, make sure that there is a very good reason + // (in your debug testing) to be watching this many objects. + DCHECK(2 <= warning_multiple); + ++warning_multiple; + } + objects_.push_back(object); + watchers_.push_back(watcher); + } else { + watchers_[it - objects_.begin()] = watcher; + } + } else if (it != objects_.end()) { + std::vector<HANDLE>::difference_type index = it - objects_.begin(); + objects_.erase(it); + watchers_.erase(watchers_.begin() + index); + } +} + +void MessagePumpWin::AddObserver(Observer* observer) { + observers_.AddObserver(observer); +} + +void MessagePumpWin::RemoveObserver(Observer* observer) { + observers_.RemoveObserver(observer); +} + +void MessagePumpWin::WillProcessMessage(const MSG& msg) { + FOR_EACH_OBSERVER(Observer, observers_, WillProcessMessage(msg)); +} + +void MessagePumpWin::DidProcessMessage(const MSG& msg) { + FOR_EACH_OBSERVER(Observer, observers_, DidProcessMessage(msg)); +} + +void MessagePumpWin::PumpOutPendingPaintMessages() { + // Create a mini-message-pump to force immediate processing of only Windows + // WM_PAINT messages. Don't provide an infinite loop, but do enough peeking + // to get the job done. Actual common max is 4 peeks, but we'll be a little + // safe here. + const int kMaxPeekCount = 20; + bool win2k = win_util::GetWinVersion() <= win_util::WINVERSION_2000; + int peek_count; + for (peek_count = 0; peek_count < kMaxPeekCount; ++peek_count) { + MSG msg; + if (win2k) { + if (!PeekMessage(&msg, NULL, WM_PAINT, WM_PAINT, PM_REMOVE)) + break; + } else { + if (!PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_QS_PAINT)) + break; + } + ProcessMessageHelper(msg); + if (state_->should_quit) // Handle WM_QUIT. + break; + } + // Histogram what was really being used, to help to adjust kMaxPeekCount. + DHISTOGRAM_COUNTS(L"Loop.PumpOutPendingPaintMessages Peeks", peek_count); +} + +void MessagePumpWin::RunWithDispatcher( + Delegate* delegate, Dispatcher* dispatcher) { + RunState s; + s.delegate = delegate; + s.dispatcher = dispatcher; + s.should_quit = false; + s.run_depth = state_ ? state_->run_depth + 1 : 1; + + RunState* previous_state = state_; + state_ = &s; + + DoRunLoop(); + + state_ = previous_state; +} + +void MessagePumpWin::Quit() { + DCHECK(state_); + state_->should_quit = true; +} + +void MessagePumpWin::ScheduleWork() { + if (InterlockedExchange(&have_work_, 1)) + return; // Someone else continued the pumping. + + // Make sure the MessagePump does some work for us. + PostMessage(message_hwnd_, kMsgHaveWork, reinterpret_cast<WPARAM>(this), 0); +} + +void MessagePumpWin::ScheduleDelayedWork(const TimeDelta& delay) { + // + // We would *like* to provide high resolution timers. Windows timers using + // SetTimer() have a 10ms granularity. We have to use WM_TIMER as a wakeup + // mechanism because the application can enter modal windows loops where it + // is not running our MessageLoop; the only way to have our timers fire in + // these cases is to post messages there. + // + // To provide sub-10ms timers, we process timers directly from our run loop. + // For the common case, timers will be processed there as the run loop does + // its normal work. However, we *also* set the system timer so that WM_TIMER + // events fire. This mops up the case of timers not being able to work in + // modal message loops. It is possible for the SetTimer to pop and have no + // pending timers, because they could have already been processed by the + // run loop itself. + // + // We use a single SetTimer corresponding to the timer that will expire + // soonest. As new timers are created and destroyed, we update SetTimer. + // Getting a spurrious SetTimer event firing is benign, as we'll just be + // processing an empty timer queue. + // + int delay_msec = static_cast<int>(delay.InMilliseconds()); + if (delay_msec < USER_TIMER_MINIMUM) + delay_msec = USER_TIMER_MINIMUM; + + // Create a WM_TIMER event that will wake us up to check for any pending + // timers (in case we are running within a nested, external sub-pump). + SetTimer(message_hwnd_, reinterpret_cast<UINT_PTR>(this), delay_msec, NULL); +} + +//----------------------------------------------------------------------------- +// MessagePumpWin private: + +// static +LRESULT CALLBACK MessagePumpWin::WndProcThunk( + HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { + switch (message) { + case kMsgHaveWork: + reinterpret_cast<MessagePumpWin*>(wparam)->HandleWorkMessage(); + break; + case WM_TIMER: + reinterpret_cast<MessagePumpWin*>(wparam)->HandleTimerMessage(); + break; + } + return DefWindowProc(hwnd, message, wparam, lparam); +} + +void MessagePumpWin::InitMessageWnd() { + HINSTANCE hinst = GetModuleHandle(NULL); + + WNDCLASSEX wc = {0}; + wc.cbSize = sizeof(wc); + wc.lpfnWndProc = WndProcThunk; + wc.hInstance = hinst; + wc.lpszClassName = kWndClass; + RegisterClassEx(&wc); + + message_hwnd_ = + CreateWindow(kWndClass, 0, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, hinst, 0); + DCHECK(message_hwnd_); +} + +void MessagePumpWin::HandleWorkMessage() { + // If we are being called outside of the context of Run, then don't do + // anything. This could correspond to a MessageBox call or something of + // that sort. + if (!state_) + return; + + // Let whatever would have run had we not been putting messages in the queue + // run now. This is an attempt to make our dummy message not starve other + // messages that may be in the Windows message queue. + ProcessPumpReplacementMessage(); + + // Now give the delegate a chance to do some work. He'll let us know if he + // needs to do more work. + state_->delegate->DoWork(); +} + +void MessagePumpWin::HandleTimerMessage() { + KillTimer(message_hwnd_, reinterpret_cast<UINT_PTR>(this)); + + state_->delegate->DoDelayedWork(); +} + +void MessagePumpWin::DoRunLoop() { + // IF this was just a simple PeekMessage() loop (servicing all passible work + // queues), then Windows would try to achieve the following order according + // to MSDN documentation about PeekMessage with no filter): + // * Sent messages + // * Posted messages + // * Sent messages (again) + // * WM_PAINT messages + // * WM_TIMER messages + // + // Summary: none of the above classes is starved, and sent messages has twice + // the chance of being processed (i.e., reduced service time). + + for (;;) { + // If we do any work, we may create more messages etc., and more work may + // possibly be waiting in another task group. When we (for example) + // ProcessNextWindowsMessage(), there is a good chance there are still more + // messages waiting (same thing for ProcessNextObject(), which responds to + // only one signaled object; etc.). On the other hand, when any of these + // methods return having done no work, then it is pretty unlikely that + // calling them again quickly will find any work to do. Finally, if they + // all say they had no work, then it is a good time to consider sleeping + // (waiting) for more work. + + bool more_work_is_plausible = ProcessNextWindowsMessage(); + if (state_->should_quit) + break; + + more_work_is_plausible |= state_->delegate->DoWork(); + if (state_->should_quit) + break; + + more_work_is_plausible |= ProcessNextObject(); + if (state_->should_quit) + break; + + if (more_work_is_plausible) + continue; + + more_work_is_plausible = state_->delegate->DoDelayedWork(); + if (state_->should_quit) + break; + + if (more_work_is_plausible) + continue; + + more_work_is_plausible = state_->delegate->DoIdleWork(); + if (state_->should_quit) + break; + + if (more_work_is_plausible) + continue; + + // We service APCs in WaitForWork, without returning. + WaitForWork(); // Wait (sleep) until we have work to do again. + } +} + +// If we handle more than the OS limit on the number of objects that can be +// waited for, we'll need to poll (sequencing through subsets of the objects +// that can be passed in a single OS wait call). The following is the polling +// interval used in that (unusual) case. (I don't have a lot of justifcation +// for the specific value, but it needed to be short enough that it would not +// add a lot of latency, and long enough that we wouldn't thrash the CPU for no +// reason... especially considering the silly user probably has a million tabs +// open, etc.) +static const int kMultipleWaitPollingInterval = 20; + +void MessagePumpWin::WaitForWork() { + // Wait until either an object is signaled or a message is available. Handle + // (without returning) any APCs (only the IO thread currently has APCs.) + + // We do not support nested message loops when we have watched objects. This + // is to avoid messy recursion problems. + DCHECK(objects_.empty() || state_->run_depth == 1) << + "Cannot nest a message loop when there are watched objects!"; + + int wait_flags = MWMO_ALERTABLE | MWMO_INPUTAVAILABLE; + + bool use_polling = false; // Poll if too many objects for one OS Wait call. + for (;;) { + // Do initialization here, in case APC modifies object list. + size_t total_objs = objects_.size(); + + int delay; + size_t polling_index = 0; // The first unprocessed object index. + do { + size_t objs_len = + (polling_index < total_objs) ? total_objs - polling_index : 0; + if (objs_len >= MAXIMUM_WAIT_OBJECTS) { + objs_len = MAXIMUM_WAIT_OBJECTS - 1; + use_polling = true; + } + HANDLE* objs = objs_len ? polling_index + &objects_.front() : NULL; + + // Only wait up to the time needed by the timer manager to fire the next + // set of timers. + delay = GetCurrentDelay(); + if (use_polling && delay > kMultipleWaitPollingInterval) + delay = kMultipleWaitPollingInterval; + if (delay < 0) // Negative value means no timers waiting. + delay = INFINITE; + + DWORD result; + result = MsgWaitForMultipleObjectsEx(static_cast<DWORD>(objs_len), objs, + delay, QS_ALLINPUT, wait_flags); + + if (WAIT_IO_COMPLETION == result) { + // We'll loop here when we service an APC. At it currently stands, + // *ONLY* the IO thread uses *any* APCs, so this should have no impact + // on the UI thread. + break; // Break to outer loop, and waitforwork() again. + } + + // Use unsigned type to simplify range detection; + size_t signaled_index = result - WAIT_OBJECT_0; + if (signaled_index < objs_len) { + SignalWatcher(polling_index + signaled_index); + return; // We serviced a signaled object. + } + + if (objs_len == signaled_index) + return; // A WM_* message is available. + + DCHECK_NE(WAIT_FAILED, result) << GetLastError(); + + DCHECK(!objs || result == WAIT_TIMEOUT); + if (!use_polling) + return; + polling_index += objs_len; + } while (polling_index < total_objs); + // For compatibility, we didn't return sooner. This made us do *some* wait + // call(s) before returning. This will probably change in next rev. + if (!delay || !GetCurrentDelay()) + return; // No work done, but timer is ready to fire. + } +} + +bool MessagePumpWin::ProcessNextWindowsMessage() { + MSG msg; + if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + return ProcessMessageHelper(msg); + return false; +} + +bool MessagePumpWin::ProcessMessageHelper(const MSG& msg) { + if (WM_QUIT == msg.message) { + // Repost the QUIT message so that it will be retrieved by the primary + // GetMessage() loop. + state_->should_quit = true; + PostQuitMessage(static_cast<int>(msg.wParam)); + return false; + } + + // While running our main message pump, we discard kMsgHaveWork messages. + if (msg.message == kMsgHaveWork && msg.hwnd == message_hwnd_) + return ProcessPumpReplacementMessage(); + + WillProcessMessage(msg); + + if (state_->dispatcher) { + if (!state_->dispatcher->Dispatch(msg)) + state_->should_quit = true; + } else { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + DidProcessMessage(msg); + return true; +} + +bool MessagePumpWin::ProcessPumpReplacementMessage() { + // When we encounter a kMsgHaveWork message, this method is called to peek + // and process a replacement message, such as a WM_PAINT or WM_TIMER. The + // goal is to make the kMsgHaveWork as non-intrusive as possible, even though + // a continuous stream of such messages are posted. This method carefully + // peeks a message while there is no chance for a kMsgHaveWork to be pending, + // then resets the have_work_ flag (allowing a replacement kMsgHaveWork to + // possibly be posted), and finally dispatches that peeked replacement. Note + // that the re-post of kMsgHaveWork may be asynchronous to this thread!! + + MSG msg; + bool have_message = (0 != PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)); + DCHECK(!have_message || kMsgHaveWork != msg.message || + msg.hwnd != message_hwnd_); + +#if 1 + + // Since we discarded a kMsgHaveWork message, we must update the flag. + InterlockedExchange(&have_work_, 0); + + // TODO(darin,jar): There is risk of being lost in a sub-pump within the call + // to ProcessMessageHelper, which could result in no longer getting a + // kMsgHaveWork message until the next out-of-band call to ScheduleWork. + + return have_message && ProcessMessageHelper(msg); + +#else + + PostMessage(message_hwnd_, kMsgHaveWork, reinterpret_cast<WPARAM>(this), 0); + + bool result = have_message && ProcessMessageHelper(msg); + + PeekMessage(&msg, message_hwnd_, kMsgHaveWork, kMsgHaveWork, PM_REMOVE); + InterlockedExchange(&have_work_, 0); + // We know that are going to call DoWork next! + + return result; + +#endif +} + +// Note: MsgWaitMultipleObjects() can't take a nil list, and that is why I had +// to use SleepEx() to handle APCs when there were no objects. +bool MessagePumpWin::ProcessNextObject() { + size_t total_objs = objects_.size(); + if (!total_objs) { + return false; + } + + size_t polling_index = 0; // The first unprocessed object index. + do { + DCHECK(polling_index < total_objs); + size_t objs_len = total_objs - polling_index; + if (objs_len >= kMaxWaitObjects) + objs_len = kMaxWaitObjects - 1; + HANDLE* objs = polling_index + &objects_.front(); + + // Identify 1 pending object, or allow an IO APC to be completed. + DWORD result = WaitForMultipleObjectsEx(static_cast<DWORD>(objs_len), objs, + FALSE, // 1 signal is sufficient. + 0, // Wait 0ms. + false); // Not alertable (no APC). + + // Use unsigned type to simplify range detection; + size_t signaled_index = result - WAIT_OBJECT_0; + if (signaled_index < objs_len) { + SignalWatcher(polling_index + signaled_index); + return true; // We serviced a signaled object. + } + + // If an handle is invalid, it will be WAIT_FAILED. + DCHECK_EQ(WAIT_TIMEOUT, result) << GetLastError(); + polling_index += objs_len; + } while (polling_index < total_objs); + return false; // We serviced nothing. +} + +bool MessagePumpWin::SignalWatcher(size_t object_index) { + // Signal the watcher corresponding to the given index. + + DCHECK(objects_.size() > object_index); + + // On reception of OnObjectSignaled() to a Watcher object, it may call + // WatchObject(). watchers_ and objects_ will be modified. This is expected, + // so don't be afraid if, while tracing a OnObjectSignaled() function, the + // corresponding watchers_[result] is non-existant. + watchers_[object_index]->OnObjectSignaled(objects_[object_index]); + + // Signaled objects tend to be removed from the watch list, and then added + // back (appended). As a result, they move to the end of the objects_ array, + // and this should make their service "fair" (no HANDLEs should be starved). + + return true; +} + +int MessagePumpWin::GetCurrentDelay() const { + if (delayed_work_time_.is_null()) + return -1; + + // This could be a negative value, but that's OK. + return static_cast<int>((Time::Now() - delayed_work_time_).InMilliseconds()); +} + +} // namespace base |