Mir

Merge lp:~afrantzis/mir/display-server-main-loop-1 into lp:~mir-team/mir/trunk

Proposed by Alexandros Frantzis
Status: Merged
Approved by: Alan Griffiths
Approved revision: no longer in the source branch.
Merged at revision: 576
Proposed branch: lp:~afrantzis/mir/display-server-main-loop-1
Merge into: lp:~mir-team/mir/trunk
Diff against target: 1089 lines (+487/-392)
18 files modified
include/server/mir/asio_main_loop.h (+53/-0)
include/server/mir/default_server_configuration.h (+3/-0)
include/server/mir/main_loop.h (+48/-0)
include/server/mir/server_configuration.h (+3/-0)
include/server/mir/signal_dispatcher.h (+0/-61)
src/server/CMakeLists.txt (+1/-1)
src/server/asio_main_loop.cpp (+96/-0)
src/server/default_server_configuration.cpp (+10/-0)
src/server/display_server.cpp (+5/-16)
src/server/run_mir.cpp (+14/-21)
src/server/signal_dispatcher.cpp (+0/-170)
tests/integration-tests/CMakeLists.txt (+1/-0)
tests/integration-tests/cucumber/test_session_management_context.cpp (+1/-0)
tests/integration-tests/process/CMakeLists.txt (+0/-1)
tests/integration-tests/process/test_signal_dispatcher.cpp (+0/-121)
tests/integration-tests/test_display_server_main_loop_events.cpp (+102/-0)
tests/unit-tests/CMakeLists.txt (+6/-1)
tests/unit-tests/test_asio_main_loop.cpp (+144/-0)
To merge this branch: bzr merge lp:~afrantzis/mir/display-server-main-loop-1
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Alan Griffiths Approve
Robert Ancell Pending
Review via email: mp+157693@code.launchpad.net

This proposal supersedes a proposal from 2013-04-05.

Commit message

server: Use a main loop to handle events in the display server

Description of the change

server: Use a main loop to handle events in the display server

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
Robert Ancell (robert-ancell) wrote : Posted in a previous version of this proposal

LGTM

review: Approve
Revision history for this message
Alan Griffiths (alan-griffiths) wrote : Posted in a previous version of this proposal

361 + auto asio_event = std::static_pointer_cast<AsioMainLoopEvent>(event);
...
370 + auto asio_event = std::static_pointer_cast<AsioMainLoopEvent>(event);

These casts look wrong - there should not be undefined behaviour if a conforming parameter is supplied.

I'm still trying to decide if the casts should be dynamic, if the function signature is wrong, or if some other way to recover type information is appropriate (e.g. double dispatch).

~~~~

982 +TEST(DisplayServerMainLoopEvents, display_server_shuts_down_properly_on_sigint)
983 +{
984 + ServerConfig server_config;
985 +
986 + mir::run_mir(server_config,
987 + [](mir::DisplayServer&)
988 + {
989 + kill(getpid(), SIGINT);
990 + });
991 +}

Does this really do what it claims? (In run_mir() init() is called before invoking run() - so it may not be the best way to test the required behaviour.)

~~~~

482 + std::shared_ptr<mir::MainLoopFactory> main_loop_factory;

Why does display server need a dependency on MainLoopFactory? AFAICS it just needs a MainLoop.

Can't MainLoopFactory be subsumed into DefaultServerConfiguration?

review: Needs Fixing
Revision history for this message
Alexandros Frantzis (afrantzis) wrote : Posted in a previous version of this proposal

> 361 + auto asio_event = std::static_pointer_cast<AsioMainLoopEvent>(event);
> ...
> 370 + auto asio_event = std::static_pointer_cast<AsioMainLoopEvent>(event);
>
> These casts look wrong - there should not be undefined behaviour if a conforming parameter is supplied.
>
> I'm still trying to decide if the casts should be dynamic, if the function signature is wrong, or if some other
> way to recover type information is appropriate (e.g. double dispatch).

The rationale for this is that there should be a single factory in use providing compatible events (i.e. that can be safely downcasted) , but this is indeed a fragile assumption. I am happy to change this to a dynamic_cast (or we can discuss more about the other options).

