Mir

Merge lp:~andreas-pokorny/mir/split-main-loop-and-fix-races into lp:mir

Proposed by Andreas Pokorny
Status: Work in progress
Proposed branch: lp:~andreas-pokorny/mir/split-main-loop-and-fix-races
Merge into: lp:mir
Diff against target: 3020 lines (+1675/-617)
31 files modified
include/platform/mir/graphics/event_handler_register.h (+3/-0)
include/server/mir/default_server_configuration.h (+5/-0)
include/server/mir/loop.h (+37/-0)
include/server/mir/main_loop.h (+1/-2)
include/server/mir/server_configuration.h (+2/-0)
include/server/mir/time/timer_loop.h (+37/-0)
include/test/mir_test_doubles/mock_event_handler_register.h (+53/-0)
include/test/mir_test_doubles/mock_loop.h (+44/-0)
include/test/mir_test_doubles/mock_server_action_queue.h (+44/-0)
src/platform/graphics/mesa/display.cpp (+1/-0)
src/server/CMakeLists.txt (+3/-0)
src/server/asio_main_loop.cpp (+88/-307)
src/server/asio_main_loop.h (+21/-27)
src/server/asio_server_action_queue.cpp (+91/-0)
src/server/asio_server_action_queue.h (+58/-0)
src/server/asio_timer_service.cpp (+260/-0)
src/server/asio_timer_service.h (+58/-0)
src/server/compositor/default_configuration.cpp (+1/-1)
src/server/default_server_configuration.cpp (+47/-2)
src/server/display_server.cpp (+14/-1)
src/server/synchronous_server_action.cpp (+50/-0)
src/server/synchronous_server_action.h (+48/-0)
tests/acceptance-tests/test_display_configuration.cpp (+1/-0)
tests/integration-tests/test_display_server_main_loop_events.cpp (+44/-0)
tests/unit-tests/CMakeLists.txt (+3/-0)
tests/unit-tests/graphics/mesa/test_display.cpp (+5/-17)
tests/unit-tests/graphics/mesa/test_linux_virtual_terminal.cpp (+2/-16)
tests/unit-tests/test_asio_main_loop.cpp (+45/-244)
tests/unit-tests/test_asio_server_action_queue.cpp (+219/-0)
tests/unit-tests/test_asio_timer_service.cpp (+312/-0)
tests/unit-tests/test_synchronous_server_action.cpp (+78/-0)
To merge this branch: bzr merge lp:~andreas-pokorny/mir/split-main-loop-and-fix-races
Reviewer Review Type Date Requested Status
Alberto Aguirre (community) Needs Fixing
Alan Griffiths Needs Fixing
PS Jenkins bot (community) continuous-integration Approve
Alexandros Frantzis (community) Needs Fixing
Robert Carr (community) Approve
Andreas Pokorny (community) Approve
Review via email: mp+220571@code.launchpad.net

Commit message

Add the ability to unregister fd callbacks from AsioMainLoop
Moves Timer handling out of MainLoop into a separate TimerService

To identify previously registered callbacks the caller has to submit a void pointer. The supplied fd set does need to match the full registered set, only those supplied will be removed.

Reconfiguration operations are now executed sequentially together with the action handlers in both the TimerService and MainLoop.

Description of the change

Adds a method to unregister some of the file descriptors from a FDhandler in mir::AsioMainLoop. This is needed by the upcoming InputSender to unregister from InputChannels of removed surfaces.

The FDHandler is associated with a void const* on registry and when removing a file descriptor again from the main loop.

Since Asio copies handlers, cancelling operations does not really stop it
from execution. This can cause update issues in AlarmImpl::update_timer
that lead to alarms not being triggered again. The solution applied here
is sequential execution of all handlers inside the ServerActionQueues and reconfigurations of any kind are executed using the utility class SentinelAction which blocks until the action completes. This utility either executes inside the queue or on the callers thread, depending on the current thread id.

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
Alexandros Frantzis (afrantzis) wrote :

What's the use case for this?

166 + remove_if(begin(stream_descriptors),

Do we also want to erase the items?

203 + end(stream_descriptors) != find_if(begin(stream_descriptors),
204 + end(stream_descriptors),
205 + [s](stream_descriptor_ptr const& item)
206 + {return item.get() == s;} ))

Doesn't this code need to be protected from concurrent access to stream descriptors? For example, handling an fd while concurrently reducing the fd set?

Also the code would be clearer if this piece of code was placed in a function.

Needs Information/Needs Fixing

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

> What's the use case for this?

The input sender, I am just preparing, observes those fds for incoming input ACK messages, which contain Conumed/NotConsumed flags. Those fds are closed and opened together with surfaces.

> 166 + remove_if(begin(stream_descriptors),
>
> Do we also want to erase the items?

I thought that would happen here. The destruction of the unique_ptr instances should delete the instance.

>
> 203 + end(stream_descriptors) != find_if(begin(stream_descriptors),
> 204 + end(stream_descriptors),
> 205 + [s](stream_descriptor_ptr const& item)
> 206 + {return item.get() == s;} ))
>
> Doesn't this code need to be protected from concurrent access to stream
> descriptors? For example, handling an fd while concurrently reducing the fd
> set?

Yes. Thanks! Just fixed.

> Also the code would be clearer if this piece of code was placed in a function.
>
> Needs Information/Needs Fixing

ACK

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

> > 166 + remove_if(begin(stream_descriptors),
> >
> > Do we also want to erase the items?
>
> I thought that would happen here. The destruction of the unique_ptr instances should delete the
> instance.

I was referring to the erase-remove idiom: std::remove and remove_if just move the items to the end of the data structure (invalidating them in the process), so we also need to erase them like:

v.erase(std::remove_if(...), v.end());

(Unfortunately the name 'remove' is not very descriptive of what the function is actually doing.)

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

> I was referring to the erase-remove idiom: std::remove and remove_if just move
> the items to the end of the data structure (invalidating them in the process),
> so we also need to erase them like:
>
> v.erase(std::remove_if(...), v.end());
>
> (Unfortunately the name 'remove' is not very descriptive of what the function
> is actually doing.)

Maybe not descriptive but it was not thoughtful to assume that an algorithm could make that kind of changes to the container. Now that I fixed it I have to deal with the issue that asio might still execute a destroyed callback that refers to a recently destroyed Handler... fixing.

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
Alexandros Frantzis (afrantzis) wrote :

Is the current plan to use the server main loop, or will we have a different object?

I wonder if using the main loop mechanism to handle the FDs for input is appropriate. Do we need total serialization of the messages? I could imagine using a more focused class for this purpose (but I am not familiar with the details).

This is not a NACK, just a call for information.

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

> Is the current plan to use the server main loop, or will we have a different
> object?
>
> I wonder if using the main loop mechanism to handle the FDs for input is
> appropriate. Do we need total serialization of the messages? I could imagine
> using a more focused class for this purpose (but I am not familiar with the
> details).

It is not strictly necessary to handles those responses in a specific or dedicated thread. Handling user input sequentially is . Up to now the android based input dispatcher forwards user input notifications into another thread for dispatch. This thread was also used to receive those responses.

In the InputSender implementation I assumed the main loop to be a natural pick as I did not assume a lot of load caused by those notifications and assumed that creating another thread for that use case is not necessary. A lot of assumptions... so to resolve that, how about:

class EventLoop : public graphics::EventHandlerRegister, public time::Timer, public ServerActionQueue
{
public:
     virtual void run() = 0;
     virtual void stop() = 0;
}; // or WorkLoop or just Loop ..

class MainLoop : EventLoop
{
};

class AsioMainLoop : MainLoop
{
  ...
};

That way the decision which thread should handle that could be done inside the DefaultServerConfiguration. i.e. through std::shared_ptr<EventLoop> the_sender_loop(), std::shared_ptr<EventLoop> the_dispatcher_loop(), ... and components could 'indicate' the necessity of strictly sequential execution, by requiring the type MainLoop.

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

> Is the current plan to use the server main loop, or will we have a different
> object?
>
> I wonder if using the main loop mechanism to handle the FDs for input is
> appropriate. Do we need total serialization of the messages? I could imagine
> using a more focused class for this purpose (but I am not familiar with the
> details).
>
> This is not a NACK, just a call for information.

Now you can have a look at lp:~andreas-pokorny/mir/input-sender ~> lines 600ff

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

There are races inside reconfiguration options like timer reschedule and unregistering the fd handler (the code around 270 + races for "still_registered")..

review: Needs Fixing
1649. By Andreas Pokorny

cleaning up empty lines and removing debug code

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1650. By Andreas Pokorny

fix missing update of alarm state to pending and missing timer_thread update

1651. By Andreas Pokorny

make AsioTimerService::stop synchronous

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

The code is slightly simpler than the intermediate combined solution, but has grown in a way that replacing asio with a different solution looks like a good idea.

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

+ in a later step (for main loop and timer fd)

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) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Alexandros Frantzis (afrantzis) :
1652. By Andreas Pokorny

Renamed SentinelAction to SynchronousServerAction

and all remaining references of main loop in timer service tests got renamed to timer service.

1653. By Andreas Pokorny

renamed test again

Revision history for this message
Andreas Pokorny (andreas-pokorny) :
1654. By Andreas Pokorny

Getting rid of InternalState in FDHandler

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
1655. By Andreas Pokorny

remove action queue again

1656. By Andreas Pokorny

switching from mutex to atomic<State>

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alexandros Frantzis (afrantzis) :
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Andreas Pokorny (andreas-pokorny) :
1657. By Andreas Pokorny

Explain synchronizatio aspects of main loop and fix c++ style guide issues

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

First pass basically looks ok...

Not sure about handling the finished signals in main loop...while they aren't high bandwidth they are timing sensitive and my intuition is a thread will provide better average latency. Of course this is all just speculation without benchmarking of some realistic input scenarios.

The timeouts in the unit test of a few hundred milliseconds may need bumping, such timeouts frequently fail under QEMU on Jenkins.
oh

Revision history for this message
Robert Carr (robertcarr) wrote :

Not sure where "oh" came from at the end there ;)

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

Why do we have one MP coupling two changes?

1. "Add the ability to unregister fd callbacks from AsioMainLoop" and
2. "Moves Timer handling out of MainLoop into a separate TimerService"

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

We've used the base "mir" namespace for the utilities that help bring up the display server implemented by components in nested namespaces. I.e. it contains display_server, run_mir and default_configuration and a couple of files to support this.

I don't think src/server is the right place for asio_timer_service or synchronous_server_action. (Nor, necessarily, asio_main_loop but that's pre-existing.) Whether these belong in "time" (or a new "scheduling" namespace) is less clear to me.

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

> Why do we have one MP coupling two changes?
>
> 1. "Add the ability to unregister fd callbacks from AsioMainLoop" and
> 2. "Moves Timer handling out of MainLoop into a separate TimerService"

It started with just 1. and when giving the rationale for 1, doing 2. was suggested, for keepig those things out of the main loop.

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

833 + boost::optional<std::thread::id> main_loop_thread;
1486 + boost::optional<std::thread::id> queue_thread_id,

No need for boost::optional, since std::thread::id() constructs a thread id guaranteed to not represent any valid thread (it will compare unequal to any valid thread ids).

1511 + if (done)
1512 + return;

Unprotected access to 'done'.

1540 +#ifndef MIR_SENTINEL_ACTION_H
1541 +#define MIR_SENTINEL_ACTION_H

Wrong guard name (also missing '_' at the end of the name).

2240 + std::atomic<int> p1_handler_never_triggers{-1};
2241 + std::atomic<int> p2_handler_executes{-1};

No need for atomic, since the handlers are guaranteed not to run after run() returns.

2850 +TEST(SynchronousServerActionTest, enqueues_action_if_thread_differs)

It would be better to use an expectation on mtd::MockServerActionQueue::enqueue(), since
currently we don't check that the queue is actually used. For example, if we mess up the code and call action() directly instead of enqueuing this test will still succeed.

mtd::MockServerActionQueue mock_queue;
EXPECT_CALL(mock_queue, enqueue(_,_))
   .WillOnce(...invoke arg2...);

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

> 833 + boost::optional<std::thread::id> main_loop_thread;
> 1486 + boost::optional<std::thread::id> queue_thread_id,
>
> No need for boost::optional, since std::thread::id() constructs a thread id
> guaranteed to not represent any valid thread (it will compare unequal to any
> valid thread ids).

I use those three states to differ between - queue is not running, queue is running on a different thread, queue is running on the same thread.

> 1511 + if (done)
> 1512 + return;
>
> Unprotected access to 'done'.
>
> 1540 +#ifndef MIR_SENTINEL_ACTION_H
> 1541 +#define MIR_SENTINEL_ACTION_H
>
> Wrong guard name (also missing '_' at the end of the name).
>
> 2240 + std::atomic<int> p1_handler_never_triggers{-1};
> 2241 + std::atomic<int> p2_handler_executes{-1};
>
> No need for atomic, since the handlers are guaranteed not to run after run()
> returns.

ack fixing.

> 2850 +TEST(SynchronousServerActionTest, enqueues_action_if_thread_differs)
>
> It would be better to use an expectation on
> mtd::MockServerActionQueue::enqueue(), since
> currently we don't check that the queue is actually used. For example, if we
> mess up the code and call action() directly instead of enqueuing this test
> will still succeed.
>
> mtd::MockServerActionQueue mock_queue;
> EXPECT_CALL(mock_queue, enqueue(_,_))
> .WillOnce(...invoke arg2...);

See above, this is tested by the first unit test.

1658. By Andreas Pokorny

merged devel, and addressed findings

1659. By Andreas Pokorny

fixes strange indent

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

fixed the weird indent

> We've used the base "mir" namespace for the utilities that help bring up the
> display server implemented by components in nested namespaces. I.e. it
> contains display_server, run_mir and default_configuration and a couple of
> files to support this.
>
> I don't think src/server is the right place for asio_timer_service or
> synchronous_server_action. (Nor, necessarily, asio_main_loop but that's pre-
> existing.) Whether these belong in "time" (or a new "scheduling" namespace) is
> less clear to me.

I proposed one solution (with this branch as prerequisite) here: https://code.launchpad.net/~andreas-pokorny/mir/move-loops-to-scheduler-namespace/+merge/222616

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

111 +class TimerService : public Timer
112 +{
113 +public:
114 + virtual void run() = 0;
115 + virtual void stop() = 0;
116 +};

I don't find "TimerService" a particularly helpful name. Saying "TimerService *is a* Timer" seems wrong.

Suggestions: "GlobalTimer", "MainTimer" or "SystemTimer"?

OTOH looking at mt::Timer ... that could be plausibly renamed "AlarmService".

Anyway, something about the naming needs fixing. ;)

review: Needs Fixing
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Andreas Pokorny (andreas-pokorny) wrote :

> 111 +class TimerService : public Timer
> 112 +{
> 113 +public:
> 114 + virtual void run() = 0;
> 115 + virtual void stop() = 0;
> 116 +};
>
> I don't find "TimerService" a particularly helpful name. Saying "TimerService
> *is a* Timer" seems wrong.
> Suggestions: "GlobalTimer", "MainTimer" or "SystemTimer"?

That part of the interface is all about starting (run) and stopping.. like in MainLoop so:

AlarmLoop?

> OTOH looking at mt::Timer ... that could be plausibly renamed "AlarmService".
>
> Anyway, something about the naming needs fixing. ;)

Agreed Timer is a bad pick for something that provides Alarms (aka Timers).
AlarmService sounds good ..

1660. By Andreas Pokorny

restore fix for alarm unit tests

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

> [...]
>
> Anyway, something about the naming needs fixing.

I tried to address that in the MP that also moves the classes into a better namespace: https://code.launchpad.net/~andreas-pokorny/mir/move-loops-to-scheduler-namespace/+merge/222616

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

1061 + // of boost::asio only allowing static methods inside the taits type.

s/taits/traits/

~~~~

