summaryrefslogtreecommitdiffstats
path: root/base/message_pump_win.cc
diff options
context:
space:
mode:
Diffstat (limited to 'base/message_pump_win.cc')
-rw-r--r--base/message_pump_win.cc666
1 files changed, 259 insertions, 407 deletions
diff --git a/base/message_pump_win.cc b/base/message_pump_win.cc
index 542fcf1..7ada5eb7 100644
--- a/base/message_pump_win.cc
+++ b/base/message_pump_win.cc
@@ -11,41 +11,6 @@
using base::Time;
-namespace {
-
-class HandlerData : public base::MessagePumpForIO::Watcher {
- public:
- typedef base::MessagePumpForIO::IOHandler IOHandler;
- HandlerData(OVERLAPPED* context, IOHandler* handler)
- : context_(context), handler_(handler) {}
- ~HandlerData() {}
-
- virtual void OnObjectSignaled(HANDLE object);
-
- private:
- OVERLAPPED* context_;
- IOHandler* handler_;
-
- DISALLOW_COPY_AND_ASSIGN(HandlerData);
-};
-
-void HandlerData::OnObjectSignaled(HANDLE object) {
- DCHECK(object == context_->hEvent);
- DWORD transfered;
- DWORD error = ERROR_SUCCESS;
- BOOL ret = GetOverlappedResult(NULL, context_, &transfered, FALSE);
- if (!ret) {
- error = GetLastError();
- DCHECK(ERROR_HANDLE_EOF == error || ERROR_BROKEN_PIPE == error);
- transfered = 0;
- }
-
- ResetEvent(context_->hEvent);
- handler_->OnIOCompleted(context_, transfered, error);
-}
-
-} // namespace
-
namespace base {
static const wchar_t kWndClass[] = L"Chrome_MessagePumpWindow";
@@ -54,24 +19,9 @@ static const wchar_t kWndClass[] = L"Chrome_MessagePumpWindow";
// 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::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
@@ -88,36 +38,6 @@ void MessagePumpWin::DidProcessMessage(const MSG& msg) {
FOR_EACH_OBSERVER(Observer, observers_, DidProcessMessage(msg));
}
-void MessagePumpWin::PumpOutPendingPaintMessages() {
- // If we are being called outside of the context of Run, then don't try to do
- // any work.
- if (!state_)
- return;
-
- // 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;
@@ -139,7 +59,38 @@ void MessagePumpWin::Quit() {
state_->should_quit = true;
}
-void MessagePumpWin::ScheduleWork() {
+//-----------------------------------------------------------------------------
+// MessagePumpWin protected:
+
+int MessagePumpWin::GetCurrentDelay() const {
+ if (delayed_work_time_.is_null())
+ return -1;
+
+ // Be careful here. TimeDelta has a precision of microseconds, but we want a
+ // value in milliseconds. If there are 5.5ms left, should the delay be 5 or
+ // 6? It should be 6 to avoid executing delayed work too early.
+ double timeout = ceil((delayed_work_time_ - Time::Now()).InMillisecondsF());
+
+ // If this value is negative, then we need to run delayed work soon.
+ int delay = static_cast<int>(timeout);
+ if (delay < 0)
+ delay = 0;
+
+ return delay;
+}
+
+//-----------------------------------------------------------------------------
+// MessagePumpForUI public:
+
+MessagePumpForUI::MessagePumpForUI() {
+ InitMessageWnd();
+}
+
+MessagePumpForUI::~MessagePumpForUI() {
+ DestroyWindow(message_hwnd_);
+}
+
+void MessagePumpForUI::ScheduleWork() {
if (InterlockedExchange(&have_work_, 1))
return; // Someone else continued the pumping.
@@ -147,7 +98,7 @@ void MessagePumpWin::ScheduleWork() {
PostMessage(message_hwnd_, kMsgHaveWork, reinterpret_cast<WPARAM>(this), 0);
}
-void MessagePumpWin::ScheduleDelayedWork(const Time& delayed_work_time) {
+void MessagePumpForUI::ScheduleDelayedWork(const Time& delayed_work_time) {
//
// 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
@@ -180,24 +131,110 @@ void MessagePumpWin::ScheduleDelayedWork(const Time& delayed_work_time) {
SetTimer(message_hwnd_, reinterpret_cast<UINT_PTR>(this), delay_msec, NULL);
}
+void MessagePumpForUI::PumpOutPendingPaintMessages() {
+ // If we are being called outside of the context of Run, then don't try to do
+ // any work.
+ if (!state_)
+ return;
+
+ // 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);
+}
+
//-----------------------------------------------------------------------------
-// MessagePumpWin protected:
+// MessagePumpForUI private:
// static
-LRESULT CALLBACK MessagePumpWin::WndProcThunk(
+LRESULT CALLBACK MessagePumpForUI::WndProcThunk(
HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
switch (message) {
case kMsgHaveWork:
- reinterpret_cast<MessagePumpWin*>(wparam)->HandleWorkMessage();
+ reinterpret_cast<MessagePumpForUI*>(wparam)->HandleWorkMessage();
break;
case WM_TIMER:
- reinterpret_cast<MessagePumpWin*>(wparam)->HandleTimerMessage();
+ reinterpret_cast<MessagePumpForUI*>(wparam)->HandleTimerMessage();
break;
}
return DefWindowProc(hwnd, message, wparam, lparam);
}
-void MessagePumpWin::InitMessageWnd() {
+void MessagePumpForUI::DoRunLoop() {
+ // IF this was just a simple PeekMessage() loop (servicing all possible 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. 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 |=
+ state_->delegate->DoDelayedWork(&delayed_work_time_);
+ // If we did not process any delayed work, then we can assume that our
+ // existing WM_TIMER if any will fire when delayed work should run. We
+ // don't want to disturb that timer if it is already in flight. However,
+ // if we did do all remaining delayed work, then lets kill the WM_TIMER.
+ if (more_work_is_plausible && delayed_work_time_.is_null())
+ KillTimer(message_hwnd_, reinterpret_cast<UINT_PTR>(this));
+ 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;
+
+ WaitForWork(); // Wait (sleep) until we have work to do again.
+ }
+}
+
+void MessagePumpForUI::InitMessageWnd() {
HINSTANCE hinst = GetModuleHandle(NULL);
WNDCLASSEX wc = {0};
@@ -212,7 +249,41 @@ void MessagePumpWin::InitMessageWnd() {
DCHECK(message_hwnd_);
}
-void MessagePumpWin::HandleWorkMessage() {
+void MessagePumpForUI::WaitForWork() {
+ // Wait until a message is available, up to the time needed by the timer
+ // manager to fire the next set of timers.
+ int delay = GetCurrentDelay();
+ if (delay < 0) // Negative value means no timers waiting.
+ delay = INFINITE;
+
+ DWORD result;
+ result = MsgWaitForMultipleObjectsEx(0, NULL, delay, QS_ALLINPUT,
+ MWMO_INPUTAVAILABLE);
+
+ if (WAIT_OBJECT_0 == result) {
+ // A WM_* message is available.
+ // If a parent child relationship exists between windows across threads
+ // then their thread inputs are implicitly attached.
+ // This causes the MsgWaitForMultipleObjectsEx API to return indicating
+ // that messages are ready for processing (specifically mouse messages
+ // intended for the child window. Occurs if the child window has capture)
+ // The subsequent PeekMessages call fails to return any messages thus
+ // causing us to enter a tight loop at times.
+ // The WaitMessage call below is a workaround to give the child window
+ // sometime to process its input messages.
+ MSG msg = {0};
+ DWORD queue_status = GetQueueStatus(QS_MOUSE);
+ if (HIWORD(queue_status) & QS_MOUSE &&
+ !PeekMessage(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_NOREMOVE)) {
+ WaitMessage();
+ }
+ return;
+ }
+
+ DCHECK_NE(WAIT_FAILED, result) << GetLastError();
+}
+
+void MessagePumpForUI::HandleWorkMessage() {
// If we are being called outside of the context of Run, then don't try to do
// any work. This could correspond to a MessageBox call or something of that
// sort.
@@ -233,7 +304,7 @@ void MessagePumpWin::HandleWorkMessage() {
ScheduleWork();
}
-void MessagePumpWin::HandleTimerMessage() {
+void MessagePumpForUI::HandleTimerMessage() {
KillTimer(message_hwnd_, reinterpret_cast<UINT_PTR>(this));
// If we are being called outside of the context of Run, then don't do
@@ -249,7 +320,7 @@ void MessagePumpWin::HandleTimerMessage() {
}
}
-bool MessagePumpWin::ProcessNextWindowsMessage() {
+bool MessagePumpForUI::ProcessNextWindowsMessage() {
// If there are sent messages in the queue then PeekMessage internally
// dispatches the message and returns false. We return true in this
// case to ensure that the message loop peeks again instead of calling
@@ -266,7 +337,7 @@ bool MessagePumpWin::ProcessNextWindowsMessage() {
return sent_messages_in_queue;
}
-bool MessagePumpWin::ProcessMessageHelper(const MSG& msg) {
+bool MessagePumpForUI::ProcessMessageHelper(const MSG& msg) {
if (WM_QUIT == msg.message) {
// Repost the QUIT message so that it will be retrieved by the primary
// GetMessage() loop.
@@ -293,7 +364,7 @@ bool MessagePumpWin::ProcessMessageHelper(const MSG& msg) {
return true;
}
-bool MessagePumpWin::ProcessPumpReplacementMessage() {
+bool MessagePumpForUI::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
@@ -307,7 +378,7 @@ bool MessagePumpWin::ProcessPumpReplacementMessage() {
bool have_message = (0 != PeekMessage(&msg, NULL, 0, 0, PM_REMOVE));
DCHECK(!have_message || kMsgHaveWork != msg.message ||
msg.hwnd != message_hwnd_);
-
+
// Since we discarded a kMsgHaveWork message, we must update the flag.
InterlockedExchange(&have_work_, 0);
@@ -318,233 +389,63 @@ bool MessagePumpWin::ProcessPumpReplacementMessage() {
return have_message && ProcessMessageHelper(msg);
}
-int MessagePumpWin::GetCurrentDelay() const {
- if (delayed_work_time_.is_null())
- return -1;
-
- // Be careful here. TimeDelta has a precision of microseconds, but we want a
- // value in milliseconds. If there are 5.5ms left, should the delay be 5 or
- // 6? It should be 6 to avoid executing delayed work too early.
- double timeout = ceil((delayed_work_time_ - Time::Now()).InMillisecondsF());
-
- // If this value is negative, then we need to run delayed work soon.
- int delay = static_cast<int>(timeout);
- if (delay < 0)
- delay = 0;
-
- return delay;
-}
-
//-----------------------------------------------------------------------------
-// MessagePumpForUI private:
-
-void MessagePumpForUI::DoRunLoop() {
- // IF this was just a simple PeekMessage() loop (servicing all possible 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. 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 |=
- state_->delegate->DoDelayedWork(&delayed_work_time_);
- // If we did not process any delayed work, then we can assume that our
- // existing WM_TIMER if any will fire when delayed work should run. We
- // don't want to disturb that timer if it is already in flight. However,
- // if we did do all remaining delayed work, then lets kill the WM_TIMER.
- if (more_work_is_plausible && delayed_work_time_.is_null())
- KillTimer(message_hwnd_, reinterpret_cast<UINT_PTR>(this));
- 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;
+// MessagePumpForIO public:
- WaitForWork(); // Wait (sleep) until we have work to do again.
- }
+MessagePumpForIO::MessagePumpForIO() {
+ port_.Set(CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, 1));
+ DCHECK(port_.IsValid());
}
-void MessagePumpForUI::WaitForWork() {
- // Wait until a message is available, up to the time needed by the timer
- // manager to fire the next set of timers.
- int delay = GetCurrentDelay();
- if (delay < 0) // Negative value means no timers waiting.
- delay = INFINITE;
-
- DWORD result;
- result = MsgWaitForMultipleObjectsEx(0, NULL, delay, QS_ALLINPUT,
- MWMO_INPUTAVAILABLE);
-
- if (WAIT_OBJECT_0 == result) {
- // A WM_* message is available.
- // If a parent child relationship exists between windows across threads
- // then their thread inputs are implicitly attached.
- // This causes the MsgWaitForMultipleObjectsEx API to return indicating
- // that messages are ready for processing (specifically mouse messages
- // intended for the child window. Occurs if the child window has capture)
- // The subsequent PeekMessages call fails to return any messages thus
- // causing us to enter a tight loop at times.
- // The WaitMessage call below is a workaround to give the child window
- // sometime to process its input messages.
- MSG msg = {0};
- DWORD queue_status = GetQueueStatus(QS_MOUSE);
- if (HIWORD(queue_status) & QS_MOUSE &&
- !PeekMessage(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_NOREMOVE)) {
- WaitMessage();
- }
- return;
- }
+void MessagePumpForIO::ScheduleWork() {
+ if (InterlockedExchange(&have_work_, 1))
+ return; // Someone else continued the pumping.
- DCHECK_NE(WAIT_FAILED, result) << GetLastError();
+ // Make sure the MessagePump does some work for us.
+ BOOL ret = PostQueuedCompletionStatus(port_, 0,
+ reinterpret_cast<ULONG_PTR>(this),
+ reinterpret_cast<OVERLAPPED*>(this));
+ DCHECK(ret);
}
-//-----------------------------------------------------------------------------
-// MessagePumpForIO public:
-
-void MessagePumpForIO::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 MessagePumpForIO::ScheduleDelayedWork(const Time& delayed_work_time) {
+ // We know that we can't be blocked right now since this method can only be
+ // called on the same thread as Run, so we only need to update our record of
+ // how long to sleep when we do sleep.
+ delayed_work_time_ = delayed_work_time;
}
void MessagePumpForIO::RegisterIOHandler(HANDLE file_handle,
IOHandler* handler) {
-#if 0
- // TODO(rvargas): This is just to give an idea of what this code will look
- // like when we actually move to completion ports. Of course, we cannot
- // do this without calling GetQueuedCompletionStatus().
ULONG_PTR key = reinterpret_cast<ULONG_PTR>(handler);
HANDLE port = CreateIoCompletionPort(file_handle, port_, key, 1);
- if (!port_.IsValid())
- port_.Set(port);
-#endif
-}
-
-void MessagePumpForIO::RegisterIOContext(OVERLAPPED* context,
- IOHandler* handler) {
- DCHECK(context->hEvent);
- if (handler) {
- HandlerData* watcher = new HandlerData(context, handler);
- WatchObject(context->hEvent, watcher);
- } else {
- std::vector<HANDLE>::iterator it =
- find(objects_.begin(), objects_.end(), context->hEvent);
-
- if (it == objects_.end()) {
- NOTREACHED();
- return;
- }
-
- std::vector<HANDLE>::difference_type index = it - objects_.begin();
- objects_.erase(it);
- delete watchers_[index];
- watchers_.erase(watchers_.begin() + index);
- }
+ DCHECK(port == port_.Get());
}
//-----------------------------------------------------------------------------
// MessagePumpForIO private:
void MessagePumpForIO::DoRunLoop() {
- // IF this was just a simple PeekMessage() loop (servicing all possible 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;
+ // WaitForIOCompletion(), there is a good chance there are still more
+ // messages waiting. 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.
- more_work_is_plausible |= state_->delegate->DoWork();
+ bool more_work_is_plausible = state_->delegate->DoWork();
if (state_->should_quit)
break;
- more_work_is_plausible |= ProcessNextObject();
+ more_work_is_plausible |= WaitForIOCompletion(0, NULL);
if (state_->should_quit)
break;
more_work_is_plausible |=
state_->delegate->DoDelayedWork(&delayed_work_time_);
- // If we did not process any delayed work, then we can assume that our
- // existing WM_TIMER if any will fire when delayed work should run. We
- // don't want to disturb that timer if it is already in flight. However,
- // if we did do all remaining delayed work, then lets kill the WM_TIMER.
- if (more_work_is_plausible && delayed_work_time_.is_null())
- KillTimer(message_hwnd_, reinterpret_cast<UINT_PTR>(this));
if (state_->should_quit)
break;
@@ -558,141 +459,92 @@ void MessagePumpForIO::DoRunLoop() {
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;
-
+// Wait until IO completes, up to the time needed by the timer manager to fire
+// the next set of timers.
void MessagePumpForIO::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!";
+ // We do not support nested IO message loops. This is to avoid messy
+ // recursion problems.
+ DCHECK(state_->run_depth == 1) << "Cannot nest an IO message loop!";
- int wait_flags = MWMO_ALERTABLE | MWMO_INPUTAVAILABLE;
+ int timeout = GetCurrentDelay();
+ if (timeout < 0) // Negative value means no timers waiting.
+ timeout = INFINITE;
- 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.
- }
+ WaitForIOCompletion(timeout, NULL);
}
-bool MessagePumpForIO::ProcessNextObject() {
- size_t total_objs = objects_.size();
- if (!total_objs) {
- return false;
+bool MessagePumpForIO::WaitForIOCompletion(DWORD timeout, IOHandler* filter) {
+ IOItem item;
+ if (completed_io_.empty() || !MatchCompletedIOItem(filter, &item)) {
+ // We have to ask the system for another IO completion.
+ if (!GetIOItem(timeout, &item))
+ return false;
+
+ if (ProcessInternalIOItem(item))
+ return true;
}
- 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 (item.context->handler) {
+ if (filter && item.handler != filter) {
+ // Save this item for later
+ completed_io_.push_back(item);
+ } else {
+ DCHECK(item.context->handler == item.handler);
+ item.handler->OnIOCompleted(item.context, item.bytes_transfered,
+ item.error);
}
-
- // 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.
+ } else {
+ // The handler must be gone by now, just cleanup the mess.
+ delete item.context;
+ }
+ return true;
}
-bool MessagePumpForIO::SignalWatcher(size_t object_index) {
- // Signal the watcher corresponding to the given index.
-
- DCHECK(objects_.size() > object_index);
+// Asks the OS for another IO completion result.
+bool MessagePumpForIO::GetIOItem(DWORD timeout, IOItem* item) {
+ memset(item, 0, sizeof(*item));
+ ULONG_PTR key = NULL;
+ OVERLAPPED* overlapped = NULL;
+ if (!GetQueuedCompletionStatus(port_.Get(), &item->bytes_transfered, &key,
+ &overlapped, timeout)) {
+ if (!overlapped)
+ return false; // Nothing in the queue.
+ item->error = GetLastError();
+ item->bytes_transfered = 0;
+ }
- // 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]);
+ item->handler = reinterpret_cast<IOHandler*>(key);
+ item->context = reinterpret_cast<IOContext*>(overlapped);
+ return true;
+}
- // 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).
+bool MessagePumpForIO::ProcessInternalIOItem(const IOItem& item) {
+ if (this == reinterpret_cast<MessagePumpForIO*>(item.context) &&
+ this == reinterpret_cast<MessagePumpForIO*>(item.handler)) {
+ // This is our internal completion.
+ DCHECK(!item.bytes_transfered);
+ InterlockedExchange(&have_work_, 0);
+ return true;
+ }
+ return false;
+}
- return true;
+// Returns a completion item that was previously received.
+bool MessagePumpForIO::MatchCompletedIOItem(IOHandler* filter, IOItem* item) {
+ DCHECK(!completed_io_.empty());
+ for (std::list<IOItem>::iterator it = completed_io_.begin();
+ it != completed_io_.end(); ++it) {
+ if (!filter || it->handler == filter) {
+ *item = *it;
+ completed_io_.erase(it);
+ return true;
+ }
+ }
+ return false;
}
} // namespace base