> Does this really do what it claims? (In run_mir() init() is called before invoking run() -
> so it may not be the best > way to test the required behaviour.

At the moment we don't have a reliable mechanism to detect when the server has actually started. However, we start queuing signal events from the moment we create the DisplayServer, not from the moment we start running it, so the code above will work fine. I considered adding a way to notify the caller that the display server has actually started, e.g., a display_server_started callback, but I didn't want to overload this MP.

> 482 + std::shared_ptr<mir::MainLoopFactory> main_loop_factory;
> Why does display server need a dependency on MainLoopFactory? AFAICS it just needs a MainLoop.
> Can't MainLoopFactory be subsumed into DefaultServerConfiguration?

The Display server needs to create a stop event, which needs a handle to a main loop factory.

Also, in the near future the Platform will need to be able to create a platform events (e.g. PauseResume), which again need the main loop factory. The main loop factory can be supplied either at Platform construction time, or at event creation time(platform->create_bla_event(factory)).

So one option is:

1. DisplayConfiguration::the_main_loop_factory()
2. DisplayServer uses the main loop factory to create the stop event
3. DisplayServer passes the main loop factory into platform->create_bla_event(factory) to create platform events

Alternatively the main loop factory can be subsumed into the configuration:

1. DisplayConfiguration::the_stop_event()
2. DisplayConfiguration::the_main_loop()
3. The factory is supplied at Platform creation time through mg::create_platform(MainLoopFactory) and events are created with a parameterless platform->create_bla_event()

I am fine with moving to the second option.

Revision history for this message
Alan Griffiths (alan-griffiths) wrote : Posted in a previous version of this proposal
Download full text (3.2 KiB)

> > 361 + auto asio_event = std::static_pointer_cast<AsioMainLoopEvent>(event);
> > ...
> > 370 + auto asio_event = std::static_pointer_cast<AsioMainLoopEvent>(event);
> >
> > These casts look wrong - there should not be undefined behaviour if a
> conforming parameter is supplied.
> >
> > I'm still trying to decide if the casts should be dynamic, if the function
> signature is wrong, or if some other
> > way to recover type information is appropriate (e.g. double dispatch).
>
> The rationale for this is that there should be a single factory in use
> providing compatible events (i.e. that can be safely downcasted) , but this is
> indeed a fragile assumption. I am happy to change this to a dynamic_cast (or
> we can discuss more about the other options).

I now think this relates to my concerns about roles. (see below)

> > Does this really do what it claims? (In run_mir() init() is called before
> invoking run() -
> > so it may not be the best > way to test the required behaviour.
>
> At the moment we don't have a reliable mechanism to detect when the server has
> actually started. However, we start queuing signal events from the moment we
> create the DisplayServer, not from the moment we start running it, so the code
> above will work fine. I considered adding a way to notify the caller that the
> display server has actually started, e.g., a display_server_started callback,
> but I didn't want to overload this MP.

OK

> > 482 + std::shared_ptr<mir::MainLoopFactory> main_loop_factory;
> > Why does display server need a dependency on MainLoopFactory? AFAICS it just
> needs a MainLoop.
> > Can't MainLoopFactory be subsumed into DefaultServerConfiguration?
>
> The Display server needs to create a stop event, which needs a handle to a
> main loop factory.

Why is that the role of the DisplayServer? AFAICS it has no need to know anything about StopEvent.

The equivalent registration was in run_mir() which seems a better place to me.

> Also, in the near future the Platform will need to be able to create a
> platform events (e.g. PauseResume), which again need the main loop factory.
> The main loop factory can be supplied either at Platform construction time, or
> at event creation time(platform->create_bla_event(factory)).
>
> So one option is:
>
> 1. DisplayConfiguration::the_main_loop_factory()
> 2. DisplayServer uses the main loop factory to create the stop event
> 3. DisplayServer passes the main loop factory into
> platform->create_bla_event(factory) to create platform events

If it is more often for creating events I don't think that MainLoopFactory is the best name.

But do the event objects even need to be visible outside the implementation? Can't the whole interface be:

void MainLoop::register_signal_handler(std::initializer_list<int> signals, std::function<void(int)> const& handler);

> Alternatively the main loop factory can be subsumed into the configuration:
>
> 1. DisplayConfiguration::the_stop_event()
> 2. DisplayConfiguration::the_main_loop()
> 3. The factory is supplied at Platform creation time through
> mg::create_platform(MainLoopFactory) and events are created with a
> parameterless platform->create_bla_event()
>
> I a...

Read more...

Revision history for this message
Alexandros Frantzis (afrantzis) wrote : Posted in a previous version of this proposal

> Why is that the role of the DisplayServer? AFAICS it has no need to know
> anything about StopEvent.
>
> The equivalent registration was in run_mir() which seems a better place to me.

> But do the event objects even need to be visible outside the implementation?
> Can't the whole interface be:
>
> void MainLoop::register_signal_handler(std::initializer_list<int> signals,
> std::function<void(int)> const& handler);

Joining pieces from the discussion so far, the proposal becomes (leaving out details
not related to this MP):

========================
class MainLoop
{
    void register_signal_handler(std::initializer_list<int> signals,
                                 std::function<void(int)> const& handler) = 0;

    /* In the future */
    void register_fd_handler(...) = 0;
};

std::shared_ptr<MainLoop> DisplayConfiguration::the_main_loop();

mir::run_mir(DisplayConfiguration& conf)
{
    DisplayServer server{conf}
    conf.the_main_loop()->register_signal_handler({SIGINT, SIGTERM}, [&server](int){server.stop()});
    ....
}

... somewhere in display_server.cpp:

// main_loop = conf.the_main_loop();
platform->register_pause_resume_handler(main_loop, handler);
=================================

I like this.

Revision history for this message
Alan Griffiths (alan-griffiths) wrote : Posted in a previous version of this proposal

> > Why is that the role of the DisplayServer? AFAICS it has no need to know
> > anything about StopEvent.
> >
> > The equivalent registration was in run_mir() which seems a better place to
> me.
>
> > But do the event objects even need to be visible outside the implementation?
> > Can't the whole interface be:
> >
> > void MainLoop::register_signal_handler(std::initializer_list<int> signals,
> > std::function<void(int)> const& handler);
>
> Joining pieces from the discussion so far, the proposal becomes (leaving out
> details
> not related to this MP):
>
> ========================
> class MainLoop
> {
> void register_signal_handler(std::initializer_list<int> signals,
> std::function<void(int)> const& handler) = 0;
>
> /* In the future */
> void register_fd_handler(...) = 0;
> };
>
> std::shared_ptr<MainLoop> DisplayConfiguration::the_main_loop();
>
> mir::run_mir(DisplayConfiguration& conf)
> {
> DisplayServer server{conf}
> conf.the_main_loop()->register_signal_handler({SIGINT, SIGTERM},
> [&server](int){server.stop()});
> ....
> }
>
> ... somewhere in display_server.cpp:
>
> // main_loop = conf.the_main_loop();
> platform->register_pause_resume_handler(main_loop, handler);
> =================================
>
> I like this.

+1

BTW Is it worth also providing a forwarding wrapper for register_signal_handler() that gets rid of the explict creation of an initializer list in client code:

template<typename Signals...>
void register_signal_handler(std::function<void(int)> const& handler, Signals... signals)

Revision history for this message
Alexandros Frantzis (afrantzis) wrote :

> BTW Is it worth also providing a forwarding wrapper for register_signal_handler() that gets rid of the explict
> creation of an initializer list in client code:

> template<typename Signals...>
> void register_signal_handler(std::function<void(int)> const& handler, Signals... signals)

I left this for a future MP. I guess that in that case it would also make sense to reverse the arguments of the non-templated function for uniformity:

register_signal_handler(std::function<void(int)> const& handler, std::initializer_list<int> signals)

Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

Much clearer. A couple of nits:

114 + virtual ~MainLoop() {}

Are we really expecting destructors to throw?

~~~~

456 +#include <iostream>

Should be unnecessary

review: Needs Fixing
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Alexandros Frantzis (afrantzis) wrote :

> 114 + virtual ~MainLoop() {}
> Are we really expecting destructors to throw?

Fixed.

> 456 +#include <iostream>
> Should be unnecessary

Already fixed in 573, probably the diff didn't refresh quickly enough.

Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

LGTM

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'include/server/mir/asio_main_loop.h'
2--- include/server/mir/asio_main_loop.h 1970-01-01 00:00:00 +0000
3+++ include/server/mir/asio_main_loop.h 2013-04-08 16:58:20 +0000
4@@ -0,0 +1,53 @@
5+/*
6+ * Copyright © 2013 Canonical Ltd.
7+ *
8+ * This program is free software: you can redistribute it and/or modify it
9+ * under the terms of the GNU Lesser General Public License version 3,
10+ * as published by the Free Software Foundation.
11+ *
12+ * This program is distributed in the hope that it will be useful,
13+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+ * GNU General Public License for more details.
16+ *
17+ * You should have received a copy of the GNU Lesser General Public License
18+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
19+ *
20+ * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
21+ */
22+
23+#ifndef MIR_ASIO_MAIN_LOOP_H_
24+#define MIR_ASIO_MAIN_LOOP_H_
25+
26+#include "mir/main_loop.h"
27+
28+#include <boost/asio.hpp>
29+#include <memory>
30+#include <vector>
31+
32+namespace mir
33+{
34+
35+class AsioMainLoop : public MainLoop
36+{
37+public:
38+ AsioMainLoop();
39+ ~AsioMainLoop() noexcept(true);
40+
41+ void run();
42+ void stop();
43+
44+ void register_signal_handler(
45+ std::initializer_list<int> signals,
46+ std::function<void(int)> const& handler);
47+
48+private:
49+ class SignalHandler;
50+
51+ boost::asio::io_service io;
52+ std::vector<std::unique_ptr<SignalHandler>> signal_handlers;
53+};
54+
55+}
56+
57+#endif /* MIR_ASIO_MAIN_LOOP_H */
58
59=== modified file 'include/server/mir/default_server_configuration.h'
60--- include/server/mir/default_server_configuration.h 2013-03-27 15:12:09 +0000
61+++ include/server/mir/default_server_configuration.h 2013-04-08 16:58:20 +0000
62@@ -123,6 +123,8 @@
63 virtual std::shared_ptr<shell::SurfaceBuilder> the_surface_builder();
64 virtual std::shared_ptr<time::TimeSource> the_time_source();
65
66+ virtual std::shared_ptr<MainLoop> the_main_loop();
67+
68 protected:
69 virtual std::shared_ptr<options::Option> the_options() const;
70 virtual std::shared_ptr<input::InputChannelFactory> the_input_channel_factory();
71@@ -149,6 +151,7 @@
72 CachedPtr<graphics::DisplayReport> display_report;
73 CachedPtr<surfaces::SurfaceController> surface_controller;
74 CachedPtr<time::TimeSource> time_source;
75+ CachedPtr<MainLoop> main_loop;
76
77 private:
78 std::shared_ptr<options::Option> options;
79
80=== added file 'include/server/mir/main_loop.h'
81--- include/server/mir/main_loop.h 1970-01-01 00:00:00 +0000
82+++ include/server/mir/main_loop.h 2013-04-08 16:58:20 +0000
83@@ -0,0 +1,48 @@
84+/*
85+ * Copyright © 2013 Canonical Ltd.
86+ *
87+ * This program is free software: you can redistribute it and/or modify it
88+ * under the terms of the GNU Lesser General Public License version 3,
89+ * as published by the Free Software Foundation.
90+ *
91+ * This program is distributed in the hope that it will be useful,
92+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
93+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
94+ * GNU General Public License for more details.
95+ *
96+ * You should have received a copy of the GNU Lesser General Public License
97+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
98+ *
99+ * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
100+ */
101+
102+#ifndef MIR_MAIN_LOOP_H_
103+#define MIR_MAIN_LOOP_H_
104+
105+#include <functional>
106+#include <initializer_list>
107+
108+namespace mir
109+{
110+
111+class MainLoop
112+{
113+public:
114+ virtual ~MainLoop() = default;
115+
116+ virtual void run() = 0;
117+ virtual void stop() = 0;
118+
119+ virtual void register_signal_handler(
120+ std::initializer_list<int> signals,
121+ std::function<void(int)> const& handler) = 0;
122+
123+protected:
124+ MainLoop() = default;
125+ MainLoop(MainLoop const&) = delete;
126+ MainLoop& operator=(MainLoop const&) = delete;
127+};
128+
129+}
130+
131+#endif /* MIR_MAIN_LOOP_H_ */
132
133=== modified file 'include/server/mir/server_configuration.h'
134--- include/server/mir/server_configuration.h 2013-03-22 17:13:10 +0000
135+++ include/server/mir/server_configuration.h 2013-04-08 16:58:20 +0000
136@@ -41,6 +41,8 @@
137 class EventFilter;
138 }
139
140+class MainLoop;
141+
142 class ServerConfiguration
143 {
144 public:
145@@ -49,6 +51,7 @@
146 virtual std::shared_ptr<graphics::Display> the_display() = 0;
147 virtual std::shared_ptr<compositor::Compositor> the_compositor() = 0;
148 virtual std::shared_ptr<input::InputManager> the_input_manager() = 0;
149+ virtual std::shared_ptr<MainLoop> the_main_loop() = 0;
150
151 protected:
152 ServerConfiguration() = default;
153
154=== removed file 'include/server/mir/signal_dispatcher.h'
155--- include/server/mir/signal_dispatcher.h 2013-03-28 12:13:18 +0000
156+++ include/server/mir/signal_dispatcher.h 1970-01-01 00:00:00 +0000
157@@ -1,61 +0,0 @@
158-/*
159- * Copyright © 2012 Canonical Ltd.
160- *
161- * This program is free software: you can redistribute it and/or modify it
162- * under the terms of the GNU Lesser General Public License version 3,
163- * as published by the Free Software Foundation.
164- *
165- * This program is distributed in the hope that it will be useful,
166- * but WITHOUT ANY WARRANTY; without even the implied warranty of
167- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
168- * GNU General Public License for more details.
169- *
170- * You should have received a copy of the GNU Lesser General Public License
171- * along with this program. If not, see <http://www.gnu.org/licenses/>.
172- *
173- * Authored by: Thomas Voss <thomas.voss@canonical.com>
174- */
175-
176-#ifndef MIR_SIGNAL_DISPATCHER_H_
177-#define MIR_SIGNAL_DISPATCHER_H_
178-
179-#include <boost/signals2.hpp>
180-
181-#include <memory>
182-
183-namespace mir
184-{
185-
186-// Singleton class that decouples
187-// signal handling from reacting to
188-// signals to ensure that the actual signal
189-// handler only calls async signal-safe functions
190-// according to man 7 signal.
191-class SignalDispatcher
192-{
193-public:
194- struct Constructor;
195-
196- typedef boost::signals2::signal<void(int)> SignalType;
197-
198- static std::shared_ptr<SignalDispatcher> instance();
199-
200- ~SignalDispatcher();
201-
202- SignalType& signal_channel();
203-
204- void enable_for(int signal);
205-
206-private:
207- friend struct Constructor;
208-
209- SignalDispatcher();
210- SignalDispatcher(const SignalDispatcher&) = delete;
211- SignalDispatcher& operator=(const SignalDispatcher&) = delete;
212-
213- struct Private;
214- std::unique_ptr<Private> p;
215-};
216-}
217-
218-#endif // MIR_SIGNAL_DISPATCHER_H_
219
220=== modified file 'src/server/CMakeLists.txt'
221--- src/server/CMakeLists.txt 2013-04-08 02:55:16 +0000
222+++ src/server/CMakeLists.txt 2013-04-08 16:58:20 +0000
223@@ -25,7 +25,7 @@
224 run_mir.cpp
225 display_server.cpp
226 default_server_configuration.cpp
227- signal_dispatcher.cpp
228+ asio_main_loop.cpp
229 )
230
231 set(MIRSERVER_LINKAGE SHARED)
232
233=== added file 'src/server/asio_main_loop.cpp'
234--- src/server/asio_main_loop.cpp 1970-01-01 00:00:00 +0000
235+++ src/server/asio_main_loop.cpp 2013-04-08 16:58:20 +0000
236@@ -0,0 +1,96 @@
237+/*
238+ * Copyright © 2013 Canonical Ltd.
239+ *
240+ * This program is free software: you can redistribute it and/or modify it
241+ * under the terms of the GNU Lesser General Public License version 3,
242+ * as published by the Free Software Foundation.
243+ *
244+ * This program is distributed in the hope that it will be useful,
245+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
246+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
247+ * GNU General Public License for more details.
248+ *
249+ * You should have received a copy of the GNU Lesser General Public License
250+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
251+ *
252+ * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
253+ */
254+
255+#include "mir/asio_main_loop.h"
256+
257+#include <cassert>
258+
259+class mir::AsioMainLoop::SignalHandler
260+{
261+public:
262+ SignalHandler(boost::asio::io_service& io,
263+ std::initializer_list<int> signals,
264+ std::function<void(int)> const& handler)
265+ : signal_set{io},
266+ handler{handler}
267+ {
268+ for (auto sig : signals)
269+ signal_set.add(sig);
270+ }
271+
272+ void async_wait()
273+ {
274+ signal_set.async_wait(
275+ std::bind(&SignalHandler::handle, this,
276+ std::placeholders::_1, std::placeholders::_2));
277+ }
278+
279+private:
280+ void handle(boost::system::error_code err, int sig)
281+ {
282+ if (!err)
283+ {
284+ handler(sig);
285+ signal_set.async_wait(
286+ std::bind(&SignalHandler::handle, this,
287+ std::placeholders::_1, std::placeholders::_2));
288+ }
289+ }
290+
291+ boost::asio::signal_set signal_set;
292+ std::function<void(int)> handler;
293+};
294+
295+/*
296+ * We need to define an empty constructor and destructor in the .cpp file,
297+ * so that we can use unique_ptr to hold SignalHandler. Otherwise, users
298+ * of AsioMainLoop end up creating default constructors and destructors
299+ * that don't have complete type information for SignalHandler and fail
300+ * to compile.
301+ */
302+mir::AsioMainLoop::AsioMainLoop()
303+{
304+}
305+
306+mir::AsioMainLoop::~AsioMainLoop() noexcept(true)
307+{
308+}
309+
310+void mir::AsioMainLoop::run()
311+{
312+ io.run();
313+}
314+
315+void mir::AsioMainLoop::stop()
316+{
317+ io.stop();
318+}
319+
320+void mir::AsioMainLoop::register_signal_handler(
321+ std::initializer_list<int> signals,
322+ std::function<void(int)> const& handler)
323+{
324+ assert(handler);
325+
326+ auto sig_handler = std::unique_ptr<SignalHandler>{
327+ new SignalHandler{io, signals, handler}};
328+
329+ sig_handler->async_wait();
330+
331+ signal_handlers.push_back(std::move(sig_handler));
332+}
333
334=== modified file 'src/server/default_server_configuration.cpp'
335--- src/server/default_server_configuration.cpp 2013-04-02 20:56:09 +0000
336+++ src/server/default_server_configuration.cpp 2013-04-08 16:58:20 +0000
337@@ -18,6 +18,7 @@
338
339 #include "mir/default_server_configuration.h"
340 #include "mir/abnormal_exit.h"
341+#include "mir/asio_main_loop.h"
342
343 #include "mir/options/program_option.h"
344 #include "mir/compositor/buffer_allocation_strategy.h"
345@@ -515,3 +516,12 @@
346 return std::make_shared<mir::time::HighResolutionClock>();
347 });
348 }
349+
350+std::shared_ptr<mir::MainLoop> mir::DefaultServerConfiguration::the_main_loop()
351+{
352+ return main_loop(
353+ []()
354+ {
355+ return std::make_shared<mir::AsioMainLoop>();
356+ });
357+}
358
359=== modified file 'src/server/display_server.cpp'
360--- src/server/display_server.cpp 2013-03-21 03:32:59 +0000
361+++ src/server/display_server.cpp 2013-04-08 16:58:20 +0000
362@@ -20,6 +20,7 @@
363
364 #include "mir/display_server.h"
365 #include "mir/server_configuration.h"
366+#include "mir/main_loop.h"
367
368 #include "mir/compositor/compositor.h"
369 #include "mir/frontend/shell.h"
370@@ -27,11 +28,6 @@
371 #include "mir/graphics/display.h"
372 #include "mir/input/input_manager.h"
373
374-#include <mutex>
375-#include <thread>
376-#include <mutex>
377-#include <condition_variable>
378-
379 namespace mc = mir::compositor;
380 namespace mf = mir::frontend;
381 namespace mg = mir::graphics;
382@@ -45,7 +41,7 @@
383 shell{config.the_frontend_shell()},
384 communicator{config.the_communicator()},
385 input_manager{config.the_input_manager()},
386- exit(false)
387+ main_loop{config.the_main_loop()}
388 {
389 }
390
391@@ -54,9 +50,7 @@
392 std::shared_ptr<frontend::Shell> shell;
393 std::shared_ptr<mf::Communicator> communicator;
394 std::shared_ptr<mi::InputManager> input_manager;
395- std::mutex exit_guard;
396- std::condition_variable exit_cv;
397- bool exit;
398+ std::shared_ptr<mir::MainLoop> main_loop;
399 };
400
401 mir::DisplayServer::DisplayServer(ServerConfiguration& config) :
402@@ -72,14 +66,11 @@
403
404 void mir::DisplayServer::run()
405 {
406- std::unique_lock<std::mutex> lk(p->exit_guard);
407-
408 p->communicator->start();
409 p->compositor->start();
410 p->input_manager->start();
411
412- while (!p->exit)
413- p->exit_cv.wait(lk);
414+ p->main_loop->run();
415
416 p->input_manager->stop();
417 p->compositor->stop();
418@@ -87,7 +78,5 @@
419
420 void mir::DisplayServer::stop()
421 {
422- std::unique_lock<std::mutex> lk(p->exit_guard);
423- p->exit = true;
424- p->exit_cv.notify_one();
425+ p->main_loop->stop();
426 }
427
428=== modified file 'src/server/run_mir.cpp'
429--- src/server/run_mir.cpp 2013-03-28 12:52:24 +0000
430+++ src/server/run_mir.cpp 2013-04-08 16:58:20 +0000
431@@ -18,34 +18,27 @@
432
433 #include "mir/run_mir.h"
434 #include "mir/display_server.h"
435-#include "mir/signal_dispatcher.h"
436+#include "mir/main_loop.h"
437+#include "mir/server_configuration.h"
438
439-#include <atomic>
440-#include <thread>
441 #include <csignal>
442-
443-namespace
444-{
445-std::atomic<mir::DisplayServer*> signal_display_server;
446-
447-void signal_terminate(int)
448-{
449- while (!signal_display_server.load())
450- std::this_thread::yield();
451-
452- signal_display_server.load()->stop();
453-}
454-}
455+#include <cassert>
456
457 void mir::run_mir(ServerConfiguration& config, std::function<void(DisplayServer&)> init)
458 {
459- auto const sdp = SignalDispatcher::instance();
460- sdp->enable_for(SIGINT);
461- sdp->enable_for(SIGTERM);
462- sdp->signal_channel().connect(&signal_terminate);
463+ DisplayServer* server_ptr{nullptr};
464+ auto main_loop = config.the_main_loop();
465+
466+ main_loop->register_signal_handler(
467+ {SIGINT, SIGTERM},
468+ [&server_ptr](int)
469+ {
470+ assert(server_ptr);
471+ server_ptr->stop();
472+ });
473
474 DisplayServer server(config);
475- signal_display_server.store(&server);
476+ server_ptr = &server;
477
478 init(server);
479 server.run();
480
481=== removed file 'src/server/signal_dispatcher.cpp'
482--- src/server/signal_dispatcher.cpp 2013-04-02 15:43:52 +0000
483+++ src/server/signal_dispatcher.cpp 1970-01-01 00:00:00 +0000
484@@ -1,170 +0,0 @@
485-/*
486- * Copyright © 2012 Canonical Ltd.
487- *
488- * This program is free software: you can redistribute it and/or modify
489- * it under the terms of the GNU General Public License version 3 as
490- * published by the Free Software Foundation.
491- *
492- * This program is distributed in the hope that it will be useful,
493- * but WITHOUT ANY WARRANTY; without even the implied warranty of
494- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
495- * GNU General Public License for more details.
496- *
497- * You should have received a copy of the GNU General Public License
498- * along with this program. If not, see <http://www.gnu.org/licenses/>.
499- *
500- * Authored by: Thomas Voss <thomas.voss@canonical.com>
501- */
502-
503-#include "mir/signal_dispatcher.h"
504-
505-#include <mutex>
506-#include <thread>
507-#include <signal.h>
508-#include <sys/types.h>
509-#include <sys/socket.h>
510-
511-
512-namespace
513-{
514-
515-struct EventSocketPair
516-{
517- typedef unsigned int SocketIdentifier;
518-
519- static const SocketIdentifier socket_a = 0;
520- static const SocketIdentifier socket_b = 1;
521-
522- EventSocketPair()
523- {
524- if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) < 0)
525- throw std::runtime_error("Couldn't create socketpair");
526- }
527-
528- ~EventSocketPair()
529- {
530- ::close(sockets[0]);
531- ::close(sockets[1]);
532- }
533-
534- void write_signal_to(SocketIdentifier id, int signal)
535- {
536- if (::write(sockets[id], &signal, sizeof(signal)) < 0)
537- {
538- // TODO: We cannot throw an exception in code that is
539- // called from a signal handler. Reenable once we have
540- // signal-safe logging in place.
541- // throw std::runtime_error("Problem writing to socket");
542- }
543- }
544-
545- int read_signal_from(SocketIdentifier id)
546- {
547- int signal{0};
548- ssize_t nread{0};
549-
550- while (nread <= 0)
551- {
552- nread = ::read(sockets[id], &signal, sizeof(signal));
553-
554- if (nread == 0)
555- throw std::runtime_error("Write end of socket has been closed");
556- else if (nread > 0 && nread < static_cast<ssize_t>(sizeof(signal)))
557- throw std::runtime_error("Incomplete message retrieved");
558- else if (nread < 0 && errno != EINTR)
559- throw std::runtime_error("Problem reading from socket");
560- }
561-
562- return signal;
563- }
564-
565- int sockets[2];
566-} event_socket_pair;
567-
568-}
569-
570-struct mir::SignalDispatcher::Constructor
571-{
572- static SignalDispatcher* construct()
573- {
574- return new SignalDispatcher();
575- }
576-};
577-
578-namespace
579-{
580-std::shared_ptr<mir::SignalDispatcher> instance;
581-std::once_flag init_flag;
582-
583-void init()
584-{
585- instance.reset(mir::SignalDispatcher::Constructor::construct());
586-}
587-
588-void signal_handler(int signal)
589-{
590- event_socket_pair.write_signal_to(
591- EventSocketPair::socket_a,
592- signal);
593-}
594-
595-}
596-
597-struct mir::SignalDispatcher::Private
598-{
599- Private() : worker_thread(std::bind(&Private::worker, this))
600- {
601- worker_thread.detach();
602- }
603-
604- void worker()
605- {
606- while(true)
607- {
608- // Todo: Use polling here.
609- try
610- {
611- int s = event_socket_pair.read_signal_from(
612- EventSocketPair::socket_b);
613- signal_channel(s);
614- } catch(...)
615- {
616- break;
617- }
618- }
619- }
620-
621- std::thread worker_thread;
622- mir::SignalDispatcher::SignalType signal_channel;
623-};
624-
625-std::shared_ptr<mir::SignalDispatcher> mir::SignalDispatcher::instance()
626-{
627- std::call_once(::init_flag, ::init);
628-
629- return ::instance;
630-}
631-
632-mir::SignalDispatcher::SignalDispatcher() : p(new Private())
633-{
634-}
635-
636-mir::SignalDispatcher::~SignalDispatcher()
637-{
638-}
639-
640-mir::SignalDispatcher::SignalType& mir::SignalDispatcher::signal_channel()
641-{
642- return p->signal_channel;
643-}
644-
645-void mir::SignalDispatcher::enable_for(int signal)
646-{
647- struct sigaction action;
648- ::memset(&action, 0, sizeof(action));
649- ::sigemptyset(&action.sa_mask);
650- ::sigaddset(&action.sa_mask, signal);
651- action.sa_handler = ::signal_handler;
652- action.sa_flags = SA_RESTART | SA_NODEFER;
653- ::sigaction(signal, &action, NULL);
654-}
655
656=== modified file 'tests/integration-tests/CMakeLists.txt'
657--- tests/integration-tests/CMakeLists.txt 2013-04-02 21:01:03 +0000
658+++ tests/integration-tests/CMakeLists.txt 2013-04-08 16:58:20 +0000
659@@ -5,6 +5,7 @@
660 test_surfaceloop.cpp
661 test_error_reporting.cpp
662 test_display_info.cpp
663+ test_display_server_main_loop_events.cpp
664 )
665
666 add_subdirectory(client/)
667
668=== modified file 'tests/integration-tests/cucumber/test_session_management_context.cpp'
669--- tests/integration-tests/cucumber/test_session_management_context.cpp 2013-03-29 16:51:35 +0000
670+++ tests/integration-tests/cucumber/test_session_management_context.cpp 2013-04-08 16:58:20 +0000
671@@ -52,6 +52,7 @@
672 MOCK_METHOD0(the_input_manager, std::shared_ptr<mi::InputManager>());
673 MOCK_METHOD0(the_display, std::shared_ptr<mg::Display>());
674 MOCK_METHOD0(the_compositor, std::shared_ptr<mc::Compositor>());
675+ MOCK_METHOD0(the_main_loop, std::shared_ptr<mir::MainLoop>());
676 };
677
678 MATCHER_P(NamedWindowWithNoGeometry, name, "")
679
680=== modified file 'tests/integration-tests/process/CMakeLists.txt'
681--- tests/integration-tests/process/CMakeLists.txt 2013-03-13 04:54:15 +0000
682+++ tests/integration-tests/process/CMakeLists.txt 2013-04-08 16:58:20 +0000
683@@ -1,6 +1,5 @@
684 list(APPEND INTEGRATION_TESTS_SRCS
685 ${CMAKE_CURRENT_SOURCE_DIR}/test_process.cpp
686- ${CMAKE_CURRENT_SOURCE_DIR}/test_signal_dispatcher.cpp
687 )
688
689 set(
690
691=== removed file 'tests/integration-tests/process/test_signal_dispatcher.cpp'
692--- tests/integration-tests/process/test_signal_dispatcher.cpp 2013-03-28 12:13:18 +0000
693+++ tests/integration-tests/process/test_signal_dispatcher.cpp 1970-01-01 00:00:00 +0000
694@@ -1,121 +0,0 @@
695-/*
696- * Copyright © 2012 Canonical Ltd.
697- *
698- * This program is free software: you can redistribute it and/or modify
699- * it under the terms of the GNU General Public License version 3 as
700- * published by the Free Software Foundation.
701- *
702- * This program is distributed in the hope that it will be useful,
703- * but WITHOUT ANY WARRANTY; without even the implied warranty of
704- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
705- * GNU General Public License for more details.
706- *
707- * You should have received a copy of the GNU General Public License
708- * along with this program. If not, see <http://www.gnu.org/licenses/>.
709- *
710- * Authored by: Thomas Voss <thomas.voss@canonical.com>
711- */
712-
713-#include "mir/signal_dispatcher.h"
714-#include "mir_test_framework/process.h"
715-
716-#include <chrono>
717-#include <thread>
718-#include <mutex>
719-#include <condition_variable>
720-#include <gtest/gtest.h>
721-
722-namespace mtf = mir_test_framework;
723-
724-struct SignalCollector
725-{
726- SignalCollector() : signal(-1)
727- {
728- }
729-
730- void operator()(int s)
731- {
732- std::unique_lock<std::mutex> lock(mutex);
733- signal = s;
734- cv.notify_all();
735- }
736-
737- int load()
738- {
739- unsigned int counter = 0;
740- std::unique_lock<std::mutex> lock(mutex);
741- while (signal == -1 && counter < 100)
742- {
743- cv.wait_for(lock, std::chrono::milliseconds(10));
744- counter++;
745- }
746- return signal;
747- }
748-
749- int signal;
750-
751- std::mutex mutex;
752- std::condition_variable cv;
753-
754- SignalCollector(SignalCollector const&) = delete;
755-};
756-
757-void a_main_function_accessing_the_signal_dispatcher()
758-{
759- // Ensure that the SignalDispatcher has been created.
760- mir::SignalDispatcher::instance();
761- // Don't return (and exit) before the parent process has a chance to signal
762- std::this_thread::sleep_for(std::chrono::milliseconds(10));
763-}
764-
765-template<int signal>
766-void a_main_function_collecting_received_signals()
767-{
768- mir::SignalDispatcher::instance()->enable_for(signal);
769- SignalCollector sc;
770- boost::signals2::scoped_connection conn(
771- mir::SignalDispatcher::instance()->signal_channel().connect(boost::ref(sc)));
772-
773- ::kill(getpid(), signal);
774-
775- EXPECT_EQ(signal, sc.load());
776-}
777-
778-int a_successful_exit_function()
779-{
780- return EXIT_SUCCESS;
781-}
782-
783-int a_gtest_exit_function()
784-{
785- return ::testing::Test::HasFailure() ? EXIT_FAILURE : EXIT_SUCCESS;
786-}
787-
788-
789-TEST(SignalDispatcher,
790- DISABLED_a_default_dispatcher_does_not_catch_any_signals)
791-{
792- auto p = mtf::fork_and_run_in_a_different_process(
793- a_main_function_accessing_the_signal_dispatcher,
794- a_successful_exit_function);
795-
796- p->terminate();
797-
798- EXPECT_TRUE(p->wait_for_termination().signalled());
799-}
800-
801-TEST(SignalDispatcher,
802- DISABLED_enabling_a_signal_results_in_signal_channel_delivering_the_signal)
803-{
804- auto p = mtf::fork_and_run_in_a_different_process(
805- a_main_function_collecting_received_signals<SIGTERM>,
806- a_successful_exit_function);
807-
808- EXPECT_TRUE(p->wait_for_termination().succeeded());
809-
810- p = mtf::fork_and_run_in_a_different_process(
811- a_main_function_collecting_received_signals<SIGINT>,
812- a_successful_exit_function);
813-
814- EXPECT_TRUE(p->wait_for_termination().succeeded());
815-}
816
817=== added file 'tests/integration-tests/test_display_server_main_loop_events.cpp'
818--- tests/integration-tests/test_display_server_main_loop_events.cpp 1970-01-01 00:00:00 +0000
819+++ tests/integration-tests/test_display_server_main_loop_events.cpp 2013-04-08 16:58:20 +0000
820@@ -0,0 +1,102 @@
821+/*
822+ * Copyright © 2013 Canonical Ltd.
823+ *
824+ * This program is free software: you can redistribute it and/or modify
825+ * it under the terms of the GNU General Public License version 3 as
826+ * published by the Free Software Foundation.
827+ *
828+ * This program is distributed in the hope that it will be useful,
829+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
830+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
831+ * GNU General Public License for more details.
832+ *
833+ * You should have received a copy of the GNU General Public License
834+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
835+ *
836+ * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
837+ */
838+
839+#include "mir/compositor/compositor.h"
840+
841+#include "mir_test_framework/testing_server_configuration.h"
842+#include "mir_test_doubles/mock_input_manager.h"
843+#include "mir/run_mir.h"
844+
845+#include <gtest/gtest.h>
846+#include <gmock/gmock.h>
847+
848+#include <sys/types.h>
849+#include <unistd.h>
850+
851+namespace mi = mir::input;
852+namespace mc = mir::compositor;
853+namespace mtd = mir::test::doubles;
854+namespace mtf = mir_test_framework;
855+
856+namespace
857+{
858+
859+class MockCompositor : public mc::Compositor
860+{
861+public:
862+ MOCK_METHOD0(start, void());
863+ MOCK_METHOD0(stop, void());
864+};
865+
866+class ServerConfig : public mtf::TestingServerConfiguration
867+{
868+public:
869+ std::shared_ptr<mi::InputManager> the_input_manager() override
870+ {
871+ if (!mock_input_manager)
872+ {
873+ mock_input_manager = std::make_shared<mtd::MockInputManager>();
874+
875+ EXPECT_CALL(*mock_input_manager, start()).Times(1);
876+ EXPECT_CALL(*mock_input_manager, stop()).Times(1);
877+ }
878+
879+ return mock_input_manager;
880+ }
881+
882+ std::shared_ptr<mc::Compositor> the_compositor() override
883+ {
884+ if (!mock_compositor)
885+ {
886+ mock_compositor = std::make_shared<MockCompositor>();
887+
888+ EXPECT_CALL(*mock_compositor, start()).Times(1);
889+ EXPECT_CALL(*mock_compositor, stop()).Times(1);
890+ }
891+
892+ return mock_compositor;
893+ }
894+
895+private:
896+ std::shared_ptr<mtd::MockInputManager> mock_input_manager;
897+ std::shared_ptr<MockCompositor> mock_compositor;
898+};
899+
900+}
901+
902+TEST(DisplayServerMainLoopEvents, display_server_shuts_down_properly_on_sigint)
903+{
904+ ServerConfig server_config;
905+
906+ mir::run_mir(server_config,
907+ [](mir::DisplayServer&)
908+ {
909+ kill(getpid(), SIGINT);
910+ });
911+}
912+
913+TEST(DisplayServerMainLoopEvents, display_server_shuts_down_properly_on_sigterm)
914+{
915+ ServerConfig server_config;
916+
917+ mir::run_mir(server_config,
918+ [](mir::DisplayServer&)
919+ {
920+ kill(getpid(), SIGTERM);
921+ });
922+}
923
924=== modified file 'tests/unit-tests/CMakeLists.txt'
925--- tests/unit-tests/CMakeLists.txt 2013-04-02 21:01:03 +0000
926+++ tests/unit-tests/CMakeLists.txt 2013-04-08 16:58:20 +0000
927@@ -1,7 +1,12 @@
928 add_definitions(-DTEST_RECORDINGS_DIR="${CMAKE_CURRENT_SOURCE_DIR}/input_recordings/")
929 include_directories(${DRM_INCLUDE_DIRS} ${GBM_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR})
930
931-set(UNIT_TEST_SOURCES test_gmock_fixes.cpp)
932+set(
933+ UNIT_TEST_SOURCES
934+
935+ test_gmock_fixes.cpp
936+ test_asio_main_loop.cpp
937+)
938
939 add_subdirectory(options/)
940 add_subdirectory(client/)
941
942=== added file 'tests/unit-tests/test_asio_main_loop.cpp'
943--- tests/unit-tests/test_asio_main_loop.cpp 1970-01-01 00:00:00 +0000
944+++ tests/unit-tests/test_asio_main_loop.cpp 2013-04-08 16:58:20 +0000
945@@ -0,0 +1,144 @@
946+/*
947+ * Copyright © 2013 Canonical Ltd.
948+ *
949+ * This program is free software: you can redistribute it and/or modify
950+ * it under the terms of the GNU General Public License version 3 as
951+ * published by the Free Software Foundation.
952+ *
953+ * This program is distributed in the hope that it will be useful,
954+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
955+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
956+ * GNU General Public License for more details.
957+ *
958+ * You should have received a copy of the GNU General Public License
959+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
960+ *
961+ * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
962+ */
963+
964+#include "mir/asio_main_loop.h"
965+
966+#include <gtest/gtest.h>
967+
968+#include <thread>
969+#include <atomic>
970+
971+#include <sys/types.h>
972+#include <unistd.h>
973+
974+TEST(AsioMainLoopTest, signal_handled)
975+{
976+ int const signum{SIGUSR1};
977+ int handled_signum{0};
978+
979+ mir::AsioMainLoop ml;
980+
981+ ml.register_signal_handler(
982+ {signum},
983+ [&handled_signum, &ml](int sig)
984+ {
985+ handled_signum = sig;
986+ ml.stop();
987+ });
988+
989+ kill(getpid(), signum);
990+
991+ ml.run();
992+
993+ ASSERT_EQ(signum, handled_signum);
994+}
995+
996+
997+TEST(AsioMainLoopTest, multiple_signals_handled)
998+{
999+ std::vector<int> const signals{SIGUSR1, SIGUSR2};
1000+ size_t const num_signals_to_send{10};
1001+ std::vector<int> handled_signals;
1002+ std::atomic<unsigned int> num_handled_signals{0};
1003+
1004+ mir::AsioMainLoop ml;
1005+
1006+ ml.register_signal_handler(
1007+ {signals[0], signals[1]},
1008+ [&handled_signals, &num_handled_signals](int sig)
1009+ {
1010+ handled_signals.push_back(sig);
1011+ ++num_handled_signals;
1012+ });
1013+
1014+
1015+ std::thread signal_sending_thread(
1016+ [&ml, num_signals_to_send, &signals, &num_handled_signals]
1017+ {
1018+ for (size_t i = 0; i < num_signals_to_send; i++)
1019+ {
1020+ kill(getpid(), signals[i % signals.size()]);
1021+ while (num_handled_signals <= i) std::this_thread::yield();
1022+ }
1023+ ml.stop();
1024+ });
1025+
1026+ ml.run();
1027+
1028+ signal_sending_thread.join();
1029+
1030+ ASSERT_EQ(num_signals_to_send, handled_signals.size());
1031+
1032+ for (size_t i = 0; i < num_signals_to_send; i++)
1033+ ASSERT_EQ(signals[i % signals.size()], handled_signals[i]) << " index " << i;
1034+}
1035+
1036+TEST(AsioMainLoopTest, all_registered_handlers_are_called)
1037+{
1038+ int const signum{SIGUSR1};
1039+ std::vector<int> handled_signum{0,0,0};
1040+
1041+ mir::AsioMainLoop ml;
1042+
1043+ ml.register_signal_handler(
1044+ {signum},
1045+ [&handled_signum, &ml](int sig)
1046+ {
1047+ handled_signum[0] = sig;
1048+ if (handled_signum[0] != 0 &&
1049+ handled_signum[1] != 0 &&
1050+ handled_signum[2] != 0)
1051+ {
1052+ ml.stop();
1053+ }
1054+ });
1055+
1056+ ml.register_signal_handler(
1057+ {signum},
1058+ [&handled_signum, &ml](int sig)
1059+ {
1060+ handled_signum[1] = sig;
1061+ if (handled_signum[0] != 0 &&
1062+ handled_signum[1] != 0 &&
1063+ handled_signum[2] != 0)
1064+ {
1065+ ml.stop();
1066+ }
1067+ });
1068+
1069+ ml.register_signal_handler(
1070+ {signum},
1071+ [&handled_signum, &ml](int sig)
1072+ {
1073+ handled_signum[2] = sig;
1074+ if (handled_signum[0] != 0 &&
1075+ handled_signum[1] != 0 &&
1076+ handled_signum[2] != 0)
1077+ {
1078+ ml.stop();
1079+ }
1080+ });
1081+
1082+ kill(getpid(), signum);
1083+
1084+ ml.run();
1085+
1086+ ASSERT_EQ(signum, handled_signum[0]);
1087+ ASSERT_EQ(signum, handled_signum[1]);
1088+ ASSERT_EQ(signum, handled_signum[2]);
1089+}

Subscribers

People subscribed via source and target branches