blob: 624c68f0bbb06eee60cc5e48a9fe5c74a2a18417 [file] [log] [blame] [view]
Elly Fong-Jones56f11aa72020-04-03 14:14:481# The Synchronous RunLoop Pattern
2
3The synchronous RunLoop pattern involves creating a new RunLoop, setting up a
4specified quit condition for it, then calling Run() on it to block the current
5thread until that quit condition is reached.
6
7## Use this pattern when:
8
9You need to **block the current thread** until an event happens, and you have a
10way to get notified of that event, via a callback or observer interface or
11similar. A couple of common scenarios might be:
12
13* Waiting for an asynchronous event (like a network request) to complete
14* Waiting for an animation to finish
15* Waiting for a page to have loaded
16* Waiting for some call that requires a thread hop to complete
17
18The fact that this blocks a thread means it is **almost never appropriate
19outside test code**.
20
21## Don't use this pattern when:
22
23* You don't really need the entire thread to wait
24* You don't have and can't add a way to get notified when the event happens
25* You're waiting for a timer to fire - for that, [TaskEnvironment] is likely a
26 better fit.
27
28## Alternatives / see also:
29
30* [TaskEnvironment]
31* Restructuring your code to not require blocking a thread
32
33## How this pattern works:
34
35This pattern relies on two important facts about [base::RunLoop]:
36
371. `base::RunLoop::Quit()` is idempotent - once a RunLoop enters the quit
38 state, quitting it again does nothing
392. Once a RunLoop is in the quit state, calling `base::RunLoop::Run()` on it is
40 a no-op
41
42That means that if your code does this:
43
44```c++
Jan Wilken Dörried02749a2020-10-26 15:51:0345base::RunLoop loop;
46maybe-asynchronously { loop.Quit(); }
47loop.Run();
48LOG(INFO) << "Hello!";
Elly Fong-Jones56f11aa72020-04-03 14:14:4849```
50
51then regardless of whether the maybe-asynchronous `loop.Quit()` is executed
52before or after `loop.Run()`, the "Hello!" message will never be printed before
53both `loop.Run()` and `loop.Quit()` have happened. If the `Quit` happens
54before the `Run`, the `Run` will be a no-op; if the `Quit` happens after the
55`Run` has started, the `Run` will exit after the `Quit`.
56
57## How to use this pattern in Chromium:
58
59If the asynchronous thing in question takes a completion callback:
60
61```c++
Jan Wilken Dörried02749a2020-10-26 15:51:0362base::RunLoop run_loop;
63Reply reply;
64DoThingAndReply(
65 base::BindLambdaForTesting([&](const Reply& r) {
66 reply = r;
67 run_loop.Quit();
68 }));
69run_loop.Run();
Elly Fong-Jones56f11aa72020-04-03 14:14:4870```
71
72or perhaps even just:
73
74```c++
Jan Wilken Dörried02749a2020-10-26 15:51:0375base::RunLoop run_loop;
76DoThing(run_loop.QuitClosure());
77run_loop.Run();
Elly Fong-Jones56f11aa72020-04-03 14:14:4878```
79
80If there exists a GizmoObserver interface with an OnThingDone event:
81
82```c++
Jan Wilken Dörried02749a2020-10-26 15:51:0383class TestGizmoObserver : public GizmoObserver {
84 public:
85 TestGizmoObserver(base::RunLoop* loop, Gizmo* thing)
86 : GizmoObserver(thing), loop_(loop) {}
Elly Fong-Jones56f11aa72020-04-03 14:14:4887
Jan Wilken Dörried02749a2020-10-26 15:51:0388 // GizmoObserver:
89 void OnThingStarted(Gizmo* observed_gizmo) override { ... }
90 void OnThingProgressed(Gizmo* observed_gizmo) override { ... }
91 void OnThingDone(Gizmo* observed_gizmo) override {
92 loop_->Quit();
93 }
94};
Elly Fong-Jones56f11aa72020-04-03 14:14:4895
Jan Wilken Dörried02749a2020-10-26 15:51:0396base::RunLoop run_loop;
97TestGizmoObserver observer(&run_loop, gizmo);
98gizmo->StartDoingThing();
99run_loop.Run();
Elly Fong-Jones56f11aa72020-04-03 14:14:48100```
101
102This is sometimes wrapped up into a helper class that internally constructs the
103RunLoop like so, if all you need to do is wait for the event but don't care
104about observing any intermediate states too:
105
106```c++
Jan Wilken Dörried02749a2020-10-26 15:51:03107class ThingDoneWaiter : public GizmoObserver {
108 public:
109 ThingDoneWaiter(Gizmo* thing) : GizmoObserver(thing) {}
Elly Fong-Jones56f11aa72020-04-03 14:14:48110
Jan Wilken Dörried02749a2020-10-26 15:51:03111 void Wait() {
112 run_loop_.Run();
113 }
Elly Fong-Jones56f11aa72020-04-03 14:14:48114
Jan Wilken Dörried02749a2020-10-26 15:51:03115 // GizmoObserver:
116 void OnThingDone(Gizmo* observed_gizmo) {
117 run_loop_.Quit();
118 }
Elly Fong-Jones56f11aa72020-04-03 14:14:48119
Jan Wilken Dörried02749a2020-10-26 15:51:03120 private:
121 RunLoop run_loop_;
122};
Elly Fong-Jones56f11aa72020-04-03 14:14:48123
Jan Wilken Dörried02749a2020-10-26 15:51:03124ThingDoneWaiter waiter(gizmo);
125gizmo->StartDoingThing();
126waiter.Wait();
Elly Fong-Jones56f11aa72020-04-03 14:14:48127```
128
Devlin Cronin759ba8a2020-04-16 16:57:06129## Events vs States
130
131It's important to differentiate between waiting on an *event* (such as a
132notification or callback being fired) vs waiting for a *state* (such as a
133property on a given object).
134
135When waiting for events, it is crucial that the observer is constructed in time
Frédéric Wang796f4db2020-08-21 08:23:21136to see the event (see also [waiting too late](#starting-to-wait-for-an-event-too-late)).
Devlin Cronin759ba8a2020-04-16 16:57:06137States, on the other hand, can be queried beforehand in the body of a
138Wait()-style function.
139
140The following is an example of a Waiter helper class that waits for a state, as
141opposed to an event:
142
143```c++
Jan Wilken Dörried02749a2020-10-26 15:51:03144class GizmoReadyWaiter : public GizmoObserver {
145 public:
146 GizmoReadyObserver(Gizmo* gizmo)
147 : gizmo_(gizmo) {}
148 ~GizmoReadyObserver() override = default;
Devlin Cronin759ba8a2020-04-16 16:57:06149
Jan Wilken Dörried02749a2020-10-26 15:51:03150 void WaitForGizmoReady() {
151 if (!gizmo_->ready()) {
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17152 gizmo_observation_.Observe(gizmo_);
Jan Wilken Dörried02749a2020-10-26 15:51:03153 run_loop_.Run();
Devlin Cronin759ba8a2020-04-16 16:57:06154 }
Jan Wilken Dörried02749a2020-10-26 15:51:03155 }
Devlin Cronin759ba8a2020-04-16 16:57:06156
Jan Wilken Dörried02749a2020-10-26 15:51:03157 // GizmoObserver:
158 void OnGizmoReady(Gizmo* observed_gizmo) {
159 run_loop_.Quit();
160 }
Devlin Cronin759ba8a2020-04-16 16:57:06161
Jan Wilken Dörried02749a2020-10-26 15:51:03162 private:
163 RunLoop run_loop_;
164 Gizmo* gizmo_;
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17165 base::ScopedObservation<Gizmo, GizmoObserver> gizmo_observation_{this};
Jan Wilken Dörried02749a2020-10-26 15:51:03166};
Devlin Cronin759ba8a2020-04-16 16:57:06167```
168
Elly Fong-Jones56f11aa72020-04-03 14:14:48169## Sharp edges
170
Devlin Cronin759ba8a2020-04-16 16:57:06171### Starting to wait for an event too late
Elly Fong-Jones56f11aa72020-04-03 14:14:48172
173A common mis-use of this pattern is like so:
174
175```c++
Jan Wilken Dörried02749a2020-10-26 15:51:03176gizmo->StartDoingThing();
177base::RunLoop run_loop;
178TestGizmoObserver observer(&run_loop, gizmo);
179run_loop.Run();
Elly Fong-Jones56f11aa72020-04-03 14:14:48180```
181
182This looks tempting because it seems that you can write a helper function:
183
184```c++
Jan Wilken Dörried02749a2020-10-26 15:51:03185void TerribleHorribleNoGoodVeryBadWaitForThing(Gizmo* gizmo) {
186 base::RunLoop run_loop;
187 TestGizmoObserver observer(&run_loop, gizmo);
188 run_loop.Run();
189}
Elly Fong-Jones56f11aa72020-04-03 14:14:48190```
191
192and then your test code can simply read:
193
194```c++
Jan Wilken Dörried02749a2020-10-26 15:51:03195gizmo->StartDoingThing();
196TerribleHorribleNoGoodVeryBadWaitForThing(gizmo);
Elly Fong-Jones56f11aa72020-04-03 14:14:48197```
198
199However, this is a recipe for a flaky test: if `gizmo->StartDoingThing()`
200*completes* and would deliver the `OnThingDone` callback before your
201`TestGizmoObserver` is ever constructed, the `TestGizmoObserver` will never
202receive `OnThingDone`, and then your `run_loop.Run()` will run forever,
203frustrating a future tree sheriff (and then probably you, shortly afterward).
204This is especially dangerous when `gizmo->StartDoingThing()` involves a thread
205hop or network request, because these can unpredictably complete before or after
206your observer gets constructed. To be safe, always begin observing the event
207*before* running the code that will eventually cause the event!
208
209If you still really want a helper function, perhaps you just want to inline the
210start:
211
212```c++
Jan Wilken Dörried02749a2020-10-26 15:51:03213void NiceFriendlyDoThingAndWait(Gizmo* gizmo) {
214 base::RunLoop run_loop;
215 TestGizmoObserver observer(&run_loop, gizmo);
216 gizmo->StartDoingThing();
217 run_loop.Run();
218}
Elly Fong-Jones56f11aa72020-04-03 14:14:48219```
220
221with the test code being:
222
223```c++
Jan Wilken Dörried02749a2020-10-26 15:51:03224NiceFriendlyDoThingAndWait(gizmo);
Elly Fong-Jones56f11aa72020-04-03 14:14:48225```
226
Devlin Cronin759ba8a2020-04-16 16:57:06227Note that this is not an issue when waiting on a *state*, since the observer can
228query to see if that state is already the current state.
229
Elly Fong-Jones56f11aa72020-04-03 14:14:48230### Guessing RunLoop cycles
231
232Sometimes, there's no easy way to observe completion of an event. In that case,
233if the code under test looks like this:
234
235```c++
Jan Wilken Dörried02749a2020-10-26 15:51:03236void StartDoingThing() { PostTask(&StepOne); }
237void StepOne() { PostTask(&StepTwo); }
238void StepTwo() { /* done! */ }
Elly Fong-Jones56f11aa72020-04-03 14:14:48239```
240
241it can be tempting to do:
242
243```c++
Jan Wilken Dörried02749a2020-10-26 15:51:03244gizmo->StartDoingThing();
245base::RunLoop().RunUntilIdle();
246/* now it's done! */
Elly Fong-Jones56f11aa72020-04-03 14:14:48247```
248
249However, doing this is adding dependencies to your test code on the exact async
250behavior of the production code - for example, the production code may depend on
251work happening on another TaskRunner, which this won't successfully wait for.
252This will make your test brittle and flaky.
253
254Instead of doing this, it's vastly better to add a way (even if it's just via a
255[test API]) to observe the event you're interested in.
256
Devlin Croninaeb91b362020-04-16 18:48:40257### Not managing lifetimes
258
259As with most patterns, lifetimes can be an issue with this pattern when using
260observers. If you are waiting on a given event to happen, and the object that's
261being observed instead goes out of scope, the test may hang.
262Similar badness can happen if the Waiter isn't properly removed as an observer,
263which could lead to Use-After-Frees.
264
265There are two good mitigation practices here.
266
267#### Keep Waiter-style helper classes as narrowly scoped as possible.
268Consider something like
269```c++
270TEST_F(GizmoTest, WaitForGizmo) {
271 GizmoWaiter waiter;
272 Gizmo gizmo;
273 gizmo.Initialize();
274 waiter.WaitForGizmoReady();
275 ASSERT_TRUE(gizmo.ready());
276}
277```
278
279This looks safe, but may not be. If GizmoObserver removes itself as an observer
280from Gizmo in its destructor, this will result in a Use-After-Free during the
281test tear down. Instead, scope the GizmoWaiter more narrowly:
282```c++
283TEST_F(GizmoTest, WaitForGizmo) {
284 Gizmo gizmo;
285 {
286 GizmoWaiter waiter;
287 gizmo.Initialize();
288 waiter.WaitForGizmoReady();
289 }
290 ASSERT_TRUE(gizmo.ready());
291}
292```
293
294Since the GizmoWaiter is now narrowly-scoped, it will be destroyed when it is
295no longer needed, and avoid Use-After-Free concerns.
296
297#### If in doubt, handle the destruction case appropriately
298If you need to potentially handle the case where the object being observed is
299destroyed while a waiter is still active, you can handle the destruction case
300gracefully.
301
302
303```c++
Jan Wilken Dörried02749a2020-10-26 15:51:03304class GizmoReadyWaiter : public GizmoObserver {
305 public:
306 GizmoReadyObserver(Gizmo* gizmo)
307 : gizmo_(gizmo) {}
308 ~GizmoReadyObserver() override = default;
Devlin Croninaeb91b362020-04-16 18:48:40309
Jan Wilken Dörried02749a2020-10-26 15:51:03310 void WaitForGizmoReady() {
311 ASSERT_TRUE(gizmo_)
312 << "Trying to call Wait() after the Gizmo was destroyed!";
313 if (!gizmo_->ready()) {
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17314 gizmo_observation_.Observe(gizmo_);
Jan Wilken Dörried02749a2020-10-26 15:51:03315 run_loop_.Run();
Devlin Croninaeb91b362020-04-16 18:48:40316 }
Jan Wilken Dörried02749a2020-10-26 15:51:03317 }
Devlin Croninaeb91b362020-04-16 18:48:40318
Jan Wilken Dörried02749a2020-10-26 15:51:03319 // GizmoObserver:
320 void OnGizmoReady(Gizmo* observed_gizmo) {
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17321 gizmo_observation_.Reset();
Jan Wilken Dörried02749a2020-10-26 15:51:03322 run_loop_.Quit();
323 }
324 void OnGizmoDestroying(Gizmo* observed_gizmo) {
325 DCHECK_EQ(gizmo_, observed_gizmo);
326 gizmo_ = nullptr;
327 // Remove the observer now, to avoid a UAF in the destructor.
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17328 gizmo_observation_.Reset();
Jan Wilken Dörried02749a2020-10-26 15:51:03329 // Bail out so we don't time out in the test waiting for a ready state
330 // that will never come.
331 run_loop_.Quit();
332 // Was this a possible expected outcome? If not, consider:
333 // ADD_FAILURE() << "The Gizmo was destroyed before it was ready!";
334 }
Devlin Croninaeb91b362020-04-16 18:48:40335
Jan Wilken Dörried02749a2020-10-26 15:51:03336 private:
337 RunLoop run_loop_;
338 Gizmo* gizmo_;
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17339 base::ScopedObservation<Gizmo, GizmoObserver> gizmo_observation_{this};
Jan Wilken Dörried02749a2020-10-26 15:51:03340};
Devlin Croninaeb91b362020-04-16 18:48:40341```
342
Elly Fong-Jones56f11aa72020-04-03 14:14:48343[base::RunLoop]: ../../base/run_loop.h
344[TaskEnvironment]: ../threading_and_tasks_testing.md
345[test API]: testapi.md