Mir

Merge lp:~andreas-pokorny/mir/add-timer-to-main-loop into lp:mir

Proposed by Andreas Pokorny
Status: Merged
Approved by: kevin gunn
Approved revision: no longer in the source branch.
Merged at revision: 1626
Proposed branch: lp:~andreas-pokorny/mir/add-timer-to-main-loop
Merge into: lp:mir
Diff against target: 909 lines (+694/-34)
12 files modified
include/server/mir/asio_main_loop.h (+5/-0)
include/server/mir/main_loop.h (+2/-1)
include/server/mir/time/alarm.h (+86/-0)
include/server/mir/time/timer.h (+70/-0)
include/test/mir_test/wait_object.h (+58/-0)
include/test/mir_test_doubles/stub_timer.h (+70/-0)
src/server/asio_main_loop.cpp (+138/-0)
tests/integration-tests/client/test_screencast.cpp (+5/-32)
tests/mir_test/CMakeLists.txt (+2/-1)
tests/mir_test/wait_object.cpp (+35/-0)
tests/unit-tests/graphics/mesa/test_linux_virtual_terminal.cpp (+1/-0)
tests/unit-tests/test_asio_main_loop.cpp (+222/-0)
To merge this branch: bzr merge lp:~andreas-pokorny/mir/add-timer-to-main-loop
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Alberto Aguirre (community) Approve
Alexandros Frantzis (community) Approve
Daniel van Vugt Needs Fixing
Chris Halse Rogers Approve
Alan Griffiths Abstain
Kevin DuBois (community) Needs Information
Review via email: mp+216881@code.launchpad.net

Commit message

Shameless Copy of Christopher Halse Rogers Timer extension

This version is an untouched copy of the Timer/Alarm changes proposed
with the 1hz-rendering-always branch. This extension is especially helpful
for a clean implementation of the ANR detection when sending input events

Description of the change

This is Christophers Timer code which was provided in the 1hz-rendering-always branch. Since the future of the MP is unsure I propose adding this part to mir/devel. Should be rejected if the other one hits devel..

This functionality is helpful for implementing the application-not-responding treatment for the input sender split. So part of the input improvements for qt based unity8 compositor in mir.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

I can't help wondering why Timer & Alarm are not in namespace mir::time

But I suspect the answer will be "I'm just copying code".

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

108 + virtual bool reschedule_in(std::chrono::milliseconds delay) = 0;

It would probably be useful to have "reschedule_for(mir::time::Timestamp timeout_time)" too.

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

I'm a bit concerned about using real time intervals in tests - and not mocking out the clock. (And the above)

Will think more prescriptively tomorrow.

review: Abstain
Revision history for this message
Andreas Pokorny (andreas-pokorny) wrote :

> I'm a bit concerned about using real time intervals in tests - and not mocking
> out the clock. (And the above)
>
> Will think more prescriptively tomorrow.

We could mock it in unit tests and have some that uses the clock in integration tests.

Revision history for this message
Andreas Pokorny (andreas-pokorny) wrote :

So far addressed apart from:

> I'm a bit concerned about using real time intervals in tests - and not mocking
> out the clock. (And the above)

Need to dig deeper into asio to understand if that is possible with reasonable effort.

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

> Will think more prescriptively tomorrow.

Sorry, been sidetracked by trust session work.

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) wrote :
review: Approve (continuous-integration)
Revision history for this message
Kevin DuBois (kdub) wrote :

could the branch be stacked upon the 1hz renderering branch somehow? some of the review comments there apply here too.

review: Needs Information
Revision history for this message
Chris Halse Rogers (raof) wrote :

I'll rebase the 1hz rendering branch on this, when it's not a public holiday.

Revision history for this message
Chris Halse Rogers (raof) wrote :

I'm not sure we need reschedule_for / notify_at (which is why I didn't add them), but they're obvious things we might want to do in the future. +0.

590 + MOCK_METHOD2(notify_in_delegate,
591 + mir::time::Alarm*(std::chrono::milliseconds,
592 + std::function<void()>));
593 +
594 + std::unique_ptr<mir::time::Alarm> notify_in(std::chrono::milliseconds delay,
595 + std::function<void()> callback)
596 + {
597 + return std::unique_ptr<mir::time::Alarm>{notify_in_delegate(delay, callback)};
598 + }

I have recently discovered that we have mir_test/gmock_fixes.h, which allows us to directly return a std::unique_ptr from a mock. The mock object should use this, rather than this indirection trick.

816 + // Somewhat counterintuitively we need some work on the mainloop to keep it running.
817 + auto dummy_alarm = ml.notify_in(std::chrono::milliseconds{1000}, [](){});

The comment (and all the other ones to that effect) is no longer true; you've pulled in the test and io::work that resolves it.

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

320 + void notify_done();
323 + template<typename rep, typename period>
324 + bool wait_for(std::chrono::duration<rep, period> delay);

I got confused trying to figure out how these operations are related to the WatchDog object lifecycle, and it seems the answer is that they are not related at all, they are there only for convenience. It would be cleaner to move them to a separate object, e.g. a WaitObject (see tests/integration-tests/client/test_screencast.cpp for a class with similar functionality).

761 + runner.notify_done();

Not needed.

704 + EXPECT_TRUE(runner.wait_for(std::chrono::milliseconds{100}));
729 + EXPECT_TRUE(runner.wait_for(std::chrono::milliseconds{100}));
...

