Mir

Merge lp:~raof/mir/observer-multiplexer into lp:mir

Proposed by Chris Halse Rogers on 2016-10-04
Status: Merged
Approved by: Daniel van Vugt on 2016-10-31
Approved revision: 3771
Merged at revision: 3795
Proposed branch: lp:~raof/mir/observer-multiplexer
Merge into: lp:mir
Prerequisite: lp:~raof/mir/shared-mutex
Diff against target: 1064 lines (+940/-2)
12 files modified
include/server/mir/executor.h (+57/-0)
include/server/mir/main_loop.h (+6/-2)
include/server/mir/observer_registrar.h (+92/-0)
src/include/server/mir/glib_main_loop.h (+2/-0)
src/include/server/mir/observer_multiplexer.h (+126/-0)
src/server/CMakeLists.txt (+5/-0)
src/server/glib_main_loop.cpp (+16/-0)
tests/include/mir/test/doubles/mock_main_loop.h (+8/-0)
tests/include/mir/test/doubles/triggered_main_loop.h (+4/-0)
tests/mir_test_doubles/triggered_main_loop.cpp (+15/-0)
tests/unit-tests/CMakeLists.txt (+1/-0)
tests/unit-tests/test_observer_multiplexer.cpp (+608/-0)
To merge this branch: bzr merge lp:~raof/mir/observer-multiplexer
Reviewer Review Type Date Requested Status
Mir CI Bot continuous-integration Approve on 2016-10-31
Andreas Pokorny (community) Approve on 2016-10-26
Alan Griffiths Abstain on 2016-10-24
Cemil Azizoglu (community) Approve on 2016-10-13
Chris Halse Rogers Abstain on 2016-10-13
Review via email: mp+307501@code.launchpad.net

Commit Message

Add ObserverRegistrar interface and an ObserverMultiplexer helper implementation.

An ObserverRegistrar manages registering and unregistering Observers of some Mir state.

The ObserverMultiplexer is an implementation of ObserverRegistrar which itself is an Observer, so can be handed out to internal Mir components as a singular Observer and to shells using Mir as an ObserverRegistrar.

At the moment ObserverMultiplexer requires code to implement Observer be provided manually. In The Glorious Future, C++ will provide compile-time reflection which will make it possible to automatically implement.

To post a comment you must log in.
Mir CI Bot (mir-ci-bot) wrote :

PASSED: Continuous integration, rev:3744
https://mir-jenkins.ubuntu.com/job/mir-ci/1876/
Executed test runs:
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-mir/2379
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/2442
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/2434
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/2434
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=yakkety/2434
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2408
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2408/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2408
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2408/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2408
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2408/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/2408
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/2408/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/2408
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/2408/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/2408
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/2408/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/1876/rebuild

review: Approve (continuous-integration)
Chris Halse Rogers (raof) wrote :

I add a needs-information here for a design discussion:

I have been tempted to make the ObserverMultiplexer offload notification to the server event loop. This means we never have to care about the context in which the observer is called.

Observers loose the ability to deny an operation by throwing an exception, and get notified slightly later.

review: Needs Information
Andreas Pokorny (andreas-pokorny) wrote :

> I add a needs-information here for a design discussion:
>
> I have been tempted to make the ObserverMultiplexer offload notification to
> the server event loop. This means we never have to care about the context in
> which the observer is called.

If you could unify the scheduling part of MultiplexingDispatchable and the server event loop - that would be great. Then again I think for users of libmirserver the server event loop might be sufficient, since they dont have access to other mir threads.

Have you considered letting the owner of the observer specify the thread(pool) of execution on registry?

> Observers loose the ability to deny an operation by throwing an exception, and
> get notified slightly later.

I believe the first sounds more like a policy interface. The delayed / asynchronous notification of observers is necessary for the use case of this utility. We cannot make a reasonable local decision that it is acceptable to synchronously execute unknown code added by 3rd parties. The case would be different for previously mentioned policy interfaces that help with a specific decision...

The problem of delayed execution and needing to know "are we done yet?" is something that we might have to address on a higher level?

Mir CI Bot (mir-ci-bot) wrote :

FAILED: Continuous integration, rev:3768
https://mir-jenkins.ubuntu.com/job/mir-ci/1949/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/2486/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-0-fetch/2549/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/2541/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/2541/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=yakkety/2541/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2515/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2515/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2515/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/2515/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/2515/console
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/2515/console

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/1949/rebuild

review: Needs Fixing (continuous-integration)
Chris Halse Rogers (raof) wrote :

There you go, now with a C++17-ish Executor abstraction.

Mir CI Bot (mir-ci-bot) wrote :

FAILED: Continuous integration, rev:3769
https://mir-jenkins.ubuntu.com/job/mir-ci/1950/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/2488/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/2551
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/2543
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/2543
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=yakkety/2543
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2517
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2517/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2517
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2517/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2517
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2517/artifact/output/*zip*/output.zip
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/2517/console
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/2517/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/2517
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/2517/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/2517
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/2517/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/1950/rebuild

review: Needs Fixing (continuous-integration)
Chris Halse Rogers (raof) wrote :

07:32:29 [ RUN ] AndroidInputReceiverSetup.slow_raw_input_doesnt_cause_frameskipping
07:32:29 /build/mir-xOiOne/mir-0.25.0+vivid2543bzr3769/tests/unit-tests/client/input/test_android_input_receiver.cpp:272: Failure
07:32:29 Expected: (duration) > (1ms), actual: 8-byte object <55-FC 0E-00 00-00 00-00> vs 8-byte object <01-00 00-00 00-00 00-00>
07:32:29 [ FAILED ] AndroidInputReceiverSetup.slow_raw_input_doesnt_cause_frameskipping (4 ms)

That's not related to this branch.

Mir CI Bot (mir-ci-bot) wrote :

FAILED: Continuous integration, rev:3769
https://mir-jenkins.ubuntu.com/job/mir-ci/1966/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/2509/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/2572
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/2564
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/2564
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=yakkety/2564
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2538
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2538/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2538
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2538/artifact/output/*zip*/output.zip
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2538/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/2538
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/2538/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/2538
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/2538/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/2538
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/2538/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/1966/rebuild

