// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/message_loop/message_pump_win.h" #include #include #include #include "base/auto_reset.h" #include "base/bind.h" #include "base/cxx17_backports.h" #include "base/debug/alias.h" #include "base/feature_list.h" #include "base/metrics/histogram_macros.h" #include "base/numerics/safe_conversions.h" #include "base/trace_event/base_tracing.h" #include "base/tracing_buildflags.h" #if BUILDFLAG(ENABLE_BASE_TRACING) #include "third_party/perfetto/protos/perfetto/trace/track_event/chrome_message_pump.pbzero.h" #endif // BUILDFLAG(ENABLE_BASE_TRACING) namespace base { namespace { enum MessageLoopProblems { MESSAGE_POST_ERROR, COMPLETION_POST_ERROR, SET_TIMER_ERROR, RECEIVED_WM_QUIT_ERROR, MESSAGE_LOOP_PROBLEM_MAX, }; // Returns the number of milliseconds before |next_task_time|, clamped between // zero and the biggest DWORD value (or INFINITE if |next_task_time.is_max()|). // Optionally, a recent value of Now() may be passed in to avoid resampling it. DWORD GetSleepTimeoutMs(TimeTicks next_task_time, TimeTicks recent_now = TimeTicks()) { // Shouldn't need to sleep or install a timer when there's pending immediate // work. DCHECK(!next_task_time.is_null()); if (next_task_time.is_max()) return INFINITE; auto now = recent_now.is_null() ? TimeTicks::Now() : recent_now; auto timeout_ms = (next_task_time - now).InMillisecondsRoundedUp(); // A saturated_cast with an unsigned destination automatically clamps negative // values at zero. static_assert(!std::is_signed::value, "DWORD is unexpectedly signed"); return saturated_cast(timeout_ms); } } // namespace // 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; //----------------------------------------------------------------------------- // MessagePumpWin public: MessagePumpWin::MessagePumpWin() = default; MessagePumpWin::~MessagePumpWin() = default; void MessagePumpWin::Run(Delegate* delegate) { DCHECK_CALLED_ON_VALID_THREAD(bound_thread_); RunState run_state(delegate); if (run_state_) run_state.is_nested = true; AutoReset auto_reset_run_state(&run_state_, &run_state); DoRunLoop(); } void MessagePumpWin::Quit() { DCHECK_CALLED_ON_VALID_THREAD(bound_thread_); DCHECK(run_state_); run_state_->should_quit = true; } //----------------------------------------------------------------------------- // MessagePumpForUI public: MessagePumpForUI::MessagePumpForUI() { bool succeeded = message_window_.Create( BindRepeating(&MessagePumpForUI::MessageCallback, Unretained(this))); DCHECK(succeeded); } MessagePumpForUI::~MessagePumpForUI() = default; void MessagePumpForUI::ScheduleWork() { // This is the only MessagePumpForUI method which can be called outside of // |bound_thread_|. bool not_scheduled = false; if (!work_scheduled_.compare_exchange_strong(not_scheduled, true)) return; // Someone else continued the pumping. // Make sure the MessagePump does some work for us. const BOOL ret = ::PostMessage(message_window_.hwnd(), kMsgHaveWork, 0, 0); if (ret) return; // There was room in the Window Message queue. // We have failed to insert a have-work message, so there is a chance that we // will starve tasks/timers while sitting in a nested run loop. Nested // loops only look at Windows Message queues, and don't look at *our* task // queues, etc., so we might not get a time slice in such. :-( // We could abort here, but the fear is that this failure mode is plausibly // common (queue is full, of about 2000 messages), so we'll do a near-graceful // recovery. Nested loops are pretty transient (we think), so this will // probably be recoverable. // Clarify that we didn't really insert. work_scheduled_ = false; UMA_HISTOGRAM_ENUMERATION("Chrome.MessageLoopProblem", MESSAGE_POST_ERROR, MESSAGE_LOOP_PROBLEM_MAX); TRACE_EVENT_INSTANT0("base", "Chrome.MessageLoopProblem.MESSAGE_POST_ERROR", TRACE_EVENT_SCOPE_THREAD); } void MessagePumpForUI::ScheduleDelayedWork( const Delegate::NextWorkInfo& next_work_info) { DCHECK_CALLED_ON_VALID_THREAD(bound_thread_); // Since this is always called from |bound_thread_|, there is almost always // nothing to do as the loop is already running. When the loop becomes idle, // it will typically WaitForWork() in DoRunLoop() with the timeout provided by // DoWork(). The only alternative to this is entering a native nested loop // (e.g. modal dialog) under a ScopedNestableTaskAllower, in which case // HandleWorkMessage() will be invoked when the system picks up kMsgHaveWork // and it will ScheduleNativeTimer() if it's out of immediate work. However, // in that alternate scenario : it's possible for a Windows native work item // (e.g. https://docs.microsoft.com/en-us/windows/desktop/winmsg/using-hooks) // to wake the native nested loop and PostDelayedTask() to the current thread // from it. This is the only case where we must install/adjust the native // timer from ScheduleDelayedWork() because if we don't, the native loop will // go back to sleep, unaware of the new |delayed_work_time|. // See MessageLoopTest.PostDelayedTaskFromSystemPump for an example. // TODO(gab): This could potentially be replaced by a ForegroundIdleProc hook // if Windows ends up being the only platform requiring ScheduleDelayedWork(). if (in_native_loop_ && !work_scheduled_) { ScheduleNativeTimer(next_work_info); } } void MessagePumpForUI::AddObserver(Observer* observer) { DCHECK_CALLED_ON_VALID_THREAD(bound_thread_); observers_.AddObserver(observer); } void MessagePumpForUI::RemoveObserver(Observer* observer) { DCHECK_CALLED_ON_VALID_THREAD(bound_thread_); observers_.RemoveObserver(observer); } //----------------------------------------------------------------------------- // MessagePumpForUI private: bool MessagePumpForUI::MessageCallback( UINT message, WPARAM wparam, LPARAM lparam, LRESULT* result) { DCHECK_CALLED_ON_VALID_THREAD(bound_thread_); switch (message) { case kMsgHaveWork: HandleWorkMessage(); break; case WM_TIMER: if (wparam == reinterpret_cast(this)) HandleTimerMessage(); break; } return false; } void MessagePumpForUI::DoRunLoop() { DCHECK_CALLED_ON_VALID_THREAD(bound_thread_); // 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. in_native_loop_ = false; bool more_work_is_plausible = ProcessNextWindowsMessage(); in_native_loop_ = false; if (run_state_->should_quit) break; Delegate::NextWorkInfo next_work_info = run_state_->delegate->DoWork(); in_native_loop_ = false; more_work_is_plausible |= next_work_info.is_immediate(); if (run_state_->should_quit) break; if (installed_native_timer_) { // As described in ScheduleNativeTimer(), the native timer is only // installed and needed while in a nested native loop. If it is installed, // it means the above work entered such a loop. Having now resumed, the // native timer is no longer needed. KillNativeTimer(); } if (more_work_is_plausible) continue; more_work_is_plausible = run_state_->delegate->DoIdleWork(); // DoIdleWork() shouldn't end up in native nested loops and thus shouldn't // have any chance of reinstalling a native timer. DCHECK(!in_native_loop_); DCHECK(!installed_native_timer_); if (run_state_->should_quit) break; if (more_work_is_plausible) continue; WaitForWork(next_work_info); } } void MessagePumpForUI::WaitForWork(Delegate::NextWorkInfo next_work_info) { DCHECK_CALLED_ON_VALID_THREAD(bound_thread_); // Wait until a message is available, up to the time needed by the timer // manager to fire the next set of timers. DWORD wait_flags = MWMO_INPUTAVAILABLE; for (DWORD delay = GetSleepTimeoutMs(next_work_info.delayed_run_time, next_work_info.recent_now); delay != 0; delay = GetSleepTimeoutMs(next_work_info.delayed_run_time)) { run_state_->delegate->BeforeWait(); // Tell the optimizer to retain these values to simplify analyzing hangs. base::debug::Alias(&delay); base::debug::Alias(&wait_flags); DWORD result = MsgWaitForMultipleObjectsEx(0, nullptr, delay, QS_ALLINPUT, wait_flags); 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 may appear if the child window has // capture). // The subsequent PeekMessages call may fail to return any messages thus // causing us to enter a tight loop at times. // The code below is a workaround to give the child window // some time to process its input messages by looping back to // MsgWaitForMultipleObjectsEx above when there are no messages for the // current thread. // As in ProcessNextWindowsMessage(). auto scoped_do_work_item = run_state_->delegate->BeginWorkItem(); { TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("base"), "MessagePumpForUI::WaitForWork GetQueueStatus"); if (HIWORD(::GetQueueStatus(QS_SENDMESSAGE)) & QS_SENDMESSAGE) return; } { MSG msg; TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("base"), "MessagePumpForUI::WaitForWork PeekMessage"); if (::PeekMessage(&msg, nullptr, 0, 0, PM_NOREMOVE)) return; } // We know there are no more messages for this thread because PeekMessage // has returned false. Reset |wait_flags| so that we wait for a *new* // message. wait_flags = 0; } DCHECK_NE(WAIT_FAILED, result) << GetLastError(); } } void MessagePumpForUI::HandleWorkMessage() { DCHECK_CALLED_ON_VALID_THREAD(bound_thread_); // The kMsgHaveWork message was consumed by a native loop, we must assume // we're in one until DoRunLoop() gets control back. in_native_loop_ = true; // 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. if (!run_state_) { // Since we handled a kMsgHaveWork message, we must still update this flag. work_scheduled_ = false; 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(); Delegate::NextWorkInfo next_work_info = run_state_->delegate->DoWork(); if (next_work_info.is_immediate()) { ScheduleWork(); } else { run_state_->delegate->BeforeWait(); ScheduleNativeTimer(next_work_info); } } void MessagePumpForUI::HandleTimerMessage() { DCHECK_CALLED_ON_VALID_THREAD(bound_thread_); // ::KillTimer doesn't remove pending WM_TIMER messages from the queue, // explicitly ignore the last WM_TIMER message in that case to avoid handling // work from here when DoRunLoop() is active (which could result in scheduling // work from two places at once). Note: we're still fine in the event that a // second native nested loop is entered before such a dead WM_TIMER message is // discarded because ::SetTimer merely resets the timer if invoked twice with // the same id. if (!installed_native_timer_) return; // We only need to fire once per specific delay, another timer may be // scheduled below but we're done with this one. KillNativeTimer(); // 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 (!run_state_) return; Delegate::NextWorkInfo next_work_info = run_state_->delegate->DoWork(); if (next_work_info.is_immediate()) { ScheduleWork(); } else { run_state_->delegate->BeforeWait(); ScheduleNativeTimer(next_work_info); } } void MessagePumpForUI::ScheduleNativeTimer( Delegate::NextWorkInfo next_work_info) { DCHECK(!next_work_info.is_immediate()); DCHECK(in_native_loop_); // Do not redundantly set the same native timer again if it was already set. // This can happen when a nested native loop goes idle with pending delayed // tasks, then gets woken up by an immediate task, and goes back to idle with // the same pending delay. No need to kill the native timer if there is // already one but the |delayed_run_time| has changed as ::SetTimer reuses the // same id and will replace and reset the existing timer. if (installed_native_timer_ && *installed_native_timer_ == next_work_info.delayed_run_time) { return; } if (next_work_info.delayed_run_time.is_max()) return; // We do not use native Windows timers in general as they have a poor, 10ms, // granularity. Instead we rely on MsgWaitForMultipleObjectsEx's // high-resolution timeout to sleep without timers in WaitForWork(). However, // when entering a nested native ::GetMessage() loop (e.g. native modal // windows) under a ScopedNestableTaskAllower, we have to rely on a native // timer when HandleWorkMessage() runs out of immediate work. Since // ScopedNestableTaskAllower invokes ScheduleWork() : we are guaranteed that // HandleWorkMessage() will be called after entering a nested native loop that // should process application tasks. But once HandleWorkMessage() is out of // immediate work, ::SetTimer() is used to guarantee we are invoked again // should the next delayed task expire before the nested native loop ends. The // native timer being unnecessary once we return to our DoRunLoop(), we // ::KillTimer when it resumes (nested native loops should be rare so we're // not worried about ::SetTimer<=>::KillTimer churn). // TODO(gab): The long-standing legacy dependency on the behavior of // ScopedNestableTaskAllower is unfortunate, would be nice to make this a // MessagePump concept (instead of requiring impls to invoke ScheduleWork() // one-way and no-op DoWork() the other way). UINT delay_msec = strict_cast(GetSleepTimeoutMs( next_work_info.delayed_run_time, next_work_info.recent_now)); if (delay_msec == 0) { ScheduleWork(); } else { // TODO(gab): ::SetTimer()'s documentation claims it does this for us. // Consider removing this safety net. delay_msec = clamp(delay_msec, UINT(USER_TIMER_MINIMUM), UINT(USER_TIMER_MAXIMUM)); // Tell the optimizer to retain the delay to simplify analyzing hangs. base::debug::Alias(&delay_msec); const UINT_PTR ret = ::SetTimer(message_window_.hwnd(), reinterpret_cast(this), delay_msec, nullptr); if (ret) { installed_native_timer_ = next_work_info.delayed_run_time; return; } // This error is likely similar to MESSAGE_POST_ERROR (i.e. native queue is // full). Since we only use ScheduleNativeTimer() in native nested loops // this likely means this pump will not be given a chance to run application // tasks until the nested loop completes. UMA_HISTOGRAM_ENUMERATION("Chrome.MessageLoopProblem", SET_TIMER_ERROR, MESSAGE_LOOP_PROBLEM_MAX); TRACE_EVENT_INSTANT0("base", "Chrome.MessageLoopProblem.SET_TIMER_ERROR", TRACE_EVENT_SCOPE_THREAD); } } void MessagePumpForUI::KillNativeTimer() { DCHECK(installed_native_timer_); const bool success = ::KillTimer(message_window_.hwnd(), reinterpret_cast(this)); DPCHECK(success); installed_native_timer_.reset(); } bool MessagePumpForUI::ProcessNextWindowsMessage() { DCHECK_CALLED_ON_VALID_THREAD(bound_thread_); MSG msg; bool has_msg = false; bool more_work_is_plausible = false; { // ::PeekMessage() may process sent and/or internal messages (regardless of // |had_messages| as ::GetQueueStatus() is an optimistic check that may // racily have missed an incoming event -- it doesn't hurt to have empty // internal units of work when ::PeekMessage turns out to be a no-op). // Instantiate |scoped_do_work| ahead of GetQueueStatus() so that // trace events it emits fully outscope GetQueueStatus' events // (GetQueueStatus() itself not being expected to do work; it's fine to use // only one ScopedDoWorkItem for both calls -- we trace them independently // just in case internal work stalls). auto scoped_do_work_item = run_state_->delegate->BeginWorkItem(); { // Individually trace ::GetQueueStatus and ::PeekMessage because sampling // profiler is hinting that we're spending a surprising amount of time // with these on top of the stack. Tracing will be able to tell us whether // this is a bias of sampling profiler (e.g. kernel takes ::GetQueueStatus // as an opportunity to swap threads and is more likely to schedule the // sampling profiler's thread while the sampled thread is swapped out on // this frame). TRACE_EVENT0( TRACE_DISABLED_BY_DEFAULT("base"), "MessagePumpForUI::ProcessNextWindowsMessage GetQueueStatus"); DWORD queue_status = ::GetQueueStatus(QS_SENDMESSAGE); // 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 // MsgWaitForMultipleObjectsEx. if (HIWORD(queue_status) & QS_SENDMESSAGE) more_work_is_plausible = true; } { // PeekMessage can run a message if there are sent messages, trace that // and emit the boolean param to see if it ever janks independently (ref. // comment on GetQueueStatus). TRACE_EVENT( TRACE_DISABLED_BY_DEFAULT("base"), "MessagePumpForUI::ProcessNextWindowsMessage PeekMessage", [&](perfetto::EventContext ctx) { perfetto::protos::pbzero::ChromeMessagePump* msg_pump_data = ctx.event()->set_chrome_message_pump(); msg_pump_data->set_sent_messages_in_queue(more_work_is_plausible); }); has_msg = ::PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE) != FALSE; } } if (has_msg) more_work_is_plausible |= ProcessMessageHelper(msg); return more_work_is_plausible; } bool MessagePumpForUI::ProcessMessageHelper(const MSG& msg) { DCHECK_CALLED_ON_VALID_THREAD(bound_thread_); if (msg.message == WM_QUIT) { // WM_QUIT is the standard way to exit a ::GetMessage() loop. Our // MessageLoop has its own quit mechanism, so WM_QUIT is generally // unexpected. UMA_HISTOGRAM_ENUMERATION("Chrome.MessageLoopProblem", RECEIVED_WM_QUIT_ERROR, MESSAGE_LOOP_PROBLEM_MAX); return true; } // While running our main message pump, we discard kMsgHaveWork messages. if (msg.message == kMsgHaveWork && msg.hwnd == message_window_.hwnd()) return ProcessPumpReplacementMessage(); auto scoped_do_work_item = run_state_->delegate->BeginWorkItem(); TRACE_EVENT("base,toplevel", "MessagePumpForUI DispatchMessage", [&](perfetto::EventContext ctx) { ctx.event() ->set_chrome_message_pump_for_ui() ->set_message_id(msg.message); }); for (Observer& observer : observers_) observer.WillDispatchMSG(msg); ::TranslateMessage(&msg); ::DispatchMessage(&msg); for (Observer& observer : observers_) observer.DidDispatchMSG(msg); return true; } bool MessagePumpForUI::ProcessPumpReplacementMessage() { DCHECK_CALLED_ON_VALID_THREAD(bound_thread_); // When we encounter a kMsgHaveWork message, this method is called to peek and // process a replacement message. 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 = false; { // Note: Ideally this call wouldn't process sent-messages (as we already did // that in the PeekMessage call that lead to receiving this kMsgHaveWork), // but there's no way to specify this (omitting PM_QS_SENDMESSAGE as in // crrev.com/791043 doesn't do anything). Hence this call must be considered // as a potential work item. TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("base"), "MessagePumpForUI::ProcessPumpReplacementMessage PeekMessage"); auto scoped_do_work_item = run_state_->delegate->BeginWorkItem(); have_message = ::PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE) != FALSE; } // Expect no message or a message different than kMsgHaveWork. DCHECK(!have_message || kMsgHaveWork != msg.message || msg.hwnd != message_window_.hwnd()); // Since we discarded a kMsgHaveWork message, we must update the flag. DCHECK(work_scheduled_); work_scheduled_ = false; // We don't need a special time slice if we didn't |have_message| to process. if (!have_message) return false; if (msg.message == WM_QUIT) { // If we're in a nested ::GetMessage() loop then we must let that loop see // the WM_QUIT in order for it to exit. If we're in DoRunLoop then the re- // posted WM_QUIT will be either ignored, or handled, by // ProcessMessageHelper() called directly from ProcessNextWindowsMessage(). ::PostQuitMessage(static_cast(msg.wParam)); // Note: we *must not* ScheduleWork() here as WM_QUIT is a low-priority // message on Windows (it is only returned by ::PeekMessage() when idle) : // https://blogs.msdn.microsoft.com/oldnewthing/20051104-33/?p=33453. As // such posting a kMsgHaveWork message via ScheduleWork() would cause an // infinite loop (kMsgHaveWork message handled first means we end up here // again and repost WM_QUIT+ScheduleWork() again, etc.). Not leaving a // kMsgHaveWork message behind however is also problematic as unwinding // multiple layers of nested ::GetMessage() loops can result in starving // application tasks. TODO(https://crbug.com/890016) : Fix this. // The return value is mostly irrelevant but return true like we would after // processing a QuitClosure() task. return true; } else if (msg.message == WM_TIMER && msg.wParam == reinterpret_cast(this)) { // This happens when a native nested loop invokes HandleWorkMessage() => // ProcessPumpReplacementMessage() which finds the WM_TIMER message // installed by ScheduleNativeTimer(). That message needs to be handled // directly as handing it off to ProcessMessageHelper() below would cause an // unnecessary ScopedDoWorkItem which may incorrectly lead the Delegate's // heuristics to conclude that the DoWork() in HandleTimerMessage() is // nested inside a native work item. It's also safe to skip the below // ScheduleWork() as it is not mandatory before invoking DoWork() and // HandleTimerMessage() handles re-installing the necessary followup // messages. HandleTimerMessage(); return true; } // Guarantee we'll get another time slice in the case where we go into native // windows code. This ScheduleWork() may hurt performance a tiny bit when // tasks appear very infrequently, but when the event queue is busy, the // kMsgHaveWork events get (percentage wise) rarer and rarer. ScheduleWork(); return ProcessMessageHelper(msg); } //----------------------------------------------------------------------------- // MessagePumpForIO public: MessagePumpForIO::IOContext::IOContext() { memset(&overlapped, 0, sizeof(overlapped)); } MessagePumpForIO::IOHandler::IOHandler(const Location& from_here) : io_handler_location_(from_here) {} MessagePumpForIO::IOHandler::~IOHandler() = default; MessagePumpForIO::MessagePumpForIO() { port_.Set(::CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, reinterpret_cast(nullptr), 1)); DCHECK(port_.is_valid()); } MessagePumpForIO::~MessagePumpForIO() = default; void MessagePumpForIO::ScheduleWork() { // This is the only MessagePumpForIO method which can be called outside of // |bound_thread_|. bool not_scheduled = false; if (!work_scheduled_.compare_exchange_strong(not_scheduled, true)) return; // Someone else continued the pumping. // Make sure the MessagePump does some work for us. const BOOL ret = ::PostQueuedCompletionStatus( port_.get(), 0, reinterpret_cast(this), reinterpret_cast(this)); if (ret) return; // Post worked perfectly. // See comment in MessagePumpForUI::ScheduleWork() for this error recovery. work_scheduled_ = false; // Clarify that we didn't succeed. UMA_HISTOGRAM_ENUMERATION("Chrome.MessageLoopProblem", COMPLETION_POST_ERROR, MESSAGE_LOOP_PROBLEM_MAX); TRACE_EVENT_INSTANT0("base", "Chrome.MessageLoopProblem.COMPLETION_POST_ERROR", TRACE_EVENT_SCOPE_THREAD); } void MessagePumpForIO::ScheduleDelayedWork( const Delegate::NextWorkInfo& next_work_info) { DCHECK_CALLED_ON_VALID_THREAD(bound_thread_); // Since this is always called from |bound_thread_|, there is nothing to do as // the loop is already running. It will WaitForWork() in // DoRunLoop() with the correct timeout when it's out of immediate tasks. } HRESULT MessagePumpForIO::RegisterIOHandler(HANDLE file_handle, IOHandler* handler) { DCHECK_CALLED_ON_VALID_THREAD(bound_thread_); HANDLE port = ::CreateIoCompletionPort( file_handle, port_.get(), reinterpret_cast(handler), 1); return (port != nullptr) ? S_OK : HRESULT_FROM_WIN32(GetLastError()); } bool MessagePumpForIO::RegisterJobObject(HANDLE job_handle, IOHandler* handler) { DCHECK_CALLED_ON_VALID_THREAD(bound_thread_); JOBOBJECT_ASSOCIATE_COMPLETION_PORT info; info.CompletionKey = handler; info.CompletionPort = port_.get(); return ::SetInformationJobObject(job_handle, JobObjectAssociateCompletionPortInformation, &info, sizeof(info)) != FALSE; } //----------------------------------------------------------------------------- // MessagePumpForIO private: void MessagePumpForIO::DoRunLoop() { DCHECK_CALLED_ON_VALID_THREAD(bound_thread_); 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) // 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. Delegate::NextWorkInfo next_work_info = run_state_->delegate->DoWork(); bool more_work_is_plausible = next_work_info.is_immediate(); if (run_state_->should_quit) break; run_state_->delegate->BeforeWait(); more_work_is_plausible |= WaitForIOCompletion(0); if (run_state_->should_quit) break; if (more_work_is_plausible) continue; more_work_is_plausible = run_state_->delegate->DoIdleWork(); if (run_state_->should_quit) break; if (more_work_is_plausible) continue; run_state_->delegate->BeforeWait(); WaitForWork(next_work_info); } } // Wait until IO completes, up to the time needed by the timer manager to fire // the next set of timers. void MessagePumpForIO::WaitForWork(Delegate::NextWorkInfo next_work_info) { DCHECK_CALLED_ON_VALID_THREAD(bound_thread_); // We do not support nested IO message loops. This is to avoid messy // recursion problems. DCHECK(!run_state_->is_nested) << "Cannot nest an IO message loop!"; DWORD timeout = GetSleepTimeoutMs(next_work_info.delayed_run_time, next_work_info.recent_now); // Tell the optimizer to retain these values to simplify analyzing hangs. base::debug::Alias(&timeout); WaitForIOCompletion(timeout); } bool MessagePumpForIO::WaitForIOCompletion(DWORD timeout) { DCHECK_CALLED_ON_VALID_THREAD(bound_thread_); IOItem item; if (!GetIOItem(timeout, &item)) return false; if (ProcessInternalIOItem(item)) return true; auto scoped_do_work_item = run_state_->delegate->BeginWorkItem(); TRACE_EVENT( "base,toplevel", "IOHandler::OnIOCompleted", [&](perfetto::EventContext ctx) { ctx.event()->set_chrome_message_pump()->set_io_handler_location_iid( base::trace_event::InternedSourceLocation::Get( &ctx, base::trace_event::TraceSourceLocation( item.handler->io_handler_location()))); }); item.handler->OnIOCompleted(item.context, item.bytes_transfered, item.error); return true; } // Asks the OS for another IO completion result. bool MessagePumpForIO::GetIOItem(DWORD timeout, IOItem* item) { DCHECK_CALLED_ON_VALID_THREAD(bound_thread_); memset(item, 0, sizeof(*item)); ULONG_PTR key = reinterpret_cast(nullptr); OVERLAPPED* overlapped = nullptr; 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; } item->handler = reinterpret_cast(key); item->context = reinterpret_cast(overlapped); return true; } bool MessagePumpForIO::ProcessInternalIOItem(const IOItem& item) { DCHECK_CALLED_ON_VALID_THREAD(bound_thread_); if (reinterpret_cast(this) == reinterpret_cast(item.context.get()) && reinterpret_cast(this) == reinterpret_cast(item.handler.get())) { // This is our internal completion. DCHECK(!item.bytes_transfered); work_scheduled_ = false; return true; } return false; } } // namespace base