1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/message_loop/message_pump_win.h"
6
7 #include <winbase.h>
8
9 #include <algorithm>
10 #include <atomic>
11 #include <cstdint>
12 #include <type_traits>
13
14 #include "base/auto_reset.h"
15 #include "base/check.h"
16 #include "base/debug/alias.h"
17 #include "base/feature_list.h"
18 #include "base/functional/bind.h"
19 #include "base/memory/raw_ptr.h"
20 #include "base/metrics/histogram_macros.h"
21 #include "base/numerics/safe_conversions.h"
22 #include "base/task/task_features.h"
23 #include "base/trace_event/base_tracing.h"
24 #include "base/tracing_buildflags.h"
25
26 #if BUILDFLAG(ENABLE_BASE_TRACING)
27 #include "third_party/perfetto/protos/perfetto/trace/track_event/chrome_message_pump.pbzero.h"
28 #endif // BUILDFLAG(ENABLE_BASE_TRACING)
29
30 namespace base {
31
32 namespace {
33
34 // Returns the number of milliseconds before |next_task_time|, clamped between
35 // zero and the biggest DWORD value (or INFINITE if |next_task_time.is_max()|).
36 // Optionally, a recent value of Now() may be passed in to avoid resampling it.
GetSleepTimeoutMs(TimeTicks next_task_time,TimeTicks recent_now=TimeTicks ())37 DWORD GetSleepTimeoutMs(TimeTicks next_task_time,
38 TimeTicks recent_now = TimeTicks()) {
39 // Shouldn't need to sleep or install a timer when there's pending immediate
40 // work.
41 DCHECK(!next_task_time.is_null());
42
43 if (next_task_time.is_max())
44 return INFINITE;
45
46 auto now = recent_now.is_null() ? TimeTicks::Now() : recent_now;
47 auto timeout_ms = (next_task_time - now).InMillisecondsRoundedUp();
48
49 // A saturated_cast with an unsigned destination automatically clamps negative
50 // values at zero.
51 static_assert(!std::is_signed_v<DWORD>, "DWORD is unexpectedly signed");
52 return saturated_cast<DWORD>(timeout_ms);
53 }
54
55 bool g_ui_pump_improvements_win = false;
56
57 } // namespace
58
59 // Message sent to get an additional time slice for pumping (processing) another
60 // task (a series of such messages creates a continuous task pump).
61 static const int kMsgHaveWork = WM_USER + 1;
62
63 //-----------------------------------------------------------------------------
64 // MessagePumpWin public:
65
66 MessagePumpWin::MessagePumpWin() = default;
67 MessagePumpWin::~MessagePumpWin() = default;
68
69 // static
InitializeFeatures()70 void MessagePumpWin::InitializeFeatures() {
71 g_ui_pump_improvements_win = FeatureList::IsEnabled(kUIPumpImprovementsWin);
72 }
73
Run(Delegate * delegate)74 void MessagePumpWin::Run(Delegate* delegate) {
75 DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
76
77 RunState run_state(delegate);
78 if (run_state_)
79 run_state.is_nested = true;
80
81 AutoReset<raw_ptr<RunState>> auto_reset_run_state(&run_state_, &run_state);
82 DoRunLoop();
83 }
84
Quit()85 void MessagePumpWin::Quit() {
86 DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
87
88 DCHECK(run_state_);
89 run_state_->should_quit = true;
90 }
91
92 //-----------------------------------------------------------------------------
93 // MessagePumpForUI public:
94
MessagePumpForUI()95 MessagePumpForUI::MessagePumpForUI() {
96 bool succeeded = message_window_.Create(
97 BindRepeating(&MessagePumpForUI::MessageCallback, Unretained(this)));
98 CHECK(succeeded);
99 }
100
101 MessagePumpForUI::~MessagePumpForUI() = default;
102
ScheduleWork()103 void MessagePumpForUI::ScheduleWork() {
104 // This is the only MessagePumpForUI method which can be called outside of
105 // |bound_thread_|.
106
107 if (g_ui_pump_improvements_win &&
108 !in_nested_native_loop_with_application_tasks_) {
109 // The pump is running using `event_` as its chrome-side synchronization
110 // variable. In this case, no deduplication is done, since the event has its
111 // own state.
112 event_.Signal();
113 return;
114 }
115
116 bool not_scheduled = false;
117 if (!native_msg_scheduled_.compare_exchange_strong(
118 not_scheduled, true, std::memory_order_relaxed)) {
119 return; // Someone else continued the pumping.
120 }
121
122 const BOOL ret = ::PostMessage(message_window_.hwnd(), kMsgHaveWork, 0, 0);
123 if (ret) {
124 return; // There was room in the Window Message queue.
125 }
126
127 // We have failed to insert a have-work message, so there is a chance that we
128 // will starve tasks/timers while sitting in a nested run loop. Nested loops
129 // only look at Windows Message queues, and don't look at *our* task queues,
130 // etc., so we might not get a time slice in such. :-(
131 // We could abort here, but the fear is that this failure mode is plausibly
132 // common (queue is full, of about 2000 messages), so we'll do a near-graceful
133 // recovery. Nested loops are pretty transient (we think), so this will
134 // probably be recoverable.
135
136 // Clarify that we didn't really insert.
137 native_msg_scheduled_.store(false, std::memory_order_relaxed);
138 TRACE_EVENT_INSTANT0("base", "Chrome.MessageLoopProblem.MESSAGE_POST_ERROR",
139 TRACE_EVENT_SCOPE_THREAD);
140 }
141
ScheduleDelayedWork(const Delegate::NextWorkInfo & next_work_info)142 void MessagePumpForUI::ScheduleDelayedWork(
143 const Delegate::NextWorkInfo& next_work_info) {
144 DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
145
146 // Since this is always called from |bound_thread_|, there is almost always
147 // nothing to do as the loop is already running. When the loop becomes idle,
148 // it will typically WaitForWork() in DoRunLoop() with the timeout provided by
149 // DoWork(). The only alternative to this is entering a native nested loop
150 // (e.g. modal dialog) under a
151 // `ScopedAllowApplicationTasksInNativeNestedLoop`, in which case
152 // HandleWorkMessage() will be invoked when the system picks up kMsgHaveWork
153 // and it will ScheduleNativeTimer() if it's out of immediate work. However,
154 // in that alternate scenario : it's possible for a Windows native work item
155 // (e.g. https://docs.microsoft.com/en-us/windows/desktop/winmsg/using-hooks)
156 // to wake the native nested loop and PostDelayedTask() to the current thread
157 // from it. This is the only case where we must install/adjust the native
158 // timer from ScheduleDelayedWork() because if we don't, the native loop will
159 // go back to sleep, unaware of the new |delayed_work_time|.
160 // See MessageLoopTest.PostDelayedTaskFromSystemPump for an example.
161 // TODO(gab): This could potentially be replaced by a ForegroundIdleProc hook
162 // if Windows ends up being the only platform requiring ScheduleDelayedWork().
163 if (in_nested_native_loop_with_application_tasks_ &&
164 !native_msg_scheduled_.load(std::memory_order_relaxed)) {
165 ScheduleNativeTimer(next_work_info);
166 }
167 }
168
HandleNestedNativeLoopWithApplicationTasks(bool application_tasks_desired)169 bool MessagePumpForUI::HandleNestedNativeLoopWithApplicationTasks(
170 bool application_tasks_desired) {
171 // It is here assumed that we will be in a native loop until either
172 // DoRunLoop() gets control back, or this is called with `false`, and thus the
173 // Windows event queue is to be used for synchronization. This is to prevent
174 // being unable to wake up for application tasks in the case of a nested loop.
175 in_nested_native_loop_with_application_tasks_ = application_tasks_desired;
176 if (application_tasks_desired) {
177 ScheduleWork();
178 }
179 return true;
180 }
181
AddObserver(Observer * observer)182 void MessagePumpForUI::AddObserver(Observer* observer) {
183 DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
184 observers_.AddObserver(observer);
185 }
186
RemoveObserver(Observer * observer)187 void MessagePumpForUI::RemoveObserver(Observer* observer) {
188 DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
189 observers_.RemoveObserver(observer);
190 }
191
192 //-----------------------------------------------------------------------------
193 // MessagePumpForUI private:
194
MessageCallback(UINT message,WPARAM wparam,LPARAM lparam,LRESULT * result)195 bool MessagePumpForUI::MessageCallback(
196 UINT message, WPARAM wparam, LPARAM lparam, LRESULT* result) {
197 DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
198 switch (message) {
199 case kMsgHaveWork:
200 HandleWorkMessage();
201 break;
202 case WM_TIMER:
203 if (wparam == reinterpret_cast<UINT_PTR>(this))
204 HandleTimerMessage();
205 break;
206 }
207 return false;
208 }
209
DoRunLoop()210 void MessagePumpForUI::DoRunLoop() {
211 DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
212
213 // IF this was just a simple PeekMessage() loop (servicing all possible work
214 // queues), then Windows would try to achieve the following order according
215 // to MSDN documentation about PeekMessage with no filter):
216 // * Sent messages
217 // * Posted messages
218 // * Sent messages (again)
219 // * WM_PAINT messages
220 // * WM_TIMER messages
221 //
222 // Summary: none of the above classes is starved, and sent messages has twice
223 // the chance of being processed (i.e., reduced service time).
224
225 wakeup_state_ = WakeupState::kRunning;
226
227 for (;;) {
228 // If we do any work, we may create more messages etc., and more work may
229 // possibly be waiting in another task group. When we (for example)
230 // ProcessNextWindowsMessage(), there is a good chance there are still more
231 // messages waiting. On the other hand, when any of these methods return
232 // having done no work, then it is pretty unlikely that calling them again
233 // quickly will find any work to do. Finally, if they all say they had no
234 // work, then it is a good time to consider sleeping (waiting) for more
235 // work.
236
237 in_nested_native_loop_with_application_tasks_ = false;
238 bool more_work_is_plausible = false;
239
240 if (!g_ui_pump_improvements_win ||
241 wakeup_state_ != WakeupState::kApplicationTask) {
242 more_work_is_plausible |= ProcessNextWindowsMessage();
243 // We can end up in native loops which allow application tasks outside of
244 // DoWork() when Windows calls back a Win32 message window owned by some
245 // Chromium code.
246 in_nested_native_loop_with_application_tasks_ = false;
247 if (run_state_->should_quit) {
248 break;
249 }
250 }
251
252 Delegate::NextWorkInfo next_work_info = run_state_->delegate->DoWork();
253 // Since nested native loops with application tasks are initiated by a
254 // scoper, they should always be cleared before exiting DoWork().
255 DCHECK(!in_nested_native_loop_with_application_tasks_);
256 wakeup_state_ = WakeupState::kRunning;
257 more_work_is_plausible |= next_work_info.is_immediate();
258
259 if (run_state_->should_quit) {
260 break;
261 }
262
263 if (installed_native_timer_) {
264 // As described in ScheduleNativeTimer(), the native timer is only
265 // installed and needed while in a nested native loop. If it is installed,
266 // it means the above work entered such a loop. Having now resumed, the
267 // native timer is no longer needed.
268 KillNativeTimer();
269 }
270
271 if (more_work_is_plausible)
272 continue;
273
274 more_work_is_plausible = run_state_->delegate->DoIdleWork();
275 // DoIdleWork() shouldn't end up in native nested loops, nor should it
276 // permit native nested loops, and thus shouldn't have any chance of
277 // reinstalling a native timer.
278 DCHECK(!in_nested_native_loop_with_application_tasks_);
279 DCHECK(!installed_native_timer_);
280 if (run_state_->should_quit) {
281 break;
282 }
283
284 if (more_work_is_plausible)
285 continue;
286
287 WaitForWork(next_work_info);
288 }
289 }
290
WaitForWork(Delegate::NextWorkInfo next_work_info)291 void MessagePumpForUI::WaitForWork(Delegate::NextWorkInfo next_work_info) {
292 DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
293
294 // Wait until a message is available, up to the time needed by the timer
295 // manager to fire the next set of timers.
296 DWORD wait_flags = MWMO_INPUTAVAILABLE;
297 bool last_wakeup_was_spurious = false;
298 for (DWORD delay = GetSleepTimeoutMs(next_work_info.delayed_run_time,
299 next_work_info.recent_now);
300 delay != 0; delay = GetSleepTimeoutMs(next_work_info.delayed_run_time)) {
301 if (!last_wakeup_was_spurious) {
302 run_state_->delegate->BeforeWait();
303 }
304 last_wakeup_was_spurious = false;
305
306 // Tell the optimizer to retain these values to simplify analyzing hangs.
307 base::debug::Alias(&delay);
308 base::debug::Alias(&wait_flags);
309 DWORD result;
310 if (g_ui_pump_improvements_win) {
311 HANDLE event_handle = event_.handle();
312 result = MsgWaitForMultipleObjectsEx(1, &event_handle, delay, QS_ALLINPUT,
313 wait_flags);
314 DPCHECK(WAIT_FAILED != result);
315 if (result == WAIT_OBJECT_0) {
316 wakeup_state_ = WakeupState::kApplicationTask;
317 } else if (result == WAIT_OBJECT_0 + 1) {
318 wakeup_state_ = WakeupState::kNative;
319 } else {
320 wakeup_state_ = WakeupState::kInactive;
321 }
322 } else {
323 result = MsgWaitForMultipleObjectsEx(0, nullptr, delay, QS_ALLINPUT,
324 wait_flags);
325 DPCHECK(WAIT_FAILED != result);
326 if (result == WAIT_OBJECT_0) {
327 wakeup_state_ = WakeupState::kNative;
328 } else {
329 wakeup_state_ = WakeupState::kInactive;
330 }
331 }
332
333 if (wakeup_state_ == WakeupState::kApplicationTask) {
334 // This can only be reached when the pump woke up via `event_`. In that
335 // case, tasks are prioritized over native.
336 return;
337 } else if (wakeup_state_ == WakeupState::kNative) {
338 // A WM_* message is available.
339 // If a parent child relationship exists between windows across threads
340 // then their thread inputs are implicitly attached.
341 // This causes the MsgWaitForMultipleObjectsEx API to return indicating
342 // that messages are ready for processing (Specifically, mouse messages
343 // intended for the child window may appear if the child window has
344 // capture).
345 // The subsequent PeekMessages call may fail to return any messages thus
346 // causing us to enter a tight loop at times.
347 // The code below is a workaround to give the child window
348 // some time to process its input messages by looping back to
349 // MsgWaitForMultipleObjectsEx above when there are no messages for the
350 // current thread.
351
352 // As in ProcessNextWindowsMessage().
353 auto scoped_do_work_item = run_state_->delegate->BeginWorkItem();
354 {
355 TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("base"),
356 "MessagePumpForUI::WaitForWork GetQueueStatus");
357 if (HIWORD(::GetQueueStatus(QS_SENDMESSAGE)) & QS_SENDMESSAGE)
358 return;
359 }
360 {
361 MSG msg;
362 TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("base"),
363 "MessagePumpForUI::WaitForWork PeekMessage");
364 if (::PeekMessage(&msg, nullptr, 0, 0, PM_NOREMOVE)) {
365 return;
366 }
367 }
368
369 // We know there are no more messages for this thread because PeekMessage
370 // has returned false. Reset |wait_flags| so that we wait for a *new*
371 // message.
372 wait_flags = 0;
373 } else {
374 DCHECK_EQ(wakeup_state_, WakeupState::kInactive);
375 last_wakeup_was_spurious = true;
376 TRACE_EVENT_INSTANT(
377 "base", "MessagePumpForUI::WaitForWork Spurious Wakeup",
378 [&](perfetto::EventContext ctx) {
379 ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>()
380 ->set_chrome_message_pump_for_ui()
381 ->set_wait_for_object_result(result);
382 });
383 }
384 }
385 }
386
HandleWorkMessage()387 void MessagePumpForUI::HandleWorkMessage() {
388 DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
389
390 // If we are being called outside of the context of Run, then don't try to do
391 // any work. This could correspond to a MessageBox call or something of that
392 // sort.
393 if (!run_state_) {
394 // Since we handled a kMsgHaveWork message, we must still update this flag.
395 native_msg_scheduled_.store(false, std::memory_order_relaxed);
396 return;
397 }
398
399 // Let whatever would have run had we not been putting messages in the queue
400 // run now. This is an attempt to make our dummy message not starve other
401 // messages that may be in the Windows message queue.
402 ProcessPumpReplacementMessage();
403
404 Delegate::NextWorkInfo next_work_info = run_state_->delegate->DoWork();
405 if (next_work_info.is_immediate()) {
406 ScheduleWork();
407 } else {
408 run_state_->delegate->BeforeWait();
409 ScheduleNativeTimer(next_work_info);
410 }
411 }
412
HandleTimerMessage()413 void MessagePumpForUI::HandleTimerMessage() {
414 DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
415
416 // ::KillTimer doesn't remove pending WM_TIMER messages from the queue,
417 // explicitly ignore the last WM_TIMER message in that case to avoid handling
418 // work from here when DoRunLoop() is active (which could result in scheduling
419 // work from two places at once). Note: we're still fine in the event that a
420 // second native nested loop is entered before such a dead WM_TIMER message is
421 // discarded because ::SetTimer merely resets the timer if invoked twice with
422 // the same id.
423 if (!installed_native_timer_)
424 return;
425
426 // We only need to fire once per specific delay, another timer may be
427 // scheduled below but we're done with this one.
428 KillNativeTimer();
429
430 // If we are being called outside of the context of Run, then don't do
431 // anything. This could correspond to a MessageBox call or something of
432 // that sort.
433 if (!run_state_)
434 return;
435
436 Delegate::NextWorkInfo next_work_info = run_state_->delegate->DoWork();
437 if (next_work_info.is_immediate()) {
438 ScheduleWork();
439 } else {
440 run_state_->delegate->BeforeWait();
441 ScheduleNativeTimer(next_work_info);
442 }
443 }
444
ScheduleNativeTimer(Delegate::NextWorkInfo next_work_info)445 void MessagePumpForUI::ScheduleNativeTimer(
446 Delegate::NextWorkInfo next_work_info) {
447 DCHECK(!next_work_info.is_immediate());
448 // We should only ScheduleNativeTimer() under the new pump implementation
449 // while nested with application tasks.
450 DCHECK(!g_ui_pump_improvements_win ||
451 in_nested_native_loop_with_application_tasks_);
452
453 // Do not redundantly set the same native timer again if it was already set.
454 // This can happen when a nested native loop goes idle with pending delayed
455 // tasks, then gets woken up by an immediate task, and goes back to idle with
456 // the same pending delay. No need to kill the native timer if there is
457 // already one but the |delayed_run_time| has changed as ::SetTimer reuses the
458 // same id and will replace and reset the existing timer.
459 if (installed_native_timer_ &&
460 *installed_native_timer_ == next_work_info.delayed_run_time) {
461 return;
462 }
463
464 if (next_work_info.delayed_run_time.is_max())
465 return;
466
467 // We do not use native Windows timers in general as they have a poor, 10ms,
468 // granularity. Instead we rely on MsgWaitForMultipleObjectsEx's
469 // high-resolution timeout to sleep without timers in WaitForWork(). However,
470 // when entering a nested native ::GetMessage() loop (e.g. native modal
471 // windows) under a `ScopedAllowApplicationTasksInNativeNestedLoop`, we have
472 // to rely on a native timer when HandleWorkMessage() runs out of immediate
473 // work. Since `ScopedAllowApplicationTasksInNativeNestedLoop` invokes
474 // ScheduleWork() : we are guaranteed that HandleWorkMessage() will be called
475 // after entering a nested native loop that should process application
476 // tasks. But once HandleWorkMessage() is out of immediate work, ::SetTimer()
477 // is used to guarantee we are invoked again should the next delayed task
478 // expire before the nested native loop ends. The native timer being
479 // unnecessary once we return to our DoRunLoop(), we ::KillTimer when it
480 // resumes (nested native loops should be rare so we're not worried about
481 // ::SetTimer<=>::KillTimer churn). TODO(gab): The long-standing legacy
482 // dependency on the behavior of
483 // `ScopedAllowApplicationTasksInNativeNestedLoop` is unfortunate, would be
484 // nice to make this a MessagePump concept (instead of requiring impls to
485 // invoke ScheduleWork() one-way and no-op DoWork() the other way).
486
487 UINT delay_msec = strict_cast<UINT>(GetSleepTimeoutMs(
488 next_work_info.delayed_run_time, next_work_info.recent_now));
489 if (delay_msec == 0) {
490 ScheduleWork();
491 } else {
492 // TODO(gab): ::SetTimer()'s documentation claims it does this for us.
493 // Consider removing this safety net.
494 delay_msec = std::clamp(delay_msec, static_cast<UINT>(USER_TIMER_MINIMUM),
495 static_cast<UINT>(USER_TIMER_MAXIMUM));
496
497 // Tell the optimizer to retain the delay to simplify analyzing hangs.
498 base::debug::Alias(&delay_msec);
499 const UINT_PTR ret =
500 ::SetTimer(message_window_.hwnd(), reinterpret_cast<UINT_PTR>(this),
501 delay_msec, nullptr);
502
503 if (ret) {
504 installed_native_timer_ = next_work_info.delayed_run_time;
505 return;
506 }
507 // This error is likely similar to MESSAGE_POST_ERROR (i.e. native queue is
508 // full). Since we only use ScheduleNativeTimer() in native nested loops
509 // this likely means this pump will not be given a chance to run application
510 // tasks until the nested loop completes.
511 TRACE_EVENT_INSTANT0("base", "Chrome.MessageLoopProblem.SET_TIMER_ERROR",
512 TRACE_EVENT_SCOPE_THREAD);
513 }
514 }
515
KillNativeTimer()516 void MessagePumpForUI::KillNativeTimer() {
517 DCHECK(installed_native_timer_);
518 const bool success =
519 ::KillTimer(message_window_.hwnd(), reinterpret_cast<UINT_PTR>(this));
520 DPCHECK(success);
521 installed_native_timer_.reset();
522 }
523
ProcessNextWindowsMessage()524 bool MessagePumpForUI::ProcessNextWindowsMessage() {
525 DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
526
527 MSG msg;
528 bool has_msg = false;
529 bool more_work_is_plausible = false;
530 {
531 // ::PeekMessage() may process sent and/or internal messages (regardless of
532 // |had_messages| as ::GetQueueStatus() is an optimistic check that may
533 // racily have missed an incoming event -- it doesn't hurt to have empty
534 // internal units of work when ::PeekMessage turns out to be a no-op).
535 // Instantiate |scoped_do_work| ahead of GetQueueStatus() so that
536 // trace events it emits fully outscope GetQueueStatus' events
537 // (GetQueueStatus() itself not being expected to do work; it's fine to use
538 // only one ScopedDoWorkItem for both calls -- we trace them independently
539 // just in case internal work stalls).
540 auto scoped_do_work_item = run_state_->delegate->BeginWorkItem();
541
542 {
543 // Individually trace ::GetQueueStatus and ::PeekMessage because sampling
544 // profiler is hinting that we're spending a surprising amount of time
545 // with these on top of the stack. Tracing will be able to tell us whether
546 // this is a bias of sampling profiler (e.g. kernel takes ::GetQueueStatus
547 // as an opportunity to swap threads and is more likely to schedule the
548 // sampling profiler's thread while the sampled thread is swapped out on
549 // this frame).
550 TRACE_EVENT0(
551 TRACE_DISABLED_BY_DEFAULT("base"),
552 "MessagePumpForUI::ProcessNextWindowsMessage GetQueueStatus");
553 DWORD queue_status = ::GetQueueStatus(QS_SENDMESSAGE);
554
555 // If there are sent messages in the queue then PeekMessage internally
556 // dispatches the message and returns false. We return true in this case
557 // to ensure that the message loop peeks again instead of calling
558 // MsgWaitForMultipleObjectsEx.
559 if (HIWORD(queue_status) & QS_SENDMESSAGE)
560 more_work_is_plausible = true;
561 }
562
563 {
564 // PeekMessage can run a message if there are sent messages, trace that
565 // and emit the boolean param to see if it ever janks independently (ref.
566 // comment on GetQueueStatus).
567 TRACE_EVENT(
568 TRACE_DISABLED_BY_DEFAULT("base"),
569 "MessagePumpForUI::ProcessNextWindowsMessage PeekMessage",
570 [&](perfetto::EventContext ctx) {
571 perfetto::protos::pbzero::ChromeMessagePump* msg_pump_data =
572 ctx.event()->set_chrome_message_pump();
573 msg_pump_data->set_sent_messages_in_queue(more_work_is_plausible);
574 });
575 has_msg = ::PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE) != FALSE;
576 }
577 }
578 if (has_msg)
579 more_work_is_plausible |= ProcessMessageHelper(msg);
580
581 return more_work_is_plausible;
582 }
583
ProcessMessageHelper(const MSG & msg)584 bool MessagePumpForUI::ProcessMessageHelper(const MSG& msg) {
585 DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
586
587 if (msg.message == WM_QUIT) {
588 // WM_QUIT is the standard way to exit a ::GetMessage() loop. Our
589 // MessageLoop has its own quit mechanism, so WM_QUIT is generally
590 // unexpected.
591 TRACE_EVENT_INSTANT0("base",
592 "Chrome.MessageLoopProblem.RECEIVED_WM_QUIT_ERROR",
593 TRACE_EVENT_SCOPE_THREAD);
594 return true;
595 }
596
597 // While running our main message pump, we discard kMsgHaveWork messages.
598 if (msg.message == kMsgHaveWork && msg.hwnd == message_window_.hwnd())
599 return ProcessPumpReplacementMessage();
600
601 run_state_->delegate->BeginNativeWorkBeforeDoWork();
602 auto scoped_do_work_item = run_state_->delegate->BeginWorkItem();
603
604 TRACE_EVENT("base,toplevel", "MessagePumpForUI DispatchMessage",
605 [&](perfetto::EventContext ctx) {
606 ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>()
607 ->set_chrome_message_pump_for_ui()
608 ->set_message_id(msg.message);
609 });
610
611 for (Observer& observer : observers_)
612 observer.WillDispatchMSG(msg);
613 ::TranslateMessage(&msg);
614 ::DispatchMessage(&msg);
615 for (Observer& observer : observers_)
616 observer.DidDispatchMSG(msg);
617
618 return true;
619 }
620
ProcessPumpReplacementMessage()621 bool MessagePumpForUI::ProcessPumpReplacementMessage() {
622 DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
623
624 // When we encounter a kMsgHaveWork message, this method is called to peek and
625 // process a replacement message. The goal is to make the kMsgHaveWork as non-
626 // intrusive as possible, even though a continuous stream of such messages are
627 // posted. This method carefully peeks a message while there is no chance for
628 // a kMsgHaveWork to be pending, then resets the |have_work_| flag (allowing a
629 // replacement kMsgHaveWork to possibly be posted), and finally dispatches
630 // that peeked replacement. Note that the re-post of kMsgHaveWork may be
631 // asynchronous to this thread!!
632
633 MSG msg;
634 bool have_message = false;
635 {
636 // Note: Ideally this call wouldn't process sent-messages (as we already did
637 // that in the PeekMessage call that lead to receiving this kMsgHaveWork),
638 // but there's no way to specify this (omitting PM_QS_SENDMESSAGE as in
639 // crrev.com/791043 doesn't do anything). Hence this call must be considered
640 // as a potential work item.
641 auto scoped_do_work_item = run_state_->delegate->BeginWorkItem();
642 TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("base"),
643 "MessagePumpForUI::ProcessPumpReplacementMessage PeekMessage");
644 have_message = ::PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE) != FALSE;
645 }
646
647 // Expect no message or a message different than kMsgHaveWork.
648 DCHECK(!have_message || kMsgHaveWork != msg.message ||
649 msg.hwnd != message_window_.hwnd());
650
651 // Since we discarded a kMsgHaveWork message, we must update the flag.
652 DCHECK(native_msg_scheduled_.load(std::memory_order_relaxed));
653 native_msg_scheduled_.store(false, std::memory_order_relaxed);
654
655 // We don't need a special time slice if we didn't |have_message| to process.
656 if (!have_message)
657 return false;
658
659 if (msg.message == WM_QUIT) {
660 // If we're in a nested ::GetMessage() loop then we must let that loop see
661 // the WM_QUIT in order for it to exit. If we're in DoRunLoop then the re-
662 // posted WM_QUIT will be either ignored, or handled, by
663 // ProcessMessageHelper() called directly from ProcessNextWindowsMessage().
664 ::PostQuitMessage(static_cast<int>(msg.wParam));
665 // Note: we *must not* ScheduleWork() here as WM_QUIT is a low-priority
666 // message on Windows (it is only returned by ::PeekMessage() when idle) :
667 // https://blogs.msdn.microsoft.com/oldnewthing/20051104-33/?p=33453. As
668 // such posting a kMsgHaveWork message via ScheduleWork() would cause an
669 // infinite loop (kMsgHaveWork message handled first means we end up here
670 // again and repost WM_QUIT+ScheduleWork() again, etc.). Not leaving a
671 // kMsgHaveWork message behind however is also problematic as unwinding
672 // multiple layers of nested ::GetMessage() loops can result in starving
673 // application tasks. TODO(https://crbug.com/890016) : Fix this.
674
675 // The return value is mostly irrelevant but return true like we would after
676 // processing a QuitClosure() task.
677 return true;
678 } else if (msg.message == WM_TIMER &&
679 msg.wParam == reinterpret_cast<UINT_PTR>(this)) {
680 // This happens when a native nested loop invokes HandleWorkMessage() =>
681 // ProcessPumpReplacementMessage() which finds the WM_TIMER message
682 // installed by ScheduleNativeTimer(). That message needs to be handled
683 // directly as handing it off to ProcessMessageHelper() below would cause an
684 // unnecessary ScopedDoWorkItem which may incorrectly lead the Delegate's
685 // heuristics to conclude that the DoWork() in HandleTimerMessage() is
686 // nested inside a native work item. It's also safe to skip the below
687 // ScheduleWork() as it is not mandatory before invoking DoWork() and
688 // HandleTimerMessage() handles re-installing the necessary followup
689 // messages.
690 HandleTimerMessage();
691 return true;
692 }
693
694 // Guarantee we'll get another time slice in the case where we go into native
695 // windows code. This ScheduleWork() may hurt performance a tiny bit when
696 // tasks appear very infrequently, but when the event queue is busy, the
697 // kMsgHaveWork events get (percentage wise) rarer and rarer.
698 ScheduleWork();
699 return ProcessMessageHelper(msg);
700 }
701
702 //-----------------------------------------------------------------------------
703 // MessagePumpForIO public:
704
IOContext()705 MessagePumpForIO::IOContext::IOContext() {
706 memset(&overlapped, 0, sizeof(overlapped));
707 }
708
IOHandler(const Location & from_here)709 MessagePumpForIO::IOHandler::IOHandler(const Location& from_here)
710 : io_handler_location_(from_here) {}
711
712 MessagePumpForIO::IOHandler::~IOHandler() = default;
713
MessagePumpForIO()714 MessagePumpForIO::MessagePumpForIO() {
715 port_.Set(::CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr,
716 reinterpret_cast<ULONG_PTR>(nullptr), 1));
717 DCHECK(port_.is_valid());
718 }
719
720 MessagePumpForIO::~MessagePumpForIO() = default;
721
ScheduleWork()722 void MessagePumpForIO::ScheduleWork() {
723 // This is the only MessagePumpForIO method which can be called outside of
724 // |bound_thread_|.
725
726 bool not_scheduled = false;
727 if (!native_msg_scheduled_.compare_exchange_strong(
728 not_scheduled, true, std::memory_order_relaxed)) {
729 return; // Work already scheduled.
730 }
731
732 // Make sure the MessagePump does some work for us.
733 const BOOL ret = ::PostQueuedCompletionStatus(
734 port_.get(), 0, reinterpret_cast<ULONG_PTR>(this),
735 reinterpret_cast<OVERLAPPED*>(this));
736 if (ret)
737 return; // Post worked perfectly.
738
739 // See comment in MessagePumpForUI::ScheduleWork() for this error recovery.
740
741 native_msg_scheduled_.store(
742 false, std::memory_order_relaxed); // Clarify that we didn't succeed.
743 TRACE_EVENT_INSTANT0("base",
744 "Chrome.MessageLoopProblem.COMPLETION_POST_ERROR",
745 TRACE_EVENT_SCOPE_THREAD);
746 }
747
ScheduleDelayedWork(const Delegate::NextWorkInfo & next_work_info)748 void MessagePumpForIO::ScheduleDelayedWork(
749 const Delegate::NextWorkInfo& next_work_info) {
750 DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
751
752 // Since this is always called from |bound_thread_|, there is nothing to do as
753 // the loop is already running. It will WaitForWork() in
754 // DoRunLoop() with the correct timeout when it's out of immediate tasks.
755 }
756
RegisterIOHandler(HANDLE file_handle,IOHandler * handler)757 HRESULT MessagePumpForIO::RegisterIOHandler(HANDLE file_handle,
758 IOHandler* handler) {
759 DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
760
761 HANDLE port = ::CreateIoCompletionPort(
762 file_handle, port_.get(), reinterpret_cast<ULONG_PTR>(handler), 1);
763 return (port != nullptr) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
764 }
765
RegisterJobObject(HANDLE job_handle,IOHandler * handler)766 bool MessagePumpForIO::RegisterJobObject(HANDLE job_handle,
767 IOHandler* handler) {
768 DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
769
770 JOBOBJECT_ASSOCIATE_COMPLETION_PORT info;
771 info.CompletionKey = handler;
772 info.CompletionPort = port_.get();
773 return ::SetInformationJobObject(job_handle,
774 JobObjectAssociateCompletionPortInformation,
775 &info, sizeof(info)) != FALSE;
776 }
777
778 //-----------------------------------------------------------------------------
779 // MessagePumpForIO private:
780
DoRunLoop()781 void MessagePumpForIO::DoRunLoop() {
782 DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
783
784 for (;;) {
785 // If we do any work, we may create more messages etc., and more work may
786 // possibly be waiting in another task group. When we (for example)
787 // WaitForIOCompletion(), there is a good chance there are still more
788 // messages waiting. On the other hand, when any of these methods return
789 // having done no work, then it is pretty unlikely that calling them
790 // again quickly will find any work to do. Finally, if they all say they
791 // had no work, then it is a good time to consider sleeping (waiting) for
792 // more work.
793
794 Delegate::NextWorkInfo next_work_info = run_state_->delegate->DoWork();
795 bool more_work_is_plausible = next_work_info.is_immediate();
796 if (run_state_->should_quit)
797 break;
798
799 more_work_is_plausible |= WaitForIOCompletion(0);
800 if (run_state_->should_quit)
801 break;
802
803 if (more_work_is_plausible)
804 continue;
805
806 more_work_is_plausible = run_state_->delegate->DoIdleWork();
807 if (run_state_->should_quit)
808 break;
809
810 if (more_work_is_plausible)
811 continue;
812
813 run_state_->delegate->BeforeWait();
814 WaitForWork(next_work_info);
815 }
816 }
817
818 // Wait until IO completes, up to the time needed by the timer manager to fire
819 // the next set of timers.
WaitForWork(Delegate::NextWorkInfo next_work_info)820 void MessagePumpForIO::WaitForWork(Delegate::NextWorkInfo next_work_info) {
821 DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
822
823 // We do not support nested IO message loops. This is to avoid messy
824 // recursion problems.
825 DCHECK(!run_state_->is_nested) << "Cannot nest an IO message loop!";
826
827 DWORD timeout = GetSleepTimeoutMs(next_work_info.delayed_run_time,
828 next_work_info.recent_now);
829
830 // Tell the optimizer to retain these values to simplify analyzing hangs.
831 base::debug::Alias(&timeout);
832 WaitForIOCompletion(timeout);
833 }
834
WaitForIOCompletion(DWORD timeout)835 bool MessagePumpForIO::WaitForIOCompletion(DWORD timeout) {
836 DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
837
838 IOItem item;
839 if (!GetIOItem(timeout, &item))
840 return false;
841
842 if (ProcessInternalIOItem(item))
843 return true;
844
845 run_state_->delegate->BeginNativeWorkBeforeDoWork();
846 auto scoped_do_work_item = run_state_->delegate->BeginWorkItem();
847
848 TRACE_EVENT(
849 "base,toplevel", "IOHandler::OnIOCompleted",
850 [&](perfetto::EventContext ctx) {
851 ctx.event()->set_chrome_message_pump()->set_io_handler_location_iid(
852 base::trace_event::InternedSourceLocation::Get(
853 &ctx, base::trace_event::TraceSourceLocation(
854 item.handler->io_handler_location())));
855 });
856
857 item.handler.ExtractAsDangling()->OnIOCompleted(
858 item.context.ExtractAsDangling(), item.bytes_transfered, item.error);
859
860 return true;
861 }
862
863 // Asks the OS for another IO completion result.
GetIOItem(DWORD timeout,IOItem * item)864 bool MessagePumpForIO::GetIOItem(DWORD timeout, IOItem* item) {
865 DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
866
867 memset(item, 0, sizeof(*item));
868 ULONG_PTR key = reinterpret_cast<ULONG_PTR>(nullptr);
869 OVERLAPPED* overlapped = nullptr;
870 if (!::GetQueuedCompletionStatus(port_.get(), &item->bytes_transfered, &key,
871 &overlapped, timeout)) {
872 if (!overlapped)
873 return false; // Nothing in the queue.
874 item->error = GetLastError();
875 item->bytes_transfered = 0;
876 }
877
878 item->handler = reinterpret_cast<IOHandler*>(key);
879 item->context = reinterpret_cast<IOContext*>(overlapped);
880 return true;
881 }
882
ProcessInternalIOItem(const IOItem & item)883 bool MessagePumpForIO::ProcessInternalIOItem(const IOItem& item) {
884 DCHECK_CALLED_ON_VALID_THREAD(bound_thread_);
885
886 if (reinterpret_cast<void*>(this) ==
887 reinterpret_cast<void*>(item.context.get()) &&
888 reinterpret_cast<void*>(this) ==
889 reinterpret_cast<void*>(item.handler.get())) {
890 // This is our internal completion.
891 DCHECK(!item.bytes_transfered);
892 native_msg_scheduled_.store(false, std::memory_order_relaxed);
893 return true;
894 }
895 return false;
896 }
897
898 } // namespace base
899