review: Needs Fixing (continuous-integration)
Mir CI Bot (mir-ci-bot) wrote :

FAILED: Continuous integration, rev:3769
https://mir-jenkins.ubuntu.com/job/mir-ci/1969/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/2512/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/2575
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/2567
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/2567
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=yakkety/2567
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2541
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2541/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2541
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2541/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2541
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2541/artifact/output/*zip*/output.zip
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/2541/console
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/2541/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/2541
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/2541/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/2541
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/2541/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/1969/rebuild

review: Needs Fixing (continuous-integration)
Chris Halse Rogers (raof) wrote :

05:00:09 [ PASSED ] 1543 tests.
05:00:09 [ FAILED ] 1 test, listed below:
05:00:09 [ FAILED ] AndroidInputReceiverSetup.slow_raw_input_doesnt_cause_frameskipping

AAAAand again.

Mir CI Bot (mir-ci-bot) wrote :

PASSED: Continuous integration, rev:3769
https://mir-jenkins.ubuntu.com/job/mir-ci/1970/
Executed test runs:
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-mir/2513
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/2576
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/2568
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/2568
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=yakkety/2568
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2542
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2542/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2542
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2542/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2542
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2542/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/2542
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/2542/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/2542
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/2542/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/2542
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/2542/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/1970/rebuild

review: Approve (continuous-integration)
Chris Halse Rogers (raof) wrote :

Oh. I no longer need information!

review: Abstain
Cemil Azizoglu (cemil-azizoglu) wrote :

Some plumbing for the dependent branch. Got it.

review: Approve
Andreas Pokorny (andreas-pokorny) wrote :

What happened to the mircommon symbols.map?

Otherwise lgtm

review: Needs Information
Mir CI Bot (mir-ci-bot) wrote :

FAILED: Continuous integration, rev:3771
https://mir-jenkins.ubuntu.com/job/mir-ci/2001/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/2563/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/2626
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/2618
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/2618
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=yakkety/2618
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2592
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2592/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2592
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2592/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2592
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2592/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/2592
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/2592/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/2592
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/2592/artifact/output/*zip*/output.zip
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/2592/console

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/2001/rebuild

review: Needs Fixing (continuous-integration)
Mir CI Bot (mir-ci-bot) wrote :

PASSED: Continuous integration, rev:3771
https://mir-jenkins.ubuntu.com/job/mir-ci/2003/
Executed test runs:
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-mir/2566
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/2629
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/2621
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/2621
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=yakkety/2621
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2595
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2595/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2595
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2595/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2595
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2595/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/2595
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/2595/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/2595
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/2595/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/2595
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/2595/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/2003/rebuild

review: Approve (continuous-integration)
Alan Griffiths (alan-griffiths) wrote :

I'm not yet quite sure what problem this solves. (No followup branch demonstrating use?)

But, if the functionality is useful, then the code looks reasonable.

review: Abstain
Mir CI Bot (mir-ci-bot) wrote :

FAILED: Autolanding.
More details in the following jenkins job:
https://mir-jenkins.ubuntu.com/job/mir-autolanding/731/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/2630/console
    None: https://mir-jenkins.ubuntu.com/job/generic-land-mp/772/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/2693
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/2685
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/2685
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=yakkety/2685
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2659
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2659/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2659
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2659/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2659
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2659/artifact/output/*zip*/output.zip
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/2659/console
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/2659/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/2659
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/2659/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/2659
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/2659/artifact/output/*zip*/output.zip

review: Needs Fixing (continuous-integration)
Mir CI Bot (mir-ci-bot) wrote :

FAILED: Autolanding.
More details in the following jenkins job:
https://mir-jenkins.ubuntu.com/job/mir-autolanding/732/
Executed test runs:
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-mir/2636/console
    None: https://mir-jenkins.ubuntu.com/job/generic-land-mp/773/console
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-0-fetch/2699
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=vivid+overlay/2691
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=xenial+overlay/2691
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-1-sourcepkg/release=yakkety/2691
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2665
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=clang,platform=mesa,release=yakkety/2665/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2665
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=xenial+overlay/2665/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2665
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=amd64,compiler=gcc,platform=mesa,release=yakkety/2665/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/2665
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=cross-armhf,compiler=gcc,platform=android,release=vivid+overlay/2665/artifact/output/*zip*/output.zip
    SUCCESS: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/2665
        deb: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=android,release=vivid+overlay/2665/artifact/output/*zip*/output.zip
    FAILURE: https://mir-jenkins.ubuntu.com/job/build-2-binpkg-mir/arch=i386,compiler=gcc,platform=mesa,release=xenial+overlay/2665/console

review: Needs Fixing (continuous-integration)
Andreas Pokorny (andreas-pokorny) wrote :

ok

review: Approve
Daniel van Vugt (vanvugt) wrote :

^^^
CI failure is bug 1523621. Try again.

