1 // Copyright 2018 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.h"
6
7 #include <type_traits>
8
9 #include "base/functional/bind.h"
10 #include "base/logging.h"
11 #include "base/memory/raw_ptr.h"
12 #include "base/message_loop/message_pump_for_io.h"
13 #include "base/message_loop/message_pump_for_ui.h"
14 #include "base/message_loop/message_pump_type.h"
15 #include "base/run_loop.h"
16 #include "base/task/single_thread_task_executor.h"
17 #include "base/test/bind.h"
18 #include "base/test/test_timeouts.h"
19 #include "base/threading/thread.h"
20 #include "build/build_config.h"
21 #include "testing/gmock/include/gmock/gmock.h"
22 #include "testing/gtest/include/gtest/gtest.h"
23
24 #if BUILDFLAG(IS_WIN)
25 #include <windows.h>
26 #endif
27
28 #if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_NACL)
29 #include "base/message_loop/message_pump_libevent.h"
30 #endif
31
32 using ::testing::_;
33 using ::testing::AnyNumber;
34 using ::testing::AtMost;
35 using ::testing::Invoke;
36 using ::testing::Return;
37
38 namespace base {
39
40 namespace {
41
42 // On most platforms, the MessagePump impl controls when native work (e.g.
43 // handling input messages) gets its turn. Tests below verify that by expecting
44 // OnBeginWorkItem() calls that cover native work. In some configurations
45 // however, the platform owns the message loop and is the one yielding to
46 // Chrome's MessagePump to DoWork(). Under those configurations, it is not
47 // possible to precisely account for OnBeginWorkItem() calls as they can occur
48 // nondeterministically. For example, on some versions of iOS, the native loop
49 // can surprisingly go through multiple cycles of
50 // kCFRunLoopAfterWaiting=>kCFRunLoopBeforeWaiting before invoking Chrome's
51 // RunWork() for the first time, triggering multiple ScopedDoWorkItem 's for
52 // potential native work before the first DoWork().
ChromeControlsNativeEventProcessing(MessagePumpType pump_type)53 constexpr bool ChromeControlsNativeEventProcessing(MessagePumpType pump_type) {
54 #if BUILDFLAG(IS_MAC)
55 return pump_type != MessagePumpType::UI;
56 #elif BUILDFLAG(IS_IOS)
57 return false;
58 #else
59 return true;
60 #endif
61 }
62
63 class MockMessagePumpDelegate : public MessagePump::Delegate {
64 public:
MockMessagePumpDelegate(MessagePumpType pump_type)65 explicit MockMessagePumpDelegate(MessagePumpType pump_type)
66 : check_work_items_(ChromeControlsNativeEventProcessing(pump_type)),
67 native_work_item_accounting_is_on_(
68 !ChromeControlsNativeEventProcessing(pump_type)) {}
69
~MockMessagePumpDelegate()70 ~MockMessagePumpDelegate() override { ValidateNoOpenWorkItems(); }
71
72 MockMessagePumpDelegate(const MockMessagePumpDelegate&) = delete;
73 MockMessagePumpDelegate& operator=(const MockMessagePumpDelegate&) = delete;
74
BeforeWait()75 void BeforeWait() override {}
76 MOCK_METHOD0(DoWork, MessagePump::Delegate::NextWorkInfo());
77 MOCK_METHOD0(DoIdleWork, bool());
78
79 // Functions invoked directly by the message pump.
OnBeginWorkItem()80 void OnBeginWorkItem() override {
81 any_work_begun_ = true;
82
83 if (check_work_items_) {
84 MockOnBeginWorkItem();
85 }
86
87 ++work_item_count_;
88 }
89
OnEndWorkItem(int run_level_depth)90 void OnEndWorkItem(int run_level_depth) override {
91 if (check_work_items_) {
92 MockOnEndWorkItem(run_level_depth);
93 }
94
95 EXPECT_EQ(run_level_depth, work_item_count_);
96
97 --work_item_count_;
98
99 // It's not possible to close more scopes than there are open ones.
100 EXPECT_GE(work_item_count_, 0);
101 }
102
RunDepth()103 int RunDepth() override { return work_item_count_; }
104
ValidateNoOpenWorkItems()105 void ValidateNoOpenWorkItems() {
106 // Upon exiting there cannot be any open scopes.
107 EXPECT_EQ(work_item_count_, 0);
108
109 if (native_work_item_accounting_is_on_) {
110 // Tests should trigger work beginning at least once except on iOS where
111 // they need a call to MessagePumpUIApplication::Attach() to do so when on
112 // the UI thread.
113 #if !BUILDFLAG(IS_IOS)
114 EXPECT_TRUE(any_work_begun_);
115 #endif
116 }
117 }
118
119 // Mock functions for asserting.
120 MOCK_METHOD0(MockOnBeginWorkItem, void(void));
121 MOCK_METHOD1(MockOnEndWorkItem, void(int));
122
123 // If native events are covered in the current configuration it's not
124 // possible to precisely test all assertions related to work items. This is
125 // because a number of speculative WorkItems are created during execution of
126 // such loops and it's not possible to determine their number before the
127 // execution of the test. In such configurations the functioning of the
128 // message pump is still verified by looking at the counts of opened and
129 // closed WorkItems.
130 const bool check_work_items_;
131 const bool native_work_item_accounting_is_on_;
132
133 int work_item_count_ = 0;
134 bool any_work_begun_ = false;
135 };
136
137 class MessagePumpTest : public ::testing::TestWithParam<MessagePumpType> {
138 public:
MessagePumpTest()139 MessagePumpTest() : message_pump_(MessagePump::Create(GetParam())) {}
140
141 protected:
142 #if defined(USE_GLIB)
143 // Because of a GLIB implementation quirk, the pump doesn't do the same things
144 // between each DoWork. In this case, it won't set/clear a ScopedDoWorkItem
145 // because we run a chrome work item in the runloop outside of GLIB's control,
146 // so we oscillate between setting and not setting PreDoWorkExpectations.
147 std::map<MessagePump::Delegate*, int> do_work_counts;
148 #endif
AddPreDoWorkExpectations(testing::StrictMock<MockMessagePumpDelegate> & delegate)149 void AddPreDoWorkExpectations(
150 testing::StrictMock<MockMessagePumpDelegate>& delegate) {
151 #if BUILDFLAG(IS_WIN)
152 if (GetParam() == MessagePumpType::UI) {
153 // The Windows MessagePumpForUI may do native work from ::PeekMessage()
154 // and labels itself as such.
155 EXPECT_CALL(delegate, MockOnBeginWorkItem);
156 EXPECT_CALL(delegate, MockOnEndWorkItem);
157
158 // If the above event was MessagePumpForUI's own kMsgHaveWork internal
159 // event, it will process another event to replace it (ref.
160 // ProcessPumpReplacementMessage).
161 EXPECT_CALL(delegate, MockOnBeginWorkItem).Times(AtMost(1));
162 EXPECT_CALL(delegate, MockOnEndWorkItem).Times(AtMost(1));
163 }
164 #endif // BUILDFLAG(IS_WIN)
165 #if defined(USE_GLIB)
166 do_work_counts.try_emplace(&delegate, 0);
167 if (GetParam() == MessagePumpType::UI) {
168 if (++do_work_counts[&delegate] % 2) {
169 // The GLib MessagePump will do native work before chrome work on
170 // startup.
171 EXPECT_CALL(delegate, MockOnBeginWorkItem);
172 EXPECT_CALL(delegate, MockOnEndWorkItem);
173 }
174 }
175 #endif // defined(USE_GLIB)
176 }
177
AddPostDoWorkExpectations(testing::StrictMock<MockMessagePumpDelegate> & delegate)178 void AddPostDoWorkExpectations(
179 testing::StrictMock<MockMessagePumpDelegate>& delegate) {
180 #if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_NACL)
181 // MessagePumpLibEvent checks for native notifications once after processing
182 // a DoWork() but only instantiates a ScopedDoWorkItem that triggers
183 // MessagePumpLibevent::OnLibeventNotification() which this test does not
184 // so there are no post-work expectations at the moment.
185 #endif
186 #if defined(USE_GLIB)
187 if (GetParam() == MessagePumpType::UI) {
188 // The GLib MessagePump can create and destroy work items between DoWorks
189 // depending on internal state.
190 EXPECT_CALL(delegate, MockOnBeginWorkItem).Times(AtMost(1));
191 EXPECT_CALL(delegate, MockOnEndWorkItem).Times(AtMost(1));
192 }
193 #endif // defined(USE_GLIB)
194 }
195
196 std::unique_ptr<MessagePump> message_pump_;
197 };
198
199 } // namespace
200
TEST_P(MessagePumpTest,QuitStopsWork)201 TEST_P(MessagePumpTest, QuitStopsWork) {
202 testing::InSequence sequence;
203 testing::StrictMock<MockMessagePumpDelegate> delegate(GetParam());
204
205 AddPreDoWorkExpectations(delegate);
206
207 // Not expecting any calls to DoIdleWork after quitting, nor any of the
208 // PostDoWorkExpectations, quitting should be instantaneous.
209 EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this] {
210 message_pump_->Quit();
211 return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
212 }));
213
214 // MessagePumpGlib uses a work item between a HandleDispatch() call and
215 // passing control back to the chrome loop, which handles the Quit() despite
216 // us not necessarily doing any native work during that time.
217 #if defined(USE_GLIB)
218 if (GetParam() == MessagePumpType::UI) {
219 AddPostDoWorkExpectations(delegate);
220 }
221 #endif
222
223 EXPECT_CALL(delegate, DoIdleWork()).Times(0);
224
225 message_pump_->ScheduleWork();
226 message_pump_->Run(&delegate);
227 }
228
TEST_P(MessagePumpTest,QuitStopsWorkWithNestedRunLoop)229 TEST_P(MessagePumpTest, QuitStopsWorkWithNestedRunLoop) {
230 testing::InSequence sequence;
231 testing::StrictMock<MockMessagePumpDelegate> delegate(GetParam());
232 testing::StrictMock<MockMessagePumpDelegate> nested_delegate(GetParam());
233
234 AddPreDoWorkExpectations(delegate);
235
236 // We first schedule a call to DoWork, which runs a nested run loop. After
237 // the nested loop exits, we schedule another DoWork which quits the outer
238 // (original) run loop. The test verifies that there are no extra calls to
239 // DoWork after the outer loop quits.
240 EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([&] {
241 message_pump_->Run(&nested_delegate);
242 // A null NextWorkInfo indicates immediate follow-up work.
243 return MessagePump::Delegate::NextWorkInfo();
244 }));
245
246 AddPreDoWorkExpectations(nested_delegate);
247 EXPECT_CALL(nested_delegate, DoWork).WillOnce(Invoke([&] {
248 // Quit the nested run loop.
249 message_pump_->Quit();
250 // The underlying pump should process the next task in the first run-level
251 // regardless of whether the nested run-level indicates there's no more work
252 // (e.g. can happen when the only remaining tasks are non-nestable).
253 return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
254 }));
255
256 // The `nested_delegate` will quit first.
257 AddPostDoWorkExpectations(nested_delegate);
258
259 // Return a delayed task with |yield_to_native| set, and exit.
260 AddPostDoWorkExpectations(delegate);
261
262 AddPreDoWorkExpectations(delegate);
263
264 EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this] {
265 message_pump_->Quit();
266 return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
267 }));
268
269 message_pump_->ScheduleWork();
270 message_pump_->Run(&delegate);
271 }
272
TEST_P(MessagePumpTest,YieldToNativeRequestedSmokeTest)273 TEST_P(MessagePumpTest, YieldToNativeRequestedSmokeTest) {
274 // The handling of the "yield_to_native" boolean in the NextWorkInfo is only
275 // implemented on the MessagePumpForUI on android. However since we inject a
276 // fake one for testing this is hard to test. This test ensures that setting
277 // this boolean doesn't cause any MessagePump to explode.
278 testing::StrictMock<MockMessagePumpDelegate> delegate(GetParam());
279
280 testing::InSequence sequence;
281
282 // Return an immediate task with |yield_to_native| set.
283 AddPreDoWorkExpectations(delegate);
284 EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([] {
285 return MessagePump::Delegate::NextWorkInfo{TimeTicks(), TimeTicks(),
286 /* yield_to_native = */ true};
287 }));
288 AddPostDoWorkExpectations(delegate);
289
290 AddPreDoWorkExpectations(delegate);
291 // Return a delayed task with |yield_to_native| set, and exit.
292 EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this] {
293 message_pump_->Quit();
294 auto now = TimeTicks::Now();
295 return MessagePump::Delegate::NextWorkInfo{now + Milliseconds(1), now,
296 true};
297 }));
298 EXPECT_CALL(delegate, DoIdleWork()).Times(AnyNumber());
299
300 message_pump_->ScheduleWork();
301 message_pump_->Run(&delegate);
302 }
303
304 namespace {
305
306 class TimerSlackTestDelegate : public MessagePump::Delegate {
307 public:
TimerSlackTestDelegate(MessagePump * message_pump)308 TimerSlackTestDelegate(MessagePump* message_pump)
309 : message_pump_(message_pump) {
310 // We first schedule a delayed task far in the future with maximum timer
311 // slack.
312 message_pump_->SetTimerSlack(TIMER_SLACK_MAXIMUM);
313 const TimeTicks now = TimeTicks::Now();
314 message_pump_->ScheduleDelayedWork({now + Hours(1), now});
315
316 // Since we have no other work pending, the pump will initially be idle.
317 action_.store(NONE);
318 }
319
OnBeginWorkItem()320 void OnBeginWorkItem() override {}
OnEndWorkItem(int run_level_depth)321 void OnEndWorkItem(int run_level_depth) override {}
RunDepth()322 int RunDepth() override { return 0; }
BeforeWait()323 void BeforeWait() override {}
324
DoWork()325 MessagePump::Delegate::NextWorkInfo DoWork() override {
326 switch (action_.load()) {
327 case NONE:
328 break;
329 case SCHEDULE_DELAYED_WORK: {
330 // After being woken up by the other thread, we let the pump know that
331 // the next delayed task is in fact much sooner than the 1 hour delay it
332 // was aware of. If the pump refreshes its timer correctly, it will wake
333 // up shortly, finishing the test.
334 action_.store(QUIT);
335 TimeTicks now = TimeTicks::Now();
336 return {now + Milliseconds(50), now};
337 }
338 case QUIT:
339 message_pump_->Quit();
340 break;
341 }
342 return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
343 }
344
DoIdleWork()345 bool DoIdleWork() override { return false; }
346
WakeUpFromOtherThread()347 void WakeUpFromOtherThread() {
348 action_.store(SCHEDULE_DELAYED_WORK);
349 message_pump_->ScheduleWork();
350 }
351
352 private:
353 enum Action {
354 NONE,
355 SCHEDULE_DELAYED_WORK,
356 QUIT,
357 };
358
359 const raw_ptr<MessagePump> message_pump_;
360 std::atomic<Action> action_;
361 };
362
363 } // namespace
364
TEST_P(MessagePumpTest,TimerSlackWithLongDelays)365 TEST_P(MessagePumpTest, TimerSlackWithLongDelays) {
366 // This is a regression test for an issue where the iOS message pump fails to
367 // run delayed work when timer slack is enabled. The steps needed to trigger
368 // this are:
369 //
370 // 1. The message pump timer slack is set to maximum.
371 // 2. A delayed task is posted for far in the future (e.g., 1h).
372 // 3. The system goes idle at least for a few seconds.
373 // 4. Another delayed task is posted with a much smaller delay.
374 //
375 // The following message pump test delegate automatically runs through this
376 // sequence.
377 TimerSlackTestDelegate delegate(message_pump_.get());
378
379 // We use another thread to wake up the pump after 2 seconds to allow the
380 // system to enter an idle state. This delay was determined experimentally on
381 // the iPhone 6S simulator.
382 Thread thread("Waking thread");
383 thread.StartAndWaitForTesting();
384 thread.task_runner()->PostDelayedTask(
385 FROM_HERE,
386 BindLambdaForTesting([&delegate] { delegate.WakeUpFromOtherThread(); }),
387 Seconds(2));
388
389 message_pump_->Run(&delegate);
390 }
391
TEST_P(MessagePumpTest,RunWithoutScheduleWorkInvokesDoWork)392 TEST_P(MessagePumpTest, RunWithoutScheduleWorkInvokesDoWork) {
393 testing::InSequence sequence;
394 testing::StrictMock<MockMessagePumpDelegate> delegate(GetParam());
395
396 AddPreDoWorkExpectations(delegate);
397
398 EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this] {
399 message_pump_->Quit();
400 return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
401 }));
402
403 AddPostDoWorkExpectations(delegate);
404
405 #if BUILDFLAG(IS_IOS)
406 EXPECT_CALL(delegate, DoIdleWork).Times(AnyNumber());
407 #endif
408
409 message_pump_->Run(&delegate);
410 }
411
TEST_P(MessagePumpTest,NestedRunWithoutScheduleWorkInvokesDoWork)412 TEST_P(MessagePumpTest, NestedRunWithoutScheduleWorkInvokesDoWork) {
413 testing::InSequence sequence;
414 testing::StrictMock<MockMessagePumpDelegate> delegate(GetParam());
415 testing::StrictMock<MockMessagePumpDelegate> nested_delegate(GetParam());
416
417 AddPreDoWorkExpectations(delegate);
418
419 EXPECT_CALL(delegate, DoWork).WillOnce(Invoke([this, &nested_delegate] {
420 message_pump_->Run(&nested_delegate);
421 message_pump_->Quit();
422 return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
423 }));
424
425 AddPreDoWorkExpectations(nested_delegate);
426
427 EXPECT_CALL(nested_delegate, DoWork).WillOnce(Invoke([this] {
428 message_pump_->Quit();
429 return MessagePump::Delegate::NextWorkInfo{TimeTicks::Max()};
430 }));
431
432 // We quit `nested_delegate` before `delegate`
433 AddPostDoWorkExpectations(nested_delegate);
434
435 AddPostDoWorkExpectations(delegate);
436
437 #if BUILDFLAG(IS_IOS)
438 EXPECT_CALL(nested_delegate, DoIdleWork).Times(AnyNumber());
439 EXPECT_CALL(delegate, DoIdleWork).Times(AnyNumber());
440 #endif
441
442 message_pump_->Run(&delegate);
443 }
444
445 INSTANTIATE_TEST_SUITE_P(All,
446 MessagePumpTest,
447 ::testing::Values(MessagePumpType::DEFAULT,
448 MessagePumpType::UI,
449 MessagePumpType::IO));
450
451 } // namespace base
452