I am wary of these expectations. It's conceivable that heavy loads in our CI builders could cause significant delays, enough for these tests to fail.

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

> I am wary of these expectations. It's conceivable that heavy loads in our CI builders could
> cause significant delays, enough for these tests to fail.

This is not a blocker, but we should keep an eye out for such failures and react accordingly.

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

Not blocking:

691 +TEST(AsioMainLoopTest, alarm_fires_with_correct_delay)
865 +TEST(AsioMainLoopTest, alarm_fires_at_correct_time_point)

The test names are misleading as there are no expectations setup to actually test what the name implies; but I can see that would be tricky to do and satisfy the expectations under valgrind and CI.

750 + EXPECT_EQ(mir::time::Alarm::Triggered, alarm->state());
766 + EXPECT_EQ(mir::time::Alarm::Pending, alarm->state());
784 + EXPECT_EQ(mir::time::Alarm::Cancelled, alarm->state());
833 + ASSERT_EQ(mir::time::Alarm::Triggered, alarm->state());
835 + EXPECT_EQ(mir::time::Alarm::Pending, alarm->state())
838 + EXPECT_EQ(mir::time::Alarm::Triggered, alarm->state());
861 + EXPECT_EQ(mir::time::Alarm::Triggered, alarm->state())

These would read better as EXPECT_THAT(alarm->state(), Eq(mir::time::Alarm::Triggered)); etc...

841 +TEST(AsioMainLoopTest, rescheduled_alarm_cancels_previous_scheduling)
857 + EXPECT_TRUE(alarm->reschedule_in(std::chrono::milliseconds{150}));
858 + EXPECT_EQ(mir::time::Alarm::Pending, alarm->state());
859 +
860 + EXPECT_TRUE(runner.wait_for(std::chrono::milliseconds{500}));
861 + EXPECT_EQ(mir::time::Alarm::Triggered, alarm->state());
862 + EXPECT_EQ(1, call_count);
863 +}

What if the previous scheduling fires? Line 860-862 could still run and satisfy the expectations. Given the current implementation this again requires checking time intervals so not blocking for this.

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

79 + enum State
80 + {
81 + Pending, /**< Will trigger the callback at some point in the future */
82 + Cancelled, /**< The callback has been cancelled before being triggered */
83 + Triggered /**< The callback has been called */
84 + };

Should be "pending", "cancelled" & "triggered"

http://unity.ubuntu.com/mir/cppguide/index.html?showone=Enumerator_Names#Enumerator_Names

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

101 + virtual bool cancel() = 0;

I wonder if it would be more useful to return the "after" state. (Also for other similar functions)

Revision history for this message
Chris Halse Rogers (raof) wrote :

@Alan - the state is guaranteed to be either cancelled or triggered, so returning the “after” state is no more useful.

That said, I think it would be more useful if it returned whether or not the cancel had any effect - ie: did it transition from Pending to Cancelled or not?

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

I'm not going to block on this, but I am going to mention a concern: This adds another collection of unit tests that are slower than *any* acceptance tests. Vis:

$ ctest | grep sec$ | sort -n -b -k 7,7 -r | head -n 5
 73/191 Test #73: mir_unit_tests.BufferQueueTest.* ........................................... Passed 3.71 sec
137/191 Test #137: mir_unit_tests.MultiThreadedCompositor.* ................................... Passed 3.36 sec
 69/191 Test #69: mir_unit_tests.AsioMainLoopAlarmTest.* ..................................... Passed 1.08 sec
181/191 Test #181: mir_unit_tests.UdevWrapperDeathTest.* ...................................... Passed 0.51 sec
 16/191 Test #16: mir_acceptance_tests.ServerShutdown/OnSignal.* ............................. Passed 0.51 sec

"Unit tests" that take more than milliseconds are a cause for concern. As are tests that rely on the system clock.

review: Abstain
Revision history for this message
Chris Halse Rogers (raof) wrote :

This seems OK to me.

While it would be nicer to divorce this from the system clock, I don't think it's really feasible. It looks like it might be possible to wedge in a fake timesource into boost::asio but that looks fraught with fraughtness.

review: Approve
Revision history for this message
Daniel van Vugt (vanvugt) wrote :

Text conflict in include/server/mir/asio_main_loop.h
Text conflict in include/server/mir/main_loop.h
Text conflict in src/server/asio_main_loop.cpp
Text conflict in tests/unit-tests/test_asio_main_loop.cpp
4 conflicts encountered.

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

@WaitObject

I recently discovered that we have mt::WaitCondition which provides similar functionality, perhaps we should merge the two classes (not a blocker, we can do this in an upcoming MP).

> I am wary of these expectations. It's conceivable that heavy loads in our CI builders could cause significant delays, enough for these tests to fail.

I am still wary... but let's see...

+class Timer

Our usual style is to make the constructor and deleted CopyAssign protected (not that it makes any practical difference).

review: Approve
Revision history for this message
Alberto Aguirre (albaguirre) wrote :

Needs rebasing as Daniel pointed out.

review: Needs Fixing
Revision history for this message
Alberto Aguirre (albaguirre) wrote :

LGTM.

review: Approve
Revision history for this message
kevin gunn (kgunn72) wrote :