Mir CI Bot (mir-ci-bot) :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'include/server/mir/executor.h'
2--- include/server/mir/executor.h 1970-01-01 00:00:00 +0000
3+++ include/server/mir/executor.h 2016-10-19 06:34:58 +0000
4@@ -0,0 +1,57 @@
5+/*
6+ * Copyright © 2016 Canonical Ltd.
7+ *
8+ * This program is free software: you can redistribute it and/or modify it
9+ * under the terms of the GNU General Public License version 3,
10+ * as published by the Free Software Foundation.
11+ *
12+ * This program is distributed in the hope that it will be useful,
13+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+ * GNU General Public License for more details.
16+ *
17+ * You should have received a copy of the GNU General Public License
18+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
19+ *
20+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
21+ */
22+
23+#ifndef MIR_EXECUTOR_H_
24+#define MIR_EXECUTOR_H_
25+
26+#include <functional>
27+
28+namespace mir
29+{
30+
31+/**
32+ * An executor abstraction mostly compatible with C++ proposal N4414
33+ *
34+ * As specified in http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0008r0.pdf
35+ *
36+ * This should hopefully be compatible with whatever the C++ Executors and Schedulers group
37+ * come up with as a final design, and will eventually be replaced by the standard version.
38+ *
39+ * If not, this minimal interface should be easy to implement on top of whatever emerges
40+ * from the standards body.
41+ */
42+class Executor
43+{
44+public:
45+ /**
46+ * Schedule some function to be called sometime in the future.
47+ *
48+ * It is expected that this \b not eagerly execute, and will return
49+ * \b before work() is executed.
50+ *
51+ * \param [in] work Function to execute in Executor specified environment.
52+ */
53+ virtual void spawn(std::function<void()>&& work) = 0;
54+
55+protected:
56+ virtual ~Executor() = default;
57+};
58+
59+}
60+
61+#endif // MIR_EXECUTOR_H_
62
63=== modified file 'include/server/mir/main_loop.h'
64--- include/server/mir/main_loop.h 2015-04-28 07:54:10 +0000
65+++ include/server/mir/main_loop.h 2016-10-19 06:34:58 +0000
66@@ -22,12 +22,16 @@
67 #include "mir/graphics/event_handler_register.h"
68 #include "mir/time/alarm_factory.h"
69 #include "mir/server_action_queue.h"
70+#include "mir/executor.h"
71
72 namespace mir
73 {
74
75-class MainLoop : public graphics::EventHandlerRegister, public time::AlarmFactory,
76- public ServerActionQueue
77+class MainLoop :
78+ public graphics::EventHandlerRegister,
79+ public time::AlarmFactory,
80+ public ServerActionQueue,
81+ public Executor
82 {
83 public:
84 virtual void run() = 0;
85
86=== added file 'include/server/mir/observer_registrar.h'
87--- include/server/mir/observer_registrar.h 1970-01-01 00:00:00 +0000
88+++ include/server/mir/observer_registrar.h 2016-10-19 06:34:58 +0000
89@@ -0,0 +1,92 @@
90+/*
91+ * Copyright © 2016 Canonical Ltd.
92+ *
93+ * This program is free software: you can redistribute it and/or modify it
94+ * under the terms of the GNU General Public License version 3,
95+ * as published by the Free Software Foundation.
96+ *
97+ * This program is distributed in the hope that it will be useful,
98+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
99+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
100+ * GNU General Public License for more details.
101+ *
102+ * You should have received a copy of the GNU General Public License
103+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
104+ *
105+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
106+ */
107+
108+#ifndef MIR_OBSERVER_REGISTRAR_H_
109+#define MIR_OBSERVER_REGISTRAR_H_
110+
111+#include <memory>
112+
113+namespace mir
114+{
115+class Executor;
116+
117+/**
118+ * Register observers for a subsystem.
119+ *
120+ * \tparam Observer The Observer type to register
121+ */
122+template<class Observer>
123+class ObserverRegistrar
124+{
125+public:
126+ /**
127+ * Add an observer to the set notified of all observations
128+ *
129+ * The ObserverRegistrar does not take any ownership of \p observer, and will
130+ * automatically remove it when \p observer expires.
131+ *
132+ * \param [in] observer The observer to register
133+ */
134+ virtual void register_interest(std::weak_ptr<Observer> const& observer) = 0;
135+
136+ /**
137+ * Add an observer with specified execution environment
138+ *
139+ * This is threadsafe and can be called in any context.
140+ *
141+ * The ObserverRegistrar does not take any ownership of \p observer, and will
142+ * automatically remove it when \p observer expires.
143+ *
144+ * All calls to \p observer methods are performed in the context of
145+ * \p executor.
146+ *
147+ * The \p executor should process work in a delayed fashion. Particularly,
148+ * executor::spawn(work) is expected to \b not run \p work in the current
149+ * stack. Eager execution of work may result in deadlocks if calls to the
150+ * observer result in calls into the ObserverRegistrar.
151+ *
152+ * \param [in] observer The observer to register
153+ * \param [in] executor Execution environment for calls to \p observer methods.
154+ * The caller is responsible for ensuring \p executor outlives
155+ * \p observer.
156+ */
157+ virtual void register_interest(
158+ std::weak_ptr<Observer> const& observer,
159+ Executor& executor) = 0;
160+
161+ /**
162+ * Remove an observer from the set notified of all observations.
163+ *
164+ * This is threadsafe and can be called in any context.
165+ * It is \b not guaranteed that methods of \p observer will not be called after
166+ * this returns.
167+ *
168+ * \param observer [in] The observer to unregister
169+ */
170+ virtual void unregister_interest(Observer const& observer) = 0;
171+
172+protected:
173+ ObserverRegistrar() = default;
174+ virtual ~ObserverRegistrar() = default;
175+ ObserverRegistrar(ObserverRegistrar const&) = delete;
176+ ObserverRegistrar& operator=(ObserverRegistrar const&) = delete;
177+};
178+
179+}
180+
181+#endif //MIR_OBSERVER_REGISTRAR_H_
182
183=== modified file 'src/include/server/mir/glib_main_loop.h'
184--- src/include/server/mir/glib_main_loop.h 2016-07-18 07:38:38 +0000
185+++ src/include/server/mir/glib_main_loop.h 2016-10-19 06:34:58 +0000
186@@ -85,6 +85,8 @@
187 std::unique_ptr<mir::time::Alarm> create_alarm(
188 std::unique_ptr<LockableCallback> callback) override;
189
190+ void spawn(std::function<void()>&& work) override;
191+
192 void reprocess_all_sources();
193
194 private:
195
196=== added file 'src/include/server/mir/observer_multiplexer.h'
197--- src/include/server/mir/observer_multiplexer.h 1970-01-01 00:00:00 +0000
198+++ src/include/server/mir/observer_multiplexer.h 2016-10-19 06:34:58 +0000
199@@ -0,0 +1,126 @@
200+/*
201+ * Copyright © 2016 Canonical Ltd.
202+ *
203+ * This program is free software: you can redistribute it and/or modify it
204+ * under the terms of the GNU General Public License version 3,
205+ * as published by the Free Software Foundation.
206+ *
207+ * This program is distributed in the hope that it will be useful,
208+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
209+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
210+ * GNU General Public License for more details.
211+ *
212+ * You should have received a copy of the GNU General Public License
213+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
214+ *
215+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
216+ */
217+
218+#ifndef MIR_OBSERVER_MULTIPLEXER_H_
219+#define MIR_OBSERVER_MULTIPLEXER_H_
220+
221+#include "mir/observer_registrar.h"
222+#include "mir/raii.h"
223+#include "mir/posix_rw_mutex.h"
224+#include "mir/executor.h"
225+#include "mir/main_loop.h"
226+
227+#include <vector>
228+#include <algorithm>
229+#include <mutex>
230+#include <thread>
231+#include <shared_mutex>
232+
233+namespace mir
234+{
235+template<class Observer>
236+class ObserverMultiplexer : public ObserverRegistrar<Observer>, public Observer
237+{
238+public:
239+ void register_interest(std::weak_ptr<Observer> const& observer) override;
240+ void register_interest(
241+ std::weak_ptr<Observer> const& observer,
242+ Executor& executor) override;
243+ void unregister_interest(Observer const& observer) override;
244+
245+protected:
246+ /**
247+ * \param [in] default_executor Executor that will be used as the execution environment
248+ * for any observer that does not specify its own.
249+ * \note \p default_executor must outlive any observer.
250+ */
251+ explicit ObserverMultiplexer(Executor& default_executor)
252+ : default_executor{default_executor}
253+ {
254+ }
255+
256+ /**
257+ * Invoke a member function of Observer on each registered observer.
258+ *
259+ * \tparam MemberFn Must be (Observer::*)(Args...)
260+ * \tparam Args Parameter pack of arguments of Observer member function.
261+ * \param f Pointer to Observer member function to invoke.
262+ * \param args Arguments for member function invocation.
263+ */
264+ template<typename MemberFn, typename... Args>
265+ void for_each_observer(MemberFn f, Args&&... args);
266+private:
267+ Executor& default_executor;
268+
269+ PosixRWMutex observer_mutex;
270+ std::vector<std::pair<Executor&, std::weak_ptr<Observer>>> observers;
271+};
272+
273+template<class Observer>
274+void ObserverMultiplexer<Observer>::register_interest(std::weak_ptr<Observer> const& observer)
275+{
276+ register_interest(observer, default_executor);
277+}
278+
279+template<class Observer>
280+void ObserverMultiplexer<Observer>::register_interest(
281+ std::weak_ptr<Observer> const& observer,
282+ Executor& executor)
283+{
284+ std::lock_guard<decltype(observer_mutex)> lock{observer_mutex};
285+
286+ observers.emplace_back(std::make_pair(std::ref(executor), observer));
287+}
288+
289+template<class Observer>
290+void ObserverMultiplexer<Observer>::unregister_interest(Observer const& observer)
291+{
292+ std::lock_guard<decltype(observer_mutex)> lock{observer_mutex};
293+ observers.erase(
294+ std::remove_if(
295+ observers.begin(),
296+ observers.end(),
297+ [&observer](auto const& candidate)
298+ {
299+ auto const resolved_candidate = candidate.second.lock().get();
300+ return (resolved_candidate == nullptr) || (resolved_candidate == &observer);
301+ }),
302+ observers.end());
303+}
304+
305+template<class Observer>
306+template<typename MemberFn, typename... Args>
307+void ObserverMultiplexer<Observer>::for_each_observer(MemberFn f, Args&&... args)
308+{
309+ static_assert(
310+ std::is_member_function_pointer<MemberFn>::value,
311+ "f must be of type (Observer::*)(Args...), a pointer to an Observer member function.");
312+ auto const invokable_mem_fn = std::mem_fn(f);
313+ std::shared_lock<decltype(observer_mutex)> lock{observer_mutex};
314+ for (auto& observer_pair: observers)
315+ {
316+ if (auto observer = observer_pair.second.lock())
317+ {
318+ observer_pair.first.spawn(std::bind(invokable_mem_fn, observer.get(), std::forward<Args>(args)...));
319+ }
320+ }
321+}
322+}
323+
324+
325+#endif //MIR_OBSERVER_MULTIPLEXER_H_
326
327=== modified file 'src/server/CMakeLists.txt'
328--- src/server/CMakeLists.txt 2016-10-12 06:03:15 +0000
329+++ src/server/CMakeLists.txt 2016-10-19 06:34:58 +0000
330@@ -61,6 +61,11 @@
331 basic_callback.cpp
332 ${PROJECT_SOURCE_DIR}/include/server/mir/time/alarm_factory.h
333 ${PROJECT_SOURCE_DIR}/include/server/mir/time/alarm.h
334+ ${PROJECT_SOURCE_DIR}/include/server/mir/observer_registrar.h
335+ ${PROJECT_SOURCE_DIR}/include/server/mir/executor.h
336+ ${PROJECT_SOURCE_DIR}/src/include/server/mir/observer_multiplexer.h
337+ ${PROJECT_SOURCE_DIR}/src/include/server/mir/glib_main_loop.h
338+ ${PROJECT_SOURCE_DIR}/src/include/server/mir/glib_main_loop_sources.h
339 )
340
341 set(MIR_SERVER_OBJECTS
342
343=== modified file 'src/server/glib_main_loop.cpp'
344--- src/server/glib_main_loop.cpp 2016-07-18 07:38:38 +0000
345+++ src/server/glib_main_loop.cpp 2016-10-19 06:34:58 +0000
346@@ -340,3 +340,19 @@
347 main_loop_exception = e;
348 stop();
349 }
350+
351+void mir::GLibMainLoop::spawn(std::function<void()>&& work)
352+{
353+ auto const action_with_exception_handling =
354+ [this, action = std::move(work)]
355+ {
356+ try { action(); }
357+ catch (...) { handle_exception(std::current_exception()); }
358+ };
359+
360+ detail::add_server_action_gsource(
361+ main_context,
362+ nullptr,
363+ action_with_exception_handling,
364+ [](auto) { return true; });
365+}
366
367=== modified file 'tests/include/mir/test/doubles/mock_main_loop.h'
368--- tests/include/mir/test/doubles/mock_main_loop.h 2016-08-11 07:24:14 +0000
369+++ tests/include/mir/test/doubles/mock_main_loop.h 2016-10-19 06:34:58 +0000
370@@ -60,6 +60,8 @@
371 MOCK_METHOD1(pause_processing_for,void (void const*));
372 MOCK_METHOD1(resume_processing_for,void (void const*));
373
374+ MOCK_METHOD1(spawn, void(std::function<void()>&));
375+
376 MOCK_METHOD1(create_alarm, std::unique_ptr<time::Alarm>(std::function<void()> const& callback));
377 MOCK_METHOD1(create_alarm, std::unique_ptr<time::Alarm>(LockableCallback* callback));
378
379@@ -82,6 +84,12 @@
380 {
381 register_fd_handler_module_ptr(fds, owner, *handler);
382 }
383+
384+ void spawn(std::function<void()>&& work) override
385+ {
386+ std::function<void()> work_copy{std::move(work)};
387+ spawn(work_copy);
388+ }
389 };
390
391 }
392
393=== modified file 'tests/include/mir/test/doubles/triggered_main_loop.h'
394--- tests/include/mir/test/doubles/triggered_main_loop.h 2015-06-25 03:00:08 +0000
395+++ tests/include/mir/test/doubles/triggered_main_loop.h 2016-10-19 06:34:58 +0000
396@@ -42,9 +42,12 @@
397 std::unique_ptr<mir::time::Alarm> create_alarm(callback const& call) override;
398 void enqueue(void const* owner, ServerAction const& action) override;
399
400+ void spawn(std::function<void()>&& work) override;
401+
402 void trigger_pending_fds();
403 void fire_all_alarms();
404 void trigger_server_actions();
405+ void trigger_spawned_work();
406
407 private:
408 std::vector<callback> timeout_callbacks;
409@@ -57,6 +60,7 @@
410 };
411 std::vector<Item> fd_callbacks;
412 std::vector<ServerAction> actions;
413+ std::vector<std::function<void()>> work;
414 };
415
416 }
417
418=== modified file 'tests/mir_test_doubles/triggered_main_loop.cpp'
419--- tests/mir_test_doubles/triggered_main_loop.cpp 2016-08-25 08:16:10 +0000
420+++ tests/mir_test_doubles/triggered_main_loop.cpp 2016-10-19 06:34:58 +0000
421@@ -101,3 +101,18 @@
422 for(auto const& callback : timeout_callbacks)
423 callback();
424 }
425+
426+void mtd::TriggeredMainLoop::spawn(std::function<void()>&& work)
427+{
428+ base::spawn(std::function<void()>{work});
429+ this->work.emplace_back(std::move(work));
430+}
431+
432+void mtd::TriggeredMainLoop::trigger_spawned_work()
433+{
434+ for (auto const& action : work)
435+ {
436+ action();
437+ }
438+ work.clear();
439+}
440
441=== modified file 'tests/unit-tests/CMakeLists.txt'
442--- tests/unit-tests/CMakeLists.txt 2016-10-13 00:44:47 +0000
443+++ tests/unit-tests/CMakeLists.txt 2016-10-19 06:34:58 +0000
444@@ -80,6 +80,7 @@
445 test_module_deleter.cpp
446 test_mir_cookie.cpp
447 test_posix_rw_mutex.cpp
448+ test_observer_multiplexer.cpp
449 )
450
451 CMAKE_DEPENDENT_OPTION(
452
453=== added file 'tests/unit-tests/test_observer_multiplexer.cpp'
454--- tests/unit-tests/test_observer_multiplexer.cpp 1970-01-01 00:00:00 +0000
455+++ tests/unit-tests/test_observer_multiplexer.cpp 2016-10-19 06:34:58 +0000
456@@ -0,0 +1,608 @@
457+/*
458+ * Copyright © 2016 Canonical Ltd.
459+ *
460+ * This program is free software: you can redistribute it and/or modify it
461+ * under the terms of the GNU General Public License version 3,
462+ * as published by the Free Software Foundation.
463+ *
464+ * This program is distributed in the hope that it will be useful,
465+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
466+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
467+ * GNU General Public License for more details.
468+ *
469+ * You should have received a copy of the GNU General Public License
470+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
471+ *
472+ * Authored by: Christopher James Halse Rogers <christopher.halse.rogers@canonical.com>
473+ */
474+
475+#include "mir/observer_multiplexer.h"
476+
477+#include "mir/test/barrier.h"
478+#include "mir/test/auto_unblock_thread.h"
479+
480+#include <memory>
481+#include <array>
482+#include <thread>
483+#include <atomic>
484+#include <queue>
485+
486+#include <gtest/gtest.h>
487+#include <gmock/gmock.h>
488+
489+namespace mt = mir::test;
490+
491+namespace
492+{
493+class TestObserver
494+{
495+public:
496+ virtual ~TestObserver() = default;
497+
498+ virtual void observation_made(std::string const& arg) = 0;
499+ virtual void multi_argument_observation(std::string const& arg, int another_one, float third) = 0;
500+};
501+
502+class MockObserver : public TestObserver
503+{
504+public:
505+ MOCK_METHOD1(observation_made, void(std::string const&));
506+ MOCK_METHOD3(multi_argument_observation, void(std::string const&, int, float));
507+};
508+
509+class ThreadedExecutor : public mir::Executor
510+{
511+public:
512+ ThreadedExecutor()
513+ {
514+ worker = std::thread{
515+ [this]()
516+ {
517+ while (running)
518+ {
519+ std::function<void()> work{nullptr};
520+ {
521+ std::unique_lock<decltype(work_mutex)> lock{work_mutex};
522+ work_changed.wait(lock, [this]() { return (work_pending.size() > 0) || !running; });
523+
524+ if (work_pending.size() > 0)
525+ {
526+ work = work_pending.front();
527+ }
528+ }
529+
530+ if (work)
531+ {
532+ work();
533+
534+ {
535+ std::lock_guard<decltype(work_mutex)> lock{work_mutex};
536+ work_pending.pop();
537+ work_changed.notify_all();
538+ }
539+ work_changed.notify_all();
540+ }
541+ }
542+ }
543+ };
544+ }
545+
546+ void drain_work()
547+ {
548+ std::unique_lock<decltype(work_mutex)> lock{work_mutex};
549+ work_changed.wait(lock, [this]() { return work_pending.size() == 0; });
550+ }
551+
552+ ~ThreadedExecutor()
553+ {
554+ drain_work();
555+
556+ running = false;
557+ work_changed.notify_all();
558+
559+ worker.join();
560+ }
561+
562+ void spawn(std::function<void()>&& work) override
563+ {
564+ std::lock_guard<decltype(work_mutex)> lock{work_mutex};
565+ work_pending.emplace(std::move(work));
566+ work_changed.notify_all();
567+ }
568+
569+private:
570+ std::thread worker;
571+ std::atomic<bool> running{true};
572+
573+ std::mutex work_mutex;
574+ std::condition_variable work_changed;
575+ std::queue<std::function<void()>> work_pending;
576+};
577+
578+class TestObserverMultiplexer : public mir::ObserverMultiplexer<TestObserver>
579+{
580+public:
581+ TestObserverMultiplexer(mir::Executor& executor)
582+ : ObserverMultiplexer(executor)
583+ {
584+ }
585+
586+ void observation_made(std::string const& arg) override
587+ {
588+ for_each_observer(&TestObserver::observation_made, arg);
589+ }
590+
591+ void multi_argument_observation(std::string const& arg, int another_one, float third) override
592+ {
593+ for_each_observer(&TestObserver::multi_argument_observation, arg, another_one, third);
594+ }
595+};
596+}
597+
598+TEST(ObserverMultiplexer, each_added_observer_recieves_observations)
599+{
600+ using namespace testing;
601+ ThreadedExecutor executor;
602+ TestObserverMultiplexer multiplexer{executor};
603+ std::string const value = "Hello, my name is Inigo Montoya.";
604+
605+
606+ auto observer_one = std::make_shared<NiceMock<MockObserver>>();
607+ auto observer_two = std::make_shared<NiceMock<MockObserver>>();
608+
609+ EXPECT_CALL(*observer_one, observation_made(StrEq(value))).Times(2);
610+ EXPECT_CALL(*observer_two, observation_made(StrEq(value))).Times(1);
611+
612+
613+ multiplexer.register_interest(observer_one);
614+ multiplexer.observation_made(value);
615+
616+ multiplexer.register_interest(observer_two);
617+ multiplexer.observation_made(value);
618+
619+ executor.drain_work();
620+}
621+
622+TEST(ObserverMultiplexer, removed_observers_do_not_recieve_observations)
623+{
624+ using namespace testing;
625+ std::string const value = "The girl of my dreams is giving me nightmares";
626+
627+ ThreadedExecutor executor;
628+ TestObserverMultiplexer multiplexer{executor};
629+
630+ auto observer_one = std::make_shared<NiceMock<MockObserver>>();
631+ auto observer_two = std::make_shared<NiceMock<MockObserver>>();
632+
633+ EXPECT_CALL(*observer_one, observation_made(StrEq(value))).Times(2);
634+ EXPECT_CALL(*observer_two, observation_made(StrEq(value))).Times(1);
635+
636+ multiplexer.register_interest(observer_one);
637+ multiplexer.register_interest(observer_two);
638+ multiplexer.observation_made(value);
639+
640+ multiplexer.unregister_interest(*observer_two);
641+ multiplexer.observation_made(value);
642+
643+ executor.drain_work();
644+}
645+
646+TEST(ObserverMultiplexer, can_remove_observer_from_callback)
647+{
648+ using namespace testing;
649+ std::string const value = "Goldfinger";
650+
651+ auto observer_one = std::make_shared<NiceMock<MockObserver>>();
652+ auto observer_two = std::make_shared<NiceMock<MockObserver>>();
653+
654+ ThreadedExecutor executor;
655+ TestObserverMultiplexer multiplexer{executor};
656+
657+ EXPECT_CALL(*observer_one, observation_made(StrEq(value)))
658+ .WillOnce(Invoke(
659+ [observer_one = observer_one.get(), &multiplexer, &value](auto)
660+ {
661+ multiplexer.unregister_interest(*observer_one);
662+ multiplexer.observation_made(value);
663+ }));
664+ EXPECT_CALL(*observer_two, observation_made(StrEq(value)))
665+ .Times(2);
666+
667+ multiplexer.register_interest(observer_one);
668+ multiplexer.register_interest(observer_two);
669+
670+ multiplexer.observation_made(value);
671+
672+ executor.drain_work();
673+}
674+
675+TEST(ObserverMultiplexer, multiple_threads_can_simultaneously_make_observations)
676+{
677+ using namespace testing;
678+
679+ std::array<bool, 10> values_seen;
680+ std::array<std::string, values_seen.size()> values;
681+ for (auto i = 0u; i < values.size(); ++i)
682+ {
683+ values[i] = std::to_string(i);
684+ }
685+
686+ auto observer = std::make_shared<NiceMock<MockObserver>>();
687+ ON_CALL(*observer, observation_made(_))
688+ .WillByDefault(
689+ Invoke(
690+ [&values, &values_seen](auto const& value)
691+ {
692+ for (auto i = 0u; i < values.size(); ++i)
693+ {
694+ if (value == values[i])
695+ {
696+ values_seen[i] = true;
697+ }
698+ }
699+ }));
700+
701+ ThreadedExecutor executor;
702+ TestObserverMultiplexer multiplexer{executor};
703+ multiplexer.register_interest(observer);
704+
705+ mt::Barrier threads_ready(values.size());
706+ mt::Barrier observations_made(values.size() + 1);
707+ std::array<mt::AutoJoinThread, values_seen.size()> threads;
708+
709+ for (auto i = 0u; i < values.size(); ++i)
710+ {
711+ threads[i] = mt::AutoJoinThread(
712+ [&threads_ready, &observations_made, &multiplexer, value = values[i]]()
713+ {
714+ threads_ready.ready();
715+ multiplexer.observation_made(value);
716+ observations_made.ready();
717+ });
718+ }
719+ observations_made.ready();
720+
721+ executor.drain_work();
722+
723+ EXPECT_THAT(
724+ std::vector<bool>(values_seen.begin(), values_seen.end()),
725+ ContainerEq(std::vector<bool>(values_seen.size(), true)));
726+}
727+
728+TEST(ObserverMultiplexer, multiple_threads_registering_unregistering_and_observing)
729+{
730+ using namespace testing;
731+
732+ auto observer_one = std::make_shared<NiceMock<MockObserver>>();
733+ auto observer_two = std::make_shared<NiceMock<MockObserver>>();
734+
735+ ThreadedExecutor executor;
736+ TestObserverMultiplexer multiplexer{executor};
737+
738+ ON_CALL(*observer_one, observation_made(_))
739+ .WillByDefault(
740+ Invoke(
741+ [&multiplexer, observer_two](std::string const& value)
742+ {
743+ if (!value.compare("3"))
744+ {
745+ multiplexer.unregister_interest(*observer_two);
746+ }
747+ if (!value.compare("5"))
748+ {
749+ multiplexer.register_interest(observer_two);
750+ }
751+ }));
752+ ON_CALL(*observer_one, observation_made(_))
753+ .WillByDefault(
754+ Invoke(
755+ [&multiplexer, observer_two](std::string const& value)
756+ {
757+ if (!value.compare("8"))
758+ {
759+ multiplexer.unregister_interest(*observer_two);
760+ }
761+ }));
762+
763+ multiplexer.register_interest(observer_two);
764+ multiplexer.register_interest(observer_one);
765+
766+ std::array<mt::AutoJoinThread, 10> threads;
767+ mt::Barrier threads_done(threads.size() + 1);
768+
769+ for (auto i = 0u; i < threads.size(); ++i)
770+ {
771+ threads[i] = mt::AutoJoinThread(
772+ [&threads_done, &multiplexer]()
773+ {
774+ for (auto i = 0; i < 50; ++i)
775+ {
776+ for (auto j = 0; j < 10; ++j)
777+ {
778+ multiplexer.observation_made(std::to_string(j));
779+ }
780+ }
781+ threads_done.ready();
782+ });
783+ }
784+ threads_done.ready();
785+
786+ executor.drain_work();
787+}
788+
789+TEST(ObserverMultiplexer, multiple_threads_unregistering_same_observer_is_safe)
790+{
791+ using namespace testing;
792+
793+ ThreadedExecutor executor;
794+ TestObserverMultiplexer multiplexer{executor};
795+
796+ auto observer_one = std::make_shared<NiceMock<MockObserver>>();
797+ auto observer_two = std::make_shared<NiceMock<MockObserver>>();
798+
799+ std::atomic<int> call_count{0};
800+ ON_CALL(*observer_one, observation_made(_))
801+ .WillByDefault(
802+ Invoke(
803+ [&multiplexer, observer_two, &call_count](auto)
804+ {
805+ ++call_count;
806+ multiplexer.unregister_interest(*observer_two);
807+ }));
808+ std::atomic<bool> victim_called{false};
809+ ON_CALL(*observer_two, observation_made(_))
810+ .WillByDefault(Invoke([&victim_called](auto) { victim_called = true; }));
811+
812+ multiplexer.register_interest(observer_one);
813+ multiplexer.register_interest(observer_two);
814+
815+ std::array<mt::AutoJoinThread, 10> threads;
816+ mt::Barrier threads_done(threads.size() + 1);
817+
818+ for (auto i = 0u; i < threads.size(); ++i)
819+ {
820+ threads[i] = mt::AutoJoinThread(
821+ [&threads_done, &multiplexer]()
822+ {
823+ multiplexer.observation_made("Hello");
824+ threads_done.ready();
825+ });
826+ }
827+ threads_done.ready();
828+
829+ executor.drain_work();
830+
831+ auto precount = call_count.load();
832+
833+ multiplexer.observation_made("Banana");
834+
835+ executor.drain_work();
836+
837+ EXPECT_THAT(call_count, Eq(precount + 1));
838+}
839+
840+TEST(ObserverMultiplexer, registering_is_threadsafe)
841+{
842+ using namespace testing;
843+
844+ std::array<bool, 100> observer_notified;
845+ std::array<std::shared_ptr<NiceMock<MockObserver>>, observer_notified.size()> observers;
846+
847+ for (auto i = 0u; i < observers.size(); ++i)
848+ {
849+ observers[i] = std::make_shared<NiceMock<MockObserver>>();
850+ ON_CALL(*observers[i], observation_made(_))
851+ .WillByDefault(Invoke([notified = &observer_notified[i]](auto) { *notified = true; }));
852+ }
853+ std::array<mt::AutoJoinThread, observer_notified.size()> threads;
854+ mt::Barrier threads_done(threads.size() + 1);
855+
856+ ThreadedExecutor executor;
857+ TestObserverMultiplexer multiplexer{executor};
858+
859+ for (auto i = 0u; i < threads.size(); ++i)
860+ {
861+ threads[i] = mt::AutoJoinThread(
862+ [&threads_done, &multiplexer, observer = observers[i]]()
863+ {
864+ multiplexer.register_interest(observer);
865+ threads_done.ready();
866+ });
867+ }
868+ threads_done.ready();
869+
870+ multiplexer.observation_made("Foo");
871+
872+ executor.drain_work();
873+
874+ EXPECT_THAT(
875+ std::vector<bool>(observer_notified.begin(), observer_notified.end()),
876+ ContainerEq(std::vector<bool>(observer_notified.size(), true)));
877+}
878+
879+TEST(ObserverMultiplexer, unregistering_is_threadsafe)
880+{
881+ using namespace testing;
882+
883+ std::array<bool, 10> observer_notified;
884+ for (auto& notified : observer_notified)
885+ {
886+ notified = false;
887+ }
888+
889+ std::array<std::shared_ptr<NiceMock<MockObserver>>, observer_notified.size()> observers;
890+
891+ for (auto i = 0u; i < observers.size(); ++i)
892+ {
893+ observers[i] = std::make_shared<NiceMock<MockObserver>>();
894+ ON_CALL(*observers[i], observation_made(_))
895+ .WillByDefault(Invoke([notified = &observer_notified[i]](auto) { *notified = true; }));
896+ }
897+ std::array<mt::AutoJoinThread, observer_notified.size()> threads;
898+ mt::Barrier threads_done(threads.size() + 1);
899+
900+ ThreadedExecutor executor;
901+ TestObserverMultiplexer multiplexer{executor};
902+
903+ for (auto observer : observers)
904+ {
905+ multiplexer.register_interest(observer);
906+ }
907+
908+ for (auto i = 0u; i < threads.size(); ++i)
909+ {
910+ threads[i] = mt::AutoJoinThread(
911+ [&threads_done, &multiplexer, observer = observers[i]]()
912+ {
913+ multiplexer.unregister_interest(*observer);
914+ threads_done.ready();
915+ });
916+ }
917+ threads_done.ready();
918+
919+ multiplexer.observation_made("Foo");
920+
921+ executor.drain_work();
922+
923+ EXPECT_THAT(
924+ std::vector<bool>(observer_notified.begin(), observer_notified.end()),
925+ ContainerEq(std::vector<bool>(observer_notified.size(), false)));
926+}
927+
928+TEST(ObserverMultiplexer, can_trigger_observers_from_observers)
929+{
930+ using namespace testing;
931+ constexpr char const* first_observation = "Elementary, my dear Watson";
932+ constexpr char const* second_observation = "You're one microscopic cog in his catastrophic plan";
933+
934+ auto observer = std::make_shared<NiceMock<MockObserver>>();
935+
936+ ThreadedExecutor executor;
937+ TestObserverMultiplexer multiplexer{executor};
938+
939+ EXPECT_CALL(*observer, observation_made(StrEq(first_observation)))
940+ .WillOnce(InvokeWithoutArgs([&multiplexer]() { multiplexer.observation_made(second_observation); }));
941+ EXPECT_CALL(*observer, observation_made(StrEq(second_observation)));
942+
943+ multiplexer.register_interest(observer);
944+
945+ multiplexer.observation_made(first_observation);
946+
947+ executor.drain_work();
948+}
949+
950+TEST(ObserverMultiplexer, addition_takes_effect_immediately_even_in_callback)
951+{
952+ using namespace testing;
953+ constexpr char const* first_observation = "Rhythm & Blues Alibi";
954+ constexpr char const* second_observation = "Blue Moon Rising";
955+
956+ ThreadedExecutor executor;
957+ TestObserverMultiplexer multiplexer{executor};
958+
959+ auto observer_one = std::make_shared<NiceMock<MockObserver>>();
960+ auto observer_two = std::make_shared<NiceMock<MockObserver>>();
961+
962+ EXPECT_CALL(*observer_one, observation_made(StrEq(first_observation)))
963+ .WillOnce(
964+ InvokeWithoutArgs(
965+ [&multiplexer, observer_two]()
966+ {
967+ multiplexer.register_interest(observer_two);
968+ multiplexer.observation_made(second_observation);
969+ }));
970+ EXPECT_CALL(*observer_one, observation_made(Not(StrEq(first_observation))))
971+ .Times(AnyNumber());
972+ EXPECT_CALL(*observer_two, observation_made(StrEq(second_observation)));
973+
974+ multiplexer.register_interest(observer_one);
975+
976+ multiplexer.observation_made(first_observation);
977+
978+ executor.drain_work();
979+}
980+
981+TEST(ObserverMultiplexer, observations_can_be_delegated_to_specified_executor)
982+{
983+ using namespace testing;
984+
985+ class CountingExecutor : public mir::Executor
986+ {
987+ public:
988+ void spawn(std::function<void()>&& work) override
989+ {
990+ std::lock_guard<decltype(work_mutex)> lock{work_mutex};
991+ ++spawn_count;
992+ work_queue.emplace(std::move(work));
993+ }
994+
995+ void do_work()
996+ {
997+ std::unique_lock<decltype(work_mutex)> lock{work_mutex};
998+ while (!work_queue.empty())
999+ {
1000+ auto work = work_queue.front();
1001+ work_queue.pop();
1002+ lock.unlock();
1003+
1004+ work();
1005+
1006+ lock.lock();
1007+ }
1008+ }
1009+
1010+ int work_spawned()
1011+ {
1012+ std::lock_guard<decltype(work_mutex)> lock{work_mutex};
1013+ return spawn_count;
1014+ }
1015+ private:
1016+ int spawn_count{0};
1017+
1018+ std::mutex work_mutex;
1019+ std::queue<std::function<void()>> work_queue;
1020+ } counting_executor;
1021+
1022+ ThreadedExecutor executor;
1023+ TestObserverMultiplexer multiplexer{executor};
1024+
1025+ auto observer_one = std::make_shared<NiceMock<MockObserver>>();
1026+ auto observer_two = std::make_shared<NiceMock<MockObserver>>();
1027+
1028+ EXPECT_CALL(*observer_one, observation_made(_));
1029+ EXPECT_CALL(*observer_two, observation_made(_));
1030+
1031+ multiplexer.register_interest(observer_one);
1032+ multiplexer.register_interest(observer_two, counting_executor);
1033+
1034+ EXPECT_THAT(counting_executor.work_spawned(), Eq(0));
1035+
1036+ multiplexer.observation_made("Hello!");
1037+
1038+ EXPECT_THAT(counting_executor.work_spawned(), Eq(1));
1039+
1040+ counting_executor.do_work();
1041+ executor.drain_work();
1042+}
1043+
1044+TEST(ObserverMultiplexer, multi_argument_observations_work)
1045+{
1046+ using namespace testing;
1047+
1048+ std::string const a{"The Owls Go"};
1049+ constexpr int b{55};
1050+ constexpr float c{3.1415f};
1051+
1052+ ThreadedExecutor executor;
1053+ TestObserverMultiplexer multiplexer{executor};
1054+
1055+ auto observer = std::make_shared<NiceMock<MockObserver>>();
1056+
1057+ multiplexer.register_interest(observer);
1058+
1059+ EXPECT_CALL(*observer, multi_argument_observation(a, b, c));
1060+
1061+ multiplexer.multi_argument_observation(a, b, c);
1062+
1063+ executor.drain_work();
1064+}

Subscribers

People subscribed via source and target branches