1 | // Copyright 2013 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | #define FML_USED_ON_EMBEDDER |
6 | |
7 | #include <iostream> |
8 | #include <thread> |
9 | |
10 | #include "flutter/fml/build_config.h" |
11 | #include "flutter/fml/concurrent_message_loop.h" |
12 | #include "flutter/fml/message_loop.h" |
13 | #include "flutter/fml/synchronization/count_down_latch.h" |
14 | #include "flutter/fml/synchronization/waitable_event.h" |
15 | #include "flutter/fml/task_runner.h" |
16 | #include "gtest/gtest.h" |
17 | |
18 | #define TIMESENSITIVE(x) TimeSensitiveTest_##x |
19 | #if OS_WIN |
20 | #define PLATFORM_SPECIFIC_CAPTURE(...) [ __VA_ARGS__, count ] |
21 | #else |
22 | #define PLATFORM_SPECIFIC_CAPTURE(...) [__VA_ARGS__] |
23 | #endif |
24 | |
25 | TEST(MessageLoop, GetCurrent) { |
26 | std::thread thread([]() { |
27 | fml::MessageLoop::EnsureInitializedForCurrentThread(); |
28 | ASSERT_TRUE(fml::MessageLoop::GetCurrent().GetTaskRunner()); |
29 | }); |
30 | thread.join(); |
31 | } |
32 | |
33 | TEST(MessageLoop, DifferentThreadsHaveDifferentLoops) { |
34 | fml::MessageLoop* loop1 = nullptr; |
35 | fml::AutoResetWaitableEvent latch1; |
36 | fml::AutoResetWaitableEvent term1; |
37 | std::thread thread1([&loop1, &latch1, &term1]() { |
38 | fml::MessageLoop::EnsureInitializedForCurrentThread(); |
39 | loop1 = &fml::MessageLoop::GetCurrent(); |
40 | latch1.Signal(); |
41 | term1.Wait(); |
42 | }); |
43 | |
44 | fml::MessageLoop* loop2 = nullptr; |
45 | fml::AutoResetWaitableEvent latch2; |
46 | fml::AutoResetWaitableEvent term2; |
47 | std::thread thread2([&loop2, &latch2, &term2]() { |
48 | fml::MessageLoop::EnsureInitializedForCurrentThread(); |
49 | loop2 = &fml::MessageLoop::GetCurrent(); |
50 | latch2.Signal(); |
51 | term2.Wait(); |
52 | }); |
53 | latch1.Wait(); |
54 | latch2.Wait(); |
55 | ASSERT_FALSE(loop1 == loop2); |
56 | term1.Signal(); |
57 | term2.Signal(); |
58 | thread1.join(); |
59 | thread2.join(); |
60 | } |
61 | |
62 | TEST(MessageLoop, CanRunAndTerminate) { |
63 | bool started = false; |
64 | bool terminated = false; |
65 | std::thread thread([&started, &terminated]() { |
66 | fml::MessageLoop::EnsureInitializedForCurrentThread(); |
67 | auto& loop = fml::MessageLoop::GetCurrent(); |
68 | ASSERT_TRUE(loop.GetTaskRunner()); |
69 | loop.GetTaskRunner()->PostTask([&terminated]() { |
70 | fml::MessageLoop::GetCurrent().Terminate(); |
71 | terminated = true; |
72 | }); |
73 | loop.Run(); |
74 | started = true; |
75 | }); |
76 | thread.join(); |
77 | ASSERT_TRUE(started); |
78 | ASSERT_TRUE(terminated); |
79 | } |
80 | |
81 | TEST(MessageLoop, NonDelayedTasksAreRunInOrder) { |
82 | const size_t count = 100; |
83 | bool started = false; |
84 | bool terminated = false; |
85 | std::thread thread([&started, &terminated, count]() { |
86 | fml::MessageLoop::EnsureInitializedForCurrentThread(); |
87 | auto& loop = fml::MessageLoop::GetCurrent(); |
88 | size_t current = 0; |
89 | for (size_t i = 0; i < count; i++) { |
90 | loop.GetTaskRunner()->PostTask( |
91 | PLATFORM_SPECIFIC_CAPTURE(&terminated, i, ¤t)() { |
92 | ASSERT_EQ(current, i); |
93 | current++; |
94 | if (count == i + 1) { |
95 | fml::MessageLoop::GetCurrent().Terminate(); |
96 | terminated = true; |
97 | } |
98 | }); |
99 | } |
100 | loop.Run(); |
101 | ASSERT_EQ(current, count); |
102 | started = true; |
103 | }); |
104 | thread.join(); |
105 | ASSERT_TRUE(started); |
106 | ASSERT_TRUE(terminated); |
107 | } |
108 | |
109 | TEST(MessageLoop, DelayedTasksAtSameTimeAreRunInOrder) { |
110 | const size_t count = 100; |
111 | bool started = false; |
112 | bool terminated = false; |
113 | std::thread thread([&started, &terminated, count]() { |
114 | fml::MessageLoop::EnsureInitializedForCurrentThread(); |
115 | auto& loop = fml::MessageLoop::GetCurrent(); |
116 | size_t current = 0; |
117 | const auto now_plus_some = |
118 | fml::TimePoint::Now() + fml::TimeDelta::FromMilliseconds(2); |
119 | for (size_t i = 0; i < count; i++) { |
120 | loop.GetTaskRunner()->PostTaskForTime( |
121 | PLATFORM_SPECIFIC_CAPTURE(&terminated, i, ¤t)() { |
122 | ASSERT_EQ(current, i); |
123 | current++; |
124 | if (count == i + 1) { |
125 | fml::MessageLoop::GetCurrent().Terminate(); |
126 | terminated = true; |
127 | } |
128 | }, |
129 | now_plus_some); |
130 | } |
131 | loop.Run(); |
132 | ASSERT_EQ(current, count); |
133 | started = true; |
134 | }); |
135 | thread.join(); |
136 | ASSERT_TRUE(started); |
137 | ASSERT_TRUE(terminated); |
138 | } |
139 | |
140 | TEST(MessageLoop, CheckRunsTaskOnCurrentThread) { |
141 | fml::RefPtr<fml::TaskRunner> runner; |
142 | fml::AutoResetWaitableEvent latch; |
143 | std::thread thread([&runner, &latch]() { |
144 | fml::MessageLoop::EnsureInitializedForCurrentThread(); |
145 | auto& loop = fml::MessageLoop::GetCurrent(); |
146 | runner = loop.GetTaskRunner(); |
147 | latch.Signal(); |
148 | ASSERT_TRUE(loop.GetTaskRunner()->RunsTasksOnCurrentThread()); |
149 | }); |
150 | latch.Wait(); |
151 | ASSERT_TRUE(runner); |
152 | ASSERT_FALSE(runner->RunsTasksOnCurrentThread()); |
153 | thread.join(); |
154 | } |
155 | |
156 | TEST(MessageLoop, TIMESENSITIVE(SingleDelayedTaskByDelta)) { |
157 | bool checked = false; |
158 | std::thread thread([&checked]() { |
159 | fml::MessageLoop::EnsureInitializedForCurrentThread(); |
160 | auto& loop = fml::MessageLoop::GetCurrent(); |
161 | auto begin = fml::TimePoint::Now(); |
162 | loop.GetTaskRunner()->PostDelayedTask( |
163 | [begin, &checked]() { |
164 | auto delta = fml::TimePoint::Now() - begin; |
165 | auto ms = delta.ToMillisecondsF(); |
166 | ASSERT_GE(ms, 3); |
167 | ASSERT_LE(ms, 7); |
168 | checked = true; |
169 | fml::MessageLoop::GetCurrent().Terminate(); |
170 | }, |
171 | fml::TimeDelta::FromMilliseconds(5)); |
172 | loop.Run(); |
173 | }); |
174 | thread.join(); |
175 | ASSERT_TRUE(checked); |
176 | } |
177 | |
178 | TEST(MessageLoop, TIMESENSITIVE(SingleDelayedTaskForTime)) { |
179 | bool checked = false; |
180 | std::thread thread([&checked]() { |
181 | fml::MessageLoop::EnsureInitializedForCurrentThread(); |
182 | auto& loop = fml::MessageLoop::GetCurrent(); |
183 | auto begin = fml::TimePoint::Now(); |
184 | loop.GetTaskRunner()->PostTaskForTime( |
185 | [begin, &checked]() { |
186 | auto delta = fml::TimePoint::Now() - begin; |
187 | auto ms = delta.ToMillisecondsF(); |
188 | ASSERT_GE(ms, 3); |
189 | ASSERT_LE(ms, 7); |
190 | checked = true; |
191 | fml::MessageLoop::GetCurrent().Terminate(); |
192 | }, |
193 | fml::TimePoint::Now() + fml::TimeDelta::FromMilliseconds(5)); |
194 | loop.Run(); |
195 | }); |
196 | thread.join(); |
197 | ASSERT_TRUE(checked); |
198 | } |
199 | |
200 | TEST(MessageLoop, TIMESENSITIVE(MultipleDelayedTasksWithIncreasingDeltas)) { |
201 | const auto count = 10; |
202 | int checked = false; |
203 | std::thread thread(PLATFORM_SPECIFIC_CAPTURE(&checked)() { |
204 | fml::MessageLoop::EnsureInitializedForCurrentThread(); |
205 | auto& loop = fml::MessageLoop::GetCurrent(); |
206 | for (int target_ms = 0 + 2; target_ms < count + 2; target_ms++) { |
207 | auto begin = fml::TimePoint::Now(); |
208 | loop.GetTaskRunner()->PostDelayedTask( |
209 | PLATFORM_SPECIFIC_CAPTURE(begin, target_ms, &checked)() { |
210 | auto delta = fml::TimePoint::Now() - begin; |
211 | auto ms = delta.ToMillisecondsF(); |
212 | ASSERT_GE(ms, target_ms - 2); |
213 | ASSERT_LE(ms, target_ms + 2); |
214 | checked++; |
215 | if (checked == count) { |
216 | fml::MessageLoop::GetCurrent().Terminate(); |
217 | } |
218 | }, |
219 | fml::TimeDelta::FromMilliseconds(target_ms)); |
220 | } |
221 | loop.Run(); |
222 | }); |
223 | thread.join(); |
224 | ASSERT_EQ(checked, count); |
225 | } |
226 | |
227 | TEST(MessageLoop, TIMESENSITIVE(MultipleDelayedTasksWithDecreasingDeltas)) { |
228 | const auto count = 10; |
229 | int checked = false; |
230 | std::thread thread(PLATFORM_SPECIFIC_CAPTURE(&checked)() { |
231 | fml::MessageLoop::EnsureInitializedForCurrentThread(); |
232 | auto& loop = fml::MessageLoop::GetCurrent(); |
233 | for (int target_ms = count + 2; target_ms > 0 + 2; target_ms--) { |
234 | auto begin = fml::TimePoint::Now(); |
235 | loop.GetTaskRunner()->PostDelayedTask( |
236 | PLATFORM_SPECIFIC_CAPTURE(begin, target_ms, &checked)() { |
237 | auto delta = fml::TimePoint::Now() - begin; |
238 | auto ms = delta.ToMillisecondsF(); |
239 | ASSERT_GE(ms, target_ms - 2); |
240 | ASSERT_LE(ms, target_ms + 2); |
241 | checked++; |
242 | if (checked == count) { |
243 | fml::MessageLoop::GetCurrent().Terminate(); |
244 | } |
245 | }, |
246 | fml::TimeDelta::FromMilliseconds(target_ms)); |
247 | } |
248 | loop.Run(); |
249 | }); |
250 | thread.join(); |
251 | ASSERT_EQ(checked, count); |
252 | } |
253 | |
254 | TEST(MessageLoop, TaskObserverFire) { |
255 | bool started = false; |
256 | bool terminated = false; |
257 | std::thread thread([&started, &terminated]() { |
258 | fml::MessageLoop::EnsureInitializedForCurrentThread(); |
259 | const size_t count = 25; |
260 | auto& loop = fml::MessageLoop::GetCurrent(); |
261 | size_t task_count = 0; |
262 | size_t obs_count = 0; |
263 | auto obs = PLATFORM_SPECIFIC_CAPTURE(&obs_count)() { obs_count++; }; |
264 | for (size_t i = 0; i < count; i++) { |
265 | loop.GetTaskRunner()->PostTask( |
266 | PLATFORM_SPECIFIC_CAPTURE(&terminated, i, &task_count)() { |
267 | ASSERT_EQ(task_count, i); |
268 | task_count++; |
269 | if (count == i + 1) { |
270 | fml::MessageLoop::GetCurrent().Terminate(); |
271 | terminated = true; |
272 | } |
273 | }); |
274 | } |
275 | loop.AddTaskObserver(0, obs); |
276 | loop.Run(); |
277 | ASSERT_EQ(task_count, count); |
278 | ASSERT_EQ(obs_count, count); |
279 | started = true; |
280 | }); |
281 | thread.join(); |
282 | ASSERT_TRUE(started); |
283 | ASSERT_TRUE(terminated); |
284 | } |
285 | |
286 | TEST(MessageLoop, ConcurrentMessageLoopHasNonZeroWorkers) { |
287 | auto loop = fml::ConcurrentMessageLoop::Create( |
288 | 0u /* explicitly specify zero workers */); |
289 | ASSERT_GT(loop->GetWorkerCount(), 0u); |
290 | } |
291 | |
292 | TEST(MessageLoop, CanCreateAndShutdownConcurrentMessageLoopsOverAndOver) { |
293 | for (size_t i = 0; i < 10; ++i) { |
294 | auto loop = fml::ConcurrentMessageLoop::Create(i + 1); |
295 | ASSERT_EQ(loop->GetWorkerCount(), i + 1); |
296 | } |
297 | } |
298 | |
299 | TEST(MessageLoop, CanCreateConcurrentMessageLoop) { |
300 | auto loop = fml::ConcurrentMessageLoop::Create(); |
301 | auto task_runner = loop->GetTaskRunner(); |
302 | const size_t kCount = 10; |
303 | fml::CountDownLatch latch(kCount); |
304 | std::mutex thread_ids_mutex; |
305 | std::set<std::thread::id> thread_ids; |
306 | for (size_t i = 0; i < kCount; ++i) { |
307 | task_runner->PostTask([&]() { |
308 | std::this_thread::sleep_for(std::chrono::seconds(1)); |
309 | std::cout << "Ran on thread: " << std::this_thread::get_id() << std::endl; |
310 | std::scoped_lock lock(thread_ids_mutex); |
311 | thread_ids.insert(std::this_thread::get_id()); |
312 | latch.CountDown(); |
313 | }); |
314 | } |
315 | latch.Wait(); |
316 | ASSERT_GE(thread_ids.size(), 1u); |
317 | } |
318 | |