1372 + return std::make_shared<mc::TimeoutFrameDroppingPolicyFactory>(the_timer_service(),

TimeoutFrameDroppingPolicyFactory requires a shared_ptr<Timer>, not a shared_ptr<TimerService> - this reinforces my feeling that these interfaces are wrong. It also requires an extra header here that shouldn't be needed. Vis:

1363 +#include "mir/time/timer_service.h"

review: Needs Fixing
1661. By Andreas Pokorny

typo

1662. By Andreas Pokorny

Removed mir::timer::TimerService the run/stop part of the interface is now represented by mir::Loop

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1663. By Andreas Pokorny

merged lp:mir/development-branch

1664. By Andreas Pokorny

merged lp:mir/development-branch

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

> 1372 + return
> std::make_shared<mc::TimeoutFrameDroppingPolicyFactory>(the_timer_service(),
>
> TimeoutFrameDroppingPolicyFactory requires a shared_ptr<Timer>, not a
> shared_ptr<TimerService> - this reinforces my feeling that these interfaces
> are wrong. It also requires an extra header here that shouldn't be needed.
> Vis:
>
> 1363 +#include "mir/time/timer_service.h"

I think we already had that before, but with MainLoop. I think it was wrong to extend Timer with start/stop and use it as "the_timer_service()". Those two parts are now separate - I only need a combined interface for CachedPtr. I think I could have avoided that with a new interface for CachedPtr to accept a "compatible" make function - assuming that every operator() call of CachedPtr has identical behaviour.

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

> assuming that every operator() call of CachedPtr
> has identical behaviour.

Responding to just this part of the comment (not the MP): it is hard to ensure this as users of Mir can override these calls.

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

41 + virtual std::shared_ptr<mir::time::Timer> the_timer();

"mir::" is not needed.

~~~~

79 +namespace mir
80 +{
81 +class Loop

I think "Loop" is an uninformative name (what is it?) in the wrong namespace (what role it has in setting up the server)?

Should it be time::Scheduler or todo::Scheduler?

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

> > assuming that every operator() call of CachedPtr
> > has identical behaviour.
>
> Responding to just this part of the comment (not the MP): it is hard to ensure
> this as users of Mir can override these calls.

Hm I thought I would be safe from that kind of abuse by making the CachedPtr private - but it still felt like a hack so I did not do it.

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

> 41 + virtual std::shared_ptr<mir::time::Timer> the_timer();
>
> "mir::" is not needed.
>
> ~~~~
>
> 79 +namespace mir
> 80 +{
> 81 +class Loop
>
> I think "Loop" is an uninformative name (what is it?) in the wrong namespace
> (what role it has in setting up the server)?

A blocking but stoppable thing? I thought within that domain (to be moved to scheduler namespace) it would make at least some artificial sense. I.e a horrible implementation of Loop::run could be a simple loop: while(!done) { execute_pending_timers }

> Should it be time::Scheduler or todo::Scheduler?

Scheduler sounds ok too.

It is in the wrong namespace still, because I thought I should to the moving in a separate MP.

1665. By Andreas Pokorny

unnecessary namespace

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

> > 833 + boost::optional<std::thread::id> main_loop_thread;
> > 1486 + boost::optional<std::thread::id> queue_thread_id,
> >
> > No need for boost::optional, since std::thread::id() constructs a thread id
> > guaranteed to not represent any valid thread (it will compare unequal to any
> > valid thread ids).
>
> I use those three states to differ between - queue is not running,
> queue is running on a different thread, queue is running on the same thread.

In the current code at least, I don't see a need to differentiate states (1) and (3):

if (current_thread == queue_thread) //state 2
{
    ...
}
else // no queue_thread or current_thread != queue_thread i.e. state 1 && state 3
{
    ...
}

Anyway, not a blocker.

+ virtual std::shared_ptr<Loop> the_timer_loop();
...and other timer related stuff

How about something like?

TimerService
{
  void run() = 0;
  void stop() = 0;
  std::shared_ptr<Timer> create_timer();
};

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

Mostly happy I think.

Not sure I see the point in the mir::Loop interface? Yes the main loop and the timer service both have run/stop methods, but which component really benefits from the loop interface?

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

1959 -class MockEventRegister : public mg::EventHandlerRegister
1960 +class MockVirtualTerminal : public mgm::VirtualTerminal

This (new) class is not needed (we have mtd::MockVirtualTerminal).

> 2850 +TEST(SynchronousServerActionTest, enqueues_action_if_thread_differs)
>
> > It would be better to use an expectation on
> > mtd::MockServerActionQueue::enqueue(), since
> > currently we don't check that the queue is actually used. For example, if we
> > mess up the code and call action() directly instead of enqueuing this test
> > will still succeed.
>
> See above, this is tested by the first unit test.

I don't see how this is covered by the first test. All other tests check that enqueue is never called. If we mess up the code in the SynchronousServerAction constructor to always do:

    done = true;
    action();

all tests will still pass. Regardless of the details, the bottom line is that this test is called "enqueues_action_if_thread_differs", but it currently really is "executes_action_if_thread_differs".

review: Needs Fixing
1666. By Andreas Pokorny

review findings

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 :

> Mostly happy I think.
>
> Not sure I see the point in the mir::Loop interface? Yes the main loop and the
> timer service both have run/stop methods, but which component really benefits
> from the loop interface?

Mmmm.

Adding an interface whose purpose is unclear to the wrong namespace is enough to make me unhappy with the MP.

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

OK I started looking at the dependent MP and decided that the comment really needs to be addressed here. It seems that the "scheduler" namespace proposed there is is going to be about dispatching events.

That implies that the Loop interface is about an EventLoop?

It makes more sense to start/stop "the event loop" than, as proposed here, to start and stop "the loop". ("Loop" is meaninglessly generic.)

A lot of the problem understanding this stack of MPs comes down to misleading or ambiguous names.

Maybe the name "scheduler" in the next MP is also setting wrong expectations. Only some of those events are schedule based - others are based on fds and interrupts. Perhaps "dispatcher" would be better.

But if both TimerLoop and MainLoop are dispatching events (albeit from different sources) I am still unsure that this is a right approach.

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

Loop as an interface doesn't seem necessary.

It seems the timer and the "thing" in which the timer runs are coupled together so it doesn't make sense to expose "the_timer" and "the_timer_service" as the interfaces do not express the coupling between them.

Instead TimerService and Timer interfaces should fold into an AlarmService interface, expose it in the config as the_alarm_service() :

namespace mir
{
namespace time
{
class AlarmService
{
  void run() = 0;
  void stop() = 0;
  virtual std::unique_ptr<Alarm> notify_in(std::chrono::milliseconds delay,
      std::function<void()> callback) = 0;
  virtual std::unique_ptr<Alarm> notify_at(Timestamp time_point,
      std::function<void()> callback) = 0;
  virtual std::unique_ptr<Alarm> create_alarm(std::function<void()> callback) = 0;
};

Then a mir user that would like to replace the alarm service can use any mechanism it wants - threads, timer fds, mainloops...etc..

review: Needs Fixing

Unmerged revisions

1666. By Andreas Pokorny

review findings

1665. By Andreas Pokorny

unnecessary namespace

1664. By Andreas Pokorny

merged lp:mir/development-branch

1663. By Andreas Pokorny

merged lp:mir/development-branch

1662. By Andreas Pokorny

Removed mir::timer::TimerService the run/stop part of the interface is now represented by mir::Loop

1661. By Andreas Pokorny

typo

1660. By Andreas Pokorny

restore fix for alarm unit tests

1659. By Andreas Pokorny

fixes strange indent

1658. By Andreas Pokorny

merged devel, and addressed findings

1657. By Andreas Pokorny

Explain synchronizatio aspects of main loop and fix c++ style guide issues

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'include/platform/mir/graphics/event_handler_register.h'
2--- include/platform/mir/graphics/event_handler_register.h 2013-08-28 03:41:48 +0000
3+++ include/platform/mir/graphics/event_handler_register.h 2014-06-16 22:41:10 +0000
4@@ -37,8 +37,11 @@
5
6 virtual void register_fd_handler(
7 std::initializer_list<int> fds,
8+ void const* owner,
9 std::function<void(int)> const& handler) = 0;
10
11+ virtual void unregister_fd_handler(void const* owner) = 0;
12+
13 protected:
14 EventHandlerRegister() = default;
15 virtual ~EventHandlerRegister() = default;
16
17=== modified file 'include/server/mir/default_server_configuration.h'
18--- include/server/mir/default_server_configuration.h 2014-06-12 15:35:08 +0000
19+++ include/server/mir/default_server_configuration.h 2014-06-16 22:41:10 +0000
20@@ -73,6 +73,8 @@
21 namespace time
22 {
23 class Clock;
24+class Timer;
25+class TimerLoop;
26 }
27 namespace scene
28 {
29@@ -159,6 +161,7 @@
30 virtual std::shared_ptr<compositor::Compositor> the_compositor();
31 virtual std::shared_ptr<input::InputManager> the_input_manager();
32 virtual std::shared_ptr<MainLoop> the_main_loop();
33+ virtual std::shared_ptr<Loop> the_timer_loop();
34 virtual std::shared_ptr<ServerStatusListener> the_server_status_listener();
35 virtual std::shared_ptr<DisplayChanger> the_display_changer();
36 virtual std::shared_ptr<graphics::Platform> the_graphics_platform();
37@@ -279,6 +282,7 @@
38
39 virtual std::shared_ptr<time::Clock> the_clock();
40 virtual std::shared_ptr<ServerActionQueue> the_server_action_queue();
41+ virtual std::shared_ptr<time::Timer> the_timer();
42
43 protected:
44 std::shared_ptr<options::Option> the_options() const;
45@@ -363,6 +367,7 @@
46 CachedPtr<graphics::DisplayReport> display_report;
47 CachedPtr<time::Clock> clock;
48 CachedPtr<MainLoop> main_loop;
49+ CachedPtr<time::TimerLoop> timer_loop;
50 CachedPtr<ServerStatusListener> server_status_listener;
51 CachedPtr<graphics::DisplayConfigurationPolicy> display_configuration_policy;
52 CachedPtr<graphics::nested::HostConnection> host_connection;
53
54=== added file 'include/server/mir/loop.h'
55--- include/server/mir/loop.h 1970-01-01 00:00:00 +0000
56+++ include/server/mir/loop.h 2014-06-16 22:41:10 +0000
57@@ -0,0 +1,37 @@
58+/*
59+ * Copyright © 2014 Canonical Ltd.
60+ *
61+ * This program is free software: you can redistribute it and/or modify it
62+ * under the terms of the GNU General Public License version 3,
63+ * as published by the Free Software Foundation.
64+ *
65+ * This program is distributed in the hope that it will be useful,
66+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
67+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
68+ * GNU General Public License for more details.
69+ *
70+ * You should have received a copy of the GNU General Public License
71+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
72+ *
73+ * Authored by: Andreas Pokorny <andreas.pokorny@canonical.com>
74+ */
75+
76+#ifndef MIR_LOOP_H_
77+#define MIR_LOOP_H_
78+
79+namespace mir
80+{
81+class Loop
82+{
83+public:
84+ Loop() = default;
85+ virtual ~Loop() = default;
86+ virtual void run() = 0;
87+ virtual void stop() = 0;
88+protected:
89+ Loop(Loop const&) = delete;
90+ Loop& operator=(Loop const&) = delete;
91+};
92+}
93+
94+#endif
95
96=== modified file 'include/server/mir/main_loop.h'
97--- include/server/mir/main_loop.h 2014-06-03 11:04:15 +0000
98+++ include/server/mir/main_loop.h 2014-06-16 22:41:10 +0000
99@@ -20,13 +20,12 @@
100 #define MIR_MAIN_LOOP_H_
101
102 #include "mir/graphics/event_handler_register.h"
103-#include "mir/time/timer.h"
104 #include "mir/server_action_queue.h"
105
106 namespace mir
107 {
108
109-class MainLoop : public graphics::EventHandlerRegister, public time::Timer,
110+class MainLoop : public graphics::EventHandlerRegister,
111 public ServerActionQueue
112 {
113 public:
114
115=== modified file 'include/server/mir/server_configuration.h'
116--- include/server/mir/server_configuration.h 2014-06-02 17:07:02 +0000
117+++ include/server/mir/server_configuration.h 2014-06-16 22:41:10 +0000
118@@ -50,6 +50,7 @@
119 }
120
121 class MainLoop;
122+class Loop;
123 class ServerStatusListener;
124 class DisplayChanger;
125 class EmergencyCleanup;
126@@ -65,6 +66,7 @@
127 virtual std::shared_ptr<input::InputManager> the_input_manager() = 0;
128 virtual std::shared_ptr<input::InputDispatcher> the_input_dispatcher() = 0;
129 virtual std::shared_ptr<MainLoop> the_main_loop() = 0;
130+ virtual std::shared_ptr<Loop> the_timer_loop() = 0;
131 virtual std::shared_ptr<ServerStatusListener> the_server_status_listener() = 0;
132 virtual std::shared_ptr<DisplayChanger> the_display_changer() = 0;
133 virtual std::shared_ptr<graphics::Platform> the_graphics_platform() = 0;
134
135=== added file 'include/server/mir/time/timer_loop.h'
136--- include/server/mir/time/timer_loop.h 1970-01-01 00:00:00 +0000
137+++ include/server/mir/time/timer_loop.h 2014-06-16 22:41:10 +0000
138@@ -0,0 +1,37 @@
139+/*
140+ * Copyright © 2014 Canonical Ltd.
141+ *
142+ * This program is free software: you can redistribute it and/or modify it
143+ * under the terms of the GNU General Public License version 3,
144+ * as published by the Free Software Foundation.
145+ *
146+ * This program is distributed in the hope that it will be useful,
147+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
148+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
149+ * GNU General Public License for more details.
150+ *
151+ * You should have received a copy of the GNU General Public License
152+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
153+ *
154+ * Authored by: Andreas Pokorny <andreas.pokorny@canonical.com>
155+ */
156+
157+#ifndef MIR_TIME_TIMER_LOOP_H_
158+#define MIR_TIME_TIMER_LOOP_H_
159+
160+#include "mir/time/timer.h"
161+#include "mir/loop.h"
162+
163+namespace mir
164+{
165+namespace time
166+{
167+
168+class TimerLoop : public Timer, public Loop
169+{
170+};
171+
172+}
173+}
174+
175+#endif
176
177=== added file 'include/test/mir_test_doubles/mock_event_handler_register.h'
178--- include/test/mir_test_doubles/mock_event_handler_register.h 1970-01-01 00:00:00 +0000
179+++ include/test/mir_test_doubles/mock_event_handler_register.h 2014-06-16 22:41:10 +0000
180@@ -0,0 +1,53 @@
181+/*
182+ * Copyright © 2014 Canonical Ltd.
183+ *
184+ * This program is free software: you can redistribute it and/or modify
185+ * it under the terms of the GNU General Public License version 3 as
186+ * published by the Free Software Foundation.
187+ *
188+ * This program is distributed in the hope that it will be useful,
189+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
190+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
191+ * GNU General Public License for more details.
192+ *
193+ * You should have received a copy of the GNU General Public License
194+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
195+ *
196+ * Authored by: Andreas Pokorny <andreas.pokorny@canonical.com>
197+ */
198+
199+#ifndef MOCK_EVENT_HANDLER_REGISTER_H_
200+#define MOCK_EVENT_HANDLER_REGISTER_H_
201+
202+#include "mir/graphics/event_handler_register.h"
203+
204+#include <gmock/gmock.h>
205+
206+namespace mir
207+{
208+namespace test
209+{
210+namespace doubles
211+{
212+
213+class MockEventHandlerRegister : public graphics::EventHandlerRegister
214+{
215+public:
216+ ~MockEventHandlerRegister() noexcept {}
217+
218+ MOCK_METHOD2(register_signal_handler,
219+ void(std::initializer_list<int>,
220+ std::function<void(int)> const&));
221+
222+ MOCK_METHOD3(register_fd_handler,
223+ void(std::initializer_list<int>, void const*,
224+ std::function<void(int)> const&));
225+
226+ MOCK_METHOD1(unregister_fd_handler, void(void const*));
227+};
228+
229+}
230+}
231+}
232+
233+#endif
234
235=== added file 'include/test/mir_test_doubles/mock_loop.h'
236--- include/test/mir_test_doubles/mock_loop.h 1970-01-01 00:00:00 +0000
237+++ include/test/mir_test_doubles/mock_loop.h 2014-06-16 22:41:10 +0000
238@@ -0,0 +1,44 @@
239+/*
240+ * Copyright © 2014 Canonical Ltd.
241+ *
242+ * This program is free software: you can redistribute it and/or modify it
243+ * under the terms of the GNU General Public License version 3,
244+ * as published by the Free Software Foundation.
245+ *
246+ * This program is distributed in the hope that it will be useful,
247+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
248+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
249+ * GNU General Public License for more details.
250+ *
251+ * You should have received a copy of the GNU General Public License
252+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
253+ *
254+ * Authored by: Andreas Pokorny <andreas.pokorny@canonical.com>
255+ */
256+
257+#ifndef MIR_MOCK_LOOP_H_
258+#define MIR_MOCK_LOOP_H_
259+
260+#include "mir/loop.h"
261+
262+#include "mir_test/gmock_fixes.h"
263+
264+namespace mir
265+{
266+namespace test
267+{
268+namespace doubles
269+{
270+
271+class MockLoop : public mir::Loop
272+{
273+public:
274+ MOCK_METHOD0(run, void());
275+ MOCK_METHOD0(stop, void());
276+};
277+
278+}
279+}
280+}
281+
282+#endif
283
284=== added file 'include/test/mir_test_doubles/mock_server_action_queue.h'
285--- include/test/mir_test_doubles/mock_server_action_queue.h 1970-01-01 00:00:00 +0000
286+++ include/test/mir_test_doubles/mock_server_action_queue.h 2014-06-16 22:41:10 +0000
287@@ -0,0 +1,44 @@
288+/*
289+ * Copyright © 2014 Canonical Ltd.
290+ *
291+ * This program is free software: you can redistribute it and/or modify it
292+ * under the terms of the GNU General Public License version 3,
293+ * as published by the Free Software Foundation.
294+ *
295+ * This program is distributed in the hope that it will be useful,
296+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
297+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
298+ * GNU General Public License for more details.
299+ *
300+ * You should have received a copy of the GNU General Public License
301+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
302+ *
303+ * Authored by: Andreas Pokorny <andreas.pokorny@canonical.com>
304+ */
305+
306+#ifndef MIR_MOCK_SERVER_ACTION_QUEUE_H_
307+#define MIR_MOCK_SERVER_ACTION_QUEUE_H_
308+
309+#include "mir/server_action_queue.h"
310+#include <gmock/gmock.h>
311+
312+namespace mir
313+{
314+namespace test
315+{
316+namespace doubles
317+{
318+
319+class MockServerActionQueue : public mir::ServerActionQueue
320+{
321+public:
322+ MOCK_METHOD2(enqueue, void (void const* /*owner*/, mir::ServerAction const& /*action*/));
323+ MOCK_METHOD1(pause_processing_for, void(void const* /*owner*/));
324+ MOCK_METHOD1(resume_processing_for, void(void const* /*owner*/));
325+};
326+
327+}
328+}
329+}
330+
331+#endif
332
333=== modified file 'src/platform/graphics/mesa/display.cpp'
334--- src/platform/graphics/mesa/display.cpp 2014-06-10 14:40:23 +0000
335+++ src/platform/graphics/mesa/display.cpp 2014-06-16 22:41:10 +0000
336@@ -233,6 +233,7 @@
337 {
338 handlers.register_fd_handler(
339 {monitor.fd()},
340+ this,
341 [conf_change_handler, this](int)
342 {
343 monitor.process_events([conf_change_handler]
344
345=== modified file 'src/server/CMakeLists.txt'
346--- src/server/CMakeLists.txt 2014-06-06 10:03:54 +0000
347+++ src/server/CMakeLists.txt 2014-06-16 22:41:10 +0000
348@@ -30,6 +30,9 @@
349 display_server.cpp
350 default_server_configuration.cpp
351 asio_main_loop.cpp
352+ asio_server_action_queue.cpp
353+ asio_timer_service.cpp
354+ synchronous_server_action.cpp
355 )
356
357 set(MIRSERVER_LINKAGE SHARED)
358
359=== modified file 'src/server/asio_main_loop.cpp'
360--- src/server/asio_main_loop.cpp 2014-06-12 15:35:08 +0000
361+++ src/server/asio_main_loop.cpp 2014-06-16 22:41:10 +0000
362@@ -16,87 +16,15 @@
363 * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
364 */
365
366-#include "mir/asio_main_loop.h"
367+#include "asio_main_loop.h"
368
369-#include "boost/date_time/posix_time/conversion.hpp"
370+#include "synchronous_server_action.h"
371
372 #include <cassert>
373 #include <mutex>
374 #include <condition_variable>
375
376-namespace
377-{
378-struct MirClockTimerTraits
379-{
380- // TODO the clock used by the main loop is a global setting, this is a restriction
381- // of boost::asio only allowing static methods inside the taits type.
382- struct TimerServiceClockStorage
383- {
384- public:
385- void set_clock(std::shared_ptr<mir::time::Clock const> const& clock)
386- {
387- std::lock_guard<std::mutex> lock(timer_service_mutex);
388- auto stored_clock = timer_service_clock.lock();
389- if (stored_clock && stored_clock != clock)
390- BOOST_THROW_EXCEPTION(std::logic_error("A clock is already in use as time source for mir::AsioMainLoop"));
391- timer_service_clock = clock;
392- }
393- mir::time::Timestamp now()
394- {
395- std::lock_guard<std::mutex> lock(timer_service_mutex);
396- auto clock = timer_service_clock.lock();
397- if (!clock)
398- BOOST_THROW_EXCEPTION(std::logic_error("No clock available to create time stamp"));
399- return clock->sample();
400- }
401- private:
402- std::mutex timer_service_mutex;
403- std::weak_ptr<mir::time::Clock const> timer_service_clock;
404- };
405-
406- static TimerServiceClockStorage clock_storage;
407-
408- static void set_clock(std::shared_ptr<mir::time::Clock const> const& clock)
409- {
410- clock_storage.set_clock(clock);
411- }
412-
413- // time_traits interface required by boost::asio::deadline_timer{_service}
414- typedef mir::time::Timestamp time_type;
415- typedef std::chrono::milliseconds duration_type;
416-
417-
418- static time_type now()
419- {
420- return clock_storage.now();
421- }
422-
423- static time_type add(const time_type& t, const duration_type& d)
424- {
425- return t + d;
426- }
427-
428- static duration_type subtract(const time_type& t1, const time_type& t2)
429- {
430- return std::chrono::duration_cast<duration_type>(t1 - t2);
431- }
432-
433- static bool less_than(const time_type& t1, const time_type& t2)
434- {
435- return t1 < t2;
436- }
437-
438- static boost::posix_time::time_duration to_posix_duration(
439- const duration_type& d)
440- {
441- return boost::posix_time::millisec(d.count());
442- }
443-};
444-
445-MirClockTimerTraits::TimerServiceClockStorage MirClockTimerTraits::clock_storage;
446-
447-typedef boost::asio::basic_deadline_timer<mir::time::Timestamp, MirClockTimerTraits> deadline_timer;
448-}
449+namespace bap = boost::asio::posix;
450
451 class mir::AsioMainLoop::SignalHandler
452 {
453@@ -137,46 +65,64 @@
454 class mir::AsioMainLoop::FDHandler
455 {
456 public:
457+ typedef std::unique_ptr<bap::stream_descriptor> stream_descriptor_ptr;
458+
459 FDHandler(boost::asio::io_service& io,
460 std::initializer_list<int> fds,
461+ void const* owner,
462 std::function<void(int)> const& handler)
463- : handler{handler}
464+ : handler{handler}, owner{owner}
465 {
466 for (auto fd : fds)
467- {
468- auto raw = new boost::asio::posix::stream_descriptor{io, fd};
469- auto s = std::unique_ptr<boost::asio::posix::stream_descriptor>(raw);
470- stream_descriptors.push_back(std::move(s));
471- }
472- }
473-
474- void async_wait()
475- {
476- for (auto const& s : stream_descriptors)
477- {
478- s->async_read_some(
479- boost::asio::null_buffers(),
480- std::bind(&FDHandler::handle, this,
481- std::placeholders::_1, std::placeholders::_2, s.get()));
482- }
483+ stream_descriptors.emplace_back(new bap::stream_descriptor{io, fd});
484+ }
485+
486+ bool is_owned_by(void const* possible_owner) const
487+ {
488+ return owner == possible_owner;
489+ }
490+
491+ static void async_wait(std::shared_ptr<FDHandler> const& fd_handler, ServerActionQueue & queue)
492+ {
493+ for (auto const& s : fd_handler->stream_descriptors)
494+ read_some(s.get(), fd_handler, queue);
495 }
496
497 private:
498- void handle(boost::system::error_code err, size_t /*bytes*/,
499- boost::asio::posix::stream_descriptor* s)
500+ static void read_some(bap::stream_descriptor* s, std::weak_ptr<FDHandler> const& possible_fd_handler, ServerActionQueue & queue)
501 {
502- if (!err)
503- {
504- handler(s->native_handle());
505- s->async_read_some(
506- boost::asio::null_buffers(),
507- std::bind(&FDHandler::handle, this,
508- std::placeholders::_1, std::placeholders::_2, s));
509- }
510+ s->async_read_some(
511+ boost::asio::null_buffers(),
512+ [possible_fd_handler,s,&queue](boost::system::error_code err, size_t /*bytes*/)
513+ {
514+ if (err)
515+ return;
516+
517+ // The actual execution of the fd handler is moved to the action queue.This allows us to synchronously
518+ // unregister FDHandlers even when they are about to be executed. We do this because of the way Asio
519+ // copies and executes pending completion handlers.
520+ // In worst case during the call to unregister the FDHandler, it may still be executed, but not after
521+ // the unregister call returned.
522+ queue.enqueue(
523+ s,
524+ [possible_fd_handler, s, &queue]()
525+ {
526+ auto fd_handler = possible_fd_handler.lock();
527+ if (!fd_handler)
528+ return;
529+
530+ fd_handler->handler(s->native_handle());
531+ fd_handler.reset();
532+
533+ if (possible_fd_handler.lock())
534+ read_some(s, possible_fd_handler, queue);
535+ });
536+ });
537 }
538
539- std::vector<std::unique_ptr<boost::asio::posix::stream_descriptor>> stream_descriptors;
540- std::function<void(int)> handler;
541+ std::vector<stream_descriptor_ptr> stream_descriptors;
542+ std::function<void(int)> const handler;
543+ void const* owner;
544 };
545
546 /*
547@@ -186,10 +132,9 @@
548 * don't have complete type information for SignalHandler and fail
549 * to compile.
550 */
551-mir::AsioMainLoop::AsioMainLoop(std::shared_ptr<time::Clock> const& clock)
552- : work{io}, clock(clock)
553+mir::AsioMainLoop::AsioMainLoop()
554+ : work{io}, action_queue{io}
555 {
556- MirClockTimerTraits::set_clock(clock);
557 }
558
559 mir::AsioMainLoop::~AsioMainLoop() noexcept(true)
560@@ -198,12 +143,14 @@
561
562 void mir::AsioMainLoop::run()
563 {
564+ main_loop_thread = std::this_thread::get_id();
565 io.run();
566 }
567
568 void mir::AsioMainLoop::stop()
569 {
570 io.stop();
571+ main_loop_thread.reset();
572 }
573
574 void mir::AsioMainLoop::register_signal_handler(
575@@ -222,220 +169,54 @@
576
577 void mir::AsioMainLoop::register_fd_handler(
578 std::initializer_list<int> fds,
579+ void const* owner,
580 std::function<void(int)> const& handler)
581 {
582 assert(handler);
583
584- auto fd_handler = std::unique_ptr<FDHandler>{
585- new FDHandler{io, fds, handler}};
586-
587- fd_handler->async_wait();
588-
589- fd_handlers.push_back(std::move(fd_handler));
590-}
591-
592-namespace
593-{
594-class AlarmImpl : public mir::time::Alarm
595-{
596-public:
597- AlarmImpl(boost::asio::io_service& io,
598- std::chrono::milliseconds delay,
599- std::function<void(void)> callback);
600-
601- AlarmImpl(boost::asio::io_service& io,
602- mir::time::Timestamp time_point,
603- std::function<void(void)> callback);
604-
605- AlarmImpl(boost::asio::io_service& io,
606- std::function<void(void)> callback);
607-
608- ~AlarmImpl() noexcept override;
609-
610- bool cancel() override;
611- State state() const override;
612-
613- bool reschedule_in(std::chrono::milliseconds delay) override;
614- bool reschedule_for(mir::time::Timestamp time_point) override;
615-private:
616- void update_timer();
617- struct InternalState
618- {
619- explicit InternalState(std::function<void(void)> callback)
620- : callback{callback}
621- {
622- }
623-
624- mutable std::mutex m;
625- std::function<void(void)> callback;
626- State state;
627- };
628-
629- ::deadline_timer timer;
630- std::shared_ptr<InternalState> data;
631-};
632-
633-AlarmImpl::AlarmImpl(boost::asio::io_service& io,
634- std::chrono::milliseconds delay,
635- std::function<void ()> callback)
636- : AlarmImpl(io, callback)
637-{
638- reschedule_in(delay);
639-}
640-
641-AlarmImpl::AlarmImpl(boost::asio::io_service& io,
642- mir::time::Timestamp time_point,
643- std::function<void ()> callback)
644- : AlarmImpl(io, callback)
645-{
646- reschedule_for(time_point);
647-}
648-
649-AlarmImpl::AlarmImpl(boost::asio::io_service& io,
650- std::function<void(void)> callback)
651- : timer{io},
652- data{std::make_shared<InternalState>(callback)}
653-{
654- data->state = triggered;
655-}
656-
657-AlarmImpl::~AlarmImpl() noexcept
658-{
659- AlarmImpl::cancel();
660-}
661-
662-bool AlarmImpl::cancel()
663-{
664- std::lock_guard<decltype(data->m)> lock(data->m);
665- if (data->state == triggered)
666- return false;
667-
668- data->state = cancelled;
669- timer.cancel();
670- return true;
671-}
672-
673-mir::time::Alarm::State AlarmImpl::state() const
674-{
675- std::lock_guard<decltype(data->m)> lock(data->m);
676-
677- return data->state;
678-}
679-
680-bool AlarmImpl::reschedule_in(std::chrono::milliseconds delay)
681-{
682- bool cancelling = timer.expires_from_now(delay);
683- update_timer();
684- return cancelling;
685-}
686-
687-bool AlarmImpl::reschedule_for(mir::time::Timestamp time_point)
688-{
689- bool cancelling = timer.expires_at(time_point);
690- update_timer();
691- return cancelling;
692-}
693-
694-void AlarmImpl::update_timer()
695-{
696- std::lock_guard<decltype(data->m)> lock(data->m);
697- // Awkwardly, we can't stop the async_wait handler from being called
698- // on a destroyed AlarmImpl. This means we need to wedge a shared_ptr
699- // into the async_wait callback.
700- std::weak_ptr<InternalState> possible_data = data;
701- timer.async_wait([possible_data](boost::system::error_code const& ec)
702- {
703- auto data = possible_data.lock();
704- if (!data)
705- return;
706-
707- std::unique_lock<decltype(data->m)> lock(data->m);
708- if (!ec && data->state == pending)
709- {
710- data->state = triggered;
711- lock.unlock();
712- data->callback();
713- }
714- });
715- data->state = pending;
716-}
717-}
718-
719-std::unique_ptr<mir::time::Alarm> mir::AsioMainLoop::notify_in(std::chrono::milliseconds delay,
720- std::function<void()> callback)
721-{
722- return std::unique_ptr<mir::time::Alarm>{new AlarmImpl{io, delay, callback}};
723-}
724-
725-std::unique_ptr<mir::time::Alarm> mir::AsioMainLoop::notify_at(mir::time::Timestamp time_point,
726- std::function<void()> callback)
727-{
728- return std::unique_ptr<mir::time::Alarm>{new AlarmImpl{io, time_point, callback}};
729-}
730-
731-std::unique_ptr<mir::time::Alarm> mir::AsioMainLoop::create_alarm(std::function<void()> callback)
732-{
733- return std::unique_ptr<mir::time::Alarm>{new AlarmImpl{io, callback}};
734+ auto fd_handler = std::make_shared<FDHandler>(io, fds, owner, handler);
735+
736+ FDHandler::async_wait(fd_handler, action_queue);
737+
738+ std::lock_guard<std::mutex> lock(fd_handlers_mutex);
739+ fd_handlers.push_back(fd_handler);
740+}
741+
742+void mir::AsioMainLoop::unregister_fd_handler(void const* owner)
743+{
744+ // The SynchronousServerAction makes sure that with the
745+ // completion of the method unregister_fd_handler the
746+ // handler will no longer be called.
747+ // There is a chance for a fd handler callback to happen before
748+ // the completion of this method.
749+ SynchronousServerAction unregister{
750+ action_queue,
751+ main_loop_thread,
752+ [this,owner]()
753+ {
754+ std::lock_guard<std::mutex> lock(fd_handlers_mutex);
755+ auto end_of_valid = remove_if(
756+ begin(fd_handlers),
757+ end(fd_handlers),
758+ [owner](std::shared_ptr<FDHandler> const& item)
759+ {
760+ return item->is_owned_by(owner);
761+ });
762+ fd_handlers.erase(end_of_valid, end(fd_handlers));
763+ }};
764 }
765
766 void mir::AsioMainLoop::enqueue(void const* owner, ServerAction const& action)
767 {
768- {
769- std::lock_guard<std::mutex> lock{server_actions_mutex};
770- server_actions.push_back({owner, action});
771- }
772-
773- io.post([this] { process_server_actions(); });
774+ action_queue.enqueue(owner, action);
775 }
776
777 void mir::AsioMainLoop::pause_processing_for(void const* owner)
778 {
779- std::lock_guard<std::mutex> lock{server_actions_mutex};
780- do_not_process.insert(owner);
781+ action_queue.pause_processing_for(owner);
782 }
783
784 void mir::AsioMainLoop::resume_processing_for(void const* owner)
785 {
786- {
787- std::lock_guard<std::mutex> lock{server_actions_mutex};
788- do_not_process.erase(owner);
789- }
790-
791- io.post([this] { process_server_actions(); });
792-}
793-
794-void mir::AsioMainLoop::process_server_actions()
795-{
796- std::unique_lock<std::mutex> lock{server_actions_mutex};
797-
798- size_t i = 0;
799-
800- while (i < server_actions.size())
801- {
802- /*
803- * It's safe to use references to elements, since std::deque<>
804- * guarantees that references remain valid after appends, which is
805- * the only operation that can be performed on server_actions outside
806- * this function (in AsioMainLoop::post()).
807- */
808- auto const& owner = server_actions[i].first;
809- auto const& action = server_actions[i].second;
810-
811- if (do_not_process.find(owner) == do_not_process.end())
812- {
813- lock.unlock();
814- action();
815- lock.lock();
816- /*
817- * This erase is always ok, since outside this function
818- * we only append to server_actions, i.e., our index i
819- * is guaranteed to remain valid and correct.
820- */
821- server_actions.erase(server_actions.begin() + i);
822- }
823- else
824- {
825- ++i;
826- }
827- }
828+ action_queue.resume_processing_for(owner);
829 }
830
831=== renamed file 'include/server/mir/asio_main_loop.h' => 'src/server/asio_main_loop.h'
832--- include/server/mir/asio_main_loop.h 2014-06-12 15:35:08 +0000
833+++ src/server/asio_main_loop.h 2014-06-16 22:41:10 +0000
834@@ -21,8 +21,13 @@
835
836 #include "mir/main_loop.h"
837
838+#include "asio_server_action_queue.h"
839+
840 #include <boost/asio.hpp>
841+#include <boost/optional.hpp>
842+
843 #include <memory>
844+#include <thread>
845 #include <vector>
846 #include <mutex>
847 #include <utility>
848@@ -32,52 +37,41 @@
849 namespace mir
850 {
851
852-namespace time
853-{
854-class Clock;
855-}
856-
857 class AsioMainLoop : public MainLoop
858 {
859 public:
860- explicit AsioMainLoop(std::shared_ptr<time::Clock> const& clock);
861+ AsioMainLoop();
862 ~AsioMainLoop() noexcept(true);
863
864- void run();
865- void stop();
866+ void run() override;
867+ void stop() override;
868
869 void register_signal_handler(
870 std::initializer_list<int> signals,
871- std::function<void(int)> const& handler);
872+ std::function<void(int)> const& handler) override;
873
874 void register_fd_handler(
875 std::initializer_list<int> fd,
876- std::function<void(int)> const& handler);
877-
878- std::unique_ptr<time::Alarm> notify_in(std::chrono::milliseconds delay,
879- std::function<void()> callback) override;
880- std::unique_ptr<time::Alarm> notify_at(mir::time::Timestamp time_point,
881- std::function<void()> callback) override;
882- std::unique_ptr<time::Alarm> create_alarm(std::function<void()> callback) override;
883-
884- void enqueue(void const* owner, ServerAction const& action);
885- void pause_processing_for(void const* owner);
886- void resume_processing_for(void const* owner);
887+ void const* owner,
888+ std::function<void(int)> const& handler) override;
889+
890+ void unregister_fd_handler(void const* owner) override;
891+
892+ void enqueue(void const* owner, ServerAction const& action) override;
893+ void pause_processing_for(void const* owner) override;
894+ void resume_processing_for(void const* owner) override;
895
896 private:
897 class SignalHandler;
898 class FDHandler;
899- bool should_process(void const*);
900- void process_server_actions();
901
902 boost::asio::io_service io;
903 boost::asio::io_service::work work;
904+ boost::optional<std::thread::id> main_loop_thread;
905 std::vector<std::unique_ptr<SignalHandler>> signal_handlers;
906- std::vector<std::unique_ptr<FDHandler>> fd_handlers;
907- std::mutex server_actions_mutex;
908- std::deque<std::pair<void const*,ServerAction>> server_actions;
909- std::set<void const*> do_not_process;
910- std::shared_ptr<time::Clock> const clock;
911+ std::vector<std::shared_ptr<FDHandler>> fd_handlers;
912+ std::mutex fd_handlers_mutex;
913+ AsioServerActionQueue action_queue;
914 };
915
916 }
917
918=== added file 'src/server/asio_server_action_queue.cpp'
919--- src/server/asio_server_action_queue.cpp 1970-01-01 00:00:00 +0000
920+++ src/server/asio_server_action_queue.cpp 2014-06-16 22:41:10 +0000
921@@ -0,0 +1,91 @@
922+/*
923+ * Copyright © 2014 Canonical Ltd.
924+ *
925+ * This program is free software: you can redistribute it and/or modify it
926+ * under the terms of the GNU General Public License version 3,
927+ * as published by the Free Software Foundation.
928+ *
929+ * This program is distributed in the hope that it will be useful,
930+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
931+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
932+ * GNU General Public License for more details.
933+ *
934+ * You should have received a copy of the GNU General Public License
935+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
936+ *
937+ * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
938+ * Andreas Pokorny <andreas.pokorny@canonical.com>
939+ */
940+
941+#include "asio_server_action_queue.h"
942+
943+mir::AsioServerActionQueue::AsioServerActionQueue(boost::asio::io_service & service)
944+ : io(service)
945+{
946+}
947+
948+mir::AsioServerActionQueue::~AsioServerActionQueue() noexcept(true)
949+{
950+}
951+
952+void mir::AsioServerActionQueue::enqueue(void const* owner, ServerAction const& action)
953+{
954+ {
955+ std::lock_guard<std::mutex> lock{server_actions_mutex};
956+ server_actions.push_back({owner, action});
957+ }
958+
959+ io.post([this] { process_server_actions(); });
960+}
961+
962+void mir::AsioServerActionQueue::pause_processing_for(void const* owner)
963+{
964+ std::lock_guard<std::mutex> lock{server_actions_mutex};
965+ do_not_process.insert(owner);
966+}
967+
968+void mir::AsioServerActionQueue::resume_processing_for(void const* owner)
969+{
970+ {
971+ std::lock_guard<std::mutex> lock{server_actions_mutex};
972+ do_not_process.erase(owner);
973+ }
974+
975+ io.post([this] { process_server_actions(); });
976+}
977+
978+void mir::AsioServerActionQueue::process_server_actions()
979+{
980+ std::unique_lock<std::mutex> lock{server_actions_mutex};
981+
982+ size_t i = 0;
983+
984+ while (i < server_actions.size())
985+ {
986+ /*
987+ * It's safe to use references to elements, since std::deque<>
988+ * guarantees that references remain valid after appends, which is
989+ * the only operation that can be performed on server_actions outside
990+ * this function (in AsioServerActionQueue::post()).
991+ */
992+ auto const& owner = server_actions[i].first;
993+ auto const& action = server_actions[i].second;
994+
995+ if (do_not_process.find(owner) == do_not_process.end())
996+ {
997+ lock.unlock();
998+ action();
999+ lock.lock();
1000+ /*
1001+ * This erase is always ok, since outside this function
1002+ * we only append to server_actions, i.e., our index i
1003+ * is guaranteed to remain valid and correct.
1004+ */
1005+ server_actions.erase(server_actions.begin() + i);
1006+ }
1007+ else
1008+ {
1009+ ++i;
1010+ }
1011+ }
1012+}
1013
1014=== added file 'src/server/asio_server_action_queue.h'
1015--- src/server/asio_server_action_queue.h 1970-01-01 00:00:00 +0000
1016+++ src/server/asio_server_action_queue.h 2014-06-16 22:41:10 +0000
1017@@ -0,0 +1,58 @@
1018+/*
1019+ * Copyright © 2014 Canonical Ltd.
1020+ *
1021+ * This program is free software: you can redistribute it and/or modify it
1022+ * under the terms of the GNU General Public License version 3,
1023+ * as published by the Free Software Foundation.
1024+ *
1025+ * This program is distributed in the hope that it will be useful,
1026+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1027+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1028+ * GNU General Public License for more details.
1029+ *
1030+ * You should have received a copy of the GNU General Public License
1031+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1032+ *
1033+ * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
1034+ * Andreas Pokorny <andreas.pokorny@canonical.com>
1035+ */
1036+
1037+#ifndef MIR_ASIO_SERVER_ACTION_QUEUE
1038+#define MIR_ASIO_SERVER_ACTION_QUEUE
1039+
1040+#include "mir/server_action_queue.h"
1041+
1042+#include <boost/asio.hpp>
1043+
1044+#include <mutex>
1045+#include <condition_variable>
1046+#include <deque>
1047+#include <set>
1048+
1049+namespace mir
1050+{
1051+
1052+class AsioServerActionQueue : public ServerActionQueue
1053+{
1054+public:
1055+ explicit AsioServerActionQueue(boost::asio::io_service & service);
1056+ ~AsioServerActionQueue() noexcept(true);
1057+
1058+ void enqueue(void const* owner, ServerAction const& action) override;
1059+ void pause_processing_for(void const* owner) override;
1060+ void resume_processing_for(void const* owner) override;
1061+
1062+private:
1063+
1064+ bool should_process(void const*);
1065+ void process_server_actions();
1066+
1067+ boost::asio::io_service & io;
1068+ std::mutex server_actions_mutex;
1069+ std::deque<std::pair<void const*,ServerAction>> server_actions;
1070+ std::set<void const*> do_not_process;
1071+};
1072+
1073+}
1074+
1075+#endif
1076
1077=== added file 'src/server/asio_timer_service.cpp'
1078--- src/server/asio_timer_service.cpp 1970-01-01 00:00:00 +0000
1079+++ src/server/asio_timer_service.cpp 2014-06-16 22:41:10 +0000
1080@@ -0,0 +1,260 @@
1081+/*
1082+ * Copyright © 2014 Canonical Ltd.
1083+ *
1084+ * This program is free software: you can redistribute it and/or modify it
1085+ * under the terms of the GNU General Public License version 3,
1086+ * as published by the Free Software Foundation.
1087+ *
1088+ * This program is distributed in the hope that it will be useful,
1089+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1090+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1091+ * GNU General Public License for more details.
1092+ *
1093+ * You should have received a copy of the GNU General Public License
1094+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1095+ *
1096+ * Authored by: Andreas Pokorny <andreas.pokorny@canonical.com>
1097+ */
1098+
1099+#include "asio_timer_service.h"
1100+
1101+#include "boost/date_time/posix_time/conversion.hpp"
1102+#include <mutex>
1103+#include <atomic>
1104+
1105+namespace
1106+{
1107+struct MirClockTimerTraits
1108+{
1109+ // TODO the clock used by the main loop is a global setting, this is a restriction
1110+ // of boost::asio only allowing static methods inside the traits type.
1111+ struct TimerServiceClockStorage
1112+ {
1113+ public:
1114+ void set_clock(std::shared_ptr<mir::time::Clock const> const& clock)
1115+ {
1116+ std::lock_guard<std::mutex> lock(timer_service_mutex);
1117+ auto stored_clock = timer_service_clock.lock();
1118+ if (stored_clock && stored_clock != clock)
1119+ BOOST_THROW_EXCEPTION(std::logic_error("A clock is already in use as time source for mir::AsioTimerService"));
1120+ timer_service_clock = clock;
1121+ }
1122+ mir::time::Timestamp now()
1123+ {
1124+ std::lock_guard<std::mutex> lock(timer_service_mutex);
1125+ auto clock = timer_service_clock.lock();
1126+ if (!clock)
1127+ BOOST_THROW_EXCEPTION(std::logic_error("No clock available to create time stamp"));
1128+ return clock->sample();
1129+ }
1130+ private:
1131+ std::mutex timer_service_mutex;
1132+ std::weak_ptr<mir::time::Clock const> timer_service_clock;
1133+ };
1134+
1135+ static TimerServiceClockStorage clock_storage;
1136+
1137+ static void set_clock(std::shared_ptr<mir::time::Clock const> const& clock)
1138+ {
1139+ clock_storage.set_clock(clock);
1140+ }
1141+
1142+ // time_traits interface required by boost::asio::deadline_timer{_service}
1143+ typedef mir::time::Timestamp time_type;
1144+ typedef std::chrono::milliseconds duration_type;
1145+
1146+
1147+ static time_type now()
1148+ {
1149+ return clock_storage.now();
1150+ }
1151+
1152+ static time_type add(const time_type& t, const duration_type& d)
1153+ {
1154+ return t + d;
1155+ }
1156+
1157+ static duration_type subtract(const time_type& t1, const time_type& t2)
1158+ {
1159+ return std::chrono::duration_cast<duration_type>(t1 - t2);
1160+ }
1161+
1162+ static bool less_than(const time_type& t1, const time_type& t2)
1163+ {
1164+ return t1 < t2;
1165+ }
1166+
1167+ static boost::posix_time::time_duration to_posix_duration(
1168+ const duration_type& d)
1169+ {
1170+ return boost::posix_time::millisec(d.count());
1171+ }
1172+};
1173+
1174+MirClockTimerTraits::TimerServiceClockStorage MirClockTimerTraits::clock_storage;
1175+
1176+typedef boost::asio::basic_deadline_timer<mir::time::Timestamp, MirClockTimerTraits> deadline_timer;
1177+}
1178+
1179+class mir::AsioTimerService::AlarmImpl : public mir::time::Alarm
1180+{
1181+public:
1182+ AlarmImpl(boost::asio::io_service& io,
1183+ std::chrono::milliseconds delay,
1184+ std::function<void(void)> callback);
1185+
1186+ AlarmImpl(boost::asio::io_service& io,
1187+ mir::time::Timestamp time_point,
1188+ std::function<void(void)> callback);
1189+
1190+ AlarmImpl(boost::asio::io_service& io,
1191+ std::function<void(void)> callback);
1192+
1193+ ~AlarmImpl() noexcept override;
1194+
1195+ // mir::time::Alarm:
1196+ bool cancel() override;
1197+ State state() const override;
1198+
1199+ bool reschedule_in(std::chrono::milliseconds delay) override;
1200+ bool reschedule_for(mir::time::Timestamp time_point) override;
1201+
1202+private:
1203+ void update_timer();
1204+
1205+ struct InternalState
1206+ {
1207+ InternalState(std::function<void(void)> callback)
1208+ : callback{callback}, state{pending}
1209+ {
1210+ }
1211+
1212+ std::function<void(void)> callback;
1213+ std::atomic<State> state;
1214+ };
1215+
1216+ ::deadline_timer timer;
1217+ std::shared_ptr<InternalState> data;
1218+};
1219+
1220+mir::AsioTimerService::AlarmImpl::AlarmImpl(boost::asio::io_service& io,
1221+ std::chrono::milliseconds delay,
1222+ std::function<void()> callback)
1223+ : AlarmImpl(io, callback)
1224+{
1225+ reschedule_in(delay);
1226+}
1227+
1228+mir::AsioTimerService::AlarmImpl::AlarmImpl(boost::asio::io_service& io,
1229+ mir::time::Timestamp time_point,
1230+ std::function<void()> callback)
1231+ : AlarmImpl(io, callback)
1232+{
1233+ reschedule_for(time_point);
1234+}
1235+
1236+mir::AsioTimerService::AlarmImpl::AlarmImpl(boost::asio::io_service& io,
1237+ std::function<void(void)> callback)
1238+ : timer{io},
1239+ data{std::make_shared<InternalState>(callback)}
1240+{
1241+ data->state = triggered;
1242+}
1243+
1244+mir::AsioTimerService::AlarmImpl::~AlarmImpl() noexcept
1245+{
1246+ AlarmImpl::cancel();
1247+}
1248+
1249+bool mir::AsioTimerService::AlarmImpl::cancel()
1250+{
1251+ State expected_state = pending;
1252+ if (data->state.compare_exchange_strong(expected_state, cancelled))
1253+ {
1254+ timer.cancel();
1255+ return true;
1256+ }
1257+ return false;
1258+}
1259+
1260+mir::time::Alarm::State mir::AsioTimerService::AlarmImpl::state() const
1261+{
1262+ return data->state;
1263+}
1264+
1265+bool mir::AsioTimerService::AlarmImpl::reschedule_in(std::chrono::milliseconds delay)
1266+{
1267+ bool cancelling = timer.expires_from_now(delay);
1268+ update_timer();
1269+ return cancelling;
1270+}
1271+
1272+bool mir::AsioTimerService::AlarmImpl::reschedule_for(mir::time::Timestamp time_point)
1273+{
1274+ bool cancelling = timer.expires_at(time_point);
1275+ update_timer();
1276+ return cancelling;
1277+}
1278+
1279+void mir::AsioTimerService::AlarmImpl::update_timer()
1280+{
1281+ auto new_internal_state = std::make_shared<InternalState>(data->callback);
1282+
1283+ // Awkwardly, we can't stop the async_wait handler from being called
1284+ // on a destroyed AlarmImpl. This means we need to wedge a shared_ptr
1285+ // into the async_wait callback.
1286+ std::weak_ptr<InternalState> possible_data = new_internal_state;
1287+ timer.async_wait([possible_data](boost::system::error_code const& ec)
1288+ {
1289+ if (ec)
1290+ return;
1291+
1292+ auto data = possible_data.lock();
1293+ if (!data)
1294+ return;
1295+
1296+ State expected_state = pending;
1297+ if (data->state.compare_exchange_strong(expected_state, triggered))
1298+ data->callback();
1299+ });
1300+
1301+ data = new_internal_state;
1302+}
1303+
1304+mir::AsioTimerService::AsioTimerService(std::shared_ptr<time::Clock> const& clock)
1305+ : work{io}, clock(clock)
1306+{
1307+ MirClockTimerTraits::set_clock(clock);
1308+}
1309+
1310+mir::AsioTimerService::~AsioTimerService() noexcept(true)
1311+{
1312+}
1313+
1314+void mir::AsioTimerService::run()
1315+{
1316+ io.run();
1317+}
1318+
1319+void mir::AsioTimerService::stop()
1320+{
1321+ io.stop();
1322+}
1323+
1324+std::unique_ptr<mir::time::Alarm> mir::AsioTimerService::notify_in(std::chrono::milliseconds delay,
1325+ std::function<void()> callback)
1326+{
1327+ return std::unique_ptr<mir::time::Alarm>{new AlarmImpl{io, delay, callback}};
1328+}
1329+
1330+std::unique_ptr<mir::time::Alarm> mir::AsioTimerService::notify_at(mir::time::Timestamp time_point,
1331+ std::function<void()> callback)
1332+{
1333+ return std::unique_ptr<mir::time::Alarm>{new AlarmImpl{io, time_point, callback}};
1334+
1335+}
1336+
1337+std::unique_ptr<mir::time::Alarm> mir::AsioTimerService::create_alarm(std::function<void()> callback)
1338+{
1339+ return std::unique_ptr<mir::time::Alarm>{new AlarmImpl{io, callback}};
1340+}
1341
1342=== added file 'src/server/asio_timer_service.h'
1343--- src/server/asio_timer_service.h 1970-01-01 00:00:00 +0000
1344+++ src/server/asio_timer_service.h 2014-06-16 22:41:10 +0000
1345@@ -0,0 +1,58 @@
1346+/*
1347+ * Copyright © 2014 Canonical Ltd.
1348+ *
1349+ * This program is free software: you can redistribute it and/or modify it
1350+ * under the terms of the GNU General Public License version 3,
1351+ * as published by the Free Software Foundation.
1352+ *
1353+ * This program is distributed in the hope that it will be useful,
1354+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1355+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1356+ * GNU General Public License for more details.
1357+ *
1358+ * You should have received a copy of the GNU General Public License
1359+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1360+ *
1361+ * Authored by: Andreas Pokorny <andreas.pokorny@canonical.com>
1362+ */
1363+
1364+#ifndef MIR_ASIO_TIMER_SERVICE_H_
1365+#define MIR_ASIO_TIMER_SERVICE_H_
1366+
1367+#include "mir/time/timer_loop.h"
1368+
1369+#include <boost/asio.hpp>
1370+#include <boost/optional.hpp>
1371+
1372+#include <memory>
1373+#include <thread>
1374+#include <mutex>
1375+
1376+namespace mir
1377+{
1378+
1379+class AsioTimerService : public time::TimerLoop
1380+{
1381+public:
1382+ explicit AsioTimerService(std::shared_ptr<time::Clock> const& clock);
1383+ ~AsioTimerService() noexcept(true);
1384+
1385+ void run() override;
1386+ void stop() override;
1387+
1388+ std::unique_ptr<time::Alarm> notify_in(std::chrono::milliseconds delay,
1389+ std::function<void()> callback) override;
1390+ std::unique_ptr<time::Alarm> notify_at(mir::time::Timestamp time_point,
1391+ std::function<void()> callback) override;
1392+ std::unique_ptr<time::Alarm> create_alarm(std::function<void()> callback) override;
1393+private:
1394+ class AlarmImpl;
1395+ boost::asio::io_service io;
1396+ boost::asio::io_service::work work;
1397+
1398+ std::shared_ptr<time::Clock> const clock;
1399+};
1400+
1401+}
1402+
1403+#endif
1404
1405=== modified file 'src/server/compositor/default_configuration.cpp'
1406--- src/server/compositor/default_configuration.cpp 2014-06-09 17:16:32 +0000
1407+++ src/server/compositor/default_configuration.cpp 2014-06-16 22:41:10 +0000
1408@@ -52,7 +52,7 @@
1409 return frame_dropping_policy_factory(
1410 [this]()
1411 {
1412- return std::make_shared<mc::TimeoutFrameDroppingPolicyFactory>(the_main_loop(),
1413+ return std::make_shared<mc::TimeoutFrameDroppingPolicyFactory>(the_timer(),
1414 std::chrono::milliseconds{100});
1415 });
1416 }
1417
1418=== modified file 'src/server/default_server_configuration.cpp'
1419--- src/server/default_server_configuration.cpp 2014-06-11 16:11:32 +0000
1420+++ src/server/default_server_configuration.cpp 2014-06-16 22:41:10 +0000
1421@@ -19,7 +19,6 @@
1422 #include "mir/default_server_configuration.h"
1423 #include "mir/options/default_configuration.h"
1424 #include "mir/abnormal_exit.h"
1425-#include "mir/asio_main_loop.h"
1426 #include "mir/default_server_status_listener.h"
1427 #include "mir/emergency_cleanup.h"
1428 #include "mir/default_configuration.h"
1429@@ -39,6 +38,9 @@
1430 #include "mir/default_configuration.h"
1431 #include "mir/scene/null_prompt_session_listener.h"
1432
1433+#include "asio_timer_service.h"
1434+#include "asio_main_loop.h"
1435+
1436 #include <map>
1437 #include <vector>
1438 #include <mutex>
1439@@ -52,6 +54,31 @@
1440 namespace msh = mir::shell;
1441 namespace mi = mir::input;
1442
1443+namespace
1444+{
1445+class AsioTimerServiceThread : public mir::AsioTimerService
1446+{
1447+public:
1448+ AsioTimerServiceThread(std::shared_ptr<mir::time::Clock> const& clock)
1449+ : AsioTimerService(clock)
1450+ {}
1451+ void run() override
1452+ {
1453+ service_thread = std::thread([this]{mir::AsioTimerService::run();});
1454+ }
1455+ void stop() override
1456+ {
1457+ if (service_thread.joinable())
1458+ {
1459+ mir::AsioTimerService::stop();
1460+ service_thread.join();
1461+ }
1462+ }
1463+private:
1464+ std::thread service_thread;
1465+};
1466+}
1467+
1468 mir::DefaultServerConfiguration::DefaultServerConfiguration(int argc, char const* argv[]) :
1469 DefaultServerConfiguration(std::make_shared<mo::DefaultConfiguration>(argc, argv))
1470 {
1471@@ -185,7 +212,25 @@
1472 return main_loop(
1473 [this]()
1474 {
1475- return std::make_shared<mir::AsioMainLoop>(the_clock());
1476+ return std::make_shared<mir::AsioMainLoop>();
1477+ });
1478+}
1479+
1480+std::shared_ptr<mir::time::Timer> mir::DefaultServerConfiguration::the_timer()
1481+{
1482+ return timer_loop(
1483+ [this]()
1484+ {
1485+ return std::make_shared<AsioTimerServiceThread>(the_clock());
1486+ });
1487+}
1488+
1489+std::shared_ptr<mir::Loop> mir::DefaultServerConfiguration::the_timer_loop()
1490+{
1491+ return timer_loop(
1492+ [this]()
1493+ {
1494+ return std::make_shared<AsioTimerServiceThread>(the_clock());
1495 });
1496 }
1497
1498
1499=== modified file 'src/server/display_server.cpp'
1500--- src/server/display_server.cpp 2014-06-03 11:04:15 +0000
1501+++ src/server/display_server.cpp 2014-06-16 22:41:10 +0000
1502@@ -21,6 +21,7 @@
1503 #include "mir/display_server.h"
1504 #include "mir/server_configuration.h"
1505 #include "mir/main_loop.h"
1506+#include "mir/loop.h"
1507 #include "mir/server_status_listener.h"
1508 #include "mir/display_changer.h"
1509
1510@@ -80,7 +81,8 @@
1511 input_manager{config.the_input_manager()},
1512 main_loop{config.the_main_loop()},
1513 server_status_listener{config.the_server_status_listener()},
1514- display_changer{config.the_display_changer()}
1515+ display_changer{config.the_display_changer()},
1516+ timer_loop{config.the_timer_loop()}
1517 {
1518 display->register_configuration_change_handler(
1519 *main_loop,
1520@@ -96,6 +98,10 @@
1521 {
1522 try
1523 {
1524+ TryButRevertIfUnwinding timers{
1525+ [this] { timer_loop->stop(); },
1526+ [this] { timer_loop->run(); }};
1527+
1528 TryButRevertIfUnwinding dispatcher{
1529 [this] { input_dispatcher->stop(); },
1530 [this] { input_dispatcher->start(); }};
1531@@ -152,6 +158,10 @@
1532 [this] { input_dispatcher->start(); },
1533 [this] { input_dispatcher->stop(); }};
1534
1535+ TryButRevertIfUnwinding timers{
1536+ [this] { timer_loop->run(); },
1537+ [this] { timer_loop->stop(); }};
1538+
1539 compositor->start();
1540 }
1541 catch(std::runtime_error const&)
1542@@ -184,6 +194,7 @@
1543 std::shared_ptr<mir::MainLoop> const main_loop;
1544 std::shared_ptr<mir::ServerStatusListener> const server_status_listener;
1545 std::shared_ptr<mir::DisplayChanger> const display_changer;
1546+ std::shared_ptr<mir::Loop> const timer_loop;
1547 };
1548
1549 mir::DisplayServer::DisplayServer(ServerConfiguration& config) :
1550@@ -206,11 +217,13 @@
1551 p->compositor->start();
1552 p->input_manager->start();
1553 p->input_dispatcher->start();
1554+ p->timer_loop->run();
1555
1556 p->server_status_listener->started();
1557
1558 p->main_loop->run();
1559
1560+ p->timer_loop->stop();
1561 p->input_dispatcher->stop();
1562 p->input_manager->stop();
1563 p->compositor->stop();
1564
1565=== added file 'src/server/synchronous_server_action.cpp'
1566--- src/server/synchronous_server_action.cpp 1970-01-01 00:00:00 +0000
1567+++ src/server/synchronous_server_action.cpp 2014-06-16 22:41:10 +0000
1568@@ -0,0 +1,50 @@
1569+/*
1570+ * Copyright © 2014 Canonical Ltd.
1571+ *
1572+ * This program is free software: you can redistribute it and/or modify it
1573+ * under the terms of the GNU General Public License version 3,
1574+ * as published by the Free Software Foundation.
1575+ *
1576+ * This program is distributed in the hope that it will be useful,
1577+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1578+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1579+ * GNU General Public License for more details.
1580+ *
1581+ * You should have received a copy of the GNU General Public License
1582+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1583+ *
1584+ * Authored by: Andreas Pokorny <andreas.pokorny@canonical.com>
1585+ */
1586+
1587+#include "synchronous_server_action.h"
1588+
1589+mir::SynchronousServerAction::SynchronousServerAction(
1590+ ServerActionQueue & queue,
1591+ boost::optional<std::thread::id> queue_thread_id,
1592+ ServerAction const& action) :
1593+ done{false}
1594+{
1595+ if (queue_thread_id &&
1596+ *queue_thread_id != std::this_thread::get_id())
1597+ {
1598+ queue.enqueue(this,
1599+ [this,action]()
1600+ {
1601+ action();
1602+ std::lock_guard<std::mutex> lock(done_mutex);
1603+ done = true;
1604+ done_condition.notify_one();
1605+ });
1606+ }
1607+ else
1608+ {
1609+ done = true;
1610+ action();
1611+ }
1612+}
1613+
1614+mir::SynchronousServerAction::~SynchronousServerAction()
1615+{
1616+ std::unique_lock<std::mutex> lock(done_mutex);
1617+ while(!done) done_condition.wait(lock);
1618+}
1619
1620=== added file 'src/server/synchronous_server_action.h'
1621--- src/server/synchronous_server_action.h 1970-01-01 00:00:00 +0000
1622+++ src/server/synchronous_server_action.h 2014-06-16 22:41:10 +0000
1623@@ -0,0 +1,48 @@
1624+/*
1625+ * Copyright © 2014 Canonical Ltd.
1626+ *
1627+ * This program is free software: you can redistribute it and/or modify it
1628+ * under the terms of the GNU General Public License version 3,
1629+ * as published by the Free Software Foundation.
1630+ *
1631+ * This program is distributed in the hope that it will be useful,
1632+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1633+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1634+ * GNU General Public License for more details.
1635+ *
1636+ * You should have received a copy of the GNU General Public License
1637+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1638+ *
1639+ * Authored by: Andreas Pokorny <andreas.pokorny@canonical.com>
1640+ */
1641+
1642+#ifndef MIR_SYNCHRONOUS_SERVER_ACTION_H_
1643+#define MIR_SYNCHRONOUS_SERVER_ACTION_H_
1644+
1645+#include "mir/server_action_queue.h"
1646+
1647+#include <boost/optional.hpp>
1648+
1649+#include <thread>
1650+#include <mutex>
1651+#include <condition_variable>
1652+
1653+namespace mir
1654+{
1655+
1656+class SynchronousServerAction
1657+{
1658+public:
1659+ SynchronousServerAction(ServerActionQueue & queue,
1660+ boost::optional<std::thread::id> queue_thread_id,
1661+ ServerAction const& action);
1662+ ~SynchronousServerAction();
1663+private:
1664+ std::mutex done_mutex;
1665+ bool done;
1666+ std::condition_variable done_condition;
1667+};
1668+
1669+}
1670+
1671+#endif
1672
1673=== modified file 'tests/acceptance-tests/test_display_configuration.cpp'
1674--- tests/acceptance-tests/test_display_configuration.cpp 2014-06-02 17:07:02 +0000
1675+++ tests/acceptance-tests/test_display_configuration.cpp 2014-06-16 22:41:10 +0000
1676@@ -103,6 +103,7 @@
1677 {
1678 handlers.register_fd_handler(
1679 {p.read_fd()},
1680+ this,
1681 [this, handler](int fd)
1682 {
1683 char c;
1684
1685=== modified file 'tests/integration-tests/test_display_server_main_loop_events.cpp'
1686--- tests/integration-tests/test_display_server_main_loop_events.cpp 2014-06-03 11:04:15 +0000
1687+++ tests/integration-tests/test_display_server_main_loop_events.cpp 2014-06-16 22:41:10 +0000
1688@@ -30,6 +30,7 @@
1689 #include "mir_test_doubles/mock_input_manager.h"
1690 #include "mir_test_doubles/mock_input_dispatcher.h"
1691 #include "mir_test_doubles/mock_compositor.h"
1692+#include "mir_test_doubles/mock_loop.h"
1693 #include "mir_test_doubles/null_display.h"
1694 #include "mir_test_doubles/mock_server_status_listener.h"
1695 #include "mir/run_mir.h"
1696@@ -104,6 +105,7 @@
1697 {
1698 handlers.register_fd_handler(
1699 {fd},
1700+ this,
1701 [this,conf_change_handler](int fd)
1702 {
1703 char c;
1704@@ -253,6 +255,14 @@
1705 return mock_input_dispatcher;
1706 }
1707
1708+ std::shared_ptr<mir::Loop> the_timer_loop() override
1709+ {
1710+ if (!mock_timer_loop)
1711+ mock_timer_loop = std::make_shared<mtd::MockLoop>();
1712+
1713+ return mock_timer_loop;
1714+ }
1715+
1716 std::shared_ptr<MockDisplay> the_mock_display()
1717 {
1718 the_display();
1719@@ -271,6 +281,12 @@
1720 return mock_connector;
1721 }
1722
1723+ std::shared_ptr<mtd::MockLoop> the_mock_timer_loop()
1724+ {
1725+ the_timer_loop();
1726+ return mock_timer_loop;
1727+ }
1728+
1729 std::shared_ptr<mtd::MockInputManager> the_mock_input_manager()
1730 {
1731 the_input_manager();
1732@@ -319,6 +335,7 @@
1733 std::shared_ptr<MockConnector> mock_connector;
1734 std::shared_ptr<mtd::MockInputManager> mock_input_manager;
1735 std::shared_ptr<mtd::MockInputDispatcher> mock_input_dispatcher;
1736+ std::shared_ptr<mtd::MockLoop> mock_timer_loop;
1737
1738 mt::Pipe p;
1739 int const pause_signal;
1740@@ -381,6 +398,7 @@
1741 auto mock_connector = server_config.the_mock_connector();
1742 auto mock_input_manager = server_config.the_mock_input_manager();
1743 auto mock_input_dispatcher = server_config.the_mock_input_dispatcher();
1744+ auto mock_timer_loop = server_config.the_mock_timer_loop();
1745
1746 {
1747 InSequence s;
1748@@ -390,8 +408,10 @@
1749 EXPECT_CALL(*mock_compositor, start()).Times(1);
1750 EXPECT_CALL(*mock_input_manager, start()).Times(1);
1751 EXPECT_CALL(*mock_input_dispatcher, start()).Times(1);
1752+ EXPECT_CALL(*mock_timer_loop, run()).Times(1);
1753
1754 /* Pause */
1755+ EXPECT_CALL(*mock_timer_loop, stop()).Times(1);
1756 EXPECT_CALL(*mock_input_dispatcher, stop()).Times(1);
1757 EXPECT_CALL(*mock_input_manager, stop()).Times(1);
1758 EXPECT_CALL(*mock_compositor, stop()).Times(1);
1759@@ -403,9 +423,11 @@
1760 EXPECT_CALL(*mock_connector, start()).Times(1);
1761 EXPECT_CALL(*mock_input_manager, start()).Times(1);
1762 EXPECT_CALL(*mock_input_dispatcher, start()).Times(1);
1763+ EXPECT_CALL(*mock_timer_loop, run()).Times(1);
1764 EXPECT_CALL(*mock_compositor, start()).Times(1);
1765
1766 /* Stop */
1767+ EXPECT_CALL(*mock_timer_loop, stop()).Times(1);
1768 EXPECT_CALL(*mock_input_dispatcher, stop()).Times(1);
1769 EXPECT_CALL(*mock_input_manager, stop()).Times(1);
1770 EXPECT_CALL(*mock_compositor, stop()).Times(1);
1771@@ -437,6 +459,7 @@
1772 auto mock_connector = server_config.the_mock_connector();
1773 auto mock_input_manager = server_config.the_mock_input_manager();
1774 auto mock_input_dispatcher = server_config.the_mock_input_dispatcher();
1775+ auto mock_timer_loop = server_config.the_mock_timer_loop();
1776
1777 {
1778 InSequence s;
1779@@ -446,8 +469,10 @@
1780 EXPECT_CALL(*mock_compositor, start()).Times(1);
1781 EXPECT_CALL(*mock_input_manager, start()).Times(1);
1782 EXPECT_CALL(*mock_input_dispatcher, start()).Times(1);
1783+ EXPECT_CALL(*mock_timer_loop, run()).Times(1);
1784
1785 /* Pause */
1786+ EXPECT_CALL(*mock_timer_loop, stop()).Times(1);
1787 EXPECT_CALL(*mock_input_dispatcher, stop()).Times(1);
1788 EXPECT_CALL(*mock_input_manager, stop()).Times(1);
1789 EXPECT_CALL(*mock_compositor, stop()).Times(1);
1790@@ -455,6 +480,7 @@
1791 EXPECT_CALL(*mock_display, pause()).Times(1);
1792
1793 /* Stop */
1794+ EXPECT_CALL(*mock_timer_loop, stop()).Times(1);
1795 EXPECT_CALL(*mock_input_dispatcher, stop()).Times(1);
1796 EXPECT_CALL(*mock_input_manager, stop()).Times(1);
1797 EXPECT_CALL(*mock_compositor, stop()).Times(1);
1798@@ -485,6 +511,7 @@
1799 auto mock_connector = server_config.the_mock_connector();
1800 auto mock_input_manager = server_config.the_mock_input_manager();
1801 auto mock_input_dispatcher = server_config.the_mock_input_dispatcher();
1802+ auto mock_timer_loop = server_config.the_mock_timer_loop();
1803
1804 {
1805 InSequence s;
1806@@ -494,8 +521,10 @@
1807 EXPECT_CALL(*mock_compositor, start()).Times(1);
1808 EXPECT_CALL(*mock_input_manager, start()).Times(1);
1809 EXPECT_CALL(*mock_input_dispatcher, start()).Times(1);
1810+ EXPECT_CALL(*mock_timer_loop, run()).Times(1);
1811
1812 /* Pause failure */
1813+ EXPECT_CALL(*mock_timer_loop, stop()).Times(1);
1814 EXPECT_CALL(*mock_input_dispatcher, stop()).Times(1);
1815 EXPECT_CALL(*mock_input_manager, stop()).Times(1);
1816 EXPECT_CALL(*mock_compositor, stop()).Times(1);
1817@@ -508,8 +537,10 @@
1818 EXPECT_CALL(*mock_compositor, start()).Times(1);
1819 EXPECT_CALL(*mock_input_manager, start()).Times(1);
1820 EXPECT_CALL(*mock_input_dispatcher, start()).Times(1);
1821+ EXPECT_CALL(*mock_timer_loop, run()).Times(1);
1822
1823 /* Stop */
1824+ EXPECT_CALL(*mock_timer_loop, stop()).Times(1);
1825 EXPECT_CALL(*mock_input_dispatcher, stop()).Times(1);
1826 EXPECT_CALL(*mock_input_manager, stop()).Times(1);
1827 EXPECT_CALL(*mock_compositor, stop()).Times(1);
1828@@ -540,6 +571,7 @@
1829 auto mock_connector = server_config.the_mock_connector();
1830 auto mock_input_manager = server_config.the_mock_input_manager();
1831 auto mock_input_dispatcher = server_config.the_mock_input_dispatcher();
1832+ auto mock_timer_loop = server_config.the_mock_timer_loop();
1833
1834 {
1835 InSequence s;
1836@@ -549,6 +581,7 @@
1837 EXPECT_CALL(*mock_compositor, start()).Times(1);
1838 EXPECT_CALL(*mock_input_manager, start()).Times(1);
1839 EXPECT_CALL(*mock_input_dispatcher, start()).Times(1);
1840+ EXPECT_CALL(*mock_timer_loop, run()).Times(1);
1841
1842 /* Change configuration */
1843 EXPECT_CALL(*mock_compositor, stop()).Times(1);
1844@@ -556,6 +589,7 @@
1845 EXPECT_CALL(*mock_compositor, start()).Times(1);
1846
1847 /* Stop */
1848+ EXPECT_CALL(*mock_timer_loop, stop()).Times(1);
1849 EXPECT_CALL(*mock_input_dispatcher, stop()).Times(1);
1850 EXPECT_CALL(*mock_input_manager, stop()).Times(1);
1851 EXPECT_CALL(*mock_compositor, stop()).Times(1);
1852@@ -587,6 +621,7 @@
1853 auto mock_connector = server_config.the_mock_connector();
1854 auto mock_input_manager = server_config.the_mock_input_manager();
1855 auto mock_input_dispatcher = server_config.the_mock_input_dispatcher();
1856+ auto mock_timer_loop = server_config.the_mock_timer_loop();
1857
1858 {
1859 InSequence s;
1860@@ -596,8 +631,10 @@
1861 EXPECT_CALL(*mock_compositor, start()).Times(1);
1862 EXPECT_CALL(*mock_input_manager, start()).Times(1);
1863 EXPECT_CALL(*mock_input_dispatcher, start()).Times(1);
1864+ EXPECT_CALL(*mock_timer_loop, run()).Times(1);
1865
1866 /* Pause event */
1867+ EXPECT_CALL(*mock_timer_loop, stop()).Times(1);
1868 EXPECT_CALL(*mock_input_dispatcher, stop()).Times(1);
1869 EXPECT_CALL(*mock_input_manager, stop()).Times(1);
1870 EXPECT_CALL(*mock_compositor, stop()).Times(1);
1871@@ -609,6 +646,7 @@
1872 EXPECT_CALL(*mock_connector, start()).Times(1);
1873 EXPECT_CALL(*mock_input_manager, start()).Times(1);
1874 EXPECT_CALL(*mock_input_dispatcher, start()).Times(1);
1875+ EXPECT_CALL(*mock_timer_loop, run()).Times(1);
1876 EXPECT_CALL(*mock_compositor, start()).Times(1);
1877
1878 /* Change configuration (after resuming) */
1879@@ -617,6 +655,7 @@
1880 EXPECT_CALL(*mock_compositor, start()).Times(1);
1881
1882 /* Stop */
1883+ EXPECT_CALL(*mock_timer_loop, stop()).Times(1);
1884 EXPECT_CALL(*mock_input_dispatcher, stop()).Times(1);
1885 EXPECT_CALL(*mock_input_manager, stop()).Times(1);
1886 EXPECT_CALL(*mock_compositor, stop()).Times(1);
1887@@ -652,6 +691,7 @@
1888 auto mock_input_manager = server_config.the_mock_input_manager();
1889 auto mock_input_dispatcher = server_config.the_mock_input_dispatcher();
1890 auto mock_server_status_listener = server_config.the_mock_server_status_listener();
1891+ auto mock_timer_loop = server_config.the_mock_timer_loop();
1892
1893 {
1894 InSequence s;
1895@@ -661,9 +701,11 @@
1896 EXPECT_CALL(*mock_compositor, start()).Times(1);
1897 EXPECT_CALL(*mock_input_manager, start()).Times(1);
1898 EXPECT_CALL(*mock_input_dispatcher, start()).Times(1);
1899+ EXPECT_CALL(*mock_timer_loop, run()).Times(1);
1900 EXPECT_CALL(*mock_server_status_listener, started()).Times(1);
1901
1902 /* "paused" is emitted after all components have been paused/stopped */
1903+ EXPECT_CALL(*mock_timer_loop, stop()).Times(1);
1904 EXPECT_CALL(*mock_input_dispatcher, stop()).Times(1);
1905 EXPECT_CALL(*mock_input_manager, stop()).Times(1);
1906 EXPECT_CALL(*mock_compositor, stop()).Times(1);
1907@@ -676,10 +718,12 @@
1908 EXPECT_CALL(*mock_connector, start()).Times(1);
1909 EXPECT_CALL(*mock_input_manager, start()).Times(1);
1910 EXPECT_CALL(*mock_input_dispatcher, start()).Times(1);
1911+ EXPECT_CALL(*mock_timer_loop, run()).Times(1);
1912 EXPECT_CALL(*mock_compositor, start()).Times(1);
1913 EXPECT_CALL(*mock_server_status_listener, resumed()).Times(1);
1914
1915 /* Stop */
1916+ EXPECT_CALL(*mock_timer_loop, stop()).Times(1);
1917 EXPECT_CALL(*mock_input_dispatcher, stop()).Times(1);
1918 EXPECT_CALL(*mock_input_manager, stop()).Times(1);
1919 EXPECT_CALL(*mock_compositor, stop()).Times(1);
1920
1921=== modified file 'tests/unit-tests/CMakeLists.txt'
1922--- tests/unit-tests/CMakeLists.txt 2014-06-04 15:30:21 +0000
1923+++ tests/unit-tests/CMakeLists.txt 2014-06-16 22:41:10 +0000
1924@@ -8,7 +8,10 @@
1925 UNIT_TEST_SOURCES
1926
1927 test_gmock_fixes.cpp
1928+ test_synchronous_server_action.cpp
1929 test_asio_main_loop.cpp
1930+ test_asio_server_action_queue.cpp
1931+ test_asio_timer_service.cpp
1932 shared_library_test.cpp
1933 test_raii.cpp
1934 test_udev_wrapper.cpp
1935
1936=== modified file 'tests/unit-tests/graphics/mesa/test_display.cpp'
1937--- tests/unit-tests/graphics/mesa/test_display.cpp 2014-06-10 14:40:23 +0000
1938+++ tests/unit-tests/graphics/mesa/test_display.cpp 2014-06-16 22:41:10 +0000
1939@@ -23,13 +23,13 @@
1940 #include "mir/logging/logger.h"
1941 #include "mir/graphics/display_buffer.h"
1942 #include "src/server/graphics/default_display_configuration_policy.h"
1943-#include "mir/time/high_resolution_clock.h"
1944-#include "mir/asio_main_loop.h"
1945+#include "src/server/asio_main_loop.h"
1946
1947 #include "mir_test_doubles/mock_egl.h"
1948 #include "mir_test_doubles/mock_gl.h"
1949 #include "src/server/report/null_report_factory.h"
1950 #include "mir_test_doubles/mock_display_report.h"
1951+#include "mir_test_doubles/mock_event_handler_register.h"
1952 #include "mir_test_doubles/null_virtual_terminal.h"
1953 #include "mir_test_doubles/stub_gl_config.h"
1954 #include "mir_test_doubles/mock_gl_config.h"
1955@@ -69,18 +69,6 @@
1956 ~MockLogger() noexcept(true) {}
1957 };
1958
1959-class MockEventRegister : public mg::EventHandlerRegister
1960-{
1961-public:
1962- MOCK_METHOD2(register_signal_handler,
1963- void(std::initializer_list<int>,
1964- std::function<void(int)> const&));
1965- MOCK_METHOD2(register_fd_handler,
1966- void(std::initializer_list<int>,
1967- std::function<void(int)> const&));
1968-};
1969-
1970-
1971 class MesaDisplayTest : public ::testing::Test
1972 {
1973 public:
1974@@ -706,9 +694,9 @@
1975 using namespace testing;
1976
1977 auto display = create_display(create_platform());
1978- MockEventRegister mock_register;
1979+ mtd::MockEventHandlerRegister mock_register;
1980
1981- EXPECT_CALL(mock_register, register_fd_handler(_,_));
1982+ EXPECT_CALL(mock_register, register_fd_handler(_,_,_));
1983
1984 display->register_configuration_change_handler(mock_register, []{});
1985 }
1986@@ -719,7 +707,7 @@
1987
1988 auto display = create_display(create_platform());
1989
1990- mir::AsioMainLoop ml{std::make_shared<mir::time::HighResolutionClock>()};
1991+ mir::AsioMainLoop ml;
1992 std::condition_variable done;
1993
1994 int const device_add_count{1};
1995
1996=== modified file 'tests/unit-tests/graphics/mesa/test_linux_virtual_terminal.cpp'
1997--- tests/unit-tests/graphics/mesa/test_linux_virtual_terminal.cpp 2014-06-14 19:52:52 +0000
1998+++ tests/unit-tests/graphics/mesa/test_linux_virtual_terminal.cpp 2014-06-16 22:41:10 +0000
1999@@ -18,10 +18,10 @@
2000
2001 #include "src/platform/graphics/mesa/linux_virtual_terminal.h"
2002 #include "src/server/report/null_report_factory.h"
2003-#include "mir/graphics/event_handler_register.h"
2004
2005 #include "mir_test/fake_shared.h"
2006 #include "mir_test_doubles/mock_display_report.h"
2007+#include "mir_test_doubles/mock_event_handler_register.h"
2008 #include "mir_test/gmock_fixes.h"
2009
2010 #include <gtest/gtest.h>
2011@@ -70,20 +70,6 @@
2012 // Add a typedef to aid clarity.
2013 typedef testing::NiceMock<MockPosixProcessOperations> StubPosixProcessOperations;
2014
2015-class MockEventHandlerRegister : public mg::EventHandlerRegister
2016-{
2017-public:
2018- ~MockEventHandlerRegister() noexcept {}
2019-
2020- MOCK_METHOD2(register_signal_handler,
2021- void(std::initializer_list<int>,
2022- std::function<void(int)> const&));
2023-
2024- MOCK_METHOD2(register_fd_handler,
2025- void(std::initializer_list<int>,
2026- std::function<void(int)> const&));
2027-};
2028-
2029 ACTION_TEMPLATE(SetIoctlPointee,
2030 HAS_1_TEMPLATE_PARAMS(typename, T),
2031 AND_1_VALUE_PARAMS(param))
2032@@ -254,7 +240,7 @@
2033 struct termios fake_tc_attr;
2034 std::function<void(int)> sig_handler;
2035 MockVTFileOperations mock_fops;
2036- MockEventHandlerRegister mock_event_handler_register;
2037+ mtd::MockEventHandlerRegister mock_event_handler_register;
2038 };
2039
2040
2041
2042=== modified file 'tests/unit-tests/test_asio_main_loop.cpp'
2043--- tests/unit-tests/test_asio_main_loop.cpp 2014-06-12 15:35:08 +0000
2044+++ tests/unit-tests/test_asio_main_loop.cpp 2014-06-16 22:41:10 +0000
2045@@ -16,8 +16,8 @@
2046 * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
2047 */
2048
2049-#include "mir/asio_main_loop.h"
2050-#include "mir/time/high_resolution_clock.h"
2051+#include "src/server/asio_main_loop.h"
2052+
2053 #include "mir_test/pipe.h"
2054 #include "mir_test/auto_unblock_thread.h"
2055 #include "mir_test/signal.h"
2056@@ -45,68 +45,7 @@
2057 class AsioMainLoopTest : public ::testing::Test
2058 {
2059 public:
2060- mir::AsioMainLoop ml{std::make_shared<mir::time::HighResolutionClock>()};
2061-};
2062-
2063-class AdvanceableClock : public mir::time::Clock
2064-{
2065-public:
2066- mir::time::Timestamp sample() const override
2067- {
2068- std::lock_guard<std::mutex> lock(time_mutex);
2069- return current_time;
2070- }
2071- void advance_by(std::chrono::milliseconds const step, mir::AsioMainLoop & ml)
2072- {
2073- bool done = false;
2074- std::mutex checkpoint_mutex;
2075- std::condition_variable checkpoint;
2076-
2077- {
2078- std::lock_guard<std::mutex> lock(time_mutex);
2079- current_time += step;
2080- }
2081- auto evaluate_clock_alarm = ml.notify_in(
2082- std::chrono::milliseconds{0},
2083- [&done, &checkpoint_mutex, &checkpoint]
2084- {
2085- std::unique_lock<std::mutex> lock(checkpoint_mutex);
2086- done = true;
2087- checkpoint.notify_one();
2088- });
2089-
2090- std::unique_lock<std::mutex> lock(checkpoint_mutex);
2091- while(!done) checkpoint.wait(lock);
2092-
2093- }
2094-private:
2095- mutable std::mutex time_mutex;
2096- mir::time::Timestamp current_time{
2097- []
2098- {
2099- mir::time::HighResolutionClock clock;
2100- return clock.sample();
2101- }()
2102- };
2103-};
2104-
2105-
2106-class AsioMainLoopAlarmTest : public ::testing::Test
2107-{
2108-public:
2109- std::shared_ptr<AdvanceableClock> clock = std::make_shared<AdvanceableClock>();
2110- mir::AsioMainLoop ml{clock};
2111- int call_count{0};
2112- mt::WaitObject wait;
2113- std::chrono::milliseconds delay{50};
2114-
2115- struct UnblockMainLoop : mt::AutoUnblockThread
2116- {
2117- UnblockMainLoop(mir::AsioMainLoop & loop)
2118- : mt::AutoUnblockThread([&loop]() {loop.stop();},
2119- [&loop]() {loop.run();})
2120- {}
2121- };
2122+ mir::AsioMainLoop ml;
2123 };
2124
2125 class Counter
2126@@ -258,6 +197,7 @@
2127
2128 ml.register_fd_handler(
2129 {p.read_fd()},
2130+ this,
2131 [&handled_fd, &data_read, this](int fd)
2132 {
2133 handled_fd = fd;
2134@@ -282,6 +222,7 @@
2135
2136 ml.register_fd_handler(
2137 {pipes[0].read_fd(), pipes[1].read_fd()},
2138+ this,
2139 [&handled_fds, &elems_read, &num_handled_fds](int fd)
2140 {
2141 handled_fds.push_back(fd);
2142@@ -329,6 +270,7 @@
2143
2144 ml.register_fd_handler(
2145 {pipes[0].read_fd()},
2146+ this,
2147 [&handled_fds, &elems_read, this](int fd)
2148 {
2149 EXPECT_EQ(static_cast<ssize_t>(sizeof(elems_read[0])),
2150@@ -344,6 +286,7 @@
2151
2152 ml.register_fd_handler(
2153 {pipes[1].read_fd()},
2154+ this,
2155 [&handled_fds, &elems_read, this](int fd)
2156 {
2157 EXPECT_EQ(static_cast<ssize_t>(sizeof(elems_read[1])),
2158@@ -359,6 +302,7 @@
2159
2160 ml.register_fd_handler(
2161 {pipes[2].read_fd()},
2162+ this,
2163 [&handled_fds, &elems_read, this](int fd)
2164 {
2165 EXPECT_EQ(static_cast<ssize_t>(sizeof(elems_read[2])),
2166@@ -390,185 +334,42 @@
2167 EXPECT_EQ(elems_to_send[2], elems_read[2]);
2168 }
2169
2170-TEST_F(AsioMainLoopAlarmTest, main_loop_runs_until_stop_called)
2171-{
2172- auto mainloop_started = std::make_shared<mt::Signal>();
2173-
2174- auto fire_on_mainloop_start = ml.notify_in(std::chrono::milliseconds{0},
2175- [mainloop_started]()
2176- {
2177- mainloop_started->raise();
2178- });
2179-
2180- UnblockMainLoop unblocker(ml);
2181-
2182- ASSERT_TRUE(mainloop_started->wait_for(std::chrono::milliseconds{100}));
2183-
2184- auto timer_fired = std::make_shared<mt::Signal>();
2185- auto alarm = ml.notify_in(std::chrono::milliseconds{10}, [timer_fired]
2186- {
2187- timer_fired->raise();
2188- });
2189-
2190- clock->advance_by(std::chrono::milliseconds{10}, ml);
2191- EXPECT_TRUE(timer_fired->wait_for(std::chrono::milliseconds{500}));
2192-
2193- ml.stop();
2194- // Main loop should be stopped now
2195-
2196- timer_fired = std::make_shared<mt::Signal>();
2197- auto should_not_fire = ml.notify_in(std::chrono::milliseconds{0},
2198- [timer_fired]()
2199- {
2200- timer_fired->raise();
2201- });
2202-
2203- EXPECT_FALSE(timer_fired->wait_for(std::chrono::milliseconds{100}));
2204-}
2205-
2206-TEST_F(AsioMainLoopAlarmTest, alarm_starts_in_pending_state)
2207-{
2208- auto alarm = ml.notify_in(delay, [this]() {});
2209-
2210- UnblockMainLoop unblocker(ml);
2211-
2212- EXPECT_EQ(mir::time::Alarm::pending, alarm->state());
2213-}
2214-
2215-TEST_F(AsioMainLoopAlarmTest, alarm_fires_with_correct_delay)
2216-{
2217- UnblockMainLoop unblocker(ml);
2218-
2219- auto alarm = ml.notify_in(delay, [](){});
2220-
2221- clock->advance_by(delay - std::chrono::milliseconds{1}, ml);
2222- EXPECT_EQ(mir::time::Alarm::pending, alarm->state());
2223-
2224- clock->advance_by(delay, ml);
2225- EXPECT_EQ(mir::time::Alarm::triggered, alarm->state());
2226-}
2227-
2228-TEST_F(AsioMainLoopAlarmTest, multiple_alarms_fire)
2229-{
2230- using namespace testing;
2231-
2232- int const alarm_count{10};
2233- Counter call_count;
2234- std::array<std::unique_ptr<mir::time::Alarm>, alarm_count> alarms;
2235-
2236- for (auto& alarm : alarms)
2237- alarm = ml.notify_in(delay, [&call_count](){++call_count;});
2238-
2239- UnblockMainLoop unblocker(ml);
2240- clock->advance_by(delay, ml);
2241-
2242- call_count.wait_for(delay, alarm_count);
2243- EXPECT_THAT(call_count, Eq(alarm_count));
2244-
2245- for (auto const& alarm : alarms)
2246- EXPECT_EQ(mir::time::Alarm::triggered, alarm->state());
2247-}
2248-
2249-TEST_F(AsioMainLoopAlarmTest, alarm_changes_to_triggered_state)
2250-{
2251- auto alarm_fired = std::make_shared<mt::Signal>();
2252- auto alarm = ml.notify_in(std::chrono::milliseconds{5}, [alarm_fired]()
2253- {
2254- alarm_fired->raise();
2255- });
2256-
2257- UnblockMainLoop unblocker(ml);
2258-
2259- clock->advance_by(delay, ml);
2260- ASSERT_TRUE(alarm_fired->wait_for(std::chrono::milliseconds{100}));
2261-
2262- EXPECT_EQ(mir::time::Alarm::triggered, alarm->state());
2263-}
2264-
2265-TEST_F(AsioMainLoopAlarmTest, cancelled_alarm_doesnt_fire)
2266-{
2267- UnblockMainLoop unblocker(ml);
2268- auto alarm = ml.notify_in(std::chrono::milliseconds{100},
2269- [](){ FAIL() << "Alarm handler of canceld alarm called";});
2270-
2271- EXPECT_TRUE(alarm->cancel());
2272-
2273- EXPECT_EQ(mir::time::Alarm::cancelled, alarm->state());
2274-
2275- clock->advance_by(std::chrono::milliseconds{100}, ml);
2276-
2277- EXPECT_EQ(mir::time::Alarm::cancelled, alarm->state());
2278-}
2279-
2280-TEST_F(AsioMainLoopAlarmTest, destroyed_alarm_doesnt_fire)
2281-{
2282- auto alarm = ml.notify_in(std::chrono::milliseconds{200},
2283- [](){ FAIL() << "Alarm handler of destroyed alarm called"; });
2284-
2285- UnblockMainLoop unblocker(ml);
2286-
2287- alarm.reset(nullptr);
2288- clock->advance_by(std::chrono::milliseconds{200}, ml);
2289-}
2290-
2291-TEST_F(AsioMainLoopAlarmTest, rescheduled_alarm_fires_again)
2292-{
2293- std::atomic<int> call_count{0};
2294-
2295- auto alarm = ml.notify_in(std::chrono::milliseconds{0}, [&call_count]()
2296- {
2297- if (call_count++ > 1)
2298- FAIL() << "Alarm called too many times";
2299- });
2300-
2301- UnblockMainLoop unblocker(ml);
2302-
2303- clock->advance_by(std::chrono::milliseconds{0}, ml);
2304- ASSERT_EQ(mir::time::Alarm::triggered, alarm->state());
2305-
2306- alarm->reschedule_in(std::chrono::milliseconds{100});
2307- EXPECT_EQ(mir::time::Alarm::pending, alarm->state());
2308-
2309- clock->advance_by(std::chrono::milliseconds{100}, ml);
2310- EXPECT_EQ(mir::time::Alarm::triggered, alarm->state());
2311-}
2312-
2313-TEST_F(AsioMainLoopAlarmTest, rescheduled_alarm_cancels_previous_scheduling)
2314-{
2315- std::atomic<int> call_count{0};
2316-
2317- auto alarm = ml.notify_in(std::chrono::milliseconds{100}, [&call_count]()
2318- {
2319- call_count++;
2320- });
2321-
2322- UnblockMainLoop unblocker(ml);
2323- clock->advance_by(std::chrono::milliseconds{90}, ml);
2324-
2325- EXPECT_EQ(mir::time::Alarm::pending, alarm->state());
2326- EXPECT_EQ(0, call_count);
2327- EXPECT_TRUE(alarm->reschedule_in(std::chrono::milliseconds{100}));
2328- EXPECT_EQ(mir::time::Alarm::pending, alarm->state());
2329-
2330- clock->advance_by(std::chrono::milliseconds{110}, ml);
2331-
2332- EXPECT_EQ(mir::time::Alarm::triggered, alarm->state());
2333- EXPECT_EQ(1, call_count);
2334-}
2335-
2336-TEST_F(AsioMainLoopAlarmTest, alarm_fires_at_correct_time_point)
2337-{
2338- mir::time::Timestamp real_soon = clock->sample() + std::chrono::milliseconds{120};
2339-
2340- auto alarm = ml.notify_at(real_soon, []{});
2341-
2342- UnblockMainLoop unblocker(ml);
2343-
2344- clock->advance_by(std::chrono::milliseconds{119}, ml);
2345- EXPECT_EQ(mir::time::Alarm::pending, alarm->state());
2346-
2347- clock->advance_by(std::chrono::milliseconds{1}, ml);
2348- EXPECT_EQ(mir::time::Alarm::triggered, alarm->state());
2349+TEST_F(AsioMainLoopTest, unregister_prevents_callback_and_does_not_harm_other_callbacks)
2350+{
2351+ mt::Pipe p1, p2;
2352+ char const data_to_write{'a'};
2353+ int p1_handler_never_triggers{-1};
2354+ int p2_handler_executes{-1};
2355+ char data_read{0};
2356+
2357+ ml.register_fd_handler(
2358+ {p1.read_fd()},
2359+ this,
2360+ [&p1_handler_never_triggers](int fd)
2361+ {
2362+ p1_handler_never_triggers = fd;
2363+ });
2364+
2365+ ml.register_fd_handler(
2366+ {p2.read_fd()},
2367+ this+2,
2368+ [&p2_handler_executes,&data_read,this](int fd)
2369+ {
2370+ p2_handler_executes = fd;
2371+ EXPECT_EQ(1, read(fd, &data_read, 1));
2372+ ml.stop();
2373+ });
2374+
2375+ ml.unregister_fd_handler(this);
2376+
2377+ EXPECT_EQ(-1, send(p1.write_fd(), &data_to_write, 1, MSG_NOSIGNAL));
2378+ EXPECT_EQ(1, write(p2.write_fd(), &data_to_write, 1));
2379+
2380+ ml.run();
2381+
2382+ EXPECT_EQ(data_to_write, data_read);
2383+ EXPECT_EQ(-1, p1_handler_never_triggers);
2384+ EXPECT_EQ(p2.read_fd(), p2_handler_executes);
2385 }
2386
2387 TEST_F(AsioMainLoopTest, dispatches_action)
2388@@ -718,7 +519,7 @@
2389 {
2390 int const id = 0;
2391 actions.push_back(id);
2392-
2393+
2394 for (int i = 1; i < num_actions; ++i)
2395 {
2396 ml.enqueue(
2397
2398=== added file 'tests/unit-tests/test_asio_server_action_queue.cpp'
2399--- tests/unit-tests/test_asio_server_action_queue.cpp 1970-01-01 00:00:00 +0000
2400+++ tests/unit-tests/test_asio_server_action_queue.cpp 2014-06-16 22:41:10 +0000
2401@@ -0,0 +1,219 @@
2402+/*
2403+ * Copyright © 2013 Canonical Ltd.
2404+ *
2405+ * This program is free software: you can redistribute it and/or modify
2406+ * it under the terms of the GNU General Public License version 3 as
2407+ * published by the Free Software Foundation.
2408+ *
2409+ * This program is distributed in the hope that it will be useful,
2410+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2411+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2412+ * GNU General Public License for more details.
2413+ *
2414+ * You should have received a copy of the GNU General Public License
2415+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2416+ *
2417+ * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
2418+ */
2419+
2420+#include "src/server/asio_server_action_queue.h"
2421+
2422+#include "mir_test/pipe.h"
2423+#include "mir_test/auto_unblock_thread.h"
2424+#include "mir_test/wait_object.h"
2425+
2426+#include <gtest/gtest.h>
2427+#include <gmock/gmock.h>
2428+
2429+#include <thread>
2430+#include <atomic>
2431+#include <functional>
2432+#include <mutex>
2433+#include <condition_variable>
2434+#include <array>
2435+#include <boost/throw_exception.hpp>
2436+
2437+#include <sys/types.h>
2438+#include <unistd.h>
2439+
2440+namespace mt = mir::test;
2441+
2442+namespace
2443+{
2444+
2445+class AsioServerActionQueueTest : public ::testing::Test
2446+{
2447+public:
2448+ boost::asio::io_service service;
2449+ mir::AsioServerActionQueue queue{service};
2450+};
2451+
2452+}
2453+
2454+TEST_F(AsioServerActionQueueTest, dispatches_action)
2455+{
2456+ using namespace testing;
2457+
2458+ int num_actions{0};
2459+ int const owner{0};
2460+
2461+ queue.enqueue(
2462+ &owner,
2463+ [&]
2464+ {
2465+ ++num_actions;
2466+ service.stop();
2467+ });
2468+
2469+ service.run();
2470+
2471+ EXPECT_THAT(num_actions, Eq(1));
2472+}
2473+
2474+TEST_F(AsioServerActionQueueTest, dispatches_multiple_actions_in_order)
2475+{
2476+ using namespace testing;
2477+
2478+ int const num_actions{5};
2479+ std::vector<int> actions;
2480+ int const owner{0};
2481+
2482+ for (int i = 0; i < num_actions; ++i)
2483+ {
2484+ queue.enqueue(
2485+ &owner,
2486+ [&,i]
2487+ {
2488+ actions.push_back(i);
2489+ if (i == num_actions - 1)
2490+ service.stop();
2491+ });
2492+ }
2493+
2494+ service.run();
2495+
2496+ ASSERT_THAT(actions.size(), Eq(num_actions));
2497+ for (int i = 0; i < num_actions; ++i)
2498+ EXPECT_THAT(actions[i], Eq(i)) << "i = " << i;
2499+}
2500+
2501+TEST_F(AsioServerActionQueueTest, does_not_dispatch_paused_actions)
2502+{
2503+ using namespace testing;
2504+
2505+ std::vector<int> actions;
2506+ int const owner1{0};
2507+ int const owner2{0};
2508+
2509+ queue.enqueue(
2510+ &owner1,
2511+
2512+ [&]
2513+ {
2514+ int const id = 0;
2515+ actions.push_back(id);
2516+ });
2517+
2518+ queue.enqueue(
2519+ &owner2,
2520+ [&]
2521+ {
2522+ int const id = 1;
2523+ actions.push_back(id);
2524+ });
2525+
2526+ queue.enqueue(
2527+ &owner1,
2528+ [&]
2529+ {
2530+ int const id = 2;
2531+ actions.push_back(id);
2532+ });
2533+
2534+ queue.enqueue(
2535+ &owner2,
2536+ [&]
2537+ {
2538+ int const id = 3;
2539+ actions.push_back(id);
2540+ service.stop();
2541+ });
2542+
2543+ queue.pause_processing_for(&owner1);
2544+
2545+ service.run();
2546+
2547+ ASSERT_THAT(actions.size(), Eq(2));
2548+ EXPECT_THAT(actions[0], Eq(1));
2549+ EXPECT_THAT(actions[1], Eq(3));
2550+}
2551+
2552+TEST_F(AsioServerActionQueueTest, dispatches_resumed_actions)
2553+{
2554+ using namespace testing;
2555+
2556+ std::vector<int> actions;
2557+ void const* const owner1_ptr{&actions};
2558+ int const owner2{0};
2559+
2560+ queue.enqueue(
2561+ owner1_ptr,
2562+ [&]
2563+ {
2564+ int const id = 0;
2565+ actions.push_back(id);
2566+ service.stop();
2567+ });
2568+
2569+ queue.enqueue(
2570+ &owner2,
2571+ [&]
2572+ {
2573+ int const id = 1;
2574+ actions.push_back(id);
2575+ queue.resume_processing_for(owner1_ptr);
2576+ });
2577+
2578+ queue.pause_processing_for(owner1_ptr);
2579+
2580+ service.run();
2581+
2582+ ASSERT_THAT(actions.size(), Eq(2));
2583+ EXPECT_THAT(actions[0], Eq(1));
2584+ EXPECT_THAT(actions[1], Eq(0));
2585+}
2586+
2587+TEST_F(AsioServerActionQueueTest, handles_enqueue_from_within_action)
2588+{
2589+ using namespace testing;
2590+
2591+ std::vector<int> actions;
2592+ int const num_actions{10};
2593+ void const* const owner{&num_actions};
2594+
2595+ queue.enqueue(
2596+ owner,
2597+ [&]
2598+ {
2599+ int const id = 0;
2600+ actions.push_back(id);
2601+
2602+ for (int i = 1; i < num_actions; ++i)
2603+ {
2604+ queue.enqueue(
2605+ owner,
2606+ [&,i]
2607+ {
2608+ actions.push_back(i);
2609+ if (i == num_actions - 1)
2610+ service.stop();
2611+ });
2612+ }
2613+ });
2614+
2615+ service.run();
2616+
2617+ ASSERT_THAT(actions.size(), Eq(num_actions));
2618+ for (int i = 0; i < num_actions; ++i)
2619+ EXPECT_THAT(actions[i], Eq(i)) << "i = " << i;
2620+}
2621
2622=== added file 'tests/unit-tests/test_asio_timer_service.cpp'
2623--- tests/unit-tests/test_asio_timer_service.cpp 1970-01-01 00:00:00 +0000
2624+++ tests/unit-tests/test_asio_timer_service.cpp 2014-06-16 22:41:10 +0000
2625@@ -0,0 +1,312 @@
2626+/*
2627+ * Copyright © 2014 Canonical Ltd.
2628+ *
2629+ * This program is free software: you can redistribute it and/or modify
2630+ * it under the terms of the GNU General Public License version 3 as
2631+ * published by the Free Software Foundation.
2632+ *
2633+ * This program is distributed in the hope that it will be useful,
2634+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2635+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2636+ * GNU General Public License for more details.
2637+ *
2638+ * You should have received a copy of the GNU General Public License
2639+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2640+ *
2641+ * Authored by: Andreas Pokorny <andreas.pokorny@canonical.com>
2642+ */
2643+
2644+#include "src/server/asio_timer_service.h"
2645+
2646+#include "mir/time/high_resolution_clock.h"
2647+#include "mir_test/auto_unblock_thread.h"
2648+#include "mir_test/wait_object.h"
2649+
2650+#include <gtest/gtest.h>
2651+#include <gmock/gmock.h>
2652+
2653+#include <thread>
2654+#include <atomic>
2655+#include <functional>
2656+#include <mutex>
2657+#include <condition_variable>
2658+#include <array>
2659+#include <boost/throw_exception.hpp>
2660+
2661+#include <sys/types.h>
2662+#include <unistd.h>
2663+
2664+namespace mt = mir::test;
2665+
2666+namespace
2667+{
2668+
2669+class AdvanceableClock : public mir::time::Clock
2670+{
2671+public:
2672+ mir::time::Timestamp sample() const override
2673+ {
2674+ std::lock_guard<std::mutex> lock(time_mutex);
2675+ return current_time;
2676+ }
2677+ void advance_by(std::chrono::milliseconds const step, mir::AsioTimerService & timer_service)
2678+ {
2679+ {
2680+ std::lock_guard<std::mutex> lock(time_mutex);
2681+ current_time += step;
2682+ }
2683+
2684+ bool done = false;
2685+ auto evaluate_clock_alarm = timer_service.notify_in(
2686+ std::chrono::milliseconds{0},
2687+ [&done, this]
2688+ {
2689+ std::unique_lock<std::mutex> lock(update_time_checkpoint_mutex);
2690+ done = true;
2691+ update_time_checkpoint.notify_one();
2692+ });
2693+
2694+ std::unique_lock<std::mutex> lock(update_time_checkpoint_mutex);
2695+ while(!done) update_time_checkpoint.wait(lock);
2696+ }
2697+
2698+private:
2699+ mutable std::mutex time_mutex;
2700+
2701+ std::mutex update_time_checkpoint_mutex;
2702+ std::condition_variable update_time_checkpoint;
2703+
2704+
2705+ mir::time::Timestamp current_time{
2706+ []
2707+ {
2708+ mir::time::HighResolutionClock clock;
2709+ return clock.sample();
2710+ }()
2711+ };
2712+};
2713+
2714+
2715+class AsioTimerServiceTest : public ::testing::Test
2716+{
2717+public:
2718+ std::shared_ptr<AdvanceableClock> clock = std::make_shared<AdvanceableClock>();
2719+ mir::AsioTimerService timer_service{clock};
2720+ int call_count{0};
2721+ mt::WaitObject wait;
2722+ std::chrono::milliseconds delay{50};
2723+
2724+ struct UnblockTimerService : mt::AutoUnblockThread
2725+ {
2726+ UnblockTimerService(mir::AsioTimerService & timer_service)
2727+ : mt::AutoUnblockThread([&timer_service]() {timer_service.stop();},
2728+ [&timer_service]() {timer_service.run();})
2729+ {}
2730+ };
2731+};
2732+
2733+class Counter
2734+{
2735+public:
2736+ int operator++()
2737+ {
2738+ std::lock_guard<decltype(mutex)> lock(mutex);
2739+ cv.notify_one();
2740+ return ++counter;
2741+ }
2742+
2743+ bool wait_for(std::chrono::milliseconds const& delay, int expected)
2744+ {
2745+ std::unique_lock<decltype(mutex)> lock(mutex);
2746+ return cv.wait_for(lock, delay, [&]{ return counter == expected;});
2747+ }
2748+
2749+ operator int() const
2750+ {
2751+ std::lock_guard<decltype(mutex)> lock(mutex);
2752+ return counter;
2753+ }
2754+
2755+private:
2756+ std::mutex mutable mutex;
2757+ std::condition_variable cv;
2758+ int counter{0};
2759+};
2760+
2761+}
2762+
2763+TEST_F(AsioTimerServiceTest, runs_until_stopped)
2764+{
2765+ std::mutex checkpoint_mutex;
2766+ std::condition_variable checkpoint;
2767+ bool hit_checkpoint{false};
2768+
2769+ auto fire_on_timer_service_start = timer_service.notify_in(
2770+ std::chrono::milliseconds{0},
2771+ [&checkpoint_mutex, &checkpoint, &hit_checkpoint]()
2772+ {
2773+ std::unique_lock<decltype(checkpoint_mutex)> lock(checkpoint_mutex);
2774+ hit_checkpoint = true;
2775+ checkpoint.notify_all();
2776+ });
2777+
2778+ UnblockTimerService unblocker(timer_service);
2779+
2780+ // TODO time dependency:
2781+ {
2782+ std::unique_lock<decltype(checkpoint_mutex)> lock(checkpoint_mutex);
2783+ ASSERT_TRUE(checkpoint.wait_for(lock, std::chrono::milliseconds{500}, [&hit_checkpoint]() { return hit_checkpoint; }));
2784+ }
2785+
2786+ auto alarm = timer_service.notify_in(std::chrono::milliseconds{10}, [this]
2787+ {
2788+ wait.notify_ready();
2789+ });
2790+
2791+ clock->advance_by(std::chrono::milliseconds{10}, timer_service);
2792+ EXPECT_NO_THROW(wait.wait_until_ready(std::chrono::milliseconds{500}));
2793+
2794+ timer_service.stop();
2795+ // Timer Service should be stopped now
2796+
2797+ hit_checkpoint = false;
2798+ auto should_not_fire = timer_service.notify_in(std::chrono::milliseconds{0},
2799+ [&checkpoint_mutex, &checkpoint, &hit_checkpoint]()
2800+ {
2801+ std::unique_lock<decltype(checkpoint_mutex)> lock(checkpoint_mutex);
2802+ hit_checkpoint = true;
2803+ checkpoint.notify_all();
2804+ });
2805+
2806+ std::unique_lock<decltype(checkpoint_mutex)> lock(checkpoint_mutex);
2807+ EXPECT_FALSE(checkpoint.wait_for(lock, std::chrono::milliseconds{50}, [&hit_checkpoint]() { return hit_checkpoint; }));
2808+}
2809+
2810+TEST_F(AsioTimerServiceTest, alarm_starts_in_pending_state)
2811+{
2812+ auto alarm = timer_service.notify_in(delay, [this]() {});
2813+
2814+ UnblockTimerService unblocker(timer_service);
2815+
2816+ EXPECT_EQ(mir::time::Alarm::pending, alarm->state());
2817+}
2818+
2819+TEST_F(AsioTimerServiceTest, alarm_fires_with_correct_delay)
2820+{
2821+ auto alarm = timer_service.notify_in(delay, [](){});
2822+
2823+ UnblockTimerService unblocker(timer_service);
2824+
2825+ clock->advance_by(delay - std::chrono::milliseconds{1}, timer_service);
2826+ EXPECT_EQ(mir::time::Alarm::pending, alarm->state());
2827+
2828+ clock->advance_by(delay, timer_service);
2829+ EXPECT_EQ(mir::time::Alarm::triggered, alarm->state());
2830+}
2831+
2832+TEST_F(AsioTimerServiceTest, multiple_alarms_fire)
2833+{
2834+ using namespace testing;
2835+
2836+ int const alarm_count{10};
2837+ Counter call_count;
2838+ std::array<std::unique_ptr<mir::time::Alarm>, alarm_count> alarms;
2839+
2840+ for (auto& alarm : alarms)
2841+ alarm = timer_service.notify_in(delay, [&call_count](){++call_count;});
2842+
2843+ UnblockTimerService unblocker(timer_service);
2844+ clock->advance_by(delay, timer_service);
2845+
2846+ call_count.wait_for(delay, alarm_count);
2847+ EXPECT_THAT(call_count, Eq(alarm_count));
2848+
2849+ for (auto const& alarm : alarms)
2850+ EXPECT_EQ(mir::time::Alarm::triggered, alarm->state());
2851+}
2852+
2853+TEST_F(AsioTimerServiceTest, cancelled_alarm_doesnt_fire)
2854+{
2855+ UnblockTimerService unblocker(timer_service);
2856+ auto alarm = timer_service.notify_in(std::chrono::milliseconds{100},
2857+ [](){ FAIL() << "Alarm handler of canceld alarm called";});
2858+
2859+ EXPECT_TRUE(alarm->cancel());
2860+
2861+ EXPECT_EQ(mir::time::Alarm::cancelled, alarm->state());
2862+
2863+ clock->advance_by(std::chrono::milliseconds{100}, timer_service);
2864+
2865+ EXPECT_EQ(mir::time::Alarm::cancelled, alarm->state());
2866+}
2867+
2868+TEST_F(AsioTimerServiceTest, destroyed_alarm_doesnt_fire)
2869+{
2870+ auto alarm = timer_service.notify_in(std::chrono::milliseconds{200},
2871+ [](){ FAIL() << "Alarm handler of destroyed alarm called"; });
2872+
2873+ UnblockTimerService unblocker(timer_service);
2874+
2875+ alarm.reset(nullptr);
2876+ clock->advance_by(std::chrono::milliseconds{200}, timer_service);
2877+}
2878+
2879+TEST_F(AsioTimerServiceTest, rescheduled_alarm_fires_again)
2880+{
2881+ std::atomic<int> call_count{0};
2882+
2883+ auto alarm = timer_service.notify_in(std::chrono::milliseconds{0}, [&call_count]()
2884+ {
2885+ if (call_count++ > 1)
2886+ FAIL() << "Alarm called too many times";
2887+ });
2888+
2889+ UnblockTimerService unblocker(timer_service);
2890+
2891+ clock->advance_by(std::chrono::milliseconds{0}, timer_service);
2892+ ASSERT_EQ(mir::time::Alarm::triggered, alarm->state());
2893+
2894+ alarm->reschedule_in(std::chrono::milliseconds{100});
2895+ EXPECT_EQ(mir::time::Alarm::pending, alarm->state());
2896+
2897+ clock->advance_by(std::chrono::milliseconds{100}, timer_service);
2898+ EXPECT_EQ(mir::time::Alarm::triggered, alarm->state());
2899+}
2900+
2901+TEST_F(AsioTimerServiceTest, rescheduled_alarm_cancels_previous_scheduling)
2902+{
2903+ std::atomic<int> call_count{0};
2904+
2905+ auto alarm = timer_service.notify_in(std::chrono::milliseconds{100}, [&call_count]()
2906+ {
2907+ call_count++;
2908+ });
2909+
2910+ UnblockTimerService unblocker(timer_service);
2911+ clock->advance_by(std::chrono::milliseconds{90}, timer_service);
2912+
2913+ EXPECT_EQ(mir::time::Alarm::pending, alarm->state());
2914+ EXPECT_EQ(0, call_count);
2915+ EXPECT_TRUE(alarm->reschedule_in(std::chrono::milliseconds{100}));
2916+ EXPECT_EQ(mir::time::Alarm::pending, alarm->state());
2917+
2918+ clock->advance_by(std::chrono::milliseconds{110}, timer_service);
2919+
2920+ EXPECT_EQ(mir::time::Alarm::triggered, alarm->state());
2921+ EXPECT_EQ(1, call_count);
2922+}
2923+
2924+TEST_F(AsioTimerServiceTest, alarm_fires_at_correct_time_point)
2925+{
2926+ mir::time::Timestamp real_soon = clock->sample() + std::chrono::milliseconds{120};
2927+
2928+ auto alarm = timer_service.notify_at(real_soon, []{});
2929+
2930+ UnblockTimerService unblocker(timer_service);
2931+
2932+ clock->advance_by(std::chrono::milliseconds{119}, timer_service);
2933+ EXPECT_EQ(mir::time::Alarm::pending, alarm->state());
2934+
2935+ clock->advance_by(std::chrono::milliseconds{1}, timer_service);
2936+ EXPECT_EQ(mir::time::Alarm::triggered, alarm->state());
2937+}
2938
2939=== added file 'tests/unit-tests/test_synchronous_server_action.cpp'
2940--- tests/unit-tests/test_synchronous_server_action.cpp 1970-01-01 00:00:00 +0000
2941+++ tests/unit-tests/test_synchronous_server_action.cpp 2014-06-16 22:41:10 +0000
2942@@ -0,0 +1,78 @@
2943+/*
2944+ * Copyright © 2014 Canonical Ltd.
2945+ *
2946+ * This program is free software: you can redistribute it and/or modify it
2947+ * under the terms of the GNU General Public License version 3,
2948+ * as published by the Free Software Foundation.
2949+ *
2950+ * This program is distributed in the hope that it will be useful,
2951+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2952+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2953+ * GNU General Public License for more details.
2954+ *
2955+ * You should have received a copy of the GNU General Public License
2956+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2957+ *
2958+ * Authored by: Andreas Pokorny <andreas.pokorny@canonical.com>
2959+ */
2960+
2961+#include "src/server/synchronous_server_action.h"
2962+#include "mir_test_doubles/mock_server_action_queue.h"
2963+
2964+#include <thread>
2965+
2966+namespace mtd = mir::test::doubles;
2967+
2968+TEST(SynchronousServerActionTest, just_executes_action_if_queue_is_not_running)
2969+{
2970+ using namespace ::testing;
2971+
2972+ NiceMock<mtd::MockServerActionQueue> mock_queue;
2973+ EXPECT_CALL(mock_queue, enqueue(_,_)).Times(0);
2974+ int val = 0;
2975+ boost::optional<std::thread::id> empty_queue_thread_id;
2976+ {
2977+ mir::SynchronousServerAction action(mock_queue, empty_queue_thread_id, [&val]{val=1;});
2978+ }
2979+ EXPECT_EQ(val, 1);
2980+}
2981+
2982+TEST(SynchronousServerActionTest, just_executes_action_if_thread_identical)
2983+{
2984+ using namespace ::testing;
2985+
2986+ NiceMock<mtd::MockServerActionQueue> mock_queue;
2987+ EXPECT_CALL(mock_queue, enqueue(_,_)).Times(0);
2988+ int val = 0;
2989+ boost::optional<std::thread::id> this_one{std::this_thread::get_id()};
2990+ {
2991+ mir::SynchronousServerAction action(mock_queue, this_one, [&val]{val=1;});
2992+ }
2993+ EXPECT_EQ(val, 1);
2994+}
2995+
2996+TEST(SynchronousServerActionTest, enqueues_action_if_thread_differs)
2997+{
2998+ using namespace ::testing;
2999+
3000+ class DirectlyExecutingServerActionQueue : public StrictMock<mtd::MockServerActionQueue>
3001+ {
3002+ void enqueue(void const* ptr, mir::ServerAction const& action) override
3003+ {
3004+ action();
3005+ StrictMock<mtd::MockServerActionQueue>::enqueue(ptr, action);
3006+ }
3007+ };
3008+
3009+ DirectlyExecutingServerActionQueue execute_it;
3010+ EXPECT_CALL(execute_it, enqueue(_, _));
3011+
3012+ int val = 0;
3013+ std::thread::id arbitrary_thread;
3014+ boost::optional<std::thread::id> not_this_one{arbitrary_thread};
3015+
3016+ {
3017+ mir::SynchronousServerAction action(execute_it, not_this_one, [&val]{val=1;});
3018+ }
3019+ EXPECT_EQ(1,val);
3020+}

Subscribers

People subscribed via source and target branches