Mir

Merge lp:~afrantzis/mir/glib-main-loop-spike-wip into lp:mir

Proposed by Alexandros Frantzis
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
Reviewer Review Type Date Requested Status
Mir development team Pending
Review via email: mp+240150@code.launchpad.net

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/straightforward than our asio version and whether you
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://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#GSourceFuncs

To post a comment you must log in.
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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+}

Subscribers

People subscribed via source and target branches