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
=== modified file 'CMakeLists.txt'
--- CMakeLists.txt 2014-10-21 16:21:14 +0000
+++ CMakeLists.txt 2014-10-31 11:59:50 +0000
@@ -154,6 +154,7 @@
154find_package(XKBCOMMON REQUIRED)154find_package(XKBCOMMON REQUIRED)
155find_package(LTTngUST REQUIRED)155find_package(LTTngUST REQUIRED)
156pkg_check_modules(UDEV REQUIRED libudev)156pkg_check_modules(UDEV REQUIRED libudev)
157pkg_check_modules(GLIB REQUIRED glib-2.0)
157158
158include_directories (${GLESv2_INCLUDE_DIRS})159include_directories (${GLESv2_INCLUDE_DIRS})
159include_directories (${EGL_INCLUDE_DIRS})160include_directories (${EGL_INCLUDE_DIRS})
160161
=== modified file 'debian/control'
--- debian/control 2014-10-30 18:37:11 +0000
+++ debian/control 2014-10-31 11:59:50 +0000
@@ -39,6 +39,7 @@
39 libudev-dev,39 libudev-dev,
40 google-mock (>= 1.6.0+svn437),40 google-mock (>= 1.6.0+svn437),
41 valgrind [!arm64],41 valgrind [!arm64],
42 libglib2.0-dev,
42Standards-Version: 3.9.443Standards-Version: 3.9.4
43Homepage: https://launchpad.net/mir44Homepage: https://launchpad.net/mir
44# If you aren't a member of ~mir-team but need to upload packaging changes,45# If you aren't a member of ~mir-team but need to upload packaging changes,
4546
=== modified file 'include/common/mir/time/clock.h'
--- include/common/mir/time/clock.h 2014-10-01 06:25:56 +0000
+++ include/common/mir/time/clock.h 2014-10-31 11:59:50 +0000
@@ -35,6 +35,7 @@
35 virtual ~Clock() = default;35 virtual ~Clock() = default;
3636
37 virtual Timestamp sample() const = 0;37 virtual Timestamp sample() const = 0;
38 virtual Duration timeout_until(Timestamp) const { return Duration{0}; }
3839
39protected:40protected:
40 Clock() = default;41 Clock() = default;
4142
=== modified file 'src/common/time/high_resolution_clock.cpp'
--- src/common/time/high_resolution_clock.cpp 2014-10-01 06:25:56 +0000
+++ src/common/time/high_resolution_clock.cpp 2014-10-31 11:59:50 +0000
@@ -22,3 +22,12 @@
22{22{
23 return clock.now();23 return clock.now();
24}24}
25
26mir::time::Duration mir::time::HighResolutionClock::timeout_until(Timestamp t) const
27{
28 auto const now = clock.now();
29 if (t <= now)
30 return Duration{0};
31 else
32 return t - now;
33}
2534
=== modified file 'src/include/common/mir/basic_observers.h'
--- src/include/common/mir/basic_observers.h 2014-10-21 16:21:14 +0000
+++ src/include/common/mir/basic_observers.h 2014-10-31 11:59:50 +0000
@@ -19,108 +19,15 @@
19#ifndef MIR_BASIC_OBSERVERS_H_19#ifndef MIR_BASIC_OBSERVERS_H_
20#define MIR_BASIC_OBSERVERS_H_20#define MIR_BASIC_OBSERVERS_H_
2121
22#include "mir/recursive_read_write_mutex.h"22#include "mir/thread_safe_list.h"
23
24#include <atomic>
25#include <memory>23#include <memory>
2624
27namespace mir25namespace mir
28{26{
29template<class Observer>27template<class Observer>
30class BasicObservers28class BasicObservers : protected ThreadSafeList<std::shared_ptr<Observer>>
31{29{
32protected:
33 void add(std::shared_ptr<Observer> const& observer);
34 void remove(std::shared_ptr<Observer> const& observer);
35 void for_each(std::function<void(std::shared_ptr<Observer> const& observer)> const& f);
36
37private:
38 struct ListItem
39 {
40 ListItem() {}
41 RecursiveReadWriteMutex mutex;
42 std::shared_ptr<Observer> observer;
43 std::atomic<ListItem*> next{nullptr};
44
45 ~ListItem() { delete next.load(); }
46 } head;
47};30};
48
49template<class Observer>
50void BasicObservers<Observer>::for_each(
51 std::function<void(std::shared_ptr<Observer> const& observer)> const& f)
52{
53 ListItem* current_item = &head;
54
55 while (current_item)
56 {
57 RecursiveReadLock lock{current_item->mutex};
58
59 // We need to take a copy in case we recursively remove during call
60 if (auto const copy_of_observer = current_item->observer) f(copy_of_observer);
61
62 current_item = current_item->next;
63 }
64}
65
66template<class Observer>
67void BasicObservers<Observer>::add(std::shared_ptr<Observer> const& observer)
68{
69 ListItem* current_item = &head;
70
71 do
72 {
73 // Note: we release the read lock to avoid two threads calling add at
74 // the same time mutually blocking the other's upgrade to write lock.
75 {
76 RecursiveReadLock lock{current_item->mutex};
77 if (current_item->observer) continue;
78 }
79
80 RecursiveWriteLock lock{current_item->mutex};
81
82 if (!current_item->observer)
83 {
84 current_item->observer = observer;
85 return;
86 }
87 }
88 while (current_item->next && (current_item = current_item->next));
89
90 // No empty Items so append a new one
91 auto new_item = new ListItem;
92 new_item->observer = observer;
93
94 for (ListItem* expected{nullptr};
95 !current_item->next.compare_exchange_weak(expected, new_item);
96 expected = nullptr)
97 {
98 if (expected) current_item = expected;
99 }
100}
101
102template<class Observer>
103void BasicObservers<Observer>::remove(std::shared_ptr<Observer> const& observer)
104{
105 ListItem* current_item = &head;
106
107 do
108 {
109 {
110 RecursiveReadLock lock{current_item->mutex};
111 if (current_item->observer != observer) continue;
112 }
113
114 RecursiveWriteLock lock{current_item->mutex};
115
116 if (current_item->observer == observer)
117 {
118 current_item->observer.reset();
119 return;
120 }
121 }
122 while ((current_item = current_item->next));
123}
124}31}
12532
126#endif /* MIR_BASIC_OBSERVERS_H_ */33#endif /* MIR_BASIC_OBSERVERS_H_ */
12734
=== added file 'src/include/common/mir/thread_safe_list.h'
--- src/include/common/mir/thread_safe_list.h 1970-01-01 00:00:00 +0000
+++ src/include/common/mir/thread_safe_list.h 2014-10-31 11:59:50 +0000
@@ -0,0 +1,186 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored By: Alan Griffiths <alan@octopull.co.uk>
17 Alexandros Frantzis <alexandros.frantzis@canonical.com>
18 */
19
20#ifndef MIR_THREAD_SAFE_LIST_H_
21#define MIR_THREAD_SAFE_LIST_H_
22
23#include "mir/recursive_read_write_mutex.h"
24
25#include <atomic>
26
27namespace mir
28{
29
30/*
31 * Requirements for type 'Element'
32 * - for_each():
33 * - copy-assignable
34 * - add()
35 * - copy-assignable
36 * - operator bool: returns whether this is a valid element
37 * - remove(), remove_all():
38 * - copy-assignable
39 * - Element{}: default construction should create an invalid element
40 * - bool operator==: equality between two elements
41 * - bool operator!=: inequality between two elements
42 * - clear():
43 * - copy-assignable
44 * - Element{}: default construction should create an invalid element
45 */
46
47template<class Element>
48class ThreadSafeList
49{
50public:
51 void add(Element const& element);
52 void remove(Element const& element);
53 unsigned int remove_all(Element const& element);
54 void clear();
55 void for_each(std::function<void(Element const& element)> const& f);
56
57private:
58 struct ListItem
59 {
60 ListItem() {}
61 RecursiveReadWriteMutex mutex;
62 Element element;
63 std::atomic<ListItem*> next{nullptr};
64
65 ~ListItem() { delete next.load(); }
66 } head;
67};
68
69template<class Element>
70void ThreadSafeList<Element>::for_each(
71 std::function<void(Element const& element)> const& f)
72{
73 ListItem* current_item = &head;
74
75 while (current_item)
76 {
77 RecursiveReadLock lock{current_item->mutex};
78
79 // We need to take a copy in case we recursively remove during call
80 if (auto const copy_of_element = current_item->element) f(copy_of_element);
81
82 current_item = current_item->next;
83 }
84}
85
86template<class Element>
87void ThreadSafeList<Element>::add(Element const& element)
88{
89 ListItem* current_item = &head;
90
91 do
92 {
93 // Note: we release the read lock to avoid two threads calling add at
94 // the same time mutually blocking the other's upgrade to write lock.
95 {
96 RecursiveReadLock lock{current_item->mutex};
97 if (current_item->element) continue;
98 }
99
100 RecursiveWriteLock lock{current_item->mutex};
101
102 if (!current_item->element)
103 {
104 current_item->element = element;
105 return;
106 }
107 }
108 while (current_item->next && (current_item = current_item->next));
109
110 // No empty Items so append a new one
111 auto new_item = new ListItem;
112 new_item->element = element;
113
114 for (ListItem* expected{nullptr};
115 !current_item->next.compare_exchange_weak(expected, new_item);
116 expected = nullptr)
117 {
118 if (expected) current_item = expected;
119 }
120}
121
122template<class Element>
123void ThreadSafeList<Element>::remove(Element const& element)
124{
125 ListItem* current_item = &head;
126
127 do
128 {
129 {
130 RecursiveReadLock lock{current_item->mutex};
131 if (current_item->element != element) continue;
132 }
133
134 RecursiveWriteLock lock{current_item->mutex};
135
136 if (current_item->element == element)
137 {
138 current_item->element = Element{};
139 return;
140 }
141 }
142 while ((current_item = current_item->next));
143}
144
145template<class Element>
146unsigned int ThreadSafeList<Element>::remove_all(Element const& element)
147{
148 ListItem* current_item = &head;
149 auto removed = 0u;
150
151 do
152 {
153 {
154 RecursiveReadLock lock{current_item->mutex};
155 if (current_item->element != element) continue;
156 }
157
158 RecursiveWriteLock lock{current_item->mutex};
159
160 if (current_item->element == element)
161 {
162 current_item->element = Element{};
163 ++removed;
164 }
165 }
166 while ((current_item = current_item->next));
167
168 return removed;
169}
170
171template<class Element>
172void ThreadSafeList<Element>::clear()
173{
174 ListItem* current_item = &head;
175
176 do
177 {
178 RecursiveWriteLock lock{current_item->mutex};
179 current_item->element = Element{};
180 }
181 while ((current_item = current_item->next));
182}
183
184}
185
186#endif /* MIR_THREAD_SAFE_LIST_H_ */
0187
=== modified file 'src/include/common/mir/time/high_resolution_clock.h'
--- src/include/common/mir/time/high_resolution_clock.h 2014-10-01 06:25:56 +0000
+++ src/include/common/mir/time/high_resolution_clock.h 2014-10-31 11:59:50 +0000
@@ -29,7 +29,8 @@
29class HighResolutionClock : public Clock29class HighResolutionClock : public Clock
30{30{
31public:31public:
32 virtual Timestamp sample() const override;32 Timestamp sample() const override;
33 Duration timeout_until(Timestamp t) const override;
3334
34private:35private:
35 std::chrono::high_resolution_clock clock;36 std::chrono::high_resolution_clock clock;
3637
=== added file 'src/include/server/mir/glib_main_loop.h'
--- src/include/server/mir/glib_main_loop.h 1970-01-01 00:00:00 +0000
+++ src/include/server/mir/glib_main_loop.h 2014-10-31 11:59:50 +0000
@@ -0,0 +1,84 @@
1#ifndef MIR_GLIB_MAIN_LOOP_H_
2#define MIR_GLIB_MAIN_LOOP_H_
3
4#include "mir/main_loop.h"
5
6#include <glib.h>
7#include <memory>
8#include <thread>
9#include <vector>
10#include <atomic>
11#include <mutex>
12#include <unordered_set>
13
14namespace mir
15{
16namespace time
17{
18class Clock;
19}
20namespace detail
21{
22class SignalDispatch;
23struct FdGSource;
24struct TimerGSource;
25struct ServerActionGSource;
26}
27
28class GLibMainLoop : public MainLoop
29{
30public:
31 explicit GLibMainLoop(std::shared_ptr<time::Clock> const& clock);
32 ~GLibMainLoop();
33
34 void run() override;
35 void stop() override;
36
37 void register_signal_handler(
38 std::initializer_list<int> signals,
39 std::function<void(int)> const& handler) override;
40
41 void register_fd_handler(
42 std::initializer_list<int> fds,
43 void const* owner,
44 std::function<void(int)> const& handler) override;
45
46 void unregister_fd_handler(
47 void const* owner) override;
48
49 std::unique_ptr<mir::time::Alarm> notify_in(
50 std::chrono::milliseconds delay,
51 std::function<void()> callback) override;
52
53 std::unique_ptr<mir::time::Alarm> notify_at(
54 mir::time::Timestamp t,
55 std::function<void()> callback) override;
56
57 std::unique_ptr<time::Alarm> create_alarm(std::function<void()> callback) override;
58
59 void enqueue(void const* owner, ServerAction const& action) override;
60 void pause_processing_for(void const* owner) override;
61 void resume_processing_for(void const* owner) override;
62
63 void flush();
64
65private:
66 std::shared_ptr<time::Clock> const clock;
67 std::shared_ptr<GMainContext> main_context;
68 std::atomic<bool> running;
69
70 std::mutex fd_gsources_mutex;
71 std::vector<std::shared_ptr<detail::FdGSource>> fd_gsources;
72
73 std::unique_ptr<detail::SignalDispatch> signal_dispatch;
74
75 std::mutex before_next_iteration_mutex;
76 std::function<void()> before_next_iteration;
77
78 std::mutex server_actions_mutex;
79 std::unordered_set<void const*> do_not_process;
80};
81
82}
83
84#endif
085
=== modified file 'src/server/CMakeLists.txt'
--- src/server/CMakeLists.txt 2014-10-21 16:21:14 +0000
+++ src/server/CMakeLists.txt 2014-10-31 11:59:50 +0000
@@ -3,6 +3,7 @@
3 ${PROJECT_SOURCE_DIR}/src/include/platform3 ${PROJECT_SOURCE_DIR}/src/include/platform
4 ${PROJECT_SOURCE_DIR}/include/server4 ${PROJECT_SOURCE_DIR}/include/server
5 ${PROJECT_SOURCE_DIR}/src/include/server5 ${PROJECT_SOURCE_DIR}/src/include/server
6 ${GLIB_INCLUDE_DIRS}
6)7)
78
8add_subdirectory(compositor/)9add_subdirectory(compositor/)
@@ -31,6 +32,7 @@
31 display_server.cpp32 display_server.cpp
32 default_server_configuration.cpp33 default_server_configuration.cpp
33 asio_main_loop.cpp34 asio_main_loop.cpp
35 glib_main_loop.cpp
34 default_emergency_cleanup.cpp36 default_emergency_cleanup.cpp
35 server.cpp37 server.cpp
36)38)
@@ -59,6 +61,7 @@
59 ${EGL_LDFLAGS} ${EGL_LIBRARIES}61 ${EGL_LDFLAGS} ${EGL_LIBRARIES}
60 ${GLESv2_LDFLAGS} ${GLESv2_LIBRARIES}62 ${GLESv2_LDFLAGS} ${GLESv2_LIBRARIES}
61 ${UDEV_LDFLAGS} ${UDEV_LIBRARIES}63 ${UDEV_LDFLAGS} ${UDEV_LIBRARIES}
64 ${GLIB_LDFLAGS} ${GLIB_LIBRARIES}
62)65)
6366
64set(MIR_SERVER_OBJECTS ${MIR_SERVER_OBJECTS} PARENT_SCOPE)67set(MIR_SERVER_OBJECTS ${MIR_SERVER_OBJECTS} PARENT_SCOPE)
@@ -79,6 +82,7 @@
79 ${EGL_LDFLAGS} ${EGL_LIBRARIES}82 ${EGL_LDFLAGS} ${EGL_LIBRARIES}
80 ${GLESv2_LDFLAGS} ${GLESv2_LIBRARIES}83 ${GLESv2_LDFLAGS} ${GLESv2_LIBRARIES}
81 ${UDEV_LDFLAGS} ${UDEV_LIBRARIES}84 ${UDEV_LDFLAGS} ${UDEV_LIBRARIES}
85 ${GLIB_LDFLAGS} ${GLIB_LIBRARIES}
82)86)
8387
84install(TARGETS mirserver88install(TARGETS mirserver
8589
=== modified file 'src/server/default_server_configuration.cpp'
--- src/server/default_server_configuration.cpp 2014-10-01 06:25:56 +0000
+++ src/server/default_server_configuration.cpp 2014-10-31 11:59:50 +0000
@@ -21,6 +21,7 @@
21#include "mir/options/default_configuration.h"21#include "mir/options/default_configuration.h"
22#include "mir/abnormal_exit.h"22#include "mir/abnormal_exit.h"
23#include "mir/asio_main_loop.h"23#include "mir/asio_main_loop.h"
24#include "mir/glib_main_loop.h"
24#include "mir/default_server_status_listener.h"25#include "mir/default_server_status_listener.h"
25#include "mir/emergency_cleanup.h"26#include "mir/emergency_cleanup.h"
26#include "mir/default_configuration.h"27#include "mir/default_configuration.h"
@@ -166,7 +167,7 @@
166 return main_loop(167 return main_loop(
167 [this]()168 [this]()
168 {169 {
169 return std::make_shared<mir::AsioMainLoop>(the_clock());170 return std::make_shared<mir::GLibMainLoop>(the_clock());
170 });171 });
171}172}
172173
173174
=== modified file 'src/server/display_server.cpp'
--- src/server/display_server.cpp 2014-10-21 16:21:14 +0000
+++ src/server/display_server.cpp 2014-10-31 11:59:50 +0000
@@ -32,6 +32,7 @@
32#include "mir/input/input_dispatcher.h"32#include "mir/input/input_dispatcher.h"
3333
34#include <stdexcept>34#include <stdexcept>
35#include <signal.h>
3536
36namespace mc = mir::compositor;37namespace mc = mir::compositor;
37namespace mf = mir::frontend;38namespace mf = mir::frontend;
3839
=== added file 'src/server/glib_main_loop.cpp'
--- src/server/glib_main_loop.cpp 1970-01-01 00:00:00 +0000
+++ src/server/glib_main_loop.cpp 2014-10-31 11:59:50 +0000
@@ -0,0 +1,726 @@
1#include "mir/glib_main_loop.h"
2#include "mir/fd.h"
3
4#include <sys/signalfd.h>
5#include <unistd.h>
6#include <boost/throw_exception.hpp>
7#include <iostream>
8#include <algorithm>
9#include <atomic>
10#include <future>
11#include "mir/thread_safe_list.h"
12#include <glib-unix.h>
13
14#define ALFDEBUG if (false)
15
16namespace
17{
18
19template<typename T> using Handlers = mir::ThreadSafeList<T>;
20
21class MainLoopSource
22{
23public:
24 MainLoopSource(GSource* gsource) : gsource{gsource} {}
25
26 virtual ~MainLoopSource() = default;
27
28 virtual gboolean prepare(gint* timeout) = 0;
29 virtual gboolean check() = 0;
30 virtual gboolean dispatch() = 0;
31
32protected:
33 GSource* gsource;
34};
35
36class FdMainLoopSource : public MainLoopSource
37{
38public:
39 FdMainLoopSource(GSource* source, int fd)
40 : MainLoopSource{source}, fd_{fd},
41 fd_tag{g_source_add_unix_fd(source, fd, G_IO_IN)}
42 {
43 if (fd_tag == nullptr)
44 BOOST_THROW_EXCEPTION(std::runtime_error("g source fd failed"));
45 }
46
47 void add_handler(std::function<void(int)> const& handler, void const* owner)
48 {
49 handlers.add({handler, owner});
50 ++num_handlers;
51 }
52
53 void remove_handlers_of(void const* owner)
54 {
55 num_handlers -= handlers.remove_all({{}, owner});
56 }
57
58 bool has_handlers() const
59 {
60 return num_handlers > 0;
61 }
62
63 int fd() const
64 {
65 return fd_;
66 }
67
68 gboolean prepare(gint* timeout) override
69 {
70 *timeout = -1;
71 return (g_source_query_unix_fd(gsource, fd_tag) == G_IO_IN);
72 }
73
74 gboolean check() override
75 {
76 return (g_source_query_unix_fd(gsource, fd_tag) == G_IO_IN);
77 }
78
79 gboolean dispatch() override
80 {
81 if (g_source_query_unix_fd(gsource, fd_tag) == G_IO_IN)
82 {
83 handlers.for_each(
84 [&] (HandlerElement const& element)
85 {
86 element.handler(fd_);
87 });
88 }
89
90 return TRUE;
91 }
92
93private:
94 struct HandlerElement
95 {
96 HandlerElement() : owner{nullptr} {}
97 HandlerElement(std::function<void(int)> const& handler,
98 void const* owner) : handler{handler}, owner{owner} {}
99
100 operator bool() const { return owner != nullptr; }
101 bool operator==(HandlerElement const& other) const { return other.owner == owner; }
102 bool operator!=(HandlerElement const& other) const { return other.owner != owner; }
103
104 std::function<void(int)> handler;
105 void const* owner;
106 };
107
108 Handlers<HandlerElement> handlers;
109 std::atomic<int> num_handlers{0};
110 int const fd_;
111 gpointer fd_tag;
112};
113
114class TimerMainLoopSource : public MainLoopSource
115{
116public:
117 TimerMainLoopSource(GSource* source,
118 std::shared_ptr<mir::time::Clock> const& clock,
119 mir::time::Timestamp target)
120 : MainLoopSource{source}, clock{clock},
121 target{target}
122 {
123 ALFDEBUG std::cerr << this << " TimeSource create " << std::endl;
124 }
125
126 void add_handler(std::function<void()> const& handler)
127 {
128 handlers.add({handler});
129 }
130
131 void clear_handlers()
132 {
133 handlers.clear();
134 }
135
136 gboolean prepare(gint* timeout) override
137 {
138 auto const now = clock->sample();
139 bool const ready = (now >= target);
140 if (ready)
141 *timeout = -1;
142 else
143 *timeout = std::chrono::duration_cast<std::chrono::milliseconds>(
144 clock->timeout_until(target)).count();
145
146 ALFDEBUG std::cerr << this << " TimeSource prepare: " << ready << " " << *timeout << std::endl;
147 return ready;
148 }
149
150 gboolean check() override
151 {
152 auto const now = clock->sample();
153 bool const ready = (now >= target);
154 ALFDEBUG std::cerr << this << " TimeSource check: " << ready << std::endl;
155 return ready;
156 }
157
158 gboolean dispatch() override
159 {
160 ALFDEBUG std::cerr << this << " TimeSource dispatch: " << std::endl;
161 handlers.for_each(
162 [&] (HandlerElement const& element)
163 {
164 element.handler();
165 });
166
167 return FALSE;
168 }
169
170private:
171 struct HandlerElement
172 {
173 HandlerElement() = default;
174 HandlerElement(std::function<void(void)> const& handler) : handler{handler} {}
175 operator bool() const { return !!handler; }
176 std::function<void(void)> handler;
177 };
178
179 Handlers<HandlerElement> handlers;
180 std::shared_ptr<mir::time::Clock> const clock;
181 mir::time::Timestamp const target;
182};
183
184class ServerActionMainLoopSource : public MainLoopSource
185{
186public:
187 ServerActionMainLoopSource(
188 GSource* gsource, void const* owner, mir::ServerAction const& action,
189 std::function<bool(void const*)> const& should_dispatch)
190 : MainLoopSource{gsource}, owner{owner}, action{action},
191 should_dispatch{should_dispatch}
192 {
193 ALFDEBUG std::cerr << this << " TimeSource create " << std::endl;
194 }
195
196 gboolean prepare(gint* timeout) override
197 {
198 ALFDEBUG std::cerr << this << " ServerAction prepare: " << std::endl;
199 *timeout = -1;
200 return should_dispatch(owner);
201 }
202
203 gboolean check() override
204 {
205 ALFDEBUG std::cerr << this << " ServerAction check: " << std::endl;
206 return should_dispatch(owner);
207 }
208
209 gboolean dispatch() override
210 {
211 ALFDEBUG std::cerr << this << " ServerAction dispatch: " << std::endl;
212 action();
213 return FALSE;
214 }
215
216private:
217 void const* const owner;
218 mir::ServerAction const action;
219 std::function<bool(void const*)> const should_dispatch;
220};
221
222
223
224struct MainLoopGSource
225{
226 GSource gsource;
227 MainLoopSource* source;
228
229 void attach(GMainContext* main_context)
230 {
231 g_source_attach(&gsource, main_context);
232 }
233};
234
235gboolean ml_prepare(GSource* source, gint *timeout)
236{
237 ALFDEBUG std::cerr << "ml_prepare" << std::endl;
238 auto ml_source = reinterpret_cast<MainLoopGSource*>(source);
239 if (!g_source_is_destroyed(source))
240 return ml_source->source->prepare(timeout);
241 return FALSE;
242}
243
244gboolean ml_check(GSource* source)
245{
246 ALFDEBUG std::cerr << "ml_check" << std::endl;
247 auto ml_source = reinterpret_cast<MainLoopGSource*>(source);
248 if (!g_source_is_destroyed(source))
249 return ml_source->source->check();
250 return FALSE;
251}
252
253gboolean ml_dispatch(GSource* source, GSourceFunc, gpointer)
254{
255 ALFDEBUG std::cerr << "ml_dispatch" << std::endl;
256 auto ml_source = reinterpret_cast<MainLoopGSource*>(source);
257 if (!g_source_is_destroyed(source))
258 return ml_source->source->dispatch();
259 return FALSE;
260}
261
262void ml_finalize(GSource* source)
263{
264 ALFDEBUG std::cerr << "ml_finalize" << std::endl;
265 auto ml_source = reinterpret_cast<MainLoopGSource*>(source);
266 delete ml_source->source;
267}
268
269GSourceFuncs gsource_funcs {
270 ml_prepare,
271 ml_check,
272 ml_dispatch,
273 ml_finalize,
274 nullptr,
275 nullptr
276};
277
278}
279
280class mir::detail::SignalDispatch
281{
282public:
283 SignalDispatch(GMainContext* main_context)
284 : main_context{main_context}
285 {
286 }
287
288 void add_handler(std::vector<int> const& sigs, std::function<void(int)> const& handler)
289 {
290 for (auto sig : sigs)
291 ensure_handle_signal(sig);
292 handlers.add({sigs, handler});
293 }
294
295 void ensure_handle_signal(int sig)
296 {
297 std::lock_guard<std::mutex> lock{handled_signals_mutex};
298
299 if (handled_signals.find(sig) != handled_signals.end())
300 return;
301
302 auto const gsource = g_unix_signal_source_new(sig);
303 auto sc = new SignalContext{this, sig};
304 g_source_set_callback(
305 gsource,
306 reinterpret_cast<GSourceFunc>(static_dispatch), sc,
307 reinterpret_cast<GDestroyNotify>(destroy_sc));
308 g_source_attach(gsource, main_context);
309 g_source_unref(gsource);
310 }
311
312 void dispatch(int sig)
313 {
314 handlers.for_each(
315 [&] (HandlerElement const& element)
316 {
317 ALFDEBUG std::cerr << this << " SignalDispatch calling handler sig " << sig << std::endl;
318 if (std::find(element.sigs.begin(), element.sigs.end(), sig) != element.sigs.end())
319 element.handler(sig);
320 });
321 }
322
323private:
324 struct SignalContext
325 {
326 mir::detail::SignalDispatch* sd;
327 int sig;
328 };
329
330 static void destroy_sc(SignalContext* d) { delete d; }
331
332 struct HandlerElement
333 {
334 operator bool() const { return !!handler; }
335 std::vector<int> sigs;
336 std::function<void(int)> handler;
337 };
338
339 static gboolean static_dispatch(SignalContext* sc)
340 {
341 sc->sd->dispatch(sc->sig);
342 return TRUE;
343 }
344
345
346 GMainContext* const main_context;
347 Handlers<HandlerElement> handlers;
348 std::mutex handled_signals_mutex;
349 std::unordered_set<int> handled_signals;
350};
351
352struct mir::detail::FdGSource : MainLoopGSource
353{
354 FdMainLoopSource* fd_source()
355 {
356 return static_cast<FdMainLoopSource*>(source);
357 }
358};
359
360std::shared_ptr<mir::detail::FdGSource> make_fd_gsource(int fd)
361{
362 auto const gsource = g_source_new(&gsource_funcs, sizeof(mir::detail::FdGSource));
363 auto const fd_gsource = reinterpret_cast<mir::detail::FdGSource*>(gsource);
364
365 fd_gsource->source = new FdMainLoopSource{gsource, fd};
366
367 return {
368 fd_gsource,
369 [] (mir::detail::FdGSource* s)
370 {
371 auto const gsource = reinterpret_cast<GSource*>(s);
372 g_source_destroy(gsource);
373 g_source_unref(gsource);
374 }};
375}
376
377struct mir::detail::TimerGSource : MainLoopGSource
378{
379 TimerMainLoopSource* timer_source()
380 {
381 return static_cast<TimerMainLoopSource*>(source);
382 }
383};
384
385std::shared_ptr<mir::detail::TimerGSource> make_timer_gsource(
386 std::shared_ptr<mir::time::Clock> const& clock,
387 mir::time::Timestamp target)
388{
389 auto const gsource = g_source_new(&gsource_funcs, sizeof(mir::detail::TimerGSource));
390 auto const timer_gsource = reinterpret_cast<mir::detail::TimerGSource*>(gsource);
391
392 timer_gsource->source = new TimerMainLoopSource{gsource, clock, target};
393
394 return {
395 timer_gsource,
396 [] (mir::detail::TimerGSource* s)
397 {
398 // Clear handlers to ensure that no handlers will be called after this object
399 // has been destroyed (in case we are destroying while the source is being
400 // dispatched).
401 s->timer_source()->clear_handlers();
402 auto const gsource = reinterpret_cast<GSource*>(s);
403 g_source_destroy(gsource);
404 g_source_unref(gsource);
405 }};
406}
407
408struct mir::detail::ServerActionGSource : MainLoopGSource
409{
410};
411
412std::shared_ptr<mir::detail::ServerActionGSource> make_server_action_gsource(
413 void const* owner, mir::ServerAction const& action,
414 std::function<bool(void const*)> const& should_dispatch)
415{
416 auto const gsource = g_source_new(&gsource_funcs, sizeof(mir::detail::ServerActionGSource));
417 auto const server_action_gsource = reinterpret_cast<mir::detail::ServerActionGSource*>(gsource);
418
419 server_action_gsource->source =
420 new ServerActionMainLoopSource{gsource, owner, action, should_dispatch};
421
422 return {
423 server_action_gsource,
424 [] (mir::detail::ServerActionGSource* s)
425 {
426 auto const gsource = reinterpret_cast<GSource*>(s);
427 // NB: we shouldn't destroy the gsource
428 //g_source_destroy(gsource);
429 g_source_unref(gsource);
430 }};
431}
432
433class AlarmImpl : public mir::time::Alarm
434{
435public:
436 AlarmImpl(
437 GMainContext* main_context,
438 std::shared_ptr<mir::time::Clock> const& clock,
439 std::function<void()> const& callback)
440 : clock{clock},
441 callback{callback},
442 state_{State::cancelled},
443 main_context{main_context}
444 {
445 }
446
447 bool cancel() override
448 {
449 std::lock_guard<std::mutex> lock{alarm_mutex};
450
451 source.reset();
452 state_ = State::cancelled;
453 return true;
454 }
455
456 State state() const override
457 {
458 std::lock_guard<std::mutex> lock{alarm_mutex};
459
460 return state_;
461 }
462
463 bool reschedule_in(std::chrono::milliseconds delay) override
464 {
465 return reschedule_for(clock->sample() + delay);
466 }
467
468 bool reschedule_for(mir::time::Timestamp time_point) override
469 {
470 std::lock_guard<std::mutex> lock{alarm_mutex};
471
472 state_ = State::pending;
473 source = make_timer_gsource(clock, time_point);
474 source->timer_source()->add_handler(
475 [&] { state_ = State::triggered; callback(); } );
476 source->attach(main_context);
477 return true;
478 }
479
480private:
481 mutable std::mutex alarm_mutex;
482 std::shared_ptr<mir::time::Clock> const clock;
483 std::function<void()> const callback;
484 State state_;
485 std::shared_ptr<mir::detail::TimerGSource> source;
486 GMainContext* main_context;
487};
488
489
490
491mir::GLibMainLoop::GLibMainLoop(
492 std::shared_ptr<time::Clock> const& clock)
493 : clock{clock},
494 main_context{g_main_context_new(),
495 [] (GMainContext* ctx)
496 {
497 if (ctx) g_main_context_unref(ctx);
498 }},
499 signal_dispatch{new detail::SignalDispatch{main_context.get()}},
500 before_next_iteration{[]{}}
501{
502}
503
504mir::GLibMainLoop::~GLibMainLoop()
505{
506 stop();
507}
508
509void mir::GLibMainLoop::run()
510{
511 running = true;
512 ALFDEBUG std::cerr << "run start" << std::endl;
513 while (running)
514 {
515 ALFDEBUG std::cerr << "run iter start " << running << std::endl;
516 {
517 std::lock_guard<std::mutex> lock{before_next_iteration_mutex};
518 before_next_iteration();
519 }
520 ALFDEBUG std::cerr << "run iter 2 " << running << std::endl;
521 g_main_context_iteration(main_context.get(), TRUE);
522 ALFDEBUG std::cerr << "run iter end " << running << std::endl;
523 }
524 ALFDEBUG std::cerr << "run end" << std::endl;
525}
526
527struct StopContext
528{
529 std::atomic<bool>* running;
530 GMainContext* main_context;
531};
532
533gboolean stop_func(gpointer data)
534{
535 auto stop_context = static_cast<StopContext*>(data);
536
537 *stop_context->running = false;
538 g_main_context_wakeup(stop_context->main_context);
539 return FALSE;
540}
541
542
543void delete_stop_context(StopContext* ctx)
544{
545 delete ctx;
546}
547
548void mir::GLibMainLoop::stop()
549{
550 ALFDEBUG std::cerr << "stop() " << running << std::endl;
551 int const very_high_priority = -1000;
552 auto source = g_idle_source_new();
553
554 g_source_set_priority(source, very_high_priority);
555 g_source_set_callback(
556 source, stop_func,
557 new StopContext{&running, main_context.get()},
558 reinterpret_cast<GDestroyNotify>(delete_stop_context));
559 g_source_attach(source, main_context.get());
560 g_source_unref(source);
561}
562
563void mir::GLibMainLoop::register_signal_handler(
564 std::initializer_list<int> signals,
565 std::function<void(int)> const& handler)
566{
567 signal_dispatch->add_handler(signals, handler);
568}
569
570void mir::GLibMainLoop::register_fd_handler(
571 std::initializer_list<int> fds,
572 void const* owner,
573 std::function<void(int)> const& handler)
574{
575 std::lock_guard<std::mutex> lock{fd_gsources_mutex};
576
577 for (auto fd : fds)
578 {
579 std::shared_ptr<detail::FdGSource> gsource;
580 bool new_gsource{false};
581
582 auto iter = std::find_if(
583 fd_gsources.begin(),
584 fd_gsources.end(),
585 [&] (std::shared_ptr<detail::FdGSource> const& f)
586 {
587 return f->fd_source()->fd() == fd;
588 });
589
590 if (iter == fd_gsources.end())
591 {
592 gsource = make_fd_gsource(fd);
593 new_gsource = true;
594 fd_gsources.push_back(gsource);
595 }
596 else
597 {
598 gsource = *iter;
599 }
600
601 gsource->fd_source()->add_handler(handler, owner);
602 if (new_gsource)
603 gsource->attach(main_context.get());
604 }
605}
606
607void mir::GLibMainLoop::unregister_fd_handler(
608 void const* owner)
609{
610 std::lock_guard<std::mutex> lock{fd_gsources_mutex};
611
612 ALFDEBUG std::cerr << "unregister_fd_handler " << owner << std::endl;
613
614 auto it = fd_gsources.rbegin();
615
616 while (it != fd_gsources.rend())
617 {
618 auto const gsource = *it;
619 gsource->fd_source()->remove_handlers_of(owner);
620
621 ALFDEBUG std::cerr << "removed handler" << std::endl;
622 if (!gsource->fd_source()->has_handlers())
623 {
624 ALFDEBUG std::cerr << "no handler left " << std::endl;
625 it = decltype(fd_gsources)::reverse_iterator(
626 fd_gsources.erase(std::prev(it.base())));
627 }
628 else
629 {
630 ++it;
631 }
632 }
633}
634
635std::unique_ptr<mir::time::Alarm> mir::GLibMainLoop::notify_in(
636 std::chrono::milliseconds delay,
637 std::function<void()> callback)
638{
639 auto alarm = std::unique_ptr<mir::time::Alarm>{
640 new AlarmImpl{main_context.get(), clock, callback}};
641
642 alarm->reschedule_in(delay);
643
644 return alarm;
645}
646
647std::unique_ptr<mir::time::Alarm> mir::GLibMainLoop::notify_at(
648 mir::time::Timestamp t,
649 std::function<void()> callback)
650{
651 auto alarm = std::unique_ptr<mir::time::Alarm>{
652 new AlarmImpl{main_context.get(), clock, callback}};
653
654 alarm->reschedule_for(t);
655
656 return alarm;
657}
658
659std::unique_ptr<mir::time::Alarm> mir::GLibMainLoop::create_alarm(
660 std::function<void()> callback)
661{
662 return std::unique_ptr<mir::time::Alarm>{
663 new AlarmImpl{main_context.get(), clock, callback}};
664}
665
666void mir::GLibMainLoop::enqueue(void const* owner, ServerAction const& action)
667{
668 auto gsource = make_server_action_gsource(owner, action,
669 [&] (void const* owner)
670 {
671 std::lock_guard<std::mutex> lock{server_actions_mutex};
672 return (do_not_process.find(owner) == do_not_process.end());
673 });
674 gsource->attach(main_context.get());
675}
676
677void mir::GLibMainLoop::pause_processing_for(void const* owner)
678{
679 std::lock_guard<std::mutex> lock{server_actions_mutex};
680 do_not_process.insert(owner);
681}
682
683void mir::GLibMainLoop::resume_processing_for(void const* owner)
684{
685 std::lock_guard<std::mutex> lock{server_actions_mutex};
686 do_not_process.erase(owner);
687
688 g_main_context_wakeup(main_context.get());
689}
690
691gboolean flush_func(gpointer data)
692{
693 ALFDEBUG std::cerr << "Flush!" << std::endl;
694 auto to_flush = static_cast<std::promise<void>*>(data);
695 to_flush->set_value();
696
697 return FALSE;
698}
699
700void mir::GLibMainLoop::flush()
701{
702 std::promise<void> to_flush;
703 auto flushed = to_flush.get_future();
704
705 {
706 std::lock_guard<std::mutex> lock{before_next_iteration_mutex};
707 before_next_iteration = [&] {
708 ALFDEBUG std::cerr << "g_idle_add_full" << std::endl;
709
710 auto source = g_idle_source_new();
711 g_source_set_priority(source, G_PRIORITY_LOW);
712 g_source_set_callback(source, flush_func, &to_flush, nullptr);
713 g_source_attach(source, main_context.get());
714 g_source_unref(source);
715
716 before_next_iteration = []{};
717 };
718 }
719
720 g_main_context_wakeup(main_context.get());
721
722 ALFDEBUG std::cerr << "Waiting for before_next_iteration done" << std::endl;
723
724 flushed.get();
725 ALFDEBUG std::cerr << "flush finished" << std::endl;
726}
0727
=== modified file 'tests/integration-tests/CMakeLists.txt'
--- tests/integration-tests/CMakeLists.txt 2014-10-28 13:59:51 +0000
+++ tests/integration-tests/CMakeLists.txt 2014-10-31 11:59:50 +0000
@@ -7,6 +7,7 @@
7 ${PROJECT_SOURCE_DIR}/src/include/platform7 ${PROJECT_SOURCE_DIR}/src/include/platform
8 ${PROJECT_SOURCE_DIR}/src/include/server8 ${PROJECT_SOURCE_DIR}/src/include/server
9 ${PROJECT_SOURCE_DIR}/src/include/client9 ${PROJECT_SOURCE_DIR}/src/include/client
10 ${GLIB_INCLUDE_DIRS}
10)11)
1112
12protobuf_generate_cpp(13protobuf_generate_cpp(
@@ -99,6 +100,7 @@
99 ${CMAKE_THREAD_LIBS_INIT} # Link in pthread.100 ${CMAKE_THREAD_LIBS_INIT} # Link in pthread.
100 ${DRM_LDFLAGS} ${DRM_LIBRARIES}101 ${DRM_LDFLAGS} ${DRM_LIBRARIES}
101 ${GBM_LDFLAGS} ${GBM_LIBRARIES}102 ${GBM_LDFLAGS} ${GBM_LIBRARIES}
103 ${GLIB_LDFLAGS} ${GLIB_LIBRARIES}
102 ${MIR_PLATFORM_REFERENCES}104 ${MIR_PLATFORM_REFERENCES}
103 ${MIR_COMMON_REFERENCES}105 ${MIR_COMMON_REFERENCES}
104 ${MIR_SERVER_REFERENCES}106 ${MIR_SERVER_REFERENCES}
105107
=== modified file 'tests/unit-tests/CMakeLists.txt'
--- tests/unit-tests/CMakeLists.txt 2014-10-27 18:00:20 +0000
+++ tests/unit-tests/CMakeLists.txt 2014-10-31 11:59:50 +0000
@@ -25,6 +25,7 @@
25 test_gmock_fixes.cpp25 test_gmock_fixes.cpp
26 test_recursive_read_write_mutex.cpp26 test_recursive_read_write_mutex.cpp
27 test_asio_main_loop.cpp27 test_asio_main_loop.cpp
28 test_glib_main_loop.cpp
28 shared_library_test.cpp29 shared_library_test.cpp
29 test_raii.cpp30 test_raii.cpp
30 test_udev_wrapper.cpp31 test_udev_wrapper.cpp
@@ -32,6 +33,7 @@
32 test_thread_name.cpp33 test_thread_name.cpp
33 test_default_emergency_cleanup.cpp34 test_default_emergency_cleanup.cpp
34 test_basic_observers.cpp35 test_basic_observers.cpp
36 test_thread_safe_list.cpp
35 test_fatal.cpp37 test_fatal.cpp
36 test_fd.cpp38 test_fd.cpp
37 test_shared_library_prober.cpp39 test_shared_library_prober.cpp
3840
=== modified file 'tests/unit-tests/client/test_periodic_perf_report.cpp'
--- tests/unit-tests/client/test_periodic_perf_report.cpp 2014-09-05 08:00:44 +0000
+++ tests/unit-tests/client/test_periodic_perf_report.cpp 2014-10-31 11:59:50 +0000
@@ -37,6 +37,10 @@
37 {37 {
38 return now;38 return now;
39 }39 }
40 time::Duration timeout_until(time::Timestamp) const override
41 {
42 return time::Duration{0};
43 }
40private:44private:
41 time::Timestamp now;45 time::Timestamp now;
42};46};
4347
=== modified file 'tests/unit-tests/graphics/mesa/test_display.cpp'
--- tests/unit-tests/graphics/mesa/test_display.cpp 2014-10-01 06:25:56 +0000
+++ tests/unit-tests/graphics/mesa/test_display.cpp 2014-10-31 11:59:50 +0000
@@ -25,6 +25,7 @@
25#include "src/server/graphics/default_display_configuration_policy.h"25#include "src/server/graphics/default_display_configuration_policy.h"
26#include "mir/time/high_resolution_clock.h"26#include "mir/time/high_resolution_clock.h"
27#include "mir/asio_main_loop.h"27#include "mir/asio_main_loop.h"
28#include "mir/glib_main_loop.h"
2829
29#include "mir_test_doubles/mock_egl.h"30#include "mir_test_doubles/mock_egl.h"
30#include "mir_test_doubles/mock_gl.h"31#include "mir_test_doubles/mock_gl.h"
@@ -717,7 +718,7 @@
717718
718 auto display = create_display(create_platform());719 auto display = create_display(create_platform());
719720
720 mir::AsioMainLoop ml{std::make_shared<mir::time::HighResolutionClock>()};721 mir::GLibMainLoop ml{std::make_shared<mir::time::HighResolutionClock>()};
721 std::condition_variable done;722 std::condition_variable done;
722723
723 int const device_add_count{1};724 int const device_add_count{1};
724725
=== modified file 'tests/unit-tests/logging/message_processor_report.cpp'
--- tests/unit-tests/logging/message_processor_report.cpp 2014-03-06 06:05:17 +0000
+++ tests/unit-tests/logging/message_processor_report.cpp 2014-10-31 11:59:50 +0000
@@ -32,6 +32,10 @@
32{32{
33public:33public:
34 MOCK_CONST_METHOD0(sample, mir::time::Timestamp());34 MOCK_CONST_METHOD0(sample, mir::time::Timestamp());
35 mir::time::Duration timeout_until(mir::time::Timestamp) const override
36 {
37 return mir::time::Duration{0};
38 }
3539
36 ~MockClock() noexcept(true) {}40 ~MockClock() noexcept(true) {}
37};41};
3842
=== modified file 'tests/unit-tests/test_asio_main_loop.cpp'
--- tests/unit-tests/test_asio_main_loop.cpp 2014-10-21 16:21:14 +0000
+++ tests/unit-tests/test_asio_main_loop.cpp 2014-10-31 11:59:50 +0000
@@ -56,6 +56,10 @@
56 std::lock_guard<std::mutex> lock(time_mutex);56 std::lock_guard<std::mutex> lock(time_mutex);
57 return current_time;57 return current_time;
58 }58 }
59 mir::time::Duration timeout_until(mir::time::Timestamp) const override
60 {
61 return mir::time::Duration{0};
62 }
59 void advance_by(std::chrono::milliseconds const step, mir::AsioMainLoop & ml)63 void advance_by(std::chrono::milliseconds const step, mir::AsioMainLoop & ml)
60 {64 {
61 bool done = false;65 bool done = false;
6266
=== added file 'tests/unit-tests/test_glib_main_loop.cpp'
--- tests/unit-tests/test_glib_main_loop.cpp 1970-01-01 00:00:00 +0000
+++ tests/unit-tests/test_glib_main_loop.cpp 2014-10-31 11:59:50 +0000
@@ -0,0 +1,901 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
17 */
18
19#include "mir/glib_main_loop.h"
20#include "mir/time/high_resolution_clock.h"
21
22#include "mir_test/pipe.h"
23#include "mir_test/signal.h"
24#include "mir_test/auto_unblock_thread.h"
25
26#include <gtest/gtest.h>
27#include <gmock/gmock.h>
28
29#include <atomic>
30#include <mutex>
31#include <condition_variable>
32
33namespace mt = mir::test;
34
35class Counter
36{
37public:
38 int operator++()
39 {
40 std::lock_guard<decltype(mutex)> lock(mutex);
41 cv.notify_one();
42 return ++counter;
43 }
44
45 bool wait_for(std::chrono::milliseconds const& delay, int expected)
46 {
47 std::unique_lock<decltype(mutex)> lock(mutex);
48 return cv.wait_for(lock, delay, [&]{ return counter == expected;});
49 }
50
51 operator int() const
52 {
53 std::lock_guard<decltype(mutex)> lock(mutex);
54 return counter;
55 }
56
57private:
58 std::mutex mutable mutex;
59 std::condition_variable cv;
60 int counter{0};
61};
62
63struct UnblockMainLoop : mt::AutoUnblockThread
64{
65 UnblockMainLoop(mir::GLibMainLoop& loop)
66 : mt::AutoUnblockThread([&loop]() {loop.stop();},
67 [&loop]() {loop.run();})
68 {}
69};
70
71
72class AdvanceableClock : public mir::time::Clock
73{
74public:
75 mir::time::Timestamp sample() const override
76 {
77 std::lock_guard<std::mutex> lock(time_mutex);
78 return current_time;
79 }
80
81 mir::time::Duration timeout_until(mir::time::Timestamp) const override
82 {
83 std::lock_guard<std::mutex> lock(time_mutex);
84 return mir::time::Duration{0};
85 }
86
87 void advance_by(std::chrono::milliseconds const step, mir::GLibMainLoop& ml)
88 {
89 {
90 std::lock_guard<std::mutex> lock(time_mutex);
91 current_time += step;
92 }
93 ml.flush();
94 //std::cerr << "Advance by finish" << std::endl;
95 }
96
97private:
98 mutable std::mutex time_mutex;
99 mir::time::Timestamp current_time{
100 []
101 {
102 mir::time::HighResolutionClock clock;
103 return clock.sample();
104 }()
105 };
106};
107
108struct GLibMainLoopTest : ::testing::Test
109{
110 mir::GLibMainLoop ml{std::make_shared<mir::time::HighResolutionClock>()};
111};
112
113TEST_F(GLibMainLoopTest, signal_handled)
114{
115 int const signum{SIGUSR1};
116 int handled_signum{0};
117
118 ml.register_signal_handler(
119 {signum},
120 [&handled_signum, this](int sig)
121 {
122 handled_signum = sig;
123 ml.stop();
124 });
125
126 kill(getpid(), signum);
127
128 ml.run();
129
130 ASSERT_EQ(signum, handled_signum);
131}
132
133TEST_F(GLibMainLoopTest, multiple_signals_handled)
134{
135 std::vector<int> const signals{SIGUSR1, SIGUSR2};
136 size_t const num_signals_to_send{10};
137 std::vector<int> handled_signals;
138 std::atomic<unsigned int> num_handled_signals{0};
139
140 ml.register_signal_handler(
141 {signals[0], signals[1]},
142 [&handled_signals, &num_handled_signals](int sig)
143 {
144 handled_signals.push_back(sig);
145 ++num_handled_signals;
146 });
147
148
149 std::thread signal_sending_thread(
150 [this, num_signals_to_send, &signals, &num_handled_signals]
151 {
152 for (size_t i = 0; i < num_signals_to_send; i++)
153 {
154 kill(getpid(), signals[i % signals.size()]);
155 while (num_handled_signals <= i) std::this_thread::yield();
156 }
157 ml.stop();
158 });
159
160 ml.run();
161
162 signal_sending_thread.join();
163
164 ASSERT_EQ(num_signals_to_send, handled_signals.size());
165
166 for (size_t i = 0; i < num_signals_to_send; i++)
167 ASSERT_EQ(signals[i % signals.size()], handled_signals[i]) << " index " << i;
168}
169
170TEST_F(GLibMainLoopTest, all_registered_handlers_are_called)
171{
172 int const signum{SIGUSR1};
173 std::vector<int> handled_signum{0,0,0};
174
175 ml.register_signal_handler(
176 {signum},
177 [&handled_signum, this](int sig)
178 {
179 handled_signum[0] = sig;
180 if (handled_signum[0] != 0 &&
181 handled_signum[1] != 0 &&
182 handled_signum[2] != 0)
183 {
184 ml.stop();
185 }
186 });
187
188 ml.register_signal_handler(
189 {signum},
190 [&handled_signum, this](int sig)
191 {
192 handled_signum[1] = sig;
193 if (handled_signum[0] != 0 &&
194 handled_signum[1] != 0 &&
195 handled_signum[2] != 0)
196 {
197 ml.stop();
198 }
199 });
200
201 ml.register_signal_handler(
202 {signum},
203 [&handled_signum, this](int sig)
204 {
205 handled_signum[2] = sig;
206 if (handled_signum[0] != 0 &&
207 handled_signum[1] != 0 &&
208 handled_signum[2] != 0)
209 {
210 ml.stop();
211 }
212 });
213
214 kill(getpid(), signum);
215
216 ml.run();
217
218 ASSERT_EQ(signum, handled_signum[0]);
219 ASSERT_EQ(signum, handled_signum[1]);
220 ASSERT_EQ(signum, handled_signum[2]);
221}
222
223TEST_F(GLibMainLoopTest, fd_data_handled)
224{
225 mt::Pipe p;
226 char const data_to_write{'a'};
227 int handled_fd{0};
228 char data_read{0};
229
230 ml.register_fd_handler(
231 {p.read_fd()},
232 this,
233 [&handled_fd, &data_read, this](int fd)
234 {
235 handled_fd = fd;
236 EXPECT_EQ(1, read(fd, &data_read, 1));
237 ml.stop();
238 });
239
240 EXPECT_EQ(1, write(p.write_fd(), &data_to_write, 1));
241
242 ml.run();
243
244 EXPECT_EQ(data_to_write, data_read);
245}
246
247TEST_F(GLibMainLoopTest, multiple_fds_with_single_handler_handled)
248{
249 std::vector<mt::Pipe> const pipes(2);
250 size_t const num_elems_to_send{10};
251 std::vector<int> handled_fds;
252 std::vector<size_t> elems_read;
253 std::atomic<unsigned int> num_handled_fds{0};
254
255 ml.register_fd_handler(
256 {pipes[0].read_fd(), pipes[1].read_fd()},
257 this,
258 [&handled_fds, &elems_read, &num_handled_fds](int fd)
259 {
260 handled_fds.push_back(fd);
261
262 size_t i;
263 EXPECT_EQ(static_cast<ssize_t>(sizeof(i)),
264 read(fd, &i, sizeof(i)));
265 elems_read.push_back(i);
266
267 ++num_handled_fds;
268 });
269
270 std::thread fd_writing_thread{
271 [this, num_elems_to_send, &pipes, &num_handled_fds]
272 {
273 for (size_t i = 0; i < num_elems_to_send; i++)
274 {
275 EXPECT_EQ(static_cast<ssize_t>(sizeof(i)),
276 write(pipes[i % pipes.size()].write_fd(), &i, sizeof(i)));
277 while (num_handled_fds <= i) std::this_thread::yield();
278 }
279 ml.stop();
280 }};
281
282 ml.run();
283
284 fd_writing_thread.join();
285
286 ASSERT_EQ(num_elems_to_send, handled_fds.size());
287 ASSERT_EQ(num_elems_to_send, elems_read.size());
288
289 for (size_t i = 0; i < num_elems_to_send; i++)
290 {
291 EXPECT_EQ(pipes[i % pipes.size()].read_fd(), handled_fds[i]) << " index " << i;
292 EXPECT_EQ(i, elems_read[i]) << " index " << i;
293 }
294}
295
296TEST_F(GLibMainLoopTest, multiple_fd_handlers_are_called)
297{
298 std::vector<mt::Pipe> const pipes(3);
299 std::vector<int> const elems_to_send{10,11,12};
300 std::vector<int> handled_fds{0,0,0};
301 std::vector<int> elems_read{0,0,0};
302
303 ml.register_fd_handler(
304 {pipes[0].read_fd()},
305 this,
306 [&handled_fds, &elems_read, this](int fd)
307 {
308 EXPECT_EQ(static_cast<ssize_t>(sizeof(elems_read[0])),
309 read(fd, &elems_read[0], sizeof(elems_read[0])));
310 handled_fds[0] = fd;
311 if (handled_fds[0] != 0 &&
312 handled_fds[1] != 0 &&
313 handled_fds[2] != 0)
314 {
315 ml.stop();
316 }
317 });
318
319 ml.register_fd_handler(
320 {pipes[1].read_fd()},
321 this,
322 [&handled_fds, &elems_read, this](int fd)
323 {
324 EXPECT_EQ(static_cast<ssize_t>(sizeof(elems_read[1])),
325 read(fd, &elems_read[1], sizeof(elems_read[1])));
326 handled_fds[1] = fd;
327 if (handled_fds[0] != 0 &&
328 handled_fds[1] != 0 &&
329 handled_fds[2] != 0)
330 {
331 ml.stop();
332 }
333 });
334
335 ml.register_fd_handler(
336 {pipes[2].read_fd()},
337 this,
338 [&handled_fds, &elems_read, this](int fd)
339 {
340 EXPECT_EQ(static_cast<ssize_t>(sizeof(elems_read[2])),
341 read(fd, &elems_read[2], sizeof(elems_read[2])));
342 handled_fds[2] = fd;
343 if (handled_fds[0] != 0 &&
344 handled_fds[1] != 0 &&
345 handled_fds[2] != 0)
346 {
347 ml.stop();
348 }
349 });
350
351 EXPECT_EQ(static_cast<ssize_t>(sizeof(elems_to_send[0])),
352 write(pipes[0].write_fd(), &elems_to_send[0], sizeof(elems_to_send[0])));
353 EXPECT_EQ(static_cast<ssize_t>(sizeof(elems_to_send[1])),
354 write(pipes[1].write_fd(), &elems_to_send[1], sizeof(elems_to_send[1])));
355 EXPECT_EQ(static_cast<ssize_t>(sizeof(elems_to_send[2])),
356 write(pipes[2].write_fd(), &elems_to_send[2], sizeof(elems_to_send[2])));
357
358 ml.run();
359
360 EXPECT_EQ(pipes[0].read_fd(), handled_fds[0]);
361 EXPECT_EQ(pipes[1].read_fd(), handled_fds[1]);
362 EXPECT_EQ(pipes[2].read_fd(), handled_fds[2]);
363
364 EXPECT_EQ(elems_to_send[0], elems_read[0]);
365 EXPECT_EQ(elems_to_send[1], elems_read[1]);
366 EXPECT_EQ(elems_to_send[2], elems_read[2]);
367}
368
369TEST_F(GLibMainLoopTest,
370 unregister_prevents_callback_and_does_not_harm_other_callbacks)
371{
372 mt::Pipe p1, p2;
373 char const data_to_write{'a'};
374 int p2_handler_executes{-1};
375 char data_read{0};
376
377 ml.register_fd_handler(
378 {p1.read_fd()},
379 this,
380 [this](int)
381 {
382 FAIL() << "unregistered handler called";
383 ml.stop();
384 });
385
386 ml.register_fd_handler(
387 {p2.read_fd()},
388 this+2,
389 [&p2_handler_executes,&data_read,this](int fd)
390 {
391 p2_handler_executes = fd;
392 EXPECT_EQ(1, read(fd, &data_read, 1));
393 ml.stop();
394 });
395
396 ml.unregister_fd_handler(this);
397
398 EXPECT_EQ(1, write(p1.write_fd(), &data_to_write, 1));
399 EXPECT_EQ(1, write(p2.write_fd(), &data_to_write, 1));
400
401 ml.run();
402
403 EXPECT_EQ(data_to_write, data_read);
404 EXPECT_EQ(p2.read_fd(), p2_handler_executes);
405}
406
407TEST_F(GLibMainLoopTest, unregister_does_not_close_fds)
408{
409 mt::Pipe p1, p2;
410 char const data_to_write{'b'};
411 char data_read{0};
412
413 ml.register_fd_handler(
414 {p1.read_fd()},
415 this,
416 [this](int)
417 {
418 FAIL() << "unregistered handler called";
419 ml.stop();
420 });
421
422 ml.unregister_fd_handler(this);
423
424 ml.register_fd_handler(
425 {p1.read_fd()},
426 this,
427 [this,&data_read](int fd)
428 {
429 EXPECT_EQ(1, read(fd, &data_read, 1));
430 ml.stop();
431 });
432
433 EXPECT_EQ(1, write(p1.write_fd(), &data_to_write, 1));
434
435 ml.run();
436
437 EXPECT_EQ(data_to_write, data_read);
438}
439
440struct GLibMainLoopAlarmTest : ::testing::Test
441{
442 std::shared_ptr<AdvanceableClock> clock = std::make_shared<AdvanceableClock>();
443 mir::GLibMainLoop ml{clock};
444 std::chrono::milliseconds delay{50};
445};
446
447TEST_F(GLibMainLoopAlarmTest, main_loop_runs_until_stop_called)
448{
449 auto mainloop_started = std::make_shared<mt::Signal>();
450
451 auto fire_on_mainloop_start = ml.notify_in(std::chrono::milliseconds{0},
452 [mainloop_started]()
453 {
454 mainloop_started->raise();
455 });
456
457 UnblockMainLoop unblocker(ml);
458
459 ASSERT_TRUE(mainloop_started->wait_for(std::chrono::milliseconds{100}));
460
461 auto timer_fired = std::make_shared<mt::Signal>();
462 auto alarm = ml.notify_in(std::chrono::milliseconds{10}, [timer_fired]
463 {
464 timer_fired->raise();
465 });
466
467 //std::cerr << "Advance by 10 " << std::endl;
468 clock->advance_by(std::chrono::milliseconds{10}, ml);
469 //std::cerr << "after Advance by 10 " << std::endl;
470 EXPECT_TRUE(timer_fired->wait_for(std::chrono::milliseconds{500}));
471 //std::cerr << "after waitfor by500 " << std::endl;
472
473 ml.stop();
474 //std::cerr << "after stop " << std::endl;
475 // Main loop should be stopped now
476
477 timer_fired = std::make_shared<mt::Signal>();
478 auto should_not_fire = ml.notify_in(std::chrono::milliseconds{0},
479 [timer_fired]()
480 {
481 timer_fired->raise();
482 });
483
484 EXPECT_FALSE(timer_fired->wait_for(std::chrono::milliseconds{10}));
485}
486
487TEST_F(GLibMainLoopAlarmTest, alarm_starts_in_pending_state)
488{
489 auto alarm = ml.notify_in(delay, [this]() {});
490
491 UnblockMainLoop unblocker(ml);
492
493 EXPECT_EQ(mir::time::Alarm::pending, alarm->state());
494}
495
496TEST_F(GLibMainLoopAlarmTest, alarm_fires_with_correct_delay)
497{
498 UnblockMainLoop unblocker(ml);
499
500 auto alarm = ml.notify_in(delay, [](){});
501
502 clock->advance_by(delay - std::chrono::milliseconds{1}, ml);
503 EXPECT_EQ(mir::time::Alarm::pending, alarm->state());
504
505 clock->advance_by(delay, ml);
506 EXPECT_EQ(mir::time::Alarm::triggered, alarm->state());
507}
508
509TEST_F(GLibMainLoopAlarmTest, multiple_alarms_fire)
510{
511 using namespace testing;
512
513 int const alarm_count{10};
514 Counter call_count;
515 std::array<std::unique_ptr<mir::time::Alarm>, alarm_count> alarms;
516
517 for (auto& alarm : alarms)
518 alarm = ml.notify_in(delay, [&call_count](){++call_count;});
519
520 UnblockMainLoop unblocker(ml);
521 clock->advance_by(delay, ml);
522
523 call_count.wait_for(delay, alarm_count);
524 EXPECT_THAT(call_count, Eq(alarm_count));
525
526 for (auto const& alarm : alarms)
527 EXPECT_EQ(mir::time::Alarm::triggered, alarm->state());
528}
529
530TEST_F(GLibMainLoopAlarmTest, alarm_changes_to_triggered_state)
531{
532 auto alarm_fired = std::make_shared<mt::Signal>();
533 auto alarm = ml.notify_in(std::chrono::milliseconds{5}, [alarm_fired]()
534 {
535 alarm_fired->raise();
536 });
537
538 UnblockMainLoop unblocker(ml);
539
540 clock->advance_by(delay, ml);
541 ASSERT_TRUE(alarm_fired->wait_for(std::chrono::milliseconds{100}));
542
543 EXPECT_EQ(mir::time::Alarm::triggered, alarm->state());
544}
545
546TEST_F(GLibMainLoopAlarmTest, cancelled_alarm_doesnt_fire)
547{
548 UnblockMainLoop unblocker(ml);
549 auto alarm = ml.notify_in(std::chrono::milliseconds{100},
550 [](){ FAIL() << "Alarm handler of canceld alarm called";});
551
552 EXPECT_TRUE(alarm->cancel());
553
554 EXPECT_EQ(mir::time::Alarm::cancelled, alarm->state());
555
556 clock->advance_by(std::chrono::milliseconds{100}, ml);
557
558 EXPECT_EQ(mir::time::Alarm::cancelled, alarm->state());
559}
560
561TEST_F(GLibMainLoopAlarmTest, destroyed_alarm_doesnt_fire)
562{
563 auto alarm = ml.notify_in(std::chrono::milliseconds{200},
564 [](){ FAIL() << "Alarm handler of destroyed alarm called"; });
565
566 UnblockMainLoop unblocker(ml);
567
568 alarm.reset(nullptr);
569 clock->advance_by(std::chrono::milliseconds{200}, ml);
570}
571
572TEST_F(GLibMainLoopAlarmTest, rescheduled_alarm_fires_again)
573{
574 std::atomic<int> call_count{0};
575
576 auto alarm = ml.notify_in(std::chrono::milliseconds{0}, [&call_count]()
577 {
578 if (call_count++ > 1)
579 FAIL() << "Alarm called too many times";
580 });
581
582 UnblockMainLoop unblocker(ml);
583
584 clock->advance_by(std::chrono::milliseconds{0}, ml);
585 ASSERT_EQ(mir::time::Alarm::triggered, alarm->state());
586
587 alarm->reschedule_in(std::chrono::milliseconds{100});
588 EXPECT_EQ(mir::time::Alarm::pending, alarm->state());
589
590 clock->advance_by(std::chrono::milliseconds{100}, ml);
591 EXPECT_EQ(mir::time::Alarm::triggered, alarm->state());
592}
593
594TEST_F(GLibMainLoopAlarmTest, rescheduled_alarm_cancels_previous_scheduling)
595{
596 std::atomic<int> call_count{0};
597
598 auto alarm = ml.notify_in(std::chrono::milliseconds{100}, [&call_count]()
599 {
600 call_count++;
601 });
602
603 UnblockMainLoop unblocker(ml);
604 clock->advance_by(std::chrono::milliseconds{90}, ml);
605
606 EXPECT_EQ(mir::time::Alarm::pending, alarm->state());
607 EXPECT_EQ(0, call_count);
608 EXPECT_TRUE(alarm->reschedule_in(std::chrono::milliseconds{100}));
609 EXPECT_EQ(mir::time::Alarm::pending, alarm->state());
610
611 clock->advance_by(std::chrono::milliseconds{110}, ml);
612
613 EXPECT_EQ(mir::time::Alarm::triggered, alarm->state());
614 EXPECT_EQ(1, call_count);
615}
616
617TEST_F(GLibMainLoopAlarmTest, alarm_callback_cannot_deadlock)
618{ // Regression test for deadlock bug LP: #1339700
619 std::timed_mutex m;
620 std::atomic_bool failed(false);
621 int i = 0;
622 int const loops = 5;
623
624 auto alarm = ml.notify_in(std::chrono::milliseconds{0}, [&]()
625 {
626 // From this angle, ensure we can lock m (alarm should be unlocked)
627 failed = !m.try_lock_for(std::chrono::seconds{5});
628 ASSERT_FALSE(failed);
629 ++i;
630 m.unlock();
631 });
632
633 std::thread t([&]()
634 {
635 m.lock();
636 while (i < loops && !failed)
637 {
638 // From this angle, ensure we can lock alarm while holding m
639 (void)alarm->state();
640 m.unlock();
641 std::this_thread::yield();
642 m.lock();
643 }
644 m.unlock();
645 });
646
647 UnblockMainLoop unblocker(ml);
648 for (int j = 0; j < loops; ++j)
649 {
650 clock->advance_by(std::chrono::milliseconds{11}, ml);
651 alarm->reschedule_in(std::chrono::milliseconds{10});
652 }
653
654 t.join();
655}
656
657TEST_F(GLibMainLoopAlarmTest, alarm_fires_at_correct_time_point)
658{
659 mir::time::Timestamp real_soon = clock->sample() + std::chrono::milliseconds{120};
660
661 auto alarm = ml.notify_at(real_soon, []{});
662
663 UnblockMainLoop unblocker(ml);
664
665 clock->advance_by(std::chrono::milliseconds{119}, ml);
666 EXPECT_EQ(mir::time::Alarm::pending, alarm->state());
667
668 clock->advance_by(std::chrono::milliseconds{1}, ml);
669 EXPECT_EQ(mir::time::Alarm::triggered, alarm->state());
670}
671
672
673// More targeted regression test for LP: #1381925
674TEST_F(GLibMainLoopTest, stress_emits_alarm_notification_with_zero_timeout)
675{
676 using namespace ::testing;
677
678 UnblockMainLoop unblocker{ml};
679
680 for (int i = 0; i < 1000; ++i)
681 {
682 mt::Signal notification_called;
683
684 auto alarm = ml.notify_in(
685 std::chrono::milliseconds{0},
686 [&] { notification_called.raise(); });
687
688 EXPECT_TRUE(notification_called.wait_for(std::chrono::seconds{5}));
689 }
690}
691
692TEST_F(GLibMainLoopTest, dispatches_action)
693{
694 using namespace testing;
695
696 int num_actions{0};
697 int const owner{0};
698
699 ml.enqueue(
700 &owner,
701 [&]
702 {
703 ++num_actions;
704 ml.stop();
705 });
706
707 ml.run();
708
709 EXPECT_THAT(num_actions, Eq(1));
710}
711
712TEST_F(GLibMainLoopTest, dispatches_multiple_actions_in_order)
713{
714 using namespace testing;
715
716 int const num_actions{5};
717 std::vector<int> actions;
718 int const owner{0};
719
720 for (int i = 0; i < num_actions; ++i)
721 {
722 ml.enqueue(
723 &owner,
724 [&,i]
725 {
726 actions.push_back(i);
727 if (i == num_actions - 1)
728 ml.stop();
729 });
730 }
731
732 ml.run();
733
734 ASSERT_THAT(actions.size(), Eq(num_actions));
735 for (int i = 0; i < num_actions; ++i)
736 EXPECT_THAT(actions[i], Eq(i)) << "i = " << i;
737}
738
739TEST_F(GLibMainLoopTest, does_not_dispatch_paused_actions)
740{
741 using namespace testing;
742
743 std::vector<int> actions;
744 int const owner1{0};
745 int const owner2{0};
746
747 ml.enqueue(
748 &owner1,
749
750 [&]
751 {
752 int const id = 0;
753 actions.push_back(id);
754 });
755
756 ml.enqueue(
757 &owner2,
758 [&]
759 {
760 int const id = 1;
761 actions.push_back(id);
762 });
763
764 ml.enqueue(
765 &owner1,
766 [&]
767 {
768 int const id = 2;
769 actions.push_back(id);
770 });
771
772 ml.enqueue(
773 &owner2,
774 [&]
775 {
776 int const id = 3;
777 actions.push_back(id);
778 ml.stop();
779 });
780
781 ml.pause_processing_for(&owner1);
782
783 ml.run();
784
785 ASSERT_THAT(actions.size(), Eq(2));
786 EXPECT_THAT(actions[0], Eq(1));
787 EXPECT_THAT(actions[1], Eq(3));
788}
789
790TEST_F(GLibMainLoopTest, dispatches_actions_resumed_from_within_another_action)
791{
792 using namespace testing;
793
794 std::vector<int> actions;
795 void const* const owner1_ptr{&actions};
796 int const owner2{0};
797
798 ml.enqueue(
799 owner1_ptr,
800 [&]
801 {
802 int const id = 0;
803 actions.push_back(id);
804 ml.stop();
805 });
806
807 ml.enqueue(
808 &owner2,
809 [&]
810 {
811 int const id = 1;
812 actions.push_back(id);
813 ml.resume_processing_for(owner1_ptr);
814 });
815
816 ml.pause_processing_for(owner1_ptr);
817
818 ml.run();
819
820 ASSERT_THAT(actions.size(), Eq(2));
821 EXPECT_THAT(actions[0], Eq(1));
822 EXPECT_THAT(actions[1], Eq(0));
823}
824
825TEST_F(GLibMainLoopTest, handles_enqueue_from_within_action)
826{
827 using namespace testing;
828
829 std::vector<int> actions;
830 int const num_actions{10};
831 void const* const owner{&num_actions};
832
833 ml.enqueue(
834 owner,
835 [&]
836 {
837 int const id = 0;
838 actions.push_back(id);
839
840 for (int i = 1; i < num_actions; ++i)
841 {
842 ml.enqueue(
843 owner,
844 [&,i]
845 {
846 actions.push_back(i);
847 if (i == num_actions - 1)
848 ml.stop();
849 });
850 }
851 });
852
853 ml.run();
854
855 ASSERT_THAT(actions.size(), Eq(num_actions));
856 for (int i = 0; i < num_actions; ++i)
857 EXPECT_THAT(actions[i], Eq(i)) << "i = " << i;
858}
859
860TEST_F(GLibMainLoopTest, dispatches_actions_resumed_externally)
861{
862 using namespace testing;
863
864 std::vector<int> actions;
865 void const* const owner1_ptr{&actions};
866 int const owner2{0};
867
868 ml.enqueue(
869 owner1_ptr,
870 [&]
871 {
872 int const id = 0;
873 actions.push_back(id);
874 ml.stop();
875 });
876
877 ml.enqueue(
878 &owner2,
879 [&]
880 {
881 int const id = 1;
882 actions.push_back(id);
883 });
884
885 ml.pause_processing_for(owner1_ptr);
886
887 std::thread t{
888 [&]
889 {
890 std::this_thread::sleep_for(std::chrono::milliseconds{10});
891 ml.resume_processing_for(owner1_ptr);
892 }};
893
894 ml.run();
895
896 t.join();
897
898 ASSERT_THAT(actions.size(), Eq(2));
899 EXPECT_THAT(actions[0], Eq(1));
900 EXPECT_THAT(actions[1], Eq(0));
901}
0902
=== added file 'tests/unit-tests/test_thread_safe_list.cpp'
--- tests/unit-tests/test_thread_safe_list.cpp 1970-01-01 00:00:00 +0000
+++ tests/unit-tests/test_thread_safe_list.cpp 2014-10-31 11:59:50 +0000
@@ -0,0 +1,159 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
17 */
18
19#include "mir/thread_safe_list.h"
20#include "mir_test/wait_condition.h"
21
22#include <gtest/gtest.h>
23#include <gmock/gmock.h>
24
25namespace
26{
27
28struct Dummy {};
29using Element = std::shared_ptr<Dummy>;
30using SharedPtrList = mir::ThreadSafeList<Element>;
31
32struct ThreadSafeListTest : testing::Test
33{
34 SharedPtrList list;
35
36 Element const element1 = std::make_shared<Dummy>();
37 Element const element2 = std::make_shared<Dummy>();
38};
39
40}
41
42TEST_F(ThreadSafeListTest, can_remove_element_while_iterating_same_element)
43{
44 using namespace testing;
45
46 list.add(element1);
47
48 list.for_each(
49 [&] (Element const& element)
50 {
51 list.remove(element);
52 });
53
54 int elements_seen = 0;
55
56 list.for_each(
57 [&] (Element const&)
58 {
59 ++elements_seen;
60 });
61
62 EXPECT_THAT(elements_seen, Eq(0));
63}
64
65TEST_F(ThreadSafeListTest, can_remove_unused_element_while_iterating_different_element)
66{
67 using namespace testing;
68
69 list.add(element1);
70 list.add(element2);
71
72 int elements_seen = 0;
73
74 list.for_each(
75 [&] (Element const&)
76 {
77 list.remove(element2);
78 ++elements_seen;
79 });
80
81 EXPECT_THAT(elements_seen, Eq(1));
82}
83
84TEST_F(ThreadSafeListTest,
85 can_remove_unused_element_while_different_element_is_used_in_different_thread)
86{
87 using namespace testing;
88
89 list.add(element1);
90 list.add(element2);
91
92 mir::test::WaitCondition first_element_in_use;
93 mir::test::WaitCondition second_element_removed;
94
95 int elements_seen = 0;
96
97 std::thread t{
98 [&]
99 {
100 list.for_each(
101 [&] (Element const&)
102 {
103 first_element_in_use.wake_up_everyone();
104 second_element_removed.wait_for_at_most_seconds(3);
105 EXPECT_TRUE(second_element_removed.woken());
106 ++elements_seen;
107 });
108 }};
109
110 first_element_in_use.wait_for_at_most_seconds(3);
111 list.remove(element2);
112 second_element_removed.wake_up_everyone();
113
114 t.join();
115
116 EXPECT_THAT(elements_seen, Eq(1));
117}
118
119TEST_F(ThreadSafeListTest, removes_all_matching_elements)
120{
121 using namespace testing;
122
123 std::vector<Element> elements_seen;
124
125 list.add(element1);
126 list.add(element2);
127 list.add(element1);
128
129 list.remove_all(element1);
130
131 list.for_each(
132 [&] (Element const& element)
133 {
134 elements_seen.push_back(element);
135 });
136
137 EXPECT_THAT(elements_seen, ElementsAre(element2));
138}
139
140TEST_F(ThreadSafeListTest, clears_all_elements)
141{
142 using namespace testing;
143
144 int elements_seen = 0;
145
146 list.add(element1);
147 list.add(element2);
148 list.add(element1);
149
150 list.clear();
151
152 list.for_each(
153 [&] (Element const&)
154 {
155 ++elements_seen;
156 });
157
158 EXPECT_THAT(elements_seen, Eq(0));
159}

Subscribers

People subscribed via source and target branches