Flutter Engine
The Flutter Engine
FlutterThreadSynchronizerTest.mm
Go to the documentation of this file.
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#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterThreadSynchronizer.h"
6
7#import "flutter/fml/synchronization/waitable_event.h"
8#import "flutter/testing/testing.h"
9
11
12@property(nonatomic, readonly, nonnull) FlutterThreadSynchronizer* synchronizer;
13
14- (nullable instancetype)init;
15- (void)dispatchMainTask:(nonnull void (^)())task;
16- (void)dispatchRenderTask:(nonnull void (^)())task;
17- (void)joinMain;
18- (void)joinRender;
19@end
20
22 dispatch_queue_t _mainQueue;
23 std::shared_ptr<fml::AutoResetWaitableEvent> _mainLatch;
24
25 dispatch_queue_t _renderQueue;
26 std::shared_ptr<fml::AutoResetWaitableEvent> _renderLatch;
27
29}
30
31@synthesize synchronizer = _synchronizer;
32
33- (nullable instancetype)init {
34 self = [super init];
35 if (self != nil) {
36 _mainQueue = dispatch_queue_create("MAIN", DISPATCH_QUEUE_SERIAL);
37 _renderQueue = dispatch_queue_create("RENDER", DISPATCH_QUEUE_SERIAL);
38 _synchronizer = [[FlutterThreadSynchronizer alloc] initWithMainQueue:_mainQueue];
39 }
40 return self;
41}
42
43- (void)dispatchMainTask:(nonnull void (^)())task {
44 dispatch_async(_mainQueue, task);
45}
46
47- (void)dispatchRenderTask:(nonnull void (^)())task {
48 dispatch_async(_renderQueue, task);
49}
50
51- (void)joinMain {
53 fml::AutoResetWaitableEvent* pLatch = &latch;
54 dispatch_async(_mainQueue, ^{
55 pLatch->Signal();
56 });
57 latch.Wait();
58}
59
60- (void)joinRender {
62 fml::AutoResetWaitableEvent* pLatch = &latch;
63 dispatch_async(_renderQueue, ^{
64 pLatch->Signal();
65 });
66 latch.Wait();
67}
68
69@end
70
71TEST(FlutterThreadSynchronizerTest, RegularCommit) {
74 FlutterThreadSynchronizer* synchronizer = scaffold.synchronizer;
75
76 // Initial resize: does not block until the first frame.
77 __block int notifiedResize = 0;
78 [scaffold dispatchMainTask:^{
79 [synchronizer registerView:1];
80 [synchronizer beginResizeForView:1
81 size:CGSize{5, 5}
82 notify:^{
83 notifiedResize += 1;
84 }];
85 }];
86 EXPECT_FALSE([synchronizer isWaitingWhenMutexIsAvailable]);
87 [scaffold joinMain];
88 EXPECT_EQ(notifiedResize, 1);
89
90 // Still does not block.
91 [scaffold dispatchMainTask:^{
92 [synchronizer beginResizeForView:1
93 size:CGSize{7, 7}
94 notify:^{
95 notifiedResize += 1;
96 }];
97 }];
98 EXPECT_FALSE([synchronizer isWaitingWhenMutexIsAvailable]);
99 [scaffold joinMain];
100 EXPECT_EQ(notifiedResize, 2);
101
102 // First frame
103 __block int notifiedCommit = 0;
104 [scaffold dispatchRenderTask:^{
105 [synchronizer performCommitForView:1
106 size:CGSize{7, 7}
107 notify:^{
108 notifiedCommit += 1;
109 }];
110 }];
111 EXPECT_FALSE([synchronizer isWaitingWhenMutexIsAvailable]);
112 [scaffold joinRender];
113 EXPECT_EQ(notifiedCommit, 1);
114}
115
116TEST(FlutterThreadSynchronizerTest, ResizingBlocksRenderingUntilSizeMatches) {
119 FlutterThreadSynchronizer* synchronizer = scaffold.synchronizer;
120 // A latch to ensure that a beginResizeForView: call has at least executed
121 // something, so that the isWaitingWhenMutexIsAvailable: call correctly stops
122 // at either when beginResizeForView: finishes or waits half way.
123 fml::AutoResetWaitableEvent begunResizingLatch;
124 fml::AutoResetWaitableEvent* begunResizing = &begunResizingLatch;
125
126 // Initial resize: does not block until the first frame.
127 [scaffold dispatchMainTask:^{
128 [synchronizer registerView:1];
129 [synchronizer beginResizeForView:1
130 size:CGSize{5, 5}
131 notify:^{
132 }];
133 }];
134 [scaffold joinMain];
135 EXPECT_FALSE([synchronizer isWaitingWhenMutexIsAvailable]);
136
137 // First frame.
138 [scaffold dispatchRenderTask:^{
139 [synchronizer performCommitForView:1
140 size:CGSize{5, 5}
141 notify:^{
142 }];
143 }];
144 [scaffold joinRender];
145 EXPECT_FALSE([synchronizer isWaitingWhenMutexIsAvailable]);
146
147 // Resize to (7, 7): blocks until the next frame.
148 [scaffold dispatchMainTask:^{
149 [synchronizer beginResizeForView:1
150 size:CGSize{7, 7}
151 notify:^{
152 begunResizing->Signal();
153 }];
154 }];
155 begunResizing->Wait();
156 EXPECT_TRUE([synchronizer isWaitingWhenMutexIsAvailable]);
157
158 // Render with old size.
159 [scaffold dispatchRenderTask:^{
160 [synchronizer performCommitForView:1
161 size:CGSize{5, 5}
162 notify:^{
163 }];
164 }];
165 [scaffold joinRender];
166 EXPECT_TRUE([synchronizer isWaitingWhenMutexIsAvailable]);
167
168 // Render with new size.
169 [scaffold dispatchRenderTask:^{
170 [synchronizer performCommitForView:1
171 size:CGSize{7, 7}
172 notify:^{
173 }];
174 }];
175 [scaffold joinRender];
176 EXPECT_FALSE([synchronizer isWaitingWhenMutexIsAvailable]);
177
178 [scaffold joinMain];
179}
180
181TEST(FlutterThreadSynchronizerTest, ShutdownMakesEverythingNonBlocking) {
184 FlutterThreadSynchronizer* synchronizer = scaffold.synchronizer;
185 fml::AutoResetWaitableEvent begunResizingLatch;
186 fml::AutoResetWaitableEvent* begunResizing = &begunResizingLatch;
187
188 // Initial resize
189 [scaffold dispatchMainTask:^{
190 [synchronizer registerView:1];
191 [synchronizer beginResizeForView:1
192 size:CGSize{5, 5}
193 notify:^{
194 }];
195 }];
196 [scaffold joinMain];
197 EXPECT_FALSE([synchronizer isWaitingWhenMutexIsAvailable]);
198
199 // Push a frame.
200 [scaffold dispatchRenderTask:^{
201 [synchronizer performCommitForView:1
202 size:CGSize{5, 5}
203 notify:^{
204 }];
205 }];
206 [scaffold joinRender];
207 EXPECT_FALSE([synchronizer isWaitingWhenMutexIsAvailable]);
208
209 [scaffold dispatchMainTask:^{
210 [synchronizer shutdown];
211 }];
212
213 // Resize to (7, 7). Should not block any frames since it has shut down.
214 [scaffold dispatchMainTask:^{
215 [synchronizer beginResizeForView:1
216 size:CGSize{7, 7}
217 notify:^{
218 begunResizing->Signal();
219 }];
220 }];
221 begunResizing->Wait();
222 EXPECT_FALSE([synchronizer isWaitingWhenMutexIsAvailable]);
223 [scaffold joinMain];
224
225 // All further calls should be unblocking.
226 [scaffold dispatchRenderTask:^{
227 [synchronizer performCommitForView:1
228 size:CGSize{9, 9}
229 notify:^{
230 }];
231 }];
232 [scaffold joinRender];
233 EXPECT_FALSE([synchronizer isWaitingWhenMutexIsAvailable]);
234}
235
236TEST(FlutterThreadSynchronizerTest, RegularCommitForMultipleViews) {
239 FlutterThreadSynchronizer* synchronizer = scaffold.synchronizer;
240 fml::AutoResetWaitableEvent begunResizingLatch;
241 fml::AutoResetWaitableEvent* begunResizing = &begunResizingLatch;
242
243 // Initial resize: does not block until the first frame.
244 [scaffold dispatchMainTask:^{
245 [synchronizer registerView:1];
246 [synchronizer registerView:2];
247 [synchronizer beginResizeForView:1
248 size:CGSize{5, 5}
249 notify:^{
250 }];
251 [synchronizer beginResizeForView:2
252 size:CGSize{15, 15}
253 notify:^{
254 begunResizing->Signal();
255 }];
256 }];
257 begunResizing->Wait();
258 EXPECT_FALSE([synchronizer isWaitingWhenMutexIsAvailable]);
259 [scaffold joinMain];
260
261 // Still does not block.
262 [scaffold dispatchMainTask:^{
263 [synchronizer beginResizeForView:1
264 size:CGSize{7, 7}
265 notify:^{
266 begunResizing->Signal();
267 }];
268 }];
269 begunResizing->Signal();
270 EXPECT_FALSE([synchronizer isWaitingWhenMutexIsAvailable]);
271 [scaffold joinMain];
272
273 // First frame
274 [scaffold dispatchRenderTask:^{
275 [synchronizer performCommitForView:1
276 size:CGSize{7, 7}
277 notify:^{
278 }];
279 [synchronizer performCommitForView:2
280 size:CGSize{15, 15}
281 notify:^{
282 }];
283 }];
284 [scaffold joinRender];
285 EXPECT_FALSE([synchronizer isWaitingWhenMutexIsAvailable]);
286}
287
288TEST(FlutterThreadSynchronizerTest, ResizingForMultipleViews) {
291 FlutterThreadSynchronizer* synchronizer = scaffold.synchronizer;
292 fml::AutoResetWaitableEvent begunResizingLatch;
293 fml::AutoResetWaitableEvent* begunResizing = &begunResizingLatch;
294
295 // Initial resize: does not block until the first frame.
296 [scaffold dispatchMainTask:^{
297 [synchronizer registerView:1];
298 [synchronizer registerView:2];
299 [synchronizer beginResizeForView:1
300 size:CGSize{5, 5}
301 notify:^{
302 }];
303 [synchronizer beginResizeForView:2
304 size:CGSize{15, 15}
305 notify:^{
306 }];
307 }];
308 [scaffold joinMain];
309 EXPECT_FALSE([synchronizer isWaitingWhenMutexIsAvailable]);
310
311 // First frame.
312 [scaffold dispatchRenderTask:^{
313 [synchronizer performCommitForView:1
314 size:CGSize{5, 5}
315 notify:^{
316 }];
317 [synchronizer performCommitForView:2
318 size:CGSize{15, 15}
319 notify:^{
320 }];
321 }];
322 [scaffold joinRender];
323 EXPECT_FALSE([synchronizer isWaitingWhenMutexIsAvailable]);
324
325 // Resize view 2 to (17, 17): blocks until the next frame.
326 [scaffold dispatchMainTask:^{
327 [synchronizer beginResizeForView:2
328 size:CGSize{17, 17}
329 notify:^{
330 begunResizing->Signal();
331 }];
332 }];
333 begunResizing->Wait();
334 EXPECT_TRUE([synchronizer isWaitingWhenMutexIsAvailable]);
335
336 // Render view 1 with the size. Still blocking.
337 [scaffold dispatchRenderTask:^{
338 [synchronizer performCommitForView:1
339 size:CGSize{5, 5}
340 notify:^{
341 }];
342 }];
343 [scaffold joinRender];
344 EXPECT_TRUE([synchronizer isWaitingWhenMutexIsAvailable]);
345
346 // Render view 2 with the old size. Still blocking.
347 [scaffold dispatchRenderTask:^{
348 [synchronizer performCommitForView:1
349 size:CGSize{15, 15}
350 notify:^{
351 }];
352 }];
353 [scaffold joinRender];
354 EXPECT_TRUE([synchronizer isWaitingWhenMutexIsAvailable]);
355
356 // Render view 1 with the size.
357 [scaffold dispatchRenderTask:^{
358 [synchronizer performCommitForView:1
359 size:CGSize{5, 5}
360 notify:^{
361 }];
362 }];
363 [scaffold joinRender];
364 EXPECT_TRUE([synchronizer isWaitingWhenMutexIsAvailable]);
365
366 // Render view 2 with the new size. Unblocks.
367 [scaffold dispatchRenderTask:^{
368 [synchronizer performCommitForView:2
369 size:CGSize{17, 17}
370 notify:^{
371 }];
372 }];
373 [scaffold joinRender];
374 [scaffold joinMain];
375 EXPECT_FALSE([synchronizer isWaitingWhenMutexIsAvailable]);
376}
FlutterThreadSynchronizer * _synchronizer
std::shared_ptr< fml::AutoResetWaitableEvent > _renderLatch
std::shared_ptr< fml::AutoResetWaitableEvent > _mainLatch
dispatch_queue_t _renderQueue
TEST(FlutterThreadSynchronizerTest, RegularCommit)
void beginResizeForView:size:notify:(FlutterViewIdentifier viewIdentifier,[size] CGSize size,[notify] nonnull dispatch_block_t notify)
void performCommitForView:size:notify:(FlutterViewIdentifier viewIdentifier,[size] CGSize size,[notify] nonnull dispatch_block_t notify)
void registerView:(FlutterViewIdentifier viewIdentifier)
#define EXPECT_TRUE(handle)
Definition: unit_test.h:678