i believe this covers off duflu's "needs fixing" as well.
small note, still missing api to create an unscheduled alarm, but landing now as its a prereq for power changes

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (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=== modified file 'include/server/mir/asio_main_loop.h'
2--- include/server/mir/asio_main_loop.h 2014-05-08 16:15:26 +0000
3+++ include/server/mir/asio_main_loop.h 2014-05-13 19:41:32 +0000
4@@ -49,6 +49,10 @@
5 std::initializer_list<int> fd,
6 std::function<void(int)> const& handler);
7
8+ std::unique_ptr<time::Alarm> notify_in(std::chrono::milliseconds delay,
9+ std::function<void()> callback) override;
10+ std::unique_ptr<time::Alarm> notify_at(mir::time::Timestamp time_point,
11+ std::function<void()> callback) override;
12 void enqueue(void const* owner, ServerAction const& action);
13 void pause_processing_for(void const* owner);
14 void resume_processing_for(void const* owner);
15@@ -60,6 +64,7 @@
16 void process_server_actions();
17
18 boost::asio::io_service io;
19+ boost::asio::io_service::work work;
20 std::vector<std::unique_ptr<SignalHandler>> signal_handlers;
21 std::vector<std::unique_ptr<FDHandler>> fd_handlers;
22 std::mutex server_actions_mutex;
23
24=== modified file 'include/server/mir/main_loop.h'
25--- include/server/mir/main_loop.h 2014-05-08 16:15:26 +0000
26+++ include/server/mir/main_loop.h 2014-05-13 19:41:32 +0000
27@@ -20,12 +20,13 @@
28 #define MIR_MAIN_LOOP_H_
29
30 #include "mir/graphics/event_handler_register.h"
31+#include "mir/time/timer.h"
32 #include "mir/server_action_queue.h"
33
34 namespace mir
35 {
36
37-class MainLoop : public graphics::EventHandlerRegister,
38+class MainLoop : public graphics::EventHandlerRegister, public time::Timer,
39 public ServerActionQueue
40 {
41 public:
42
43=== added file 'include/server/mir/time/alarm.h'
44--- include/server/mir/time/alarm.h 1970-01-01 00:00:00 +0000
45+++ include/server/mir/time/alarm.h 2014-05-13 19:41:32 +0000
46@@ -0,0 +1,86 @@
47+/*
48+ * Copyright © 2014 Canonical Ltd.
49+ *
50+ * This program is free software: you can redistribute it and/or modify it
51+ * under the terms of the GNU General Public License version 3,
52+ * as published by the Free Software Foundation.
53+ *
54+ * This program is distributed in the hope that it will be useful,
55+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
56+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
57+ * GNU General Public License for more details.
58+ *
59+ * You should have received a copy of the GNU General Public License
60+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
61+ *
62+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
63+ */
64+
65+#ifndef MIR_TIME_ALARM_H_
66+#define MIR_TIME_ALARM_H_
67+
68+#include "mir/time/clock.h"
69+
70+namespace mir
71+{
72+namespace time
73+{
74+
75+/**
76+ * A one-shot, resettable handle to trigger a callback at a later time
77+ * \note All members of Alarm are threadsafe
78+ */
79+class Alarm
80+{
81+public:
82+ enum State
83+ {
84+ pending, /**< Will trigger the callback at some point in the future */
85+ cancelled, /**< The callback has been cancelled before being triggered */
86+ triggered /**< The callback has been called */
87+ };
88+
89+
90+ Alarm() = default;
91+ /**
92+ * \note Destruction of the Alarm guarantees that the callback will not subsequently be called
93+ */
94+ virtual ~Alarm() = default;
95+
96+ /**
97+ * Cancels a pending alarm
98+ *
99+ * \note Has no effect if the Alarm is in the Triggered state.
100+ * \note cancel() is idempotent
101+ *
102+ * \return True iff the state of the Alarm is now Cancelled
103+ */
104+ virtual bool cancel() = 0;
105+ virtual State state() const = 0;
106+
107+ /**
108+ * Reschedule the alarm
109+ * \param delay Delay, in milliseconds, before the Alarm will be triggered
110+ * \return True if this reschedule supercedes a previous not-yet-triggered timeout
111+ *
112+ * \note This cancels any previous timeout set.
113+ */
114+ virtual bool reschedule_in(std::chrono::milliseconds delay) = 0;
115+
116+ /**
117+ * Reschedule the alarm
118+ * \param timeout Time point when the alarm should be triggered
119+ * \return True if this reschedule supercedes a previous not-yet-triggered timeout
120+ *
121+ * \note This cancels any previous timeout set.
122+ */
123+ virtual bool reschedule_for(Timestamp timeout) = 0;
124+
125+ Alarm(Alarm const&) = delete;
126+ Alarm& operator=(Alarm const&) = delete;
127+};
128+
129+}
130+}
131+#endif
132+
133
134=== added file 'include/server/mir/time/timer.h'
135--- include/server/mir/time/timer.h 1970-01-01 00:00:00 +0000
136+++ include/server/mir/time/timer.h 2014-05-13 19:41:32 +0000
137@@ -0,0 +1,70 @@
138+/*
139+ * Copyright © 2014 Canonical Ltd.
140+ *
141+ * This program is free software: you can redistribute it and/or modify it
142+ * under the terms of the GNU General Public License version 3,
143+ * as published by the Free Software Foundation.
144+ *
145+ * This program is distributed in the hope that it will be useful,
146+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
147+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
148+ * GNU General Public License for more details.
149+ *
150+ * You should have received a copy of the GNU General Public License
151+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
152+ *
153+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
154+ */
155+
156+
157+#ifndef MIR_TIME_TIMER_H_
158+#define MIR_TIME_TIMER_H_
159+
160+#include "mir/time/alarm.h"
161+
162+#include <chrono>
163+#include <functional>
164+#include <memory>
165+
166+namespace mir
167+{
168+namespace time
169+{
170+
171+class Alarm;
172+
173+class Timer
174+{
175+public:
176+ Timer() = default;
177+ virtual ~Timer() = default;
178+ /**
179+ * \brief Create an Alarm that calls the callback after the specified delay
180+ *
181+ * \param delay Time from now, in milliseconds, that the callback will fire
182+ * \param callback Function to call when the Alarm signals
183+ *
184+ * \return A handle to an Alarm that will fire after delay ms.
185+ */
186+ virtual std::unique_ptr<Alarm> notify_in(std::chrono::milliseconds delay,
187+ std::function<void()> callback) = 0;
188+ /**
189+ * \brief Create an Alarm that calls the callback at the specified time
190+ *
191+ * \param time_point Time point when the alarm should be triggered
192+ * \param callback Function to call when the Alarm signals
193+ *
194+ * \return A handle to an Alarm that will fire after delay ms.
195+ */
196+ virtual std::unique_ptr<Alarm> notify_at(Timestamp time_point,
197+ std::function<void()> callback) = 0;
198+
199+
200+ Timer(Timer const&) = delete;
201+ Timer& operator=(Timer const&) = delete;
202+};
203+
204+}
205+}
206+
207+#endif // MIR_TIME_TIMER_H_
208
209=== added file 'include/test/mir_test/wait_object.h'
210--- include/test/mir_test/wait_object.h 1970-01-01 00:00:00 +0000
211+++ include/test/mir_test/wait_object.h 2014-05-13 19:41:32 +0000
212@@ -0,0 +1,58 @@
213+/*
214+ * Copyright © 2014 Canonical Ltd.
215+ *
216+ * This program is free software: you can redistribute it and/or modify
217+ * it under the terms of the GNU General Public License version 3 as
218+ * published by the Free Software Foundation.
219+ *
220+ * This program is distributed in the hope that it will be useful,
221+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
222+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
223+ * GNU General Public License for more details.
224+ *
225+ * You should have received a copy of the GNU General Public License
226+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
227+ *
228+ * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
229+ * Andreas Pokorny <andreas.pokorny@canonical.com>
230+*/
231+
232+#ifndef MIR_TEST_WAIT_OBJECT_H_
233+#define MIR_TEST_WAIT_OBJECT_H_
234+
235+#include <mutex>
236+#include <condition_variable>
237+#include <chrono>
238+#include <boost/throw_exception.hpp>
239+
240+namespace mir
241+{
242+namespace test
243+{
244+
245+class WaitObject
246+{
247+public:
248+ void notify_ready();
249+ void wait_until_ready();
250+ template<typename Representation, typename Period>
251+ void wait_until_ready(std::chrono::duration<Representation, Period> const& span)
252+ {
253+ std::unique_lock<std::mutex> lock{mutex};
254+ if (!cv.wait_for(lock, span, [this] { return ready; }))
255+ {
256+ BOOST_THROW_EXCEPTION(std::runtime_error("WaitObject timed out"));
257+ }
258+ }
259+
260+private:
261+ std::mutex mutex;
262+ std::condition_variable cv;
263+ bool ready = false;
264+};
265+
266+}
267+}
268+
269+#endif
270+
271
272=== added file 'include/test/mir_test_doubles/stub_timer.h'
273--- include/test/mir_test_doubles/stub_timer.h 1970-01-01 00:00:00 +0000
274+++ include/test/mir_test_doubles/stub_timer.h 2014-05-13 19:41:32 +0000
275@@ -0,0 +1,70 @@
276+/*
277+ * Copyright © 2014 Canonical Ltd.
278+ *
279+ * This program is free software: you can redistribute it and/or modify it
280+ * under the terms of the GNU General Public License version 3,
281+ * as published by the Free Software Foundation.
282+ *
283+ * This program is distributed in the hope that it will be useful,
284+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
285+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
286+ * GNU General Public License for more details.
287+ *
288+ * You should have received a copy of the GNU General Public License
289+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
290+ *
291+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
292+ */
293+
294+#ifndef MIR_TEST_DOUBLES_STUB_TIMER_H_
295+#define MIR_TEST_DOUBLES_STUB_TIMER_H_
296+
297+#include "mir/time/alarm.h"
298+#include "mir/time/timer.h"
299+
300+namespace mir
301+{
302+namespace test
303+{
304+namespace doubles
305+{
306+
307+class StubAlarm : public mir::time::Alarm
308+{
309+ bool cancel() override
310+ {
311+ return false;
312+ }
313+ State state() const override
314+ {
315+ return Cancelled;
316+ }
317+ bool reschedule_in(std::chrono::milliseconds) override
318+ {
319+ return false;
320+ }
321+ bool reschedule_for(mir::time::Timestamp) override
322+ {
323+ return false;
324+ }
325+};
326+
327+class StubTimer : public mir::time::Timer
328+{
329+ std::unique_ptr<mir::time::Alarm> notify_in(std::chrono::milliseconds, std::function<void(void)>) override
330+ {
331+ return std::unique_ptr<mir::time::Alarm>{new StubAlarm};
332+ }
333+
334+ std::unique_ptr<mir::time::Alarm> notify_at(mir::time::Timestamp, std::function<void(void)>) override
335+ {
336+ return std::unique_ptr<mir::time::Alarm>{new StubAlarm};
337+ }
338+};
339+
340+}
341+}
342+}
343+
344+
345+#endif // MIR_TEST_DOUBLES_STUB_TIMER_H_
346
347=== modified file 'src/server/asio_main_loop.cpp'
348--- src/server/asio_main_loop.cpp 2014-05-08 16:15:26 +0000
349+++ src/server/asio_main_loop.cpp 2014-05-13 19:41:32 +0000
350@@ -18,7 +18,11 @@
351
352 #include "mir/asio_main_loop.h"
353
354+#include "boost/date_time/posix_time/conversion.hpp"
355+
356 #include <cassert>
357+#include <mutex>
358+#include <condition_variable>
359
360 class mir::AsioMainLoop::SignalHandler
361 {
362@@ -109,6 +113,7 @@
363 * to compile.
364 */
365 mir::AsioMainLoop::AsioMainLoop()
366+ : work{io}
367 {
368 }
369
370@@ -154,6 +159,139 @@
371 fd_handlers.push_back(std::move(fd_handler));
372 }
373
374+namespace
375+{
376+class AlarmImpl : public mir::time::Alarm
377+{
378+public:
379+ AlarmImpl(boost::asio::io_service& io,
380+ std::chrono::milliseconds delay,
381+ std::function<void(void)> callback);
382+
383+ AlarmImpl(boost::asio::io_service& io,
384+ mir::time::Timestamp time_point,
385+ std::function<void(void)> callback);
386+
387+ ~AlarmImpl() noexcept override;
388+
389+ bool cancel() override;
390+ State state() const override;
391+
392+ bool reschedule_in(std::chrono::milliseconds delay) override;
393+ bool reschedule_for(mir::time::Timestamp time_point) override;
394+private:
395+ void update_timer();
396+ struct InternalState
397+ {
398+ explicit InternalState(std::function<void(void)> callback)
399+ : callback{callback}
400+ {
401+ }
402+
403+ mutable std::mutex m;
404+ std::function<void(void)> callback;
405+ State state;
406+ };
407+
408+ boost::asio::deadline_timer timer;
409+ std::shared_ptr<InternalState> data;
410+};
411+
412+AlarmImpl::AlarmImpl(boost::asio::io_service& io,
413+ std::chrono::milliseconds delay,
414+ std::function<void ()> callback)
415+ : timer{io},
416+ data{std::make_shared<InternalState>(callback)}
417+{
418+ reschedule_in(delay);
419+}
420+
421+AlarmImpl::AlarmImpl(boost::asio::io_service& io,
422+ mir::time::Timestamp time_point,
423+ std::function<void ()> callback)
424+ : timer{io},
425+ data{std::make_shared<InternalState>(callback)}
426+{
427+ reschedule_for(time_point);
428+}
429+
430+AlarmImpl::~AlarmImpl() noexcept
431+{
432+ AlarmImpl::cancel();
433+}
434+
435+bool AlarmImpl::cancel()
436+{
437+ std::lock_guard<decltype(data->m)> lock(data->m);
438+ if (data->state == triggered)
439+ return false;
440+
441+ data->state = cancelled;
442+ timer.cancel();
443+ return true;
444+}
445+
446+mir::time::Alarm::State AlarmImpl::state() const
447+{
448+ std::lock_guard<decltype(data->m)> lock(data->m);
449+
450+ return data->state;
451+}
452+
453+bool AlarmImpl::reschedule_in(std::chrono::milliseconds delay)
454+{
455+ bool cancelling = timer.expires_from_now(boost::posix_time::milliseconds{delay.count()});
456+ update_timer();
457+ return cancelling;
458+}
459+
460+bool AlarmImpl::reschedule_for(mir::time::Timestamp time_point)
461+{
462+ bool cancelling =
463+ timer.expires_at(boost::posix_time::from_time_t(
464+ std::chrono::high_resolution_clock::to_time_t(time_point)
465+ ));
466+ update_timer();
467+ return cancelling;
468+}
469+
470+void AlarmImpl::update_timer()
471+{
472+ std::lock_guard<decltype(data->m)> lock(data->m);
473+ // Awkwardly, we can't stop the async_wait handler from being called
474+ // on a destroyed AlarmImpl. This means we need to wedge a shared_ptr
475+ // into the async_wait callback.
476+ std::weak_ptr<InternalState> possible_data = data;
477+ timer.async_wait([possible_data](boost::system::error_code const& ec)
478+ {
479+ auto data = possible_data.lock();
480+ if (!data)
481+ return;
482+
483+ std::unique_lock<decltype(data->m)> lock(data->m);
484+ if (!ec && data->state == pending)
485+ {
486+ data->state = triggered;
487+ lock.unlock();
488+ data->callback();
489+ }
490+ });
491+ data->state = pending;
492+}
493+}
494+
495+std::unique_ptr<mir::time::Alarm> mir::AsioMainLoop::notify_in(std::chrono::milliseconds delay,
496+ std::function<void()> callback)
497+{
498+ return std::unique_ptr<mir::time::Alarm>{new AlarmImpl{io, delay, callback}};
499+}
500+
501+std::unique_ptr<mir::time::Alarm> mir::AsioMainLoop::notify_at(mir::time::Timestamp time_point,
502+ std::function<void()> callback)
503+{
504+ return std::unique_ptr<mir::time::Alarm>{new AlarmImpl{io, time_point, callback}};
505+
506+}
507 void mir::AsioMainLoop::enqueue(void const* owner, ServerAction const& action)
508 {
509 {
510
511=== modified file 'tests/integration-tests/client/test_screencast.cpp'
512--- tests/integration-tests/client/test_screencast.cpp 2014-03-06 18:57:08 +0000
513+++ tests/integration-tests/client/test_screencast.cpp 2014-05-13 19:41:32 +0000
514@@ -23,13 +23,11 @@
515 #include "mir_test/test_protobuf_server.h"
516 #include "mir_test/stub_server_tool.h"
517 #include "mir_test/pipe.h"
518+#include "mir_test/wait_object.h"
519
520 #include <gtest/gtest.h>
521
522 #include <thread>
523-#include <mutex>
524-#include <condition_variable>
525-#include <boost/throw_exception.hpp>
526
527 namespace mcl = mir::client;
528 namespace mt = mir::test;
529@@ -37,31 +35,6 @@
530 namespace
531 {
532
533-class WaitObject
534-{
535-public:
536- void notify_ready()
537- {
538- std::unique_lock<std::mutex> lock{mutex};
539- ready = true;
540- cv.notify_all();
541- }
542-
543- void wait_until_ready()
544- {
545- std::unique_lock<std::mutex> lock{mutex};
546- if (!cv.wait_for(lock, std::chrono::seconds{10}, [this] { return ready; }))
547- {
548- BOOST_THROW_EXCEPTION(std::runtime_error("WaitObject timed out"));
549- }
550- }
551-
552-private:
553- std::mutex mutex;
554- std::condition_variable cv;
555- bool ready = false;
556-};
557-
558 struct StubScreencastServerTool : mt::StubServerTool
559 {
560 void create_screencast(
561@@ -129,13 +102,13 @@
562 protobuf_parameters.set_pixel_format(0);
563 mir::protobuf::Screencast protobuf_screencast;
564
565- WaitObject wait_rpc;
566+ mt::WaitObject wait_rpc;
567
568 protobuf_server->create_screencast(
569 nullptr,
570 &protobuf_parameters,
571 &protobuf_screencast,
572- google::protobuf::NewCallback(&wait_rpc, &WaitObject::notify_ready));
573+ google::protobuf::NewCallback(&wait_rpc, &mt::WaitObject::notify_ready));
574
575 wait_rpc.wait_until_ready();
576
577@@ -167,13 +140,13 @@
578 protobuf_screencast_id.set_value(0);
579 mir::protobuf::Buffer protobuf_buffer;
580
581- WaitObject wait_rpc;
582+ mt::WaitObject wait_rpc;
583
584 protobuf_server->screencast_buffer(
585 nullptr,
586 &protobuf_screencast_id,
587 &protobuf_buffer,
588- google::protobuf::NewCallback(&wait_rpc, &WaitObject::notify_ready));
589+ google::protobuf::NewCallback(&wait_rpc, &mt::WaitObject::notify_ready));
590
591 wait_rpc.wait_until_ready();
592
593
594=== modified file 'tests/mir_test/CMakeLists.txt'
595--- tests/mir_test/CMakeLists.txt 2014-04-30 21:25:02 +0000
596+++ tests/mir_test/CMakeLists.txt 2014-05-13 19:41:32 +0000
597@@ -6,4 +6,5 @@
598 cross_process_action.cpp
599 spin_wait.cpp
600 popen.cpp
601-)
602\ No newline at end of file
603+ wait_object.cpp
604+)
605
606=== added file 'tests/mir_test/wait_object.cpp'
607--- tests/mir_test/wait_object.cpp 1970-01-01 00:00:00 +0000
608+++ tests/mir_test/wait_object.cpp 2014-05-13 19:41:32 +0000
609@@ -0,0 +1,35 @@
610+/*
611+ * Copyright © 2014 Canonical Ltd.
612+ *
613+ * This program is free software: you can redistribute it and/or modify
614+ * it under the terms of the GNU General Public License version 3 as
615+ * published by the Free Software Foundation.
616+ *
617+ * This program is distributed in the hope that it will be useful,
618+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
619+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
620+ * GNU General Public License for more details.
621+ *
622+ * You should have received a copy of the GNU General Public License
623+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
624+ *
625+ * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
626+ * Andreas Pokorny <andreas.pokorny@canonical.com>
627+ */
628+
629+#include "mir_test/wait_object.h"
630+
631+namespace mt = mir::test;
632+
633+void mt::WaitObject::notify_ready()
634+{
635+ std::unique_lock<std::mutex> lock{mutex};
636+ ready = true;
637+ cv.notify_all();
638+}
639+
640+void mt::WaitObject::wait_until_ready()
641+{
642+ wait_until_ready(std::chrono::seconds{10});
643+}
644+
645
646=== modified file 'tests/unit-tests/graphics/mesa/test_linux_virtual_terminal.cpp'
647--- tests/unit-tests/graphics/mesa/test_linux_virtual_terminal.cpp 2014-05-06 14:44:57 +0000
648+++ tests/unit-tests/graphics/mesa/test_linux_virtual_terminal.cpp 2014-05-13 19:41:32 +0000
649@@ -22,6 +22,7 @@
650
651 #include "mir_test/fake_shared.h"
652 #include "mir_test_doubles/mock_display_report.h"
653+#include "mir_test/gmock_fixes.h"
654
655 #include <gtest/gtest.h>
656 #include <gmock/gmock.h>
657
658=== modified file 'tests/unit-tests/test_asio_main_loop.cpp'
659--- tests/unit-tests/test_asio_main_loop.cpp 2014-05-08 16:15:26 +0000
660+++ tests/unit-tests/test_asio_main_loop.cpp 2014-05-13 19:41:32 +0000
661@@ -17,19 +17,48 @@
662 */
663
664 #include "mir/asio_main_loop.h"
665+#include "mir/time/high_resolution_clock.h"
666 #include "mir_test/pipe.h"
667+#include "mir_test/auto_unblock_thread.h"
668+#include "mir_test/wait_object.h"
669
670 #include <gtest/gtest.h>
671 #include <gmock/gmock.h>
672
673 #include <thread>
674 #include <atomic>
675+#include <functional>
676+#include <mutex>
677+#include <condition_variable>
678+#include <array>
679+#include <boost/throw_exception.hpp>
680
681 #include <sys/types.h>
682 #include <unistd.h>
683
684 namespace mt = mir::test;
685
686+namespace
687+{
688+
689+class AsioMainLoopAlarmTest : public ::testing::Test
690+{
691+public:
692+ mir::AsioMainLoop ml;
693+ int call_count{0};
694+ mt::WaitObject wait;
695+
696+ struct UnblockMainLoop : mt::AutoUnblockThread
697+ {
698+ UnblockMainLoop(mir::AsioMainLoop & loop)
699+ : mt::AutoUnblockThread([&loop]() {loop.stop();},
700+ [&loop]() {loop.run();})
701+ {}
702+ };
703+};
704+
705+}
706+
707 TEST(AsioMainLoopTest, signal_handled)
708 {
709 int const signum{SIGUSR1};
710@@ -294,6 +323,199 @@
711 EXPECT_EQ(elems_to_send[2], elems_read[2]);
712 }
713
714+TEST_F(AsioMainLoopAlarmTest, main_loop_runs_until_stop_called)
715+{
716+ std::mutex checkpoint_mutex;
717+ std::condition_variable checkpoint;
718+ bool hit_checkpoint{false};
719+
720+ auto fire_on_mainloop_start = ml.notify_in(std::chrono::milliseconds{0},
721+ [&checkpoint_mutex, &checkpoint, &hit_checkpoint]()
722+ {
723+ std::unique_lock<decltype(checkpoint_mutex)> lock(checkpoint_mutex);
724+ hit_checkpoint = true;
725+ checkpoint.notify_all();
726+ });
727+
728+ UnblockMainLoop unblocker(ml);
729+
730+ {
731+ std::unique_lock<decltype(checkpoint_mutex)> lock(checkpoint_mutex);
732+ ASSERT_TRUE(checkpoint.wait_for(lock, std::chrono::milliseconds{10}, [&hit_checkpoint]() { return hit_checkpoint; }));
733+ }
734+
735+ auto alarm = ml.notify_in(std::chrono::milliseconds{10}, [this]
736+ {
737+ wait.notify_ready();
738+ });
739+
740+ EXPECT_NO_THROW(wait.wait_until_ready(std::chrono::milliseconds{50}));
741+
742+ ml.stop();
743+ // Main loop should be stopped now
744+
745+ hit_checkpoint = false;
746+ auto should_not_fire = ml.notify_in(std::chrono::milliseconds{0},
747+ [&checkpoint_mutex, &checkpoint, &hit_checkpoint]()
748+ {
749+ std::unique_lock<decltype(checkpoint_mutex)> lock(checkpoint_mutex);
750+ hit_checkpoint = true;
751+ checkpoint.notify_all();
752+ });
753+
754+ std::unique_lock<decltype(checkpoint_mutex)> lock(checkpoint_mutex);
755+ EXPECT_FALSE(checkpoint.wait_for(lock, std::chrono::milliseconds{50}, [&hit_checkpoint]() { return hit_checkpoint; }));
756+}
757+
758+TEST_F(AsioMainLoopAlarmTest, alarm_fires_with_correct_delay)
759+{
760+ auto alarm = ml.notify_in(std::chrono::milliseconds{50}, [this]()
761+ {
762+ wait.notify_ready();
763+ });
764+
765+ UnblockMainLoop unblocker(ml);
766+
767+ EXPECT_NO_THROW(wait.wait_until_ready(std::chrono::milliseconds{100}));
768+}
769+
770+TEST_F(AsioMainLoopAlarmTest, multiple_alarms_fire)
771+{
772+ int const alarm_count{10};
773+ std::atomic<int> call_count{0};
774+ std::array<std::unique_ptr<mir::time::Alarm>, alarm_count> alarms;
775+
776+ for (auto& alarm : alarms)
777+ {
778+ alarm = ml.notify_in(std::chrono::milliseconds{50}, [this, &call_count]()
779+ {
780+ call_count.fetch_add(1);
781+ if (call_count == alarm_count)
782+ wait.notify_ready();
783+ });
784+ }
785+
786+ UnblockMainLoop unblocker(ml);
787+
788+ EXPECT_NO_THROW(wait.wait_until_ready(std::chrono::milliseconds{100}));
789+ for (auto& alarm : alarms)
790+ EXPECT_EQ(mir::time::Alarm::triggered, alarm->state());
791+}
792+
793+
794+TEST_F(AsioMainLoopAlarmTest, alarm_changes_to_triggered_state)
795+{
796+ auto alarm = ml.notify_in(std::chrono::milliseconds{50}, [this]()
797+ {
798+ wait.notify_ready();
799+ });
800+
801+ UnblockMainLoop unblocker(ml);
802+
803+ EXPECT_NO_THROW(wait.wait_until_ready(std::chrono::milliseconds{100}));
804+
805+ EXPECT_EQ(mir::time::Alarm::triggered, alarm->state());
806+}
807+
808+TEST_F(AsioMainLoopAlarmTest, alarm_starts_in_pending_state)
809+{
810+ auto alarm = ml.notify_in(std::chrono::milliseconds{50}, [this]() {});
811+
812+ UnblockMainLoop unblocker(ml);
813+
814+ EXPECT_EQ(mir::time::Alarm::pending, alarm->state());
815+}
816+
817+TEST_F(AsioMainLoopAlarmTest, cancelled_alarm_doesnt_fire)
818+{
819+ auto alarm = ml.notify_in(std::chrono::milliseconds{200}, [this]()
820+ {
821+ wait.notify_ready();
822+ });
823+
824+ UnblockMainLoop unblocker(ml);
825+
826+ EXPECT_TRUE(alarm->cancel());
827+ EXPECT_THROW(wait.wait_until_ready(std::chrono::milliseconds{300}), std::runtime_error);
828+ EXPECT_EQ(mir::time::Alarm::cancelled, alarm->state());
829+}
830+
831+TEST_F(AsioMainLoopAlarmTest, destroyed_alarm_doesnt_fire)
832+{
833+ auto alarm = ml.notify_in(std::chrono::milliseconds{200}, [this]()
834+ {
835+ wait.notify_ready();
836+ });
837+
838+ UnblockMainLoop unblocker(ml);
839+
840+ alarm.reset(nullptr);
841+
842+ EXPECT_THROW(wait.wait_until_ready(std::chrono::milliseconds{300}), std::runtime_error);
843+}
844+
845+TEST_F(AsioMainLoopAlarmTest, rescheduled_alarm_fires_again)
846+{
847+ std::mutex m;
848+ std::condition_variable called;
849+
850+ auto alarm = ml.notify_in(std::chrono::milliseconds{0}, [this, &m, &called]()
851+ {
852+ std::unique_lock<decltype(m)> lock(m);
853+ call_count++;
854+ if (call_count == 2)
855+ wait.notify_ready();
856+ called.notify_all();
857+ });
858+
859+ UnblockMainLoop unblocker(ml);
860+
861+ {
862+ std::unique_lock<decltype(m)> lock(m);
863+ ASSERT_TRUE(called.wait_for(lock,
864+ std::chrono::milliseconds{50},
865+ [this](){ return call_count == 1; }));
866+ }
867+
868+ ASSERT_EQ(mir::time::Alarm::triggered, alarm->state());
869+ alarm->reschedule_in(std::chrono::milliseconds{100});
870+ EXPECT_EQ(mir::time::Alarm::pending, alarm->state());
871+
872+ EXPECT_NO_THROW(wait.wait_until_ready(std::chrono::milliseconds{500}));
873+ EXPECT_EQ(mir::time::Alarm::triggered, alarm->state());
874+}
875+
876+TEST_F(AsioMainLoopAlarmTest, rescheduled_alarm_cancels_previous_scheduling)
877+{
878+ auto alarm = ml.notify_in(std::chrono::milliseconds{100}, [this]()
879+ {
880+ call_count++;
881+ wait.notify_ready();
882+ });
883+
884+ UnblockMainLoop unblocker(ml);
885+
886+ EXPECT_TRUE(alarm->reschedule_in(std::chrono::milliseconds{150}));
887+ EXPECT_EQ(mir::time::Alarm::pending, alarm->state());
888+
889+ EXPECT_NO_THROW(wait.wait_until_ready(std::chrono::milliseconds{500}));
890+ EXPECT_EQ(mir::time::Alarm::triggered, alarm->state());
891+ EXPECT_EQ(1, call_count);
892+}
893+
894+TEST_F(AsioMainLoopAlarmTest, alarm_fires_at_correct_time_point)
895+{
896+ mir::time::HighResolutionClock clock;
897+
898+ mir::time::Timestamp real_soon = clock.sample() + std::chrono::microseconds{120};
899+
900+ auto alarm = ml.notify_at(real_soon, [this]() { wait.notify_ready(); });
901+
902+ UnblockMainLoop unblocker(ml);
903+
904+ EXPECT_NO_THROW(wait.wait_until_ready(std::chrono::milliseconds{200}));
905+}
906+
907 TEST(AsioMainLoopTest, dispatches_action)
908 {
909 using namespace testing;

Subscribers

People subscribed via source and target branches