Merge lp:~afrantzis/mir/glib-main-loop-spike-wip into lp:mir
- glib-main-loop-spike-wip
- Merge into development-branch
Status: | Work in progress |
---|---|
Proposed branch: | lp:~afrantzis/mir/glib-main-loop-spike-wip |
Merge into: | lp:mir |
Diff against target: |
2437 lines (+2097/-98) 20 files modified
CMakeLists.txt (+1/-0) debian/control (+1/-0) include/common/mir/time/clock.h (+1/-0) src/common/time/high_resolution_clock.cpp (+9/-0) src/include/common/mir/basic_observers.h (+2/-95) src/include/common/mir/thread_safe_list.h (+186/-0) src/include/common/mir/time/high_resolution_clock.h (+2/-1) src/include/server/mir/glib_main_loop.h (+84/-0) src/server/CMakeLists.txt (+4/-0) src/server/default_server_configuration.cpp (+2/-1) src/server/display_server.cpp (+1/-0) src/server/glib_main_loop.cpp (+726/-0) tests/integration-tests/CMakeLists.txt (+2/-0) tests/unit-tests/CMakeLists.txt (+2/-0) tests/unit-tests/client/test_periodic_perf_report.cpp (+4/-0) tests/unit-tests/graphics/mesa/test_display.cpp (+2/-1) tests/unit-tests/logging/message_processor_report.cpp (+4/-0) tests/unit-tests/test_asio_main_loop.cpp (+4/-0) tests/unit-tests/test_glib_main_loop.cpp (+901/-0) tests/unit-tests/test_thread_safe_list.cpp (+159/-0) |
To merge this branch: | bzr merge lp:~afrantzis/mir/glib-main-loop-spike-wip |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Mir development team | Pending | ||
Review via email: mp+240150@code.launchpad.net |
Commit message
Description of the change
Please don't spend time code reviewing this branch, it's only meant to
communicate the general approach taken for the GLib version of the main
loop. However, please do take a look and let me know if you feel this is
less/more clear/straightf
like the general direction or not. See [1] for the way glib sources
work, which should aid your understanding.
Some integration tests are failing/hanging with this branch and the
culprit is the signalfd based signal source. The problem turns out to be
that in order for signalfd to catch a signal, the signal needs to be
blocked in *all* threads. Usually that translates to blocking the
signals in main() so all threads will inherit the signal mask.
Unfortunately, this doesn't work for us since we are a library and can't
enforce a signal mask in the general case.
Next steps:
1. Replace signalfd with something else (probably just signal handlers
+ self-pipe to notify main loop), or (less likely) find a way to
enforce a global signal mask.
2. Start proposing the changes in cleaned-up, small steps:
* Infrastructure changes (e.g. ThreadSafeList, Clock)
* GLibMainLoop basic start/stop + tests
* GLibMainLoop signal handing + tests
* GLibMainLoop fd handing + tests
* GLibMainLoop timers + tests
* GLibMainLoop queued events (ServerAction) + tests
* GLibMainLoop as a proper MainLoop extension, and
a mechanism to select it instead of AsioMainLoop
[1] https:/
- 2015. By Alexandros Frantzis
-
Use built-in glib signal source
Unmerged revisions
- 2015. By Alexandros Frantzis
-
Use built-in glib signal source
- 2014. By Alexandros Frantzis
-
GLibMainLoop WIP spike hack-o-rama
Preview Diff
1 | === modified file 'CMakeLists.txt' |
2 | --- CMakeLists.txt 2014-10-21 16:21:14 +0000 |
3 | +++ CMakeLists.txt 2014-10-31 11:59:50 +0000 |
4 | @@ -154,6 +154,7 @@ |
5 | find_package(XKBCOMMON REQUIRED) |
6 | find_package(LTTngUST REQUIRED) |
7 | pkg_check_modules(UDEV REQUIRED libudev) |
8 | +pkg_check_modules(GLIB REQUIRED glib-2.0) |
9 | |
10 | include_directories (${GLESv2_INCLUDE_DIRS}) |
11 | include_directories (${EGL_INCLUDE_DIRS}) |
12 | |
13 | === modified file 'debian/control' |
14 | --- debian/control 2014-10-30 18:37:11 +0000 |
15 | +++ debian/control 2014-10-31 11:59:50 +0000 |
16 | @@ -39,6 +39,7 @@ |
17 | libudev-dev, |
18 | google-mock (>= 1.6.0+svn437), |
19 | valgrind [!arm64], |
20 | + libglib2.0-dev, |
21 | Standards-Version: 3.9.4 |
22 | Homepage: https://launchpad.net/mir |
23 | # If you aren't a member of ~mir-team but need to upload packaging changes, |
24 | |
25 | === modified file 'include/common/mir/time/clock.h' |
26 | --- include/common/mir/time/clock.h 2014-10-01 06:25:56 +0000 |
27 | +++ include/common/mir/time/clock.h 2014-10-31 11:59:50 +0000 |
28 | @@ -35,6 +35,7 @@ |
29 | virtual ~Clock() = default; |
30 | |
31 | virtual Timestamp sample() const = 0; |
32 | + virtual Duration timeout_until(Timestamp) const { return Duration{0}; } |
33 | |
34 | protected: |
35 | Clock() = default; |
36 | |
37 | === modified file 'src/common/time/high_resolution_clock.cpp' |
38 | --- src/common/time/high_resolution_clock.cpp 2014-10-01 06:25:56 +0000 |
39 | +++ src/common/time/high_resolution_clock.cpp 2014-10-31 11:59:50 +0000 |
40 | @@ -22,3 +22,12 @@ |
41 | { |
42 | return clock.now(); |
43 | } |
44 | + |
45 | +mir::time::Duration mir::time::HighResolutionClock::timeout_until(Timestamp t) const |
46 | +{ |
47 | + auto const now = clock.now(); |
48 | + if (t <= now) |
49 | + return Duration{0}; |
50 | + else |
51 | + return t - now; |
52 | +} |
53 | |
54 | === modified file 'src/include/common/mir/basic_observers.h' |
55 | --- src/include/common/mir/basic_observers.h 2014-10-21 16:21:14 +0000 |
56 | +++ src/include/common/mir/basic_observers.h 2014-10-31 11:59:50 +0000 |
57 | @@ -19,108 +19,15 @@ |
58 | #ifndef MIR_BASIC_OBSERVERS_H_ |
59 | #define MIR_BASIC_OBSERVERS_H_ |
60 | |
61 | -#include "mir/recursive_read_write_mutex.h" |
62 | - |
63 | -#include <atomic> |
64 | +#include "mir/thread_safe_list.h" |
65 | #include <memory> |
66 | |
67 | namespace mir |
68 | { |
69 | template<class Observer> |
70 | -class BasicObservers |
71 | +class BasicObservers : protected ThreadSafeList<std::shared_ptr<Observer>> |
72 | { |
73 | -protected: |
74 | - void add(std::shared_ptr<Observer> const& observer); |
75 | - void remove(std::shared_ptr<Observer> const& observer); |
76 | - void for_each(std::function<void(std::shared_ptr<Observer> const& observer)> const& f); |
77 | - |
78 | -private: |
79 | - struct ListItem |
80 | - { |
81 | - ListItem() {} |
82 | - RecursiveReadWriteMutex mutex; |
83 | - std::shared_ptr<Observer> observer; |
84 | - std::atomic<ListItem*> next{nullptr}; |
85 | - |
86 | - ~ListItem() { delete next.load(); } |
87 | - } head; |
88 | }; |
89 | - |
90 | -template<class Observer> |
91 | -void BasicObservers<Observer>::for_each( |
92 | - std::function<void(std::shared_ptr<Observer> const& observer)> const& f) |
93 | -{ |
94 | - ListItem* current_item = &head; |
95 | - |
96 | - while (current_item) |
97 | - { |
98 | - RecursiveReadLock lock{current_item->mutex}; |
99 | - |
100 | - // We need to take a copy in case we recursively remove during call |
101 | - if (auto const copy_of_observer = current_item->observer) f(copy_of_observer); |
102 | - |
103 | - current_item = current_item->next; |
104 | - } |
105 | -} |
106 | - |
107 | -template<class Observer> |
108 | -void BasicObservers<Observer>::add(std::shared_ptr<Observer> const& observer) |
109 | -{ |
110 | - ListItem* current_item = &head; |
111 | - |
112 | - do |
113 | - { |
114 | - // Note: we release the read lock to avoid two threads calling add at |
115 | - // the same time mutually blocking the other's upgrade to write lock. |
116 | - { |
117 | - RecursiveReadLock lock{current_item->mutex}; |
118 | - if (current_item->observer) continue; |
119 | - } |
120 | - |
121 | - RecursiveWriteLock lock{current_item->mutex}; |
122 | - |
123 | - if (!current_item->observer) |
124 | - { |
125 | - current_item->observer = observer; |
126 | - return; |
127 | - } |
128 | - } |
129 | - while (current_item->next && (current_item = current_item->next)); |
130 | - |
131 | - // No empty Items so append a new one |
132 | - auto new_item = new ListItem; |
133 | - new_item->observer = observer; |
134 | - |
135 | - for (ListItem* expected{nullptr}; |
136 | - !current_item->next.compare_exchange_weak(expected, new_item); |
137 | - expected = nullptr) |
138 | - { |
139 | - if (expected) current_item = expected; |
140 | - } |
141 | -} |
142 | - |
143 | -template<class Observer> |
144 | -void BasicObservers<Observer>::remove(std::shared_ptr<Observer> const& observer) |
145 | -{ |
146 | - ListItem* current_item = &head; |
147 | - |
148 | - do |
149 | - { |
150 | - { |
151 | - RecursiveReadLock lock{current_item->mutex}; |
152 | - if (current_item->observer != observer) continue; |
153 | - } |
154 | - |
155 | - RecursiveWriteLock lock{current_item->mutex}; |
156 | - |
157 | - if (current_item->observer == observer) |
158 | - { |
159 | - current_item->observer.reset(); |
160 | - return; |
161 | - } |
162 | - } |
163 | - while ((current_item = current_item->next)); |
164 | -} |
165 | } |
166 | |
167 | #endif /* MIR_BASIC_OBSERVERS_H_ */ |
168 | |
169 | === added file 'src/include/common/mir/thread_safe_list.h' |
170 | --- src/include/common/mir/thread_safe_list.h 1970-01-01 00:00:00 +0000 |
171 | +++ src/include/common/mir/thread_safe_list.h 2014-10-31 11:59:50 +0000 |
172 | @@ -0,0 +1,186 @@ |
173 | +/* |
174 | + * Copyright © 2014 Canonical Ltd. |
175 | + * |
176 | + * This program is free software: you can redistribute it and/or modify it |
177 | + * under the terms of the GNU Lesser General Public License version 3, |
178 | + * as published by the Free Software Foundation. |
179 | + * |
180 | + * This program is distributed in the hope that it will be useful, |
181 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
182 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
183 | + * GNU Lesser General Public License for more details. |
184 | + * |
185 | + * You should have received a copy of the GNU Lesser General Public License |
186 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
187 | + * |
188 | + * Authored By: Alan Griffiths <alan@octopull.co.uk> |
189 | + Alexandros Frantzis <alexandros.frantzis@canonical.com> |
190 | + */ |
191 | + |
192 | +#ifndef MIR_THREAD_SAFE_LIST_H_ |
193 | +#define MIR_THREAD_SAFE_LIST_H_ |
194 | + |
195 | +#include "mir/recursive_read_write_mutex.h" |
196 | + |
197 | +#include <atomic> |
198 | + |
199 | +namespace mir |
200 | +{ |
201 | + |
202 | +/* |
203 | + * Requirements for type 'Element' |
204 | + * - for_each(): |
205 | + * - copy-assignable |
206 | + * - add() |
207 | + * - copy-assignable |
208 | + * - operator bool: returns whether this is a valid element |
209 | + * - remove(), remove_all(): |
210 | + * - copy-assignable |
211 | + * - Element{}: default construction should create an invalid element |
212 | + * - bool operator==: equality between two elements |
213 | + * - bool operator!=: inequality between two elements |
214 | + * - clear(): |
215 | + * - copy-assignable |
216 | + * - Element{}: default construction should create an invalid element |
217 | + */ |
218 | + |
219 | +template<class Element> |
220 | +class ThreadSafeList |
221 | +{ |
222 | +public: |
223 | + void add(Element const& element); |
224 | + void remove(Element const& element); |
225 | + unsigned int remove_all(Element const& element); |
226 | + void clear(); |
227 | + void for_each(std::function<void(Element const& element)> const& f); |
228 | + |
229 | +private: |
230 | + struct ListItem |
231 | + { |
232 | + ListItem() {} |
233 | + RecursiveReadWriteMutex mutex; |
234 | + Element element; |
235 | + std::atomic<ListItem*> next{nullptr}; |
236 | + |
237 | + ~ListItem() { delete next.load(); } |
238 | + } head; |
239 | +}; |
240 | + |
241 | +template<class Element> |
242 | +void ThreadSafeList<Element>::for_each( |
243 | + std::function<void(Element const& element)> const& f) |
244 | +{ |
245 | + ListItem* current_item = &head; |
246 | + |
247 | + while (current_item) |
248 | + { |
249 | + RecursiveReadLock lock{current_item->mutex}; |
250 | + |
251 | + // We need to take a copy in case we recursively remove during call |
252 | + if (auto const copy_of_element = current_item->element) f(copy_of_element); |
253 | + |
254 | + current_item = current_item->next; |
255 | + } |
256 | +} |
257 | + |
258 | +template<class Element> |
259 | +void ThreadSafeList<Element>::add(Element const& element) |
260 | +{ |
261 | + ListItem* current_item = &head; |
262 | + |
263 | + do |
264 | + { |
265 | + // Note: we release the read lock to avoid two threads calling add at |
266 | + // the same time mutually blocking the other's upgrade to write lock. |
267 | + { |
268 | + RecursiveReadLock lock{current_item->mutex}; |
269 | + if (current_item->element) continue; |
270 | + } |
271 | + |
272 | + RecursiveWriteLock lock{current_item->mutex}; |
273 | + |
274 | + if (!current_item->element) |
275 | + { |
276 | + current_item->element = element; |
277 | + return; |
278 | + } |
279 | + } |
280 | + while (current_item->next && (current_item = current_item->next)); |
281 | + |
282 | + // No empty Items so append a new one |
283 | + auto new_item = new ListItem; |
284 | + new_item->element = element; |
285 | + |
286 | + for (ListItem* expected{nullptr}; |
287 | + !current_item->next.compare_exchange_weak(expected, new_item); |
288 | + expected = nullptr) |
289 | + { |
290 | + if (expected) current_item = expected; |
291 | + } |
292 | +} |
293 | + |
294 | +template<class Element> |
295 | +void ThreadSafeList<Element>::remove(Element const& element) |
296 | +{ |
297 | + ListItem* current_item = &head; |
298 | + |
299 | + do |
300 | + { |
301 | + { |
302 | + RecursiveReadLock lock{current_item->mutex}; |
303 | + if (current_item->element != element) continue; |
304 | + } |
305 | + |
306 | + RecursiveWriteLock lock{current_item->mutex}; |
307 | + |
308 | + if (current_item->element == element) |
309 | + { |
310 | + current_item->element = Element{}; |
311 | + return; |
312 | + } |
313 | + } |
314 | + while ((current_item = current_item->next)); |
315 | +} |
316 | + |
317 | +template<class Element> |
318 | +unsigned int ThreadSafeList<Element>::remove_all(Element const& element) |
319 | +{ |
320 | + ListItem* current_item = &head; |
321 | + auto removed = 0u; |
322 | + |
323 | + do |
324 | + { |
325 | + { |
326 | + RecursiveReadLock lock{current_item->mutex}; |
327 | + if (current_item->element != element) continue; |
328 | + } |
329 | + |
330 | + RecursiveWriteLock lock{current_item->mutex}; |
331 | + |
332 | + if (current_item->element == element) |
333 | + { |
334 | + current_item->element = Element{}; |
335 | + ++removed; |
336 | + } |
337 | + } |
338 | + while ((current_item = current_item->next)); |
339 | + |
340 | + return removed; |
341 | +} |
342 | + |
343 | +template<class Element> |
344 | +void ThreadSafeList<Element>::clear() |
345 | +{ |
346 | + ListItem* current_item = &head; |
347 | + |
348 | + do |
349 | + { |
350 | + RecursiveWriteLock lock{current_item->mutex}; |
351 | + current_item->element = Element{}; |
352 | + } |
353 | + while ((current_item = current_item->next)); |
354 | +} |
355 | + |
356 | +} |
357 | + |
358 | +#endif /* MIR_THREAD_SAFE_LIST_H_ */ |
359 | |
360 | === modified file 'src/include/common/mir/time/high_resolution_clock.h' |
361 | --- src/include/common/mir/time/high_resolution_clock.h 2014-10-01 06:25:56 +0000 |
362 | +++ src/include/common/mir/time/high_resolution_clock.h 2014-10-31 11:59:50 +0000 |
363 | @@ -29,7 +29,8 @@ |
364 | class HighResolutionClock : public Clock |
365 | { |
366 | public: |
367 | - virtual Timestamp sample() const override; |
368 | + Timestamp sample() const override; |
369 | + Duration timeout_until(Timestamp t) const override; |
370 | |
371 | private: |
372 | std::chrono::high_resolution_clock clock; |
373 | |
374 | === added file 'src/include/server/mir/glib_main_loop.h' |
375 | --- src/include/server/mir/glib_main_loop.h 1970-01-01 00:00:00 +0000 |
376 | +++ src/include/server/mir/glib_main_loop.h 2014-10-31 11:59:50 +0000 |
377 | @@ -0,0 +1,84 @@ |
378 | +#ifndef MIR_GLIB_MAIN_LOOP_H_ |
379 | +#define MIR_GLIB_MAIN_LOOP_H_ |
380 | + |
381 | +#include "mir/main_loop.h" |
382 | + |
383 | +#include <glib.h> |
384 | +#include <memory> |
385 | +#include <thread> |
386 | +#include <vector> |
387 | +#include <atomic> |
388 | +#include <mutex> |
389 | +#include <unordered_set> |
390 | + |
391 | +namespace mir |
392 | +{ |
393 | +namespace time |
394 | +{ |
395 | +class Clock; |
396 | +} |
397 | +namespace detail |
398 | +{ |
399 | +class SignalDispatch; |
400 | +struct FdGSource; |
401 | +struct TimerGSource; |
402 | +struct ServerActionGSource; |
403 | +} |
404 | + |
405 | +class GLibMainLoop : public MainLoop |
406 | +{ |
407 | +public: |
408 | + explicit GLibMainLoop(std::shared_ptr<time::Clock> const& clock); |
409 | + ~GLibMainLoop(); |
410 | + |
411 | + void run() override; |
412 | + void stop() override; |
413 | + |
414 | + void register_signal_handler( |
415 | + std::initializer_list<int> signals, |
416 | + std::function<void(int)> const& handler) override; |
417 | + |
418 | + void register_fd_handler( |
419 | + std::initializer_list<int> fds, |
420 | + void const* owner, |
421 | + std::function<void(int)> const& handler) override; |
422 | + |
423 | + void unregister_fd_handler( |
424 | + void const* owner) override; |
425 | + |
426 | + std::unique_ptr<mir::time::Alarm> notify_in( |
427 | + std::chrono::milliseconds delay, |
428 | + std::function<void()> callback) override; |
429 | + |
430 | + std::unique_ptr<mir::time::Alarm> notify_at( |
431 | + mir::time::Timestamp t, |
432 | + std::function<void()> callback) override; |
433 | + |
434 | + std::unique_ptr<time::Alarm> create_alarm(std::function<void()> callback) override; |
435 | + |
436 | + void enqueue(void const* owner, ServerAction const& action) override; |
437 | + void pause_processing_for(void const* owner) override; |
438 | + void resume_processing_for(void const* owner) override; |
439 | + |
440 | + void flush(); |
441 | + |
442 | +private: |
443 | + std::shared_ptr<time::Clock> const clock; |
444 | + std::shared_ptr<GMainContext> main_context; |
445 | + std::atomic<bool> running; |
446 | + |
447 | + std::mutex fd_gsources_mutex; |
448 | + std::vector<std::shared_ptr<detail::FdGSource>> fd_gsources; |
449 | + |
450 | + std::unique_ptr<detail::SignalDispatch> signal_dispatch; |
451 | + |
452 | + std::mutex before_next_iteration_mutex; |
453 | + std::function<void()> before_next_iteration; |
454 | + |
455 | + std::mutex server_actions_mutex; |
456 | + std::unordered_set<void const*> do_not_process; |
457 | +}; |
458 | + |
459 | +} |
460 | + |
461 | +#endif |
462 | |
463 | === modified file 'src/server/CMakeLists.txt' |
464 | --- src/server/CMakeLists.txt 2014-10-21 16:21:14 +0000 |
465 | +++ src/server/CMakeLists.txt 2014-10-31 11:59:50 +0000 |
466 | @@ -3,6 +3,7 @@ |
467 | ${PROJECT_SOURCE_DIR}/src/include/platform |
468 | ${PROJECT_SOURCE_DIR}/include/server |
469 | ${PROJECT_SOURCE_DIR}/src/include/server |
470 | + ${GLIB_INCLUDE_DIRS} |
471 | ) |
472 | |
473 | add_subdirectory(compositor/) |
474 | @@ -31,6 +32,7 @@ |
475 | display_server.cpp |
476 | default_server_configuration.cpp |
477 | asio_main_loop.cpp |
478 | + glib_main_loop.cpp |
479 | default_emergency_cleanup.cpp |
480 | server.cpp |
481 | ) |
482 | @@ -59,6 +61,7 @@ |
483 | ${EGL_LDFLAGS} ${EGL_LIBRARIES} |
484 | ${GLESv2_LDFLAGS} ${GLESv2_LIBRARIES} |
485 | ${UDEV_LDFLAGS} ${UDEV_LIBRARIES} |
486 | + ${GLIB_LDFLAGS} ${GLIB_LIBRARIES} |
487 | ) |
488 | |
489 | set(MIR_SERVER_OBJECTS ${MIR_SERVER_OBJECTS} PARENT_SCOPE) |
490 | @@ -79,6 +82,7 @@ |
491 | ${EGL_LDFLAGS} ${EGL_LIBRARIES} |
492 | ${GLESv2_LDFLAGS} ${GLESv2_LIBRARIES} |
493 | ${UDEV_LDFLAGS} ${UDEV_LIBRARIES} |
494 | + ${GLIB_LDFLAGS} ${GLIB_LIBRARIES} |
495 | ) |
496 | |
497 | install(TARGETS mirserver |
498 | |
499 | === modified file 'src/server/default_server_configuration.cpp' |
500 | --- src/server/default_server_configuration.cpp 2014-10-01 06:25:56 +0000 |
501 | +++ src/server/default_server_configuration.cpp 2014-10-31 11:59:50 +0000 |
502 | @@ -21,6 +21,7 @@ |
503 | #include "mir/options/default_configuration.h" |
504 | #include "mir/abnormal_exit.h" |
505 | #include "mir/asio_main_loop.h" |
506 | +#include "mir/glib_main_loop.h" |
507 | #include "mir/default_server_status_listener.h" |
508 | #include "mir/emergency_cleanup.h" |
509 | #include "mir/default_configuration.h" |
510 | @@ -166,7 +167,7 @@ |
511 | return main_loop( |
512 | [this]() |
513 | { |
514 | - return std::make_shared<mir::AsioMainLoop>(the_clock()); |
515 | + return std::make_shared<mir::GLibMainLoop>(the_clock()); |
516 | }); |
517 | } |
518 | |
519 | |
520 | === modified file 'src/server/display_server.cpp' |
521 | --- src/server/display_server.cpp 2014-10-21 16:21:14 +0000 |
522 | +++ src/server/display_server.cpp 2014-10-31 11:59:50 +0000 |
523 | @@ -32,6 +32,7 @@ |
524 | #include "mir/input/input_dispatcher.h" |
525 | |
526 | #include <stdexcept> |
527 | +#include <signal.h> |
528 | |
529 | namespace mc = mir::compositor; |
530 | namespace mf = mir::frontend; |
531 | |
532 | === added file 'src/server/glib_main_loop.cpp' |
533 | --- src/server/glib_main_loop.cpp 1970-01-01 00:00:00 +0000 |
534 | +++ src/server/glib_main_loop.cpp 2014-10-31 11:59:50 +0000 |
535 | @@ -0,0 +1,726 @@ |
536 | +#include "mir/glib_main_loop.h" |
537 | +#include "mir/fd.h" |
538 | + |
539 | +#include <sys/signalfd.h> |
540 | +#include <unistd.h> |
541 | +#include <boost/throw_exception.hpp> |
542 | +#include <iostream> |
543 | +#include <algorithm> |
544 | +#include <atomic> |
545 | +#include <future> |
546 | +#include "mir/thread_safe_list.h" |
547 | +#include <glib-unix.h> |
548 | + |
549 | +#define ALFDEBUG if (false) |
550 | + |
551 | +namespace |
552 | +{ |
553 | + |
554 | +template<typename T> using Handlers = mir::ThreadSafeList<T>; |
555 | + |
556 | +class MainLoopSource |
557 | +{ |
558 | +public: |
559 | + MainLoopSource(GSource* gsource) : gsource{gsource} {} |
560 | + |
561 | + virtual ~MainLoopSource() = default; |
562 | + |
563 | + virtual gboolean prepare(gint* timeout) = 0; |
564 | + virtual gboolean check() = 0; |
565 | + virtual gboolean dispatch() = 0; |
566 | + |
567 | +protected: |
568 | + GSource* gsource; |
569 | +}; |
570 | + |
571 | +class FdMainLoopSource : public MainLoopSource |
572 | +{ |
573 | +public: |
574 | + FdMainLoopSource(GSource* source, int fd) |
575 | + : MainLoopSource{source}, fd_{fd}, |
576 | + fd_tag{g_source_add_unix_fd(source, fd, G_IO_IN)} |
577 | + { |
578 | + if (fd_tag == nullptr) |
579 | + BOOST_THROW_EXCEPTION(std::runtime_error("g source fd failed")); |
580 | + } |
581 | + |
582 | + void add_handler(std::function<void(int)> const& handler, void const* owner) |
583 | + { |
584 | + handlers.add({handler, owner}); |
585 | + ++num_handlers; |
586 | + } |
587 | + |
588 | + void remove_handlers_of(void const* owner) |
589 | + { |
590 | + num_handlers -= handlers.remove_all({{}, owner}); |
591 | + } |
592 | + |
593 | + bool has_handlers() const |
594 | + { |
595 | + return num_handlers > 0; |
596 | + } |
597 | + |
598 | + int fd() const |
599 | + { |
600 | + return fd_; |
601 | + } |
602 | + |
603 | + gboolean prepare(gint* timeout) override |
604 | + { |
605 | + *timeout = -1; |
606 | + return (g_source_query_unix_fd(gsource, fd_tag) == G_IO_IN); |
607 | + } |
608 | + |
609 | + gboolean check() override |
610 | + { |
611 | + return (g_source_query_unix_fd(gsource, fd_tag) == G_IO_IN); |
612 | + } |
613 | + |
614 | + gboolean dispatch() override |
615 | + { |
616 | + if (g_source_query_unix_fd(gsource, fd_tag) == G_IO_IN) |
617 | + { |
618 | + handlers.for_each( |
619 | + [&] (HandlerElement const& element) |
620 | + { |
621 | + element.handler(fd_); |
622 | + }); |
623 | + } |
624 | + |
625 | + return TRUE; |
626 | + } |
627 | + |
628 | +private: |
629 | + struct HandlerElement |
630 | + { |
631 | + HandlerElement() : owner{nullptr} {} |
632 | + HandlerElement(std::function<void(int)> const& handler, |
633 | + void const* owner) : handler{handler}, owner{owner} {} |
634 | + |
635 | + operator bool() const { return owner != nullptr; } |
636 | + bool operator==(HandlerElement const& other) const { return other.owner == owner; } |
637 | + bool operator!=(HandlerElement const& other) const { return other.owner != owner; } |
638 | + |
639 | + std::function<void(int)> handler; |
640 | + void const* owner; |
641 | + }; |
642 | + |
643 | + Handlers<HandlerElement> handlers; |
644 | + std::atomic<int> num_handlers{0}; |
645 | + int const fd_; |
646 | + gpointer fd_tag; |
647 | +}; |
648 | + |
649 | +class TimerMainLoopSource : public MainLoopSource |
650 | +{ |
651 | +public: |
652 | + TimerMainLoopSource(GSource* source, |
653 | + std::shared_ptr<mir::time::Clock> const& clock, |
654 | + mir::time::Timestamp target) |
655 | + : MainLoopSource{source}, clock{clock}, |
656 | + target{target} |
657 | + { |
658 | + ALFDEBUG std::cerr << this << " TimeSource create " << std::endl; |
659 | + } |
660 | + |
661 | + void add_handler(std::function<void()> const& handler) |
662 | + { |
663 | + handlers.add({handler}); |
664 | + } |
665 | + |
666 | + void clear_handlers() |
667 | + { |
668 | + handlers.clear(); |
669 | + } |
670 | + |
671 | + gboolean prepare(gint* timeout) override |
672 | + { |
673 | + auto const now = clock->sample(); |
674 | + bool const ready = (now >= target); |
675 | + if (ready) |
676 | + *timeout = -1; |
677 | + else |
678 | + *timeout = std::chrono::duration_cast<std::chrono::milliseconds>( |
679 | + clock->timeout_until(target)).count(); |
680 | + |
681 | + ALFDEBUG std::cerr << this << " TimeSource prepare: " << ready << " " << *timeout << std::endl; |
682 | + return ready; |
683 | + } |
684 | + |
685 | + gboolean check() override |
686 | + { |
687 | + auto const now = clock->sample(); |
688 | + bool const ready = (now >= target); |
689 | + ALFDEBUG std::cerr << this << " TimeSource check: " << ready << std::endl; |
690 | + return ready; |
691 | + } |
692 | + |
693 | + gboolean dispatch() override |
694 | + { |
695 | + ALFDEBUG std::cerr << this << " TimeSource dispatch: " << std::endl; |
696 | + handlers.for_each( |
697 | + [&] (HandlerElement const& element) |
698 | + { |
699 | + element.handler(); |
700 | + }); |
701 | + |
702 | + return FALSE; |
703 | + } |
704 | + |
705 | +private: |
706 | + struct HandlerElement |
707 | + { |
708 | + HandlerElement() = default; |
709 | + HandlerElement(std::function<void(void)> const& handler) : handler{handler} {} |
710 | + operator bool() const { return !!handler; } |
711 | + std::function<void(void)> handler; |
712 | + }; |
713 | + |
714 | + Handlers<HandlerElement> handlers; |
715 | + std::shared_ptr<mir::time::Clock> const clock; |
716 | + mir::time::Timestamp const target; |
717 | +}; |
718 | + |
719 | +class ServerActionMainLoopSource : public MainLoopSource |
720 | +{ |
721 | +public: |
722 | + ServerActionMainLoopSource( |
723 | + GSource* gsource, void const* owner, mir::ServerAction const& action, |
724 | + std::function<bool(void const*)> const& should_dispatch) |
725 | + : MainLoopSource{gsource}, owner{owner}, action{action}, |
726 | + should_dispatch{should_dispatch} |
727 | + { |
728 | + ALFDEBUG std::cerr << this << " TimeSource create " << std::endl; |
729 | + } |
730 | + |
731 | + gboolean prepare(gint* timeout) override |
732 | + { |
733 | + ALFDEBUG std::cerr << this << " ServerAction prepare: " << std::endl; |
734 | + *timeout = -1; |
735 | + return should_dispatch(owner); |
736 | + } |
737 | + |
738 | + gboolean check() override |
739 | + { |
740 | + ALFDEBUG std::cerr << this << " ServerAction check: " << std::endl; |
741 | + return should_dispatch(owner); |
742 | + } |
743 | + |
744 | + gboolean dispatch() override |
745 | + { |
746 | + ALFDEBUG std::cerr << this << " ServerAction dispatch: " << std::endl; |
747 | + action(); |
748 | + return FALSE; |
749 | + } |
750 | + |
751 | +private: |
752 | + void const* const owner; |
753 | + mir::ServerAction const action; |
754 | + std::function<bool(void const*)> const should_dispatch; |
755 | +}; |
756 | + |
757 | + |
758 | + |
759 | +struct MainLoopGSource |
760 | +{ |
761 | + GSource gsource; |
762 | + MainLoopSource* source; |
763 | + |
764 | + void attach(GMainContext* main_context) |
765 | + { |
766 | + g_source_attach(&gsource, main_context); |
767 | + } |
768 | +}; |
769 | + |
770 | +gboolean ml_prepare(GSource* source, gint *timeout) |
771 | +{ |
772 | + ALFDEBUG std::cerr << "ml_prepare" << std::endl; |
773 | + auto ml_source = reinterpret_cast<MainLoopGSource*>(source); |
774 | + if (!g_source_is_destroyed(source)) |
775 | + return ml_source->source->prepare(timeout); |
776 | + return FALSE; |
777 | +} |
778 | + |
779 | +gboolean ml_check(GSource* source) |
780 | +{ |
781 | + ALFDEBUG std::cerr << "ml_check" << std::endl; |
782 | + auto ml_source = reinterpret_cast<MainLoopGSource*>(source); |
783 | + if (!g_source_is_destroyed(source)) |
784 | + return ml_source->source->check(); |
785 | + return FALSE; |
786 | +} |
787 | + |
788 | +gboolean ml_dispatch(GSource* source, GSourceFunc, gpointer) |
789 | +{ |
790 | + ALFDEBUG std::cerr << "ml_dispatch" << std::endl; |
791 | + auto ml_source = reinterpret_cast<MainLoopGSource*>(source); |
792 | + if (!g_source_is_destroyed(source)) |
793 | + return ml_source->source->dispatch(); |
794 | + return FALSE; |
795 | +} |
796 | + |
797 | +void ml_finalize(GSource* source) |
798 | +{ |
799 | + ALFDEBUG std::cerr << "ml_finalize" << std::endl; |
800 | + auto ml_source = reinterpret_cast<MainLoopGSource*>(source); |
801 | + delete ml_source->source; |
802 | +} |
803 | + |
804 | +GSourceFuncs gsource_funcs { |
805 | + ml_prepare, |
806 | + ml_check, |
807 | + ml_dispatch, |
808 | + ml_finalize, |
809 | + nullptr, |
810 | + nullptr |
811 | +}; |
812 | + |
813 | +} |
814 | + |
815 | +class mir::detail::SignalDispatch |
816 | +{ |
817 | +public: |
818 | + SignalDispatch(GMainContext* main_context) |
819 | + : main_context{main_context} |
820 | + { |
821 | + } |
822 | + |
823 | + void add_handler(std::vector<int> const& sigs, std::function<void(int)> const& handler) |
824 | + { |
825 | + for (auto sig : sigs) |
826 | + ensure_handle_signal(sig); |
827 | + handlers.add({sigs, handler}); |
828 | + } |
829 | + |
830 | + void ensure_handle_signal(int sig) |
831 | + { |
832 | + std::lock_guard<std::mutex> lock{handled_signals_mutex}; |
833 | + |
834 | + if (handled_signals.find(sig) != handled_signals.end()) |
835 | + return; |
836 | + |
837 | + auto const gsource = g_unix_signal_source_new(sig); |
838 | + auto sc = new SignalContext{this, sig}; |
839 | + g_source_set_callback( |
840 | + gsource, |
841 | + reinterpret_cast<GSourceFunc>(static_dispatch), sc, |
842 | + reinterpret_cast<GDestroyNotify>(destroy_sc)); |
843 | + g_source_attach(gsource, main_context); |
844 | + g_source_unref(gsource); |
845 | + } |
846 | + |
847 | + void dispatch(int sig) |
848 | + { |
849 | + handlers.for_each( |
850 | + [&] (HandlerElement const& element) |
851 | + { |
852 | + ALFDEBUG std::cerr << this << " SignalDispatch calling handler sig " << sig << std::endl; |
853 | + if (std::find(element.sigs.begin(), element.sigs.end(), sig) != element.sigs.end()) |
854 | + element.handler(sig); |
855 | + }); |
856 | + } |
857 | + |
858 | +private: |
859 | + struct SignalContext |
860 | + { |
861 | + mir::detail::SignalDispatch* sd; |
862 | + int sig; |
863 | + }; |
864 | + |
865 | + static void destroy_sc(SignalContext* d) { delete d; } |
866 | + |
867 | + struct HandlerElement |
868 | + { |
869 | + operator bool() const { return !!handler; } |
870 | + std::vector<int> sigs; |
871 | + std::function<void(int)> handler; |
872 | + }; |
873 | + |
874 | + static gboolean static_dispatch(SignalContext* sc) |
875 | + { |
876 | + sc->sd->dispatch(sc->sig); |
877 | + return TRUE; |
878 | + } |
879 | + |
880 | + |
881 | + GMainContext* const main_context; |
882 | + Handlers<HandlerElement> handlers; |
883 | + std::mutex handled_signals_mutex; |
884 | + std::unordered_set<int> handled_signals; |
885 | +}; |
886 | + |
887 | +struct mir::detail::FdGSource : MainLoopGSource |
888 | +{ |
889 | + FdMainLoopSource* fd_source() |
890 | + { |
891 | + return static_cast<FdMainLoopSource*>(source); |
892 | + } |
893 | +}; |
894 | + |
895 | +std::shared_ptr<mir::detail::FdGSource> make_fd_gsource(int fd) |
896 | +{ |
897 | + auto const gsource = g_source_new(&gsource_funcs, sizeof(mir::detail::FdGSource)); |
898 | + auto const fd_gsource = reinterpret_cast<mir::detail::FdGSource*>(gsource); |
899 | + |
900 | + fd_gsource->source = new FdMainLoopSource{gsource, fd}; |
901 | + |
902 | + return { |
903 | + fd_gsource, |
904 | + [] (mir::detail::FdGSource* s) |
905 | + { |
906 | + auto const gsource = reinterpret_cast<GSource*>(s); |
907 | + g_source_destroy(gsource); |
908 | + g_source_unref(gsource); |
909 | + }}; |
910 | +} |
911 | + |
912 | +struct mir::detail::TimerGSource : MainLoopGSource |
913 | +{ |
914 | + TimerMainLoopSource* timer_source() |
915 | + { |
916 | + return static_cast<TimerMainLoopSource*>(source); |
917 | + } |
918 | +}; |
919 | + |
920 | +std::shared_ptr<mir::detail::TimerGSource> make_timer_gsource( |
921 | + std::shared_ptr<mir::time::Clock> const& clock, |
922 | + mir::time::Timestamp target) |
923 | +{ |
924 | + auto const gsource = g_source_new(&gsource_funcs, sizeof(mir::detail::TimerGSource)); |
925 | + auto const timer_gsource = reinterpret_cast<mir::detail::TimerGSource*>(gsource); |
926 | + |
927 | + timer_gsource->source = new TimerMainLoopSource{gsource, clock, target}; |
928 | + |
929 | + return { |
930 | + timer_gsource, |
931 | + [] (mir::detail::TimerGSource* s) |
932 | + { |
933 | + // Clear handlers to ensure that no handlers will be called after this object |
934 | + // has been destroyed (in case we are destroying while the source is being |
935 | + // dispatched). |
936 | + s->timer_source()->clear_handlers(); |
937 | + auto const gsource = reinterpret_cast<GSource*>(s); |
938 | + g_source_destroy(gsource); |
939 | + g_source_unref(gsource); |
940 | + }}; |
941 | +} |
942 | + |
943 | +struct mir::detail::ServerActionGSource : MainLoopGSource |
944 | +{ |
945 | +}; |
946 | + |
947 | +std::shared_ptr<mir::detail::ServerActionGSource> make_server_action_gsource( |
948 | + void const* owner, mir::ServerAction const& action, |
949 | + std::function<bool(void const*)> const& should_dispatch) |
950 | +{ |
951 | + auto const gsource = g_source_new(&gsource_funcs, sizeof(mir::detail::ServerActionGSource)); |
952 | + auto const server_action_gsource = reinterpret_cast<mir::detail::ServerActionGSource*>(gsource); |
953 | + |
954 | + server_action_gsource->source = |
955 | + new ServerActionMainLoopSource{gsource, owner, action, should_dispatch}; |
956 | + |
957 | + return { |
958 | + server_action_gsource, |
959 | + [] (mir::detail::ServerActionGSource* s) |
960 | + { |
961 | + auto const gsource = reinterpret_cast<GSource*>(s); |
962 | + // NB: we shouldn't destroy the gsource |
963 | + //g_source_destroy(gsource); |
964 | + g_source_unref(gsource); |
965 | + }}; |
966 | +} |
967 | + |
968 | +class AlarmImpl : public mir::time::Alarm |
969 | +{ |
970 | +public: |
971 | + AlarmImpl( |
972 | + GMainContext* main_context, |
973 | + std::shared_ptr<mir::time::Clock> const& clock, |
974 | + std::function<void()> const& callback) |
975 | + : clock{clock}, |
976 | + callback{callback}, |
977 | + state_{State::cancelled}, |
978 | + main_context{main_context} |
979 | + { |
980 | + } |
981 | + |
982 | + bool cancel() override |
983 | + { |
984 | + std::lock_guard<std::mutex> lock{alarm_mutex}; |
985 | + |
986 | + source.reset(); |
987 | + state_ = State::cancelled; |
988 | + return true; |
989 | + } |
990 | + |
991 | + State state() const override |
992 | + { |
993 | + std::lock_guard<std::mutex> lock{alarm_mutex}; |
994 | + |
995 | + return state_; |
996 | + } |
997 | + |
998 | + bool reschedule_in(std::chrono::milliseconds delay) override |
999 | + { |
1000 | + return reschedule_for(clock->sample() + delay); |
1001 | + } |
1002 | + |
1003 | + bool reschedule_for(mir::time::Timestamp time_point) override |
1004 | + { |
1005 | + std::lock_guard<std::mutex> lock{alarm_mutex}; |
1006 | + |
1007 | + state_ = State::pending; |
1008 | + source = make_timer_gsource(clock, time_point); |
1009 | + source->timer_source()->add_handler( |
1010 | + [&] { state_ = State::triggered; callback(); } ); |
1011 | + source->attach(main_context); |
1012 | + return true; |
1013 | + } |
1014 | + |
1015 | +private: |
1016 | + mutable std::mutex alarm_mutex; |
1017 | + std::shared_ptr<mir::time::Clock> const clock; |
1018 | + std::function<void()> const callback; |
1019 | + State state_; |
1020 | + std::shared_ptr<mir::detail::TimerGSource> source; |
1021 | + GMainContext* main_context; |
1022 | +}; |
1023 | + |
1024 | + |
1025 | + |
1026 | +mir::GLibMainLoop::GLibMainLoop( |
1027 | + std::shared_ptr<time::Clock> const& clock) |
1028 | + : clock{clock}, |
1029 | + main_context{g_main_context_new(), |
1030 | + [] (GMainContext* ctx) |
1031 | + { |
1032 | + if (ctx) g_main_context_unref(ctx); |
1033 | + }}, |
1034 | + signal_dispatch{new detail::SignalDispatch{main_context.get()}}, |
1035 | + before_next_iteration{[]{}} |
1036 | +{ |
1037 | +} |
1038 | + |
1039 | +mir::GLibMainLoop::~GLibMainLoop() |
1040 | +{ |
1041 | + stop(); |
1042 | +} |
1043 | + |
1044 | +void mir::GLibMainLoop::run() |
1045 | +{ |
1046 | + running = true; |
1047 | + ALFDEBUG std::cerr << "run start" << std::endl; |
1048 | + while (running) |
1049 | + { |
1050 | + ALFDEBUG std::cerr << "run iter start " << running << std::endl; |
1051 | + { |
1052 | + std::lock_guard<std::mutex> lock{before_next_iteration_mutex}; |
1053 | + before_next_iteration(); |
1054 | + } |
1055 | + ALFDEBUG std::cerr << "run iter 2 " << running << std::endl; |
1056 | + g_main_context_iteration(main_context.get(), TRUE); |
1057 | + ALFDEBUG std::cerr << "run iter end " << running << std::endl; |
1058 | + } |
1059 | + ALFDEBUG std::cerr << "run end" << std::endl; |
1060 | +} |
1061 | + |
1062 | +struct StopContext |
1063 | +{ |
1064 | + std::atomic<bool>* running; |
1065 | + GMainContext* main_context; |
1066 | +}; |
1067 | + |
1068 | +gboolean stop_func(gpointer data) |
1069 | +{ |
1070 | + auto stop_context = static_cast<StopContext*>(data); |
1071 | + |
1072 | + *stop_context->running = false; |
1073 | + g_main_context_wakeup(stop_context->main_context); |
1074 | + return FALSE; |
1075 | +} |
1076 | + |
1077 | + |
1078 | +void delete_stop_context(StopContext* ctx) |
1079 | +{ |
1080 | + delete ctx; |
1081 | +} |
1082 | + |
1083 | +void mir::GLibMainLoop::stop() |
1084 | +{ |
1085 | + ALFDEBUG std::cerr << "stop() " << running << std::endl; |
1086 | + int const very_high_priority = -1000; |
1087 | + auto source = g_idle_source_new(); |
1088 | + |
1089 | + g_source_set_priority(source, very_high_priority); |
1090 | + g_source_set_callback( |
1091 | + source, stop_func, |
1092 | + new StopContext{&running, main_context.get()}, |
1093 | + reinterpret_cast<GDestroyNotify>(delete_stop_context)); |
1094 | + g_source_attach(source, main_context.get()); |
1095 | + g_source_unref(source); |
1096 | +} |
1097 | + |
1098 | +void mir::GLibMainLoop::register_signal_handler( |
1099 | + std::initializer_list<int> signals, |
1100 | + std::function<void(int)> const& handler) |
1101 | +{ |
1102 | + signal_dispatch->add_handler(signals, handler); |
1103 | +} |
1104 | + |
1105 | +void mir::GLibMainLoop::register_fd_handler( |
1106 | + std::initializer_list<int> fds, |
1107 | + void const* owner, |
1108 | + std::function<void(int)> const& handler) |
1109 | +{ |
1110 | + std::lock_guard<std::mutex> lock{fd_gsources_mutex}; |
1111 | + |
1112 | + for (auto fd : fds) |
1113 | + { |
1114 | + std::shared_ptr<detail::FdGSource> gsource; |
1115 | + bool new_gsource{false}; |
1116 | + |
1117 | + auto iter = std::find_if( |
1118 | + fd_gsources.begin(), |
1119 | + fd_gsources.end(), |
1120 | + [&] (std::shared_ptr<detail::FdGSource> const& f) |
1121 | + { |
1122 | + return f->fd_source()->fd() == fd; |
1123 | + }); |
1124 | + |
1125 | + if (iter == fd_gsources.end()) |
1126 | + { |
1127 | + gsource = make_fd_gsource(fd); |
1128 | + new_gsource = true; |
1129 | + fd_gsources.push_back(gsource); |
1130 | + } |
1131 | + else |
1132 | + { |
1133 | + gsource = *iter; |
1134 | + } |
1135 | + |
1136 | + gsource->fd_source()->add_handler(handler, owner); |
1137 | + if (new_gsource) |
1138 | + gsource->attach(main_context.get()); |
1139 | + } |
1140 | +} |
1141 | + |
1142 | +void mir::GLibMainLoop::unregister_fd_handler( |
1143 | + void const* owner) |
1144 | +{ |
1145 | + std::lock_guard<std::mutex> lock{fd_gsources_mutex}; |
1146 | + |
1147 | + ALFDEBUG std::cerr << "unregister_fd_handler " << owner << std::endl; |
1148 | + |
1149 | + auto it = fd_gsources.rbegin(); |
1150 | + |
1151 | + while (it != fd_gsources.rend()) |
1152 | + { |
1153 | + auto const gsource = *it; |
1154 | + gsource->fd_source()->remove_handlers_of(owner); |
1155 | + |
1156 | + ALFDEBUG std::cerr << "removed handler" << std::endl; |
1157 | + if (!gsource->fd_source()->has_handlers()) |
1158 | + { |
1159 | + ALFDEBUG std::cerr << "no handler left " << std::endl; |
1160 | + it = decltype(fd_gsources)::reverse_iterator( |
1161 | + fd_gsources.erase(std::prev(it.base()))); |
1162 | + } |
1163 | + else |
1164 | + { |
1165 | + ++it; |
1166 | + } |
1167 | + } |
1168 | +} |
1169 | + |
1170 | +std::unique_ptr<mir::time::Alarm> mir::GLibMainLoop::notify_in( |
1171 | + std::chrono::milliseconds delay, |
1172 | + std::function<void()> callback) |
1173 | +{ |
1174 | + auto alarm = std::unique_ptr<mir::time::Alarm>{ |
1175 | + new AlarmImpl{main_context.get(), clock, callback}}; |
1176 | + |
1177 | + alarm->reschedule_in(delay); |
1178 | + |
1179 | + return alarm; |
1180 | +} |
1181 | + |
1182 | +std::unique_ptr<mir::time::Alarm> mir::GLibMainLoop::notify_at( |
1183 | + mir::time::Timestamp t, |
1184 | + std::function<void()> callback) |
1185 | +{ |
1186 | + auto alarm = std::unique_ptr<mir::time::Alarm>{ |
1187 | + new AlarmImpl{main_context.get(), clock, callback}}; |
1188 | + |
1189 | + alarm->reschedule_for(t); |
1190 | + |
1191 | + return alarm; |
1192 | +} |
1193 | + |
1194 | +std::unique_ptr<mir::time::Alarm> mir::GLibMainLoop::create_alarm( |
1195 | + std::function<void()> callback) |
1196 | +{ |
1197 | + return std::unique_ptr<mir::time::Alarm>{ |
1198 | + new AlarmImpl{main_context.get(), clock, callback}}; |
1199 | +} |
1200 | + |
1201 | +void mir::GLibMainLoop::enqueue(void const* owner, ServerAction const& action) |
1202 | +{ |
1203 | + auto gsource = make_server_action_gsource(owner, action, |
1204 | + [&] (void const* owner) |
1205 | + { |
1206 | + std::lock_guard<std::mutex> lock{server_actions_mutex}; |
1207 | + return (do_not_process.find(owner) == do_not_process.end()); |
1208 | + }); |
1209 | + gsource->attach(main_context.get()); |
1210 | +} |
1211 | + |
1212 | +void mir::GLibMainLoop::pause_processing_for(void const* owner) |
1213 | +{ |
1214 | + std::lock_guard<std::mutex> lock{server_actions_mutex}; |
1215 | + do_not_process.insert(owner); |
1216 | +} |
1217 | + |
1218 | +void mir::GLibMainLoop::resume_processing_for(void const* owner) |
1219 | +{ |
1220 | + std::lock_guard<std::mutex> lock{server_actions_mutex}; |
1221 | + do_not_process.erase(owner); |
1222 | + |
1223 | + g_main_context_wakeup(main_context.get()); |
1224 | +} |
1225 | + |
1226 | +gboolean flush_func(gpointer data) |
1227 | +{ |
1228 | + ALFDEBUG std::cerr << "Flush!" << std::endl; |
1229 | + auto to_flush = static_cast<std::promise<void>*>(data); |
1230 | + to_flush->set_value(); |
1231 | + |
1232 | + return FALSE; |
1233 | +} |
1234 | + |
1235 | +void mir::GLibMainLoop::flush() |
1236 | +{ |
1237 | + std::promise<void> to_flush; |
1238 | + auto flushed = to_flush.get_future(); |
1239 | + |
1240 | + { |
1241 | + std::lock_guard<std::mutex> lock{before_next_iteration_mutex}; |
1242 | + before_next_iteration = [&] { |
1243 | + ALFDEBUG std::cerr << "g_idle_add_full" << std::endl; |
1244 | + |
1245 | + auto source = g_idle_source_new(); |
1246 | + g_source_set_priority(source, G_PRIORITY_LOW); |
1247 | + g_source_set_callback(source, flush_func, &to_flush, nullptr); |
1248 | + g_source_attach(source, main_context.get()); |
1249 | + g_source_unref(source); |
1250 | + |
1251 | + before_next_iteration = []{}; |
1252 | + }; |
1253 | + } |
1254 | + |
1255 | + g_main_context_wakeup(main_context.get()); |
1256 | + |
1257 | + ALFDEBUG std::cerr << "Waiting for before_next_iteration done" << std::endl; |
1258 | + |
1259 | + flushed.get(); |
1260 | + ALFDEBUG std::cerr << "flush finished" << std::endl; |
1261 | +} |
1262 | |
1263 | === modified file 'tests/integration-tests/CMakeLists.txt' |
1264 | --- tests/integration-tests/CMakeLists.txt 2014-10-28 13:59:51 +0000 |
1265 | +++ tests/integration-tests/CMakeLists.txt 2014-10-31 11:59:50 +0000 |
1266 | @@ -7,6 +7,7 @@ |
1267 | ${PROJECT_SOURCE_DIR}/src/include/platform |
1268 | ${PROJECT_SOURCE_DIR}/src/include/server |
1269 | ${PROJECT_SOURCE_DIR}/src/include/client |
1270 | + ${GLIB_INCLUDE_DIRS} |
1271 | ) |
1272 | |
1273 | protobuf_generate_cpp( |
1274 | @@ -99,6 +100,7 @@ |
1275 | ${CMAKE_THREAD_LIBS_INIT} # Link in pthread. |
1276 | ${DRM_LDFLAGS} ${DRM_LIBRARIES} |
1277 | ${GBM_LDFLAGS} ${GBM_LIBRARIES} |
1278 | + ${GLIB_LDFLAGS} ${GLIB_LIBRARIES} |
1279 | ${MIR_PLATFORM_REFERENCES} |
1280 | ${MIR_COMMON_REFERENCES} |
1281 | ${MIR_SERVER_REFERENCES} |
1282 | |
1283 | === modified file 'tests/unit-tests/CMakeLists.txt' |
1284 | --- tests/unit-tests/CMakeLists.txt 2014-10-27 18:00:20 +0000 |
1285 | +++ tests/unit-tests/CMakeLists.txt 2014-10-31 11:59:50 +0000 |
1286 | @@ -25,6 +25,7 @@ |
1287 | test_gmock_fixes.cpp |
1288 | test_recursive_read_write_mutex.cpp |
1289 | test_asio_main_loop.cpp |
1290 | + test_glib_main_loop.cpp |
1291 | shared_library_test.cpp |
1292 | test_raii.cpp |
1293 | test_udev_wrapper.cpp |
1294 | @@ -32,6 +33,7 @@ |
1295 | test_thread_name.cpp |
1296 | test_default_emergency_cleanup.cpp |
1297 | test_basic_observers.cpp |
1298 | + test_thread_safe_list.cpp |
1299 | test_fatal.cpp |
1300 | test_fd.cpp |
1301 | test_shared_library_prober.cpp |
1302 | |
1303 | === modified file 'tests/unit-tests/client/test_periodic_perf_report.cpp' |
1304 | --- tests/unit-tests/client/test_periodic_perf_report.cpp 2014-09-05 08:00:44 +0000 |
1305 | +++ tests/unit-tests/client/test_periodic_perf_report.cpp 2014-10-31 11:59:50 +0000 |
1306 | @@ -37,6 +37,10 @@ |
1307 | { |
1308 | return now; |
1309 | } |
1310 | + time::Duration timeout_until(time::Timestamp) const override |
1311 | + { |
1312 | + return time::Duration{0}; |
1313 | + } |
1314 | private: |
1315 | time::Timestamp now; |
1316 | }; |
1317 | |
1318 | === modified file 'tests/unit-tests/graphics/mesa/test_display.cpp' |
1319 | --- tests/unit-tests/graphics/mesa/test_display.cpp 2014-10-01 06:25:56 +0000 |
1320 | +++ tests/unit-tests/graphics/mesa/test_display.cpp 2014-10-31 11:59:50 +0000 |
1321 | @@ -25,6 +25,7 @@ |
1322 | #include "src/server/graphics/default_display_configuration_policy.h" |
1323 | #include "mir/time/high_resolution_clock.h" |
1324 | #include "mir/asio_main_loop.h" |
1325 | +#include "mir/glib_main_loop.h" |
1326 | |
1327 | #include "mir_test_doubles/mock_egl.h" |
1328 | #include "mir_test_doubles/mock_gl.h" |
1329 | @@ -717,7 +718,7 @@ |
1330 | |
1331 | auto display = create_display(create_platform()); |
1332 | |
1333 | - mir::AsioMainLoop ml{std::make_shared<mir::time::HighResolutionClock>()}; |
1334 | + mir::GLibMainLoop ml{std::make_shared<mir::time::HighResolutionClock>()}; |
1335 | std::condition_variable done; |
1336 | |
1337 | int const device_add_count{1}; |
1338 | |
1339 | === modified file 'tests/unit-tests/logging/message_processor_report.cpp' |
1340 | --- tests/unit-tests/logging/message_processor_report.cpp 2014-03-06 06:05:17 +0000 |
1341 | +++ tests/unit-tests/logging/message_processor_report.cpp 2014-10-31 11:59:50 +0000 |
1342 | @@ -32,6 +32,10 @@ |
1343 | { |
1344 | public: |
1345 | MOCK_CONST_METHOD0(sample, mir::time::Timestamp()); |
1346 | + mir::time::Duration timeout_until(mir::time::Timestamp) const override |
1347 | + { |
1348 | + return mir::time::Duration{0}; |
1349 | + } |
1350 | |
1351 | ~MockClock() noexcept(true) {} |
1352 | }; |
1353 | |
1354 | === modified file 'tests/unit-tests/test_asio_main_loop.cpp' |
1355 | --- tests/unit-tests/test_asio_main_loop.cpp 2014-10-21 16:21:14 +0000 |
1356 | +++ tests/unit-tests/test_asio_main_loop.cpp 2014-10-31 11:59:50 +0000 |
1357 | @@ -56,6 +56,10 @@ |
1358 | std::lock_guard<std::mutex> lock(time_mutex); |
1359 | return current_time; |
1360 | } |
1361 | + mir::time::Duration timeout_until(mir::time::Timestamp) const override |
1362 | + { |
1363 | + return mir::time::Duration{0}; |
1364 | + } |
1365 | void advance_by(std::chrono::milliseconds const step, mir::AsioMainLoop & ml) |
1366 | { |
1367 | bool done = false; |
1368 | |
1369 | === added file 'tests/unit-tests/test_glib_main_loop.cpp' |
1370 | --- tests/unit-tests/test_glib_main_loop.cpp 1970-01-01 00:00:00 +0000 |
1371 | +++ tests/unit-tests/test_glib_main_loop.cpp 2014-10-31 11:59:50 +0000 |
1372 | @@ -0,0 +1,901 @@ |
1373 | +/* |
1374 | + * Copyright © 2014 Canonical Ltd. |
1375 | + * |
1376 | + * This program is free software: you can redistribute it and/or modify |
1377 | + * it under the terms of the GNU General Public License version 3 as |
1378 | + * published by the Free Software Foundation. |
1379 | + * |
1380 | + * This program is distributed in the hope that it will be useful, |
1381 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1382 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1383 | + * GNU General Public License for more details. |
1384 | + * |
1385 | + * You should have received a copy of the GNU General Public License |
1386 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1387 | + * |
1388 | + * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com> |
1389 | + */ |
1390 | + |
1391 | +#include "mir/glib_main_loop.h" |
1392 | +#include "mir/time/high_resolution_clock.h" |
1393 | + |
1394 | +#include "mir_test/pipe.h" |
1395 | +#include "mir_test/signal.h" |
1396 | +#include "mir_test/auto_unblock_thread.h" |
1397 | + |
1398 | +#include <gtest/gtest.h> |
1399 | +#include <gmock/gmock.h> |
1400 | + |
1401 | +#include <atomic> |
1402 | +#include <mutex> |
1403 | +#include <condition_variable> |
1404 | + |
1405 | +namespace mt = mir::test; |
1406 | + |
1407 | +class Counter |
1408 | +{ |
1409 | +public: |
1410 | + int operator++() |
1411 | + { |
1412 | + std::lock_guard<decltype(mutex)> lock(mutex); |
1413 | + cv.notify_one(); |
1414 | + return ++counter; |
1415 | + } |
1416 | + |
1417 | + bool wait_for(std::chrono::milliseconds const& delay, int expected) |
1418 | + { |
1419 | + std::unique_lock<decltype(mutex)> lock(mutex); |
1420 | + return cv.wait_for(lock, delay, [&]{ return counter == expected;}); |
1421 | + } |
1422 | + |
1423 | + operator int() const |
1424 | + { |
1425 | + std::lock_guard<decltype(mutex)> lock(mutex); |
1426 | + return counter; |
1427 | + } |
1428 | + |
1429 | +private: |
1430 | + std::mutex mutable mutex; |
1431 | + std::condition_variable cv; |
1432 | + int counter{0}; |
1433 | +}; |
1434 | + |
1435 | +struct UnblockMainLoop : mt::AutoUnblockThread |
1436 | +{ |
1437 | + UnblockMainLoop(mir::GLibMainLoop& loop) |
1438 | + : mt::AutoUnblockThread([&loop]() {loop.stop();}, |
1439 | + [&loop]() {loop.run();}) |
1440 | + {} |
1441 | +}; |
1442 | + |
1443 | + |
1444 | +class AdvanceableClock : public mir::time::Clock |
1445 | +{ |
1446 | +public: |
1447 | + mir::time::Timestamp sample() const override |
1448 | + { |
1449 | + std::lock_guard<std::mutex> lock(time_mutex); |
1450 | + return current_time; |
1451 | + } |
1452 | + |
1453 | + mir::time::Duration timeout_until(mir::time::Timestamp) const override |
1454 | + { |
1455 | + std::lock_guard<std::mutex> lock(time_mutex); |
1456 | + return mir::time::Duration{0}; |
1457 | + } |
1458 | + |
1459 | + void advance_by(std::chrono::milliseconds const step, mir::GLibMainLoop& ml) |
1460 | + { |
1461 | + { |
1462 | + std::lock_guard<std::mutex> lock(time_mutex); |
1463 | + current_time += step; |
1464 | + } |
1465 | + ml.flush(); |
1466 | + //std::cerr << "Advance by finish" << std::endl; |
1467 | + } |
1468 | + |
1469 | +private: |
1470 | + mutable std::mutex time_mutex; |
1471 | + mir::time::Timestamp current_time{ |
1472 | + [] |
1473 | + { |
1474 | + mir::time::HighResolutionClock clock; |
1475 | + return clock.sample(); |
1476 | + }() |
1477 | + }; |
1478 | +}; |
1479 | + |
1480 | +struct GLibMainLoopTest : ::testing::Test |
1481 | +{ |
1482 | + mir::GLibMainLoop ml{std::make_shared<mir::time::HighResolutionClock>()}; |
1483 | +}; |
1484 | + |
1485 | +TEST_F(GLibMainLoopTest, signal_handled) |
1486 | +{ |
1487 | + int const signum{SIGUSR1}; |
1488 | + int handled_signum{0}; |
1489 | + |
1490 | + ml.register_signal_handler( |
1491 | + {signum}, |
1492 | + [&handled_signum, this](int sig) |
1493 | + { |
1494 | + handled_signum = sig; |
1495 | + ml.stop(); |
1496 | + }); |
1497 | + |
1498 | + kill(getpid(), signum); |
1499 | + |
1500 | + ml.run(); |
1501 | + |
1502 | + ASSERT_EQ(signum, handled_signum); |
1503 | +} |
1504 | + |
1505 | +TEST_F(GLibMainLoopTest, multiple_signals_handled) |
1506 | +{ |
1507 | + std::vector<int> const signals{SIGUSR1, SIGUSR2}; |
1508 | + size_t const num_signals_to_send{10}; |
1509 | + std::vector<int> handled_signals; |
1510 | + std::atomic<unsigned int> num_handled_signals{0}; |
1511 | + |
1512 | + ml.register_signal_handler( |
1513 | + {signals[0], signals[1]}, |
1514 | + [&handled_signals, &num_handled_signals](int sig) |
1515 | + { |
1516 | + handled_signals.push_back(sig); |
1517 | + ++num_handled_signals; |
1518 | + }); |
1519 | + |
1520 | + |
1521 | + std::thread signal_sending_thread( |
1522 | + [this, num_signals_to_send, &signals, &num_handled_signals] |
1523 | + { |
1524 | + for (size_t i = 0; i < num_signals_to_send; i++) |
1525 | + { |
1526 | + kill(getpid(), signals[i % signals.size()]); |
1527 | + while (num_handled_signals <= i) std::this_thread::yield(); |
1528 | + } |
1529 | + ml.stop(); |
1530 | + }); |
1531 | + |
1532 | + ml.run(); |
1533 | + |
1534 | + signal_sending_thread.join(); |
1535 | + |
1536 | + ASSERT_EQ(num_signals_to_send, handled_signals.size()); |
1537 | + |
1538 | + for (size_t i = 0; i < num_signals_to_send; i++) |
1539 | + ASSERT_EQ(signals[i % signals.size()], handled_signals[i]) << " index " << i; |
1540 | +} |
1541 | + |
1542 | +TEST_F(GLibMainLoopTest, all_registered_handlers_are_called) |
1543 | +{ |
1544 | + int const signum{SIGUSR1}; |
1545 | + std::vector<int> handled_signum{0,0,0}; |
1546 | + |
1547 | + ml.register_signal_handler( |
1548 | + {signum}, |
1549 | + [&handled_signum, this](int sig) |
1550 | + { |
1551 | + handled_signum[0] = sig; |
1552 | + if (handled_signum[0] != 0 && |
1553 | + handled_signum[1] != 0 && |
1554 | + handled_signum[2] != 0) |
1555 | + { |
1556 | + ml.stop(); |
1557 | + } |
1558 | + }); |
1559 | + |
1560 | + ml.register_signal_handler( |
1561 | + {signum}, |
1562 | + [&handled_signum, this](int sig) |
1563 | + { |
1564 | + handled_signum[1] = sig; |
1565 | + if (handled_signum[0] != 0 && |
1566 | + handled_signum[1] != 0 && |
1567 | + handled_signum[2] != 0) |
1568 | + { |
1569 | + ml.stop(); |
1570 | + } |
1571 | + }); |
1572 | + |
1573 | + ml.register_signal_handler( |
1574 | + {signum}, |
1575 | + [&handled_signum, this](int sig) |
1576 | + { |
1577 | + handled_signum[2] = sig; |
1578 | + if (handled_signum[0] != 0 && |
1579 | + handled_signum[1] != 0 && |
1580 | + handled_signum[2] != 0) |
1581 | + { |
1582 | + ml.stop(); |
1583 | + } |
1584 | + }); |
1585 | + |
1586 | + kill(getpid(), signum); |
1587 | + |
1588 | + ml.run(); |
1589 | + |
1590 | + ASSERT_EQ(signum, handled_signum[0]); |
1591 | + ASSERT_EQ(signum, handled_signum[1]); |
1592 | + ASSERT_EQ(signum, handled_signum[2]); |
1593 | +} |
1594 | + |
1595 | +TEST_F(GLibMainLoopTest, fd_data_handled) |
1596 | +{ |
1597 | + mt::Pipe p; |
1598 | + char const data_to_write{'a'}; |
1599 | + int handled_fd{0}; |
1600 | + char data_read{0}; |
1601 | + |
1602 | + ml.register_fd_handler( |
1603 | + {p.read_fd()}, |
1604 | + this, |
1605 | + [&handled_fd, &data_read, this](int fd) |
1606 | + { |
1607 | + handled_fd = fd; |
1608 | + EXPECT_EQ(1, read(fd, &data_read, 1)); |
1609 | + ml.stop(); |
1610 | + }); |
1611 | + |
1612 | + EXPECT_EQ(1, write(p.write_fd(), &data_to_write, 1)); |
1613 | + |
1614 | + ml.run(); |
1615 | + |
1616 | + EXPECT_EQ(data_to_write, data_read); |
1617 | +} |
1618 | + |
1619 | +TEST_F(GLibMainLoopTest, multiple_fds_with_single_handler_handled) |
1620 | +{ |
1621 | + std::vector<mt::Pipe> const pipes(2); |
1622 | + size_t const num_elems_to_send{10}; |
1623 | + std::vector<int> handled_fds; |
1624 | + std::vector<size_t> elems_read; |
1625 | + std::atomic<unsigned int> num_handled_fds{0}; |
1626 | + |
1627 | + ml.register_fd_handler( |
1628 | + {pipes[0].read_fd(), pipes[1].read_fd()}, |
1629 | + this, |
1630 | + [&handled_fds, &elems_read, &num_handled_fds](int fd) |
1631 | + { |
1632 | + handled_fds.push_back(fd); |
1633 | + |
1634 | + size_t i; |
1635 | + EXPECT_EQ(static_cast<ssize_t>(sizeof(i)), |
1636 | + read(fd, &i, sizeof(i))); |
1637 | + elems_read.push_back(i); |
1638 | + |
1639 | + ++num_handled_fds; |
1640 | + }); |
1641 | + |
1642 | + std::thread fd_writing_thread{ |
1643 | + [this, num_elems_to_send, &pipes, &num_handled_fds] |
1644 | + { |
1645 | + for (size_t i = 0; i < num_elems_to_send; i++) |
1646 | + { |
1647 | + EXPECT_EQ(static_cast<ssize_t>(sizeof(i)), |
1648 | + write(pipes[i % pipes.size()].write_fd(), &i, sizeof(i))); |
1649 | + while (num_handled_fds <= i) std::this_thread::yield(); |
1650 | + } |
1651 | + ml.stop(); |
1652 | + }}; |
1653 | + |
1654 | + ml.run(); |
1655 | + |
1656 | + fd_writing_thread.join(); |
1657 | + |
1658 | + ASSERT_EQ(num_elems_to_send, handled_fds.size()); |
1659 | + ASSERT_EQ(num_elems_to_send, elems_read.size()); |
1660 | + |
1661 | + for (size_t i = 0; i < num_elems_to_send; i++) |
1662 | + { |
1663 | + EXPECT_EQ(pipes[i % pipes.size()].read_fd(), handled_fds[i]) << " index " << i; |
1664 | + EXPECT_EQ(i, elems_read[i]) << " index " << i; |
1665 | + } |
1666 | +} |
1667 | + |
1668 | +TEST_F(GLibMainLoopTest, multiple_fd_handlers_are_called) |
1669 | +{ |
1670 | + std::vector<mt::Pipe> const pipes(3); |
1671 | + std::vector<int> const elems_to_send{10,11,12}; |
1672 | + std::vector<int> handled_fds{0,0,0}; |
1673 | + std::vector<int> elems_read{0,0,0}; |
1674 | + |
1675 | + ml.register_fd_handler( |
1676 | + {pipes[0].read_fd()}, |
1677 | + this, |
1678 | + [&handled_fds, &elems_read, this](int fd) |
1679 | + { |
1680 | + EXPECT_EQ(static_cast<ssize_t>(sizeof(elems_read[0])), |
1681 | + read(fd, &elems_read[0], sizeof(elems_read[0]))); |
1682 | + handled_fds[0] = fd; |
1683 | + if (handled_fds[0] != 0 && |
1684 | + handled_fds[1] != 0 && |
1685 | + handled_fds[2] != 0) |
1686 | + { |
1687 | + ml.stop(); |
1688 | + } |
1689 | + }); |
1690 | + |
1691 | + ml.register_fd_handler( |
1692 | + {pipes[1].read_fd()}, |
1693 | + this, |
1694 | + [&handled_fds, &elems_read, this](int fd) |
1695 | + { |
1696 | + EXPECT_EQ(static_cast<ssize_t>(sizeof(elems_read[1])), |
1697 | + read(fd, &elems_read[1], sizeof(elems_read[1]))); |
1698 | + handled_fds[1] = fd; |
1699 | + if (handled_fds[0] != 0 && |
1700 | + handled_fds[1] != 0 && |
1701 | + handled_fds[2] != 0) |
1702 | + { |
1703 | + ml.stop(); |
1704 | + } |
1705 | + }); |
1706 | + |
1707 | + ml.register_fd_handler( |
1708 | + {pipes[2].read_fd()}, |
1709 | + this, |
1710 | + [&handled_fds, &elems_read, this](int fd) |
1711 | + { |
1712 | + EXPECT_EQ(static_cast<ssize_t>(sizeof(elems_read[2])), |
1713 | + read(fd, &elems_read[2], sizeof(elems_read[2]))); |
1714 | + handled_fds[2] = fd; |
1715 | + if (handled_fds[0] != 0 && |
1716 | + handled_fds[1] != 0 && |
1717 | + handled_fds[2] != 0) |
1718 | + { |
1719 | + ml.stop(); |
1720 | + } |
1721 | + }); |
1722 | + |
1723 | + EXPECT_EQ(static_cast<ssize_t>(sizeof(elems_to_send[0])), |
1724 | + write(pipes[0].write_fd(), &elems_to_send[0], sizeof(elems_to_send[0]))); |
1725 | + EXPECT_EQ(static_cast<ssize_t>(sizeof(elems_to_send[1])), |
1726 | + write(pipes[1].write_fd(), &elems_to_send[1], sizeof(elems_to_send[1]))); |
1727 | + EXPECT_EQ(static_cast<ssize_t>(sizeof(elems_to_send[2])), |
1728 | + write(pipes[2].write_fd(), &elems_to_send[2], sizeof(elems_to_send[2]))); |
1729 | + |
1730 | + ml.run(); |
1731 | + |
1732 | + EXPECT_EQ(pipes[0].read_fd(), handled_fds[0]); |
1733 | + EXPECT_EQ(pipes[1].read_fd(), handled_fds[1]); |
1734 | + EXPECT_EQ(pipes[2].read_fd(), handled_fds[2]); |
1735 | + |
1736 | + EXPECT_EQ(elems_to_send[0], elems_read[0]); |
1737 | + EXPECT_EQ(elems_to_send[1], elems_read[1]); |
1738 | + EXPECT_EQ(elems_to_send[2], elems_read[2]); |
1739 | +} |
1740 | + |
1741 | +TEST_F(GLibMainLoopTest, |
1742 | + unregister_prevents_callback_and_does_not_harm_other_callbacks) |
1743 | +{ |
1744 | + mt::Pipe p1, p2; |
1745 | + char const data_to_write{'a'}; |
1746 | + int p2_handler_executes{-1}; |
1747 | + char data_read{0}; |
1748 | + |
1749 | + ml.register_fd_handler( |
1750 | + {p1.read_fd()}, |
1751 | + this, |
1752 | + [this](int) |
1753 | + { |
1754 | + FAIL() << "unregistered handler called"; |
1755 | + ml.stop(); |
1756 | + }); |
1757 | + |
1758 | + ml.register_fd_handler( |
1759 | + {p2.read_fd()}, |
1760 | + this+2, |
1761 | + [&p2_handler_executes,&data_read,this](int fd) |
1762 | + { |
1763 | + p2_handler_executes = fd; |
1764 | + EXPECT_EQ(1, read(fd, &data_read, 1)); |
1765 | + ml.stop(); |
1766 | + }); |
1767 | + |
1768 | + ml.unregister_fd_handler(this); |
1769 | + |
1770 | + EXPECT_EQ(1, write(p1.write_fd(), &data_to_write, 1)); |
1771 | + EXPECT_EQ(1, write(p2.write_fd(), &data_to_write, 1)); |
1772 | + |
1773 | + ml.run(); |
1774 | + |
1775 | + EXPECT_EQ(data_to_write, data_read); |
1776 | + EXPECT_EQ(p2.read_fd(), p2_handler_executes); |
1777 | +} |
1778 | + |
1779 | +TEST_F(GLibMainLoopTest, unregister_does_not_close_fds) |
1780 | +{ |
1781 | + mt::Pipe p1, p2; |
1782 | + char const data_to_write{'b'}; |
1783 | + char data_read{0}; |
1784 | + |
1785 | + ml.register_fd_handler( |
1786 | + {p1.read_fd()}, |
1787 | + this, |
1788 | + [this](int) |
1789 | + { |
1790 | + FAIL() << "unregistered handler called"; |
1791 | + ml.stop(); |
1792 | + }); |
1793 | + |
1794 | + ml.unregister_fd_handler(this); |
1795 | + |
1796 | + ml.register_fd_handler( |
1797 | + {p1.read_fd()}, |
1798 | + this, |
1799 | + [this,&data_read](int fd) |
1800 | + { |
1801 | + EXPECT_EQ(1, read(fd, &data_read, 1)); |
1802 | + ml.stop(); |
1803 | + }); |
1804 | + |
1805 | + EXPECT_EQ(1, write(p1.write_fd(), &data_to_write, 1)); |
1806 | + |
1807 | + ml.run(); |
1808 | + |
1809 | + EXPECT_EQ(data_to_write, data_read); |
1810 | +} |
1811 | + |
1812 | +struct GLibMainLoopAlarmTest : ::testing::Test |
1813 | +{ |
1814 | + std::shared_ptr<AdvanceableClock> clock = std::make_shared<AdvanceableClock>(); |
1815 | + mir::GLibMainLoop ml{clock}; |
1816 | + std::chrono::milliseconds delay{50}; |
1817 | +}; |
1818 | + |
1819 | +TEST_F(GLibMainLoopAlarmTest, main_loop_runs_until_stop_called) |
1820 | +{ |
1821 | + auto mainloop_started = std::make_shared<mt::Signal>(); |
1822 | + |
1823 | + auto fire_on_mainloop_start = ml.notify_in(std::chrono::milliseconds{0}, |
1824 | + [mainloop_started]() |
1825 | + { |
1826 | + mainloop_started->raise(); |
1827 | + }); |
1828 | + |
1829 | + UnblockMainLoop unblocker(ml); |
1830 | + |
1831 | + ASSERT_TRUE(mainloop_started->wait_for(std::chrono::milliseconds{100})); |
1832 | + |
1833 | + auto timer_fired = std::make_shared<mt::Signal>(); |
1834 | + auto alarm = ml.notify_in(std::chrono::milliseconds{10}, [timer_fired] |
1835 | + { |
1836 | + timer_fired->raise(); |
1837 | + }); |
1838 | + |
1839 | + //std::cerr << "Advance by 10 " << std::endl; |
1840 | + clock->advance_by(std::chrono::milliseconds{10}, ml); |
1841 | + //std::cerr << "after Advance by 10 " << std::endl; |
1842 | + EXPECT_TRUE(timer_fired->wait_for(std::chrono::milliseconds{500})); |
1843 | + //std::cerr << "after waitfor by500 " << std::endl; |
1844 | + |
1845 | + ml.stop(); |
1846 | + //std::cerr << "after stop " << std::endl; |
1847 | + // Main loop should be stopped now |
1848 | + |
1849 | + timer_fired = std::make_shared<mt::Signal>(); |
1850 | + auto should_not_fire = ml.notify_in(std::chrono::milliseconds{0}, |
1851 | + [timer_fired]() |
1852 | + { |
1853 | + timer_fired->raise(); |
1854 | + }); |
1855 | + |
1856 | + EXPECT_FALSE(timer_fired->wait_for(std::chrono::milliseconds{10})); |
1857 | +} |
1858 | + |
1859 | +TEST_F(GLibMainLoopAlarmTest, alarm_starts_in_pending_state) |
1860 | +{ |
1861 | + auto alarm = ml.notify_in(delay, [this]() {}); |
1862 | + |
1863 | + UnblockMainLoop unblocker(ml); |
1864 | + |
1865 | + EXPECT_EQ(mir::time::Alarm::pending, alarm->state()); |
1866 | +} |
1867 | + |
1868 | +TEST_F(GLibMainLoopAlarmTest, alarm_fires_with_correct_delay) |
1869 | +{ |
1870 | + UnblockMainLoop unblocker(ml); |
1871 | + |
1872 | + auto alarm = ml.notify_in(delay, [](){}); |
1873 | + |
1874 | + clock->advance_by(delay - std::chrono::milliseconds{1}, ml); |
1875 | + EXPECT_EQ(mir::time::Alarm::pending, alarm->state()); |
1876 | + |
1877 | + clock->advance_by(delay, ml); |
1878 | + EXPECT_EQ(mir::time::Alarm::triggered, alarm->state()); |
1879 | +} |
1880 | + |
1881 | +TEST_F(GLibMainLoopAlarmTest, multiple_alarms_fire) |
1882 | +{ |
1883 | + using namespace testing; |
1884 | + |
1885 | + int const alarm_count{10}; |
1886 | + Counter call_count; |
1887 | + std::array<std::unique_ptr<mir::time::Alarm>, alarm_count> alarms; |
1888 | + |
1889 | + for (auto& alarm : alarms) |
1890 | + alarm = ml.notify_in(delay, [&call_count](){++call_count;}); |
1891 | + |
1892 | + UnblockMainLoop unblocker(ml); |
1893 | + clock->advance_by(delay, ml); |
1894 | + |
1895 | + call_count.wait_for(delay, alarm_count); |
1896 | + EXPECT_THAT(call_count, Eq(alarm_count)); |
1897 | + |
1898 | + for (auto const& alarm : alarms) |
1899 | + EXPECT_EQ(mir::time::Alarm::triggered, alarm->state()); |
1900 | +} |
1901 | + |
1902 | +TEST_F(GLibMainLoopAlarmTest, alarm_changes_to_triggered_state) |
1903 | +{ |
1904 | + auto alarm_fired = std::make_shared<mt::Signal>(); |
1905 | + auto alarm = ml.notify_in(std::chrono::milliseconds{5}, [alarm_fired]() |
1906 | + { |
1907 | + alarm_fired->raise(); |
1908 | + }); |
1909 | + |
1910 | + UnblockMainLoop unblocker(ml); |
1911 | + |
1912 | + clock->advance_by(delay, ml); |
1913 | + ASSERT_TRUE(alarm_fired->wait_for(std::chrono::milliseconds{100})); |
1914 | + |
1915 | + EXPECT_EQ(mir::time::Alarm::triggered, alarm->state()); |
1916 | +} |
1917 | + |
1918 | +TEST_F(GLibMainLoopAlarmTest, cancelled_alarm_doesnt_fire) |
1919 | +{ |
1920 | + UnblockMainLoop unblocker(ml); |
1921 | + auto alarm = ml.notify_in(std::chrono::milliseconds{100}, |
1922 | + [](){ FAIL() << "Alarm handler of canceld alarm called";}); |
1923 | + |
1924 | + EXPECT_TRUE(alarm->cancel()); |
1925 | + |
1926 | + EXPECT_EQ(mir::time::Alarm::cancelled, alarm->state()); |
1927 | + |
1928 | + clock->advance_by(std::chrono::milliseconds{100}, ml); |
1929 | + |
1930 | + EXPECT_EQ(mir::time::Alarm::cancelled, alarm->state()); |
1931 | +} |
1932 | + |
1933 | +TEST_F(GLibMainLoopAlarmTest, destroyed_alarm_doesnt_fire) |
1934 | +{ |
1935 | + auto alarm = ml.notify_in(std::chrono::milliseconds{200}, |
1936 | + [](){ FAIL() << "Alarm handler of destroyed alarm called"; }); |
1937 | + |
1938 | + UnblockMainLoop unblocker(ml); |
1939 | + |
1940 | + alarm.reset(nullptr); |
1941 | + clock->advance_by(std::chrono::milliseconds{200}, ml); |
1942 | +} |
1943 | + |
1944 | +TEST_F(GLibMainLoopAlarmTest, rescheduled_alarm_fires_again) |
1945 | +{ |
1946 | + std::atomic<int> call_count{0}; |
1947 | + |
1948 | + auto alarm = ml.notify_in(std::chrono::milliseconds{0}, [&call_count]() |
1949 | + { |
1950 | + if (call_count++ > 1) |
1951 | + FAIL() << "Alarm called too many times"; |
1952 | + }); |
1953 | + |
1954 | + UnblockMainLoop unblocker(ml); |
1955 | + |
1956 | + clock->advance_by(std::chrono::milliseconds{0}, ml); |
1957 | + ASSERT_EQ(mir::time::Alarm::triggered, alarm->state()); |
1958 | + |
1959 | + alarm->reschedule_in(std::chrono::milliseconds{100}); |
1960 | + EXPECT_EQ(mir::time::Alarm::pending, alarm->state()); |
1961 | + |
1962 | + clock->advance_by(std::chrono::milliseconds{100}, ml); |
1963 | + EXPECT_EQ(mir::time::Alarm::triggered, alarm->state()); |
1964 | +} |
1965 | + |
1966 | +TEST_F(GLibMainLoopAlarmTest, rescheduled_alarm_cancels_previous_scheduling) |
1967 | +{ |
1968 | + std::atomic<int> call_count{0}; |
1969 | + |
1970 | + auto alarm = ml.notify_in(std::chrono::milliseconds{100}, [&call_count]() |
1971 | + { |
1972 | + call_count++; |
1973 | + }); |
1974 | + |
1975 | + UnblockMainLoop unblocker(ml); |
1976 | + clock->advance_by(std::chrono::milliseconds{90}, ml); |
1977 | + |
1978 | + EXPECT_EQ(mir::time::Alarm::pending, alarm->state()); |
1979 | + EXPECT_EQ(0, call_count); |
1980 | + EXPECT_TRUE(alarm->reschedule_in(std::chrono::milliseconds{100})); |
1981 | + EXPECT_EQ(mir::time::Alarm::pending, alarm->state()); |
1982 | + |
1983 | + clock->advance_by(std::chrono::milliseconds{110}, ml); |
1984 | + |
1985 | + EXPECT_EQ(mir::time::Alarm::triggered, alarm->state()); |
1986 | + EXPECT_EQ(1, call_count); |
1987 | +} |
1988 | + |
1989 | +TEST_F(GLibMainLoopAlarmTest, alarm_callback_cannot_deadlock) |
1990 | +{ // Regression test for deadlock bug LP: #1339700 |
1991 | + std::timed_mutex m; |
1992 | + std::atomic_bool failed(false); |
1993 | + int i = 0; |
1994 | + int const loops = 5; |
1995 | + |
1996 | + auto alarm = ml.notify_in(std::chrono::milliseconds{0}, [&]() |
1997 | + { |
1998 | + // From this angle, ensure we can lock m (alarm should be unlocked) |
1999 | + failed = !m.try_lock_for(std::chrono::seconds{5}); |
2000 | + ASSERT_FALSE(failed); |
2001 | + ++i; |
2002 | + m.unlock(); |
2003 | + }); |
2004 | + |
2005 | + std::thread t([&]() |
2006 | + { |
2007 | + m.lock(); |
2008 | + while (i < loops && !failed) |
2009 | + { |
2010 | + // From this angle, ensure we can lock alarm while holding m |
2011 | + (void)alarm->state(); |
2012 | + m.unlock(); |
2013 | + std::this_thread::yield(); |
2014 | + m.lock(); |
2015 | + } |
2016 | + m.unlock(); |
2017 | + }); |
2018 | + |
2019 | + UnblockMainLoop unblocker(ml); |
2020 | + for (int j = 0; j < loops; ++j) |
2021 | + { |
2022 | + clock->advance_by(std::chrono::milliseconds{11}, ml); |
2023 | + alarm->reschedule_in(std::chrono::milliseconds{10}); |
2024 | + } |
2025 | + |
2026 | + t.join(); |
2027 | +} |
2028 | + |
2029 | +TEST_F(GLibMainLoopAlarmTest, alarm_fires_at_correct_time_point) |
2030 | +{ |
2031 | + mir::time::Timestamp real_soon = clock->sample() + std::chrono::milliseconds{120}; |
2032 | + |
2033 | + auto alarm = ml.notify_at(real_soon, []{}); |
2034 | + |
2035 | + UnblockMainLoop unblocker(ml); |
2036 | + |
2037 | + clock->advance_by(std::chrono::milliseconds{119}, ml); |
2038 | + EXPECT_EQ(mir::time::Alarm::pending, alarm->state()); |
2039 | + |
2040 | + clock->advance_by(std::chrono::milliseconds{1}, ml); |
2041 | + EXPECT_EQ(mir::time::Alarm::triggered, alarm->state()); |
2042 | +} |
2043 | + |
2044 | + |
2045 | +// More targeted regression test for LP: #1381925 |
2046 | +TEST_F(GLibMainLoopTest, stress_emits_alarm_notification_with_zero_timeout) |
2047 | +{ |
2048 | + using namespace ::testing; |
2049 | + |
2050 | + UnblockMainLoop unblocker{ml}; |
2051 | + |
2052 | + for (int i = 0; i < 1000; ++i) |
2053 | + { |
2054 | + mt::Signal notification_called; |
2055 | + |
2056 | + auto alarm = ml.notify_in( |
2057 | + std::chrono::milliseconds{0}, |
2058 | + [&] { notification_called.raise(); }); |
2059 | + |
2060 | + EXPECT_TRUE(notification_called.wait_for(std::chrono::seconds{5})); |
2061 | + } |
2062 | +} |
2063 | + |
2064 | +TEST_F(GLibMainLoopTest, dispatches_action) |
2065 | +{ |
2066 | + using namespace testing; |
2067 | + |
2068 | + int num_actions{0}; |
2069 | + int const owner{0}; |
2070 | + |
2071 | + ml.enqueue( |
2072 | + &owner, |
2073 | + [&] |
2074 | + { |
2075 | + ++num_actions; |
2076 | + ml.stop(); |
2077 | + }); |
2078 | + |
2079 | + ml.run(); |
2080 | + |
2081 | + EXPECT_THAT(num_actions, Eq(1)); |
2082 | +} |
2083 | + |
2084 | +TEST_F(GLibMainLoopTest, dispatches_multiple_actions_in_order) |
2085 | +{ |
2086 | + using namespace testing; |
2087 | + |
2088 | + int const num_actions{5}; |
2089 | + std::vector<int> actions; |
2090 | + int const owner{0}; |
2091 | + |
2092 | + for (int i = 0; i < num_actions; ++i) |
2093 | + { |
2094 | + ml.enqueue( |
2095 | + &owner, |
2096 | + [&,i] |
2097 | + { |
2098 | + actions.push_back(i); |
2099 | + if (i == num_actions - 1) |
2100 | + ml.stop(); |
2101 | + }); |
2102 | + } |
2103 | + |
2104 | + ml.run(); |
2105 | + |
2106 | + ASSERT_THAT(actions.size(), Eq(num_actions)); |
2107 | + for (int i = 0; i < num_actions; ++i) |
2108 | + EXPECT_THAT(actions[i], Eq(i)) << "i = " << i; |
2109 | +} |
2110 | + |
2111 | +TEST_F(GLibMainLoopTest, does_not_dispatch_paused_actions) |
2112 | +{ |
2113 | + using namespace testing; |
2114 | + |
2115 | + std::vector<int> actions; |
2116 | + int const owner1{0}; |
2117 | + int const owner2{0}; |
2118 | + |
2119 | + ml.enqueue( |
2120 | + &owner1, |
2121 | + |
2122 | + [&] |
2123 | + { |
2124 | + int const id = 0; |
2125 | + actions.push_back(id); |
2126 | + }); |
2127 | + |
2128 | + ml.enqueue( |
2129 | + &owner2, |
2130 | + [&] |
2131 | + { |
2132 | + int const id = 1; |
2133 | + actions.push_back(id); |
2134 | + }); |
2135 | + |
2136 | + ml.enqueue( |
2137 | + &owner1, |
2138 | + [&] |
2139 | + { |
2140 | + int const id = 2; |
2141 | + actions.push_back(id); |
2142 | + }); |
2143 | + |
2144 | + ml.enqueue( |
2145 | + &owner2, |
2146 | + [&] |
2147 | + { |
2148 | + int const id = 3; |
2149 | + actions.push_back(id); |
2150 | + ml.stop(); |
2151 | + }); |
2152 | + |
2153 | + ml.pause_processing_for(&owner1); |
2154 | + |
2155 | + ml.run(); |
2156 | + |
2157 | + ASSERT_THAT(actions.size(), Eq(2)); |
2158 | + EXPECT_THAT(actions[0], Eq(1)); |
2159 | + EXPECT_THAT(actions[1], Eq(3)); |
2160 | +} |
2161 | + |
2162 | +TEST_F(GLibMainLoopTest, dispatches_actions_resumed_from_within_another_action) |
2163 | +{ |
2164 | + using namespace testing; |
2165 | + |
2166 | + std::vector<int> actions; |
2167 | + void const* const owner1_ptr{&actions}; |
2168 | + int const owner2{0}; |
2169 | + |
2170 | + ml.enqueue( |
2171 | + owner1_ptr, |
2172 | + [&] |
2173 | + { |
2174 | + int const id = 0; |
2175 | + actions.push_back(id); |
2176 | + ml.stop(); |
2177 | + }); |
2178 | + |
2179 | + ml.enqueue( |
2180 | + &owner2, |
2181 | + [&] |
2182 | + { |
2183 | + int const id = 1; |
2184 | + actions.push_back(id); |
2185 | + ml.resume_processing_for(owner1_ptr); |
2186 | + }); |
2187 | + |
2188 | + ml.pause_processing_for(owner1_ptr); |
2189 | + |
2190 | + ml.run(); |
2191 | + |
2192 | + ASSERT_THAT(actions.size(), Eq(2)); |
2193 | + EXPECT_THAT(actions[0], Eq(1)); |
2194 | + EXPECT_THAT(actions[1], Eq(0)); |
2195 | +} |
2196 | + |
2197 | +TEST_F(GLibMainLoopTest, handles_enqueue_from_within_action) |
2198 | +{ |
2199 | + using namespace testing; |
2200 | + |
2201 | + std::vector<int> actions; |
2202 | + int const num_actions{10}; |
2203 | + void const* const owner{&num_actions}; |
2204 | + |
2205 | + ml.enqueue( |
2206 | + owner, |
2207 | + [&] |
2208 | + { |
2209 | + int const id = 0; |
2210 | + actions.push_back(id); |
2211 | + |
2212 | + for (int i = 1; i < num_actions; ++i) |
2213 | + { |
2214 | + ml.enqueue( |
2215 | + owner, |
2216 | + [&,i] |
2217 | + { |
2218 | + actions.push_back(i); |
2219 | + if (i == num_actions - 1) |
2220 | + ml.stop(); |
2221 | + }); |
2222 | + } |
2223 | + }); |
2224 | + |
2225 | + ml.run(); |
2226 | + |
2227 | + ASSERT_THAT(actions.size(), Eq(num_actions)); |
2228 | + for (int i = 0; i < num_actions; ++i) |
2229 | + EXPECT_THAT(actions[i], Eq(i)) << "i = " << i; |
2230 | +} |
2231 | + |
2232 | +TEST_F(GLibMainLoopTest, dispatches_actions_resumed_externally) |
2233 | +{ |
2234 | + using namespace testing; |
2235 | + |
2236 | + std::vector<int> actions; |
2237 | + void const* const owner1_ptr{&actions}; |
2238 | + int const owner2{0}; |
2239 | + |
2240 | + ml.enqueue( |
2241 | + owner1_ptr, |
2242 | + [&] |
2243 | + { |
2244 | + int const id = 0; |
2245 | + actions.push_back(id); |
2246 | + ml.stop(); |
2247 | + }); |
2248 | + |
2249 | + ml.enqueue( |
2250 | + &owner2, |
2251 | + [&] |
2252 | + { |
2253 | + int const id = 1; |
2254 | + actions.push_back(id); |
2255 | + }); |
2256 | + |
2257 | + ml.pause_processing_for(owner1_ptr); |
2258 | + |
2259 | + std::thread t{ |
2260 | + [&] |
2261 | + { |
2262 | + std::this_thread::sleep_for(std::chrono::milliseconds{10}); |
2263 | + ml.resume_processing_for(owner1_ptr); |
2264 | + }}; |
2265 | + |
2266 | + ml.run(); |
2267 | + |
2268 | + t.join(); |
2269 | + |
2270 | + ASSERT_THAT(actions.size(), Eq(2)); |
2271 | + EXPECT_THAT(actions[0], Eq(1)); |
2272 | + EXPECT_THAT(actions[1], Eq(0)); |
2273 | +} |
2274 | |
2275 | === added file 'tests/unit-tests/test_thread_safe_list.cpp' |
2276 | --- tests/unit-tests/test_thread_safe_list.cpp 1970-01-01 00:00:00 +0000 |
2277 | +++ tests/unit-tests/test_thread_safe_list.cpp 2014-10-31 11:59:50 +0000 |
2278 | @@ -0,0 +1,159 @@ |
2279 | +/* |
2280 | + * Copyright © 2014 Canonical Ltd. |
2281 | + * |
2282 | + * This program is free software: you can redistribute it and/or modify it |
2283 | + * under the terms of the GNU General Public License version 3, |
2284 | + * as published by the Free Software Foundation. |
2285 | + * |
2286 | + * This program is distributed in the hope that it will be useful, |
2287 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
2288 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2289 | + * GNU General Public License for more details. |
2290 | + * |
2291 | + * You should have received a copy of the GNU General Public License |
2292 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
2293 | + * |
2294 | + * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com> |
2295 | + */ |
2296 | + |
2297 | +#include "mir/thread_safe_list.h" |
2298 | +#include "mir_test/wait_condition.h" |
2299 | + |
2300 | +#include <gtest/gtest.h> |
2301 | +#include <gmock/gmock.h> |
2302 | + |
2303 | +namespace |
2304 | +{ |
2305 | + |
2306 | +struct Dummy {}; |
2307 | +using Element = std::shared_ptr<Dummy>; |
2308 | +using SharedPtrList = mir::ThreadSafeList<Element>; |
2309 | + |
2310 | +struct ThreadSafeListTest : testing::Test |
2311 | +{ |
2312 | + SharedPtrList list; |
2313 | + |
2314 | + Element const element1 = std::make_shared<Dummy>(); |
2315 | + Element const element2 = std::make_shared<Dummy>(); |
2316 | +}; |
2317 | + |
2318 | +} |
2319 | + |
2320 | +TEST_F(ThreadSafeListTest, can_remove_element_while_iterating_same_element) |
2321 | +{ |
2322 | + using namespace testing; |
2323 | + |
2324 | + list.add(element1); |
2325 | + |
2326 | + list.for_each( |
2327 | + [&] (Element const& element) |
2328 | + { |
2329 | + list.remove(element); |
2330 | + }); |
2331 | + |
2332 | + int elements_seen = 0; |
2333 | + |
2334 | + list.for_each( |
2335 | + [&] (Element const&) |
2336 | + { |
2337 | + ++elements_seen; |
2338 | + }); |
2339 | + |
2340 | + EXPECT_THAT(elements_seen, Eq(0)); |
2341 | +} |
2342 | + |
2343 | +TEST_F(ThreadSafeListTest, can_remove_unused_element_while_iterating_different_element) |
2344 | +{ |
2345 | + using namespace testing; |
2346 | + |
2347 | + list.add(element1); |
2348 | + list.add(element2); |
2349 | + |
2350 | + int elements_seen = 0; |
2351 | + |
2352 | + list.for_each( |
2353 | + [&] (Element const&) |
2354 | + { |
2355 | + list.remove(element2); |
2356 | + ++elements_seen; |
2357 | + }); |
2358 | + |
2359 | + EXPECT_THAT(elements_seen, Eq(1)); |
2360 | +} |
2361 | + |
2362 | +TEST_F(ThreadSafeListTest, |
2363 | + can_remove_unused_element_while_different_element_is_used_in_different_thread) |
2364 | +{ |
2365 | + using namespace testing; |
2366 | + |
2367 | + list.add(element1); |
2368 | + list.add(element2); |
2369 | + |
2370 | + mir::test::WaitCondition first_element_in_use; |
2371 | + mir::test::WaitCondition second_element_removed; |
2372 | + |
2373 | + int elements_seen = 0; |
2374 | + |
2375 | + std::thread t{ |
2376 | + [&] |
2377 | + { |
2378 | + list.for_each( |
2379 | + [&] (Element const&) |
2380 | + { |
2381 | + first_element_in_use.wake_up_everyone(); |
2382 | + second_element_removed.wait_for_at_most_seconds(3); |
2383 | + EXPECT_TRUE(second_element_removed.woken()); |
2384 | + ++elements_seen; |
2385 | + }); |
2386 | + }}; |
2387 | + |
2388 | + first_element_in_use.wait_for_at_most_seconds(3); |
2389 | + list.remove(element2); |
2390 | + second_element_removed.wake_up_everyone(); |
2391 | + |
2392 | + t.join(); |
2393 | + |
2394 | + EXPECT_THAT(elements_seen, Eq(1)); |
2395 | +} |
2396 | + |
2397 | +TEST_F(ThreadSafeListTest, removes_all_matching_elements) |
2398 | +{ |
2399 | + using namespace testing; |
2400 | + |
2401 | + std::vector<Element> elements_seen; |
2402 | + |
2403 | + list.add(element1); |
2404 | + list.add(element2); |
2405 | + list.add(element1); |
2406 | + |
2407 | + list.remove_all(element1); |
2408 | + |
2409 | + list.for_each( |
2410 | + [&] (Element const& element) |
2411 | + { |
2412 | + elements_seen.push_back(element); |
2413 | + }); |
2414 | + |
2415 | + EXPECT_THAT(elements_seen, ElementsAre(element2)); |
2416 | +} |
2417 | + |
2418 | +TEST_F(ThreadSafeListTest, clears_all_elements) |
2419 | +{ |
2420 | + using namespace testing; |
2421 | + |
2422 | + int elements_seen = 0; |
2423 | + |
2424 | + list.add(element1); |
2425 | + list.add(element2); |
2426 | + list.add(element1); |
2427 | + |
2428 | + list.clear(); |
2429 | + |
2430 | + list.for_each( |
2431 | + [&] (Element const&) |
2432 | + { |
2433 | + ++elements_seen; |
2434 | + }); |
2435 | + |
2436 | + EXPECT_THAT(elements_seen, Eq(0)); |
2437 | +} |