Mir

Merge lp:~vanvugt/mir/wait-result into lp:mir

Proposed by Daniel van Vugt
Status: Work in progress
Proposed branch: lp:~vanvugt/mir/wait-result
Merge into: lp:mir
Diff against target: 575 lines (+142/-54)
13 files modified
include/client/mir_toolkit/mir_wait.h (+8/-0)
src/client/buffer_stream.cpp (+2/-2)
src/client/mir_connection.cpp (+8/-6)
src/client/mir_prompt_session.cpp (+3/-3)
src/client/mir_screencast.cpp (+4/-3)
src/client/mir_surface.cpp (+14/-7)
src/client/mir_surface.h (+1/-1)
src/client/mir_wait_api.cpp (+13/-0)
src/client/mir_wait_handle.cpp (+16/-12)
src/client/mir_wait_handle.h (+18/-3)
src/client/symbols.map (+6/-1)
tests/acceptance-tests/test_client_library.cpp (+22/-9)
tests/unit-tests/client/test_wait_handle.cpp (+27/-7)
To merge this branch: bzr merge lp:~vanvugt/mir/wait-result
Reviewer Review Type Date Requested Status
Alan Griffiths Disapprove
Chris Halse Rogers Disapprove
PS Jenkins bot (community) continuous-integration Approve
Mir development team Pending
Review via email: mp+252403@code.launchpad.net

Commit message

Add a new client function: mir_wait_for_result() which returns a bool indicating success or failure of a request.

This is needed for use with client functions that change server state but can't read back results (or simply don't care for anything more than a bool). The need for this is limited right now, but it's coming in the near future, I promise.

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
Chris Halse Rogers (raof) wrote :

On first inspection this looks unnecessary and ugly to me; maybe once I see the use you intend to put it to it'll appear more reasonable?

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

It's necessary for the reasons outlined in the description. And it avoids the need for adding new callback types, which would be very ugly.

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

Yes, but could you give an example of such a function?

The closest I can think of is along the lines of mir_surface_set_state, but you can't reasonably just wait on the boolean result - the shell could respond by changing your state but not to the exact state you asked for. Indeed, the shell can change your state at any point without you calling mir_surface_set_state, so if you need to react to state changes you already need to be listening to surface events.

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

Oh good point. You can find the answer buried deep in the diff but I will explain:

   mir_surface_set_state(surface, valid_supported_state) == true
   mir_surface_set_state(surface, invalid_state) == false
   mir_surface_set_state(surface, valid_unsupported_state) == false

The last case is the curious one. So I'm saying if result == true then the shell has successfully set exactly what you asked for.

If result == false, then the shell hasn't done what you asked for, but it might have done something different (e.g. chosen a similar state it supports instead of what you asked for).

So maybe the attribute/state result needs a rethink. But mir_surface_set_state isn't the important case here. The important case for this proposal is multi-attribute morphing in future:

   mir_surface_morph(surface, new_spec) == result

only returns true if ALL attributes of new_spec could be applied and supported together. Else result==false and nothing is changed. Totally clear and unambiguous in that case. And the behaviour of such functions should be documented clearly in the client headers.

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

Ah, right.

My feeling is that this use in particular could be better done on the callback-side by providing an aggregate surface event with all the relevant new state at once. Morph then becomes a set_state-like void call.

This also cleans up server-driven changes somewhat - for example, we can send the client can an event containing both state_fullscreen and the new surface size.

Presumably the server will be sending the updated state anyway? The client can't actually rely on the values matching new_spec even after mir_wait_for_result(mir_surface_morph(surface, new_spec)) returns true, because the server may have already sent - and the client event thread already processed - new attributes.

My feeling is that any client code that responds to attributes that isn't being directly driven from a surface event callback is fundamentally incorrect, and we shouldn't make it appear as though you can safely do anything else.

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

Ah, right.

My feeling is that this use in particular could be better done on the callback-side by providing an aggregate surface event with all the relevant new state at once. Morph then becomes a set_state-like void call.

This also cleans up server-driven changes somewhat - for example, we can send the client can an event containing both state_fullscreen and the new surface size.

Presumably the server will be sending the updated state anyway? The client can't actually rely on the values matching new_spec even after mir_wait_for_result(mir_surface_morph(surface, new_spec)) returns true, because the server may have already sent - and the client event thread already processed - new attributes.

My feeling is that any client code that responds to attributes that isn't being directly driven from a surface event callback is fundamentally incorrect, and we shouldn't make it appear as though you can safely do anything else.

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

Ah, right.

My feeling is that this use in particular could be better done on the callback-side by providing an aggregate surface event with all the relevant new state at once. Morph then becomes a set_state-like void call.

This also cleans up server-driven changes somewhat - for example, we can send the client can an event containing both state_fullscreen and the new surface size.

Presumably the server will be sending the updated state anyway? The client can't actually rely on the values matching new_spec even after mir_wait_for_result(mir_surface_morph(surface, new_spec)) returns true, because the server may have already sent - and the client event thread already processed - new attributes.

My feeling is that any client code that responds to attributes that isn't being directly driven from a surface event callback is fundamentally incorrect, and we shouldn't make it appear as though you can safely do anything else.

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

> Ah, right.
>
> My feeling is that this use in particular could be better done on the
> callback-side by providing an aggregate surface event with all the relevant
> new state at once. Morph then becomes a set_state-like void call.
>
> This also cleans up server-driven changes somewhat - for example, we can send
> the client can an event containing both state_fullscreen and the new surface
> size.
>
> Presumably the server will be sending the updated state anyway? The client
> can't actually rely on the values matching new_spec even after
> mir_wait_for_result(mir_surface_morph(surface, new_spec)) returns true,
> because the server may have already sent - and the client event thread already
> processed - new attributes.
>
> My feeling is that any client code that responds to attributes that isn't
> being directly driven from a surface event callback is fundamentally
> incorrect, and we shouldn't make it appear as though you can safely do
> anything else.

+1

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

Well, I don't need this right now.

But in the near future when you can't tell if client functions succeeded or not, we'll need something. Preferably not callbacks, because they're so awkward to use. Maybe events, but still they're much more awkward for simple clients to use than what's presented here.

I really like this approach. In theory it allows us to completely abolish callbacks in future as you could do something like:

   wait = mir_create_surface();
   ...
   ... other things ...
   ...
   surf = mir_wait_for_a_surface(wait);

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

You can't completely abolish callbacks without making asynchronously waiting on a result painful, but, yes, I do like the continuation/AsyncContext wait API I proposed (and that Windows¹ and GLib² use, among others) for our asynchronous API some years ago :).

I don't see how this is a step in that direction, though, and I think the specific uses in this MP make the API harder to use - in the case of waiting for a construction it's a duplication of foo_is_valid() without the ability to query the error condition, and in the case of waiting for set_foo() it's promoting an incorrect interaction with the client API.

I'm fully supportive of reworking our MirWaitHandle API to be less cumbersome, but that probably merits a ML discussion.

¹: Via ctx = FooBegin/result = FooEnd(ctx) pairs
²: Via GAsyncResult

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

1. The assertion at the start of your comment is incorrect. As demonstrated by this branch. A single function call is obviously less "painful" than writing callbacks. Admittedly this counts as opinion, but I think you'd probably find 99% of developers agree that a single function call that avoids the need for callbacks is preferable.

2. "in the case of waiting for a construction it's a duplication of foo_is_valid()". Yep foo_is_valid would become unnecessary, which is a good thing. That's one less function that clients need to bother calling.

3. "without the ability to query the error condition". Incorrect. I already documented future plans for that:
376 + // Suggested future uses:
377 + // const char* error;

For example (bug 1431190):
   int err = mir_wait_for_error(wait_handle);
   fprintf(stderr, "Thingy failed due to: %s\n", strerror(err));
or (without fixing bug 1431190):
   const char *err = mir_wait_for_error(wait_handle);
   fprintf(stderr, "Thingy failed due to: %s\n", err);

Another value in the approach here is that it solves the thread safety problem of querying errors we have today (mir_X_get_error_message). If you have multiple threads operating on a single Mir object then our current API will leak incorrect error values between threads. The approach proposed here will not.

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

I'm confused. How does your branch allow asynchronously waiting on a result? mir_wait_for_result blocks until the result is available; doing something with that is either synchronous or requires spinning up a thread to block.

Look, I'm all for replacing MirWaitHandle with MirAsyncResult and all the relevant things. I think that warrants a mailing list discussion, though, and I don't think that this branch gets us closer to MirAsyncResult.

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

I don't understand what's unclear. It allows asynchronously waiting as the client can choose to do other things before it gets around to calling any mir_wait_for* function. Just as the existing mir_wait_* functions allow clients to be asynchronous.

I totally agree some WaitForMultipleObjects (win32, as desrt has played with recently) would be better. But that's a potentially a whole new API and I'm not sure if or when Mir will get around to growing such functionality. It's also not mutually exclusive... such an API could live side-by-side with our existing mir_wait_for_ functions. For example:
   MirWaitHandle handles[5];
   mir_wait_for_any(handles, 5);
   mir_wait_for_all(handles, 5);

So there's no reason why such future functionality should invalidate the work proposed here.

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

I don't see anything in the recent discussion that relates to the pointlessness of being able to wait for a boolean result that tells the client nothing useful. In fact, I see little point in being able to wait on these requests to the server.

> The last case is the curious one. So I'm saying if result == true then the shell has successfully
> set exactly what you asked for.

But even if we were to make a guarantee that the corresponding state change event has been processed by the client before the wait completes with true there can be no guarantee that the state hasn't changed again.

So whether the result is true or false the boolean tells nothing about the current state.

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

On Mon, Mar 16, 2015 at 6:34 PM, Daniel van Vugt
<email address hidden> wrote:
> I don't understand what's unclear. It allows asynchronously waiting
> as the client can choose to do other things before it gets around to
> calling any mir_wait_for* function. Just as the existing mir_wait_*
> functions allow clients to be asynchronous.

Aaah! This is where my misunderstanding is; we have different
definitions of “asynchronously waiting”.

When I say “asynchronously waiting” I mean some mechanism by which
the client can be *notified* when the thing has happened, and can do
arbitrary processing in the meantime. This basically requires
callbacks, or equivalent language features (async/await for C# & Vala,
for example).

I don't think that “the client can choose to do other things before
blocking its thread” is valuable. It's insufficient for toolkits and
not much better than *_sync for the few simple clients using the client
API directly.

Additionally, I'm with Alan - having mir_surface_set_* return a wait
handle is a mistake that we should correct by removing the wait handle
(and possibly providing a “the server is not going to honour that
request, sorry” mechanism), not by providing a boolean return value
that is impossible to use correctly.

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

The value of this branch is more clear in the surface morphing prototype:
https://code.launchpad.net/~vanvugt/mir/rename-api/+merge/252864

It's not "pointless". It is /probably/ required that the client be told if their multi-attribute modification has happened or not. In the latter "not" case there is no event to wait for. So you need this wait-for-result, or something similar like wait-for-error-code-or-success.

In the mean time, it hurts nobody to not land anything in this area. Just that we'll soon have a situation where some features of the client API can't return errors to the client. Which might turn out to be acceptable...

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

On Tue, Mar 17, 2015 at 3:07 PM, Daniel van Vugt
<email address hidden> wrote:
> The value of this branch is more clear in the surface morphing
> prototype:
> https://code.launchpad.net/~vanvugt/mir/rename-api/+merge/252864
>
> It's not "pointless". It is /probably/ required that the client be
> told if their multi-attribute modification has happened or not. In
> the latter "not" case there is no event to wait for. So you need this
> wait-for-result, or something similar like
> wait-for-error-code-or-success.

I agree that we need something; I think that the something has to be an
event, because it has to be useful to toolkits for which blocking their
thread is unacceptable.

GTK isn't going to use wait_for_error_code_or_success unless we can
provide a guarantee that it doesn't block, so we'll either need an
event or to turn our wait-handles into something more continuationy.

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

Come to think of it, needs more opinions still.

I get what you're saying but you're making the assumption that some generic "event" (in win32 known as a HANDLE) can't be implemented under the name "MirWaitHandle". I think it could be, transparently. I know this because I've done the cross-platform wait handle API exercise before. In fact it's quite easy with the internal structure of MirWaitHandle right now.

On that theme, it would be easy to implement the non-blocking "peek" functionality you're talking about too.

Given our client types are all opaque, it sounds like you're just blocking because you don't like the name "MirWaitHandle". And I kind of agree, but that's a pre-existing issue and it's not a major penalty to live with that name.

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

Oh, indeed. MirWaitHandle *could* grow a mir_wait_handle_set_callback()
that would set the callback to be triggered when the wait handle
becomes ready.

I think that if we were to do that we'd want to significantly rework
the rest of the client API, though - we'd have two separate ways to do
essentially the same thing. Not that I'm opposed to that, mind.

What I'm specifically blocking on here is mir_surface_set_* returning a
wait handle that returns a value. You could resolve this by making all
the setters return a null wait handle, which we already handle as
immediately available. And later, when we feel like breaking client
API, we remove the wait handle return from them completely.

That would make me abstain, pending a ML discussion of what we actually
want WaitHandles to be.

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

"What I'm specifically blocking on here is mir_surface_set_* returning a wait handle that returns a value."

You'll notice I haven't directly modified any of those functions here. It would be inconsistent to treat those MirWaitHandles differently to any other MirWaitHandle. It should also be remembered that this proposal solves the thread safety problem without resorting to callbacks. Nobody has proposed any other solution that achieves this.

"You could resolve this by making all the setters return a null wait handle"

You're asking me to break existing behaviour that has been in place for two years already. Admittedly the wait handle on setters is low value and will be ignored by most users, but we get it for free. And any corresponding _get call needs to be synchronized with the wait handle from the _set, because all our _get functions are non-blocking (they just return the local cache contents). So we presently have non-blocking _get/_set functions with the ability to synchronize them. Sounds like a good thing to me. You're asking for some of that functionality to be removed and I'm still not sure why. It also feels off topic for this MP.

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

On Wed, Mar 18, 2015 at 2:29 PM, Daniel van Vugt
<email address hidden> wrote:
> "What I'm specifically blocking on here is mir_surface_set_*
> returning a wait handle that returns a value."
>
> You'll notice I haven't directly modified any of those functions
> here. It would be inconsistent to treat those MirWaitHandles
> differently to any other MirWaitHandle. It should also be remembered
> that this proposal solves the thread safety problem without resorting
> to callbacks. Nobody has proposed any other solution that achieves
> this.

I don't dispute that nobody has proposed a solution that doesn't have
callbacks. I equally don't think that this *is a solution*, and I think
that the reason nobody has proposed a solution that doesn't have
callbacks is because there *is* no solution that doesn't have callbacks
or language features equivalent to implicit callbacks.

This is a solution to a slightly different problem, but I don't think
it's a problem that is useful to solve.

>
> "You could resolve this by making all the setters return a null wait
> handle"
>
> You're asking me to break existing behaviour that has been in place
> for two years already.

The behaviour changes slightly, but it doesn't change correctness;
there is currently no correct way to use mir_wait_for on a wait handle
returned by one of the *_set functions.

> Admittedly the wait handle on setters is low value and will be
> ignored by most users, but we get it for free. And any corresponding
> _get call needs to be synchronized with the wait handle from the
> _set, because all our _get functions are non-blocking (they just
> return the local cache contents). So we presently have non-blocking
> _get/_set functions with the ability to synchronize them.

But we *don't* have an ability to synchronise them - if you call
mir_surface_set_state(fullscreen) and mir_wait_for_result returns true
then it is *not* guaranteed that an immediate call to
mir_surface_get_state == fullscreen.

> Sounds like a good thing to me. You're asking for some of that
> functionality to be removed and I'm still not sure why. It also feels
> off topic for this MP.

Because the functionality doesn't actually exist, but looks like it
does. This part of the API rates a -10 on Rusty's API scale - it's an
API that is *not possible* to use correctly.

I think it's not off-topic for this MP because this MP extends the
functionality that doesn't exist in a way that might tempt people to
use it and write incorrect programs.

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

These statements are either false, or describe a bug I'm not yet aware of:

"The behaviour changes slightly, but it doesn't change correctness;
there is currently no correct way to use mir_wait_for on a wait handle
returned by one of the *_set functions.
  But we *don't* have an ability to synchronise them - if you call
mir_surface_set_state(fullscreen) and mir_wait_for_result returns true
then it is *not* guaranteed that an immediate call to
mir_surface_get_state == fullscreen."

When I implemented the attribute system in 2013, it did work. Correct usage looks like:

  w = mir_surface_set_X(surface, Y);
  mir_wait_for(w);
  y = mir_surface_get_X(surface);
  assert(y == Y);

If this doesn't work any more then we have a bug. But I suspect it does still work because I revisited the test cases for it as part of this proposal.

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

Ah, what you might be talking about is multiple threads doing the same thing. Indeed in that case calling _get will be racy. There's no guarantee that w represents the request for Y in that case.

So yeah, we need something better that correctly matches result instance to request instance.

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

On Thu, Mar 19, 2015 at 1:37 PM, Daniel van Vugt
<email address hidden> wrote:
> Ah, what you might be talking about is multiple threads doing the
> same thing. Indeed in that case calling _get will be racy. There's no
> guarantee that w represents the request for Y in that case.

Not even multiple threads doing the same thing; the server can change
the surface state without the client requesting it (obvious example is
drag-title-bar-to-unmaximise), and that event can be processed before
the thread executing mir_wait_for continues execution.

Surface states are event driven; attempts to make them appear otherwise
make the client API harder to use.

Unmerged revisions

2388. By Daniel van Vugt

Fix style.

2387. By Daniel van Vugt

More acceptance tests

2386. By Daniel van Vugt

And some acceptance-testing for the new client function.

2385. By Daniel van Vugt

Use more consistent and less ambiguous mir_wait_for_ naming.

2384. By Daniel van Vugt

Add unit test for MirResult

2383. By Daniel van Vugt

Treat attribute settings with values different to what was requested
as a failure (even if the attribute successfully changed value to something
else).

2382. By Daniel van Vugt

Merge latest trunk

2381. By Daniel van Vugt

Wire up result_received

2380. By Daniel van Vugt

Prototype client API.

2379. By Daniel van Vugt

Remove fields not yet in use

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'include/client/mir_toolkit/mir_wait.h'
2--- include/client/mir_toolkit/mir_wait.h 2014-03-31 14:36:08 +0000
3+++ include/client/mir_toolkit/mir_wait.h 2015-03-10 08:02:21 +0000
4@@ -46,6 +46,14 @@
5 */
6 void mir_wait_for_one(MirWaitHandle *wait_handle);
7
8+/**
9+ * Wait on the supplied handle until one instance of the associated request
10+ * has completed. This is the same as mir_wait_for_one with the extra
11+ * feature of returning a result.
12+ * \param [in] wait_handle Handle returned by an asynchronous request
13+ * \returns Whether the request succeeded.
14+ */
15+bool mir_wait_for_result(MirWaitHandle* wait_handle);
16
17 #ifdef __cplusplus
18 }
19
20=== modified file 'src/client/buffer_stream.cpp'
21--- src/client/buffer_stream.cpp 2015-02-18 05:27:28 +0000
22+++ src/client/buffer_stream.cpp 2015-03-10 08:02:21 +0000
23@@ -226,7 +226,7 @@
24
25 done();
26
27- next_buffer_wait_handle.result_received();
28+ next_buffer_wait_handle.result_received(true);
29 }
30
31 /* mcl::EGLNativeSurface interface for EGLNativeWindow integration */
32@@ -250,7 +250,7 @@
33 void mcl::BufferStream::on_configured()
34 {
35 std::unique_lock<decltype(mutex)> lock(mutex);
36- configure_wait_handle.result_received();
37+ configure_wait_handle.result_received(true);
38 }
39
40 // TODO: It's a little strange that we use SurfaceAttrib here for the swap interval
41
42=== modified file 'src/client/mir_connection.cpp'
43--- src/client/mir_connection.cpp 2015-02-18 05:27:28 +0000
44+++ src/client/mir_connection.cpp 2015-03-10 08:02:21 +0000
45@@ -191,7 +191,7 @@
46 data.surface->handle_event(*unfocus);
47 }
48 data.callback(data.surface, data.context);
49- data.handle->result_received();
50+ data.handle->result_received(true);
51 delete data.surface;
52 }
53
54@@ -282,7 +282,7 @@
55 }
56
57 if (safe_to_callback) callback(this, context);
58- connect_wait_handle.result_received();
59+ connect_wait_handle.result_received(!connect_result.has_error());
60 }
61
62 MirWaitHandle* MirConnection::connect(
63@@ -318,7 +318,7 @@
64
65 // Ensure no racy lifecycle notifications can happen after disconnect completes
66 lifecycle_control->set_lifecycle_event_handler([](MirLifecycleState){});
67- disconnect_wait_handle.result_received();
68+ disconnect_wait_handle.result_received(true);
69 }
70
71 MirWaitHandle* MirConnection::disconnect()
72@@ -353,7 +353,8 @@
73
74 callback(this, reply, context);
75
76- platform_operation_wait_handle.result_received();
77+ platform_operation_wait_handle.result_received(
78+ !platform_operation_reply.has_error());
79 }
80
81 MirWaitHandle* MirConnection::platform_operation(
82@@ -529,10 +530,11 @@
83
84 set_error_message(display_configuration_response.error());
85
86- if (!display_configuration_response.has_error())
87+ bool ok = !display_configuration_response.has_error();
88+ if (ok)
89 display_configuration->set_configuration(display_configuration_response);
90
91- return configure_display_wait_handle.result_received();
92+ return configure_display_wait_handle.result_received(ok);
93 }
94
95 MirWaitHandle* MirConnection::configure_display(MirDisplayConfiguration* config)
96
97=== modified file 'src/client/mir_prompt_session.cpp'
98--- src/client/mir_prompt_session.cpp 2015-02-18 05:27:28 +0000
99+++ src/client/mir_prompt_session.cpp 2015-03-10 08:02:21 +0000
100@@ -114,7 +114,7 @@
101 }
102
103 callback(this, context);
104- start_wait_handle.result_received();
105+ start_wait_handle.result_received(!session.has_error());
106 }
107
108 void MirPromptSession::done_stop(mir_prompt_session_callback callback, void* context)
109@@ -122,7 +122,7 @@
110 set_state(mir_prompt_session_state_stopped);
111
112 callback(this, context);
113- stop_wait_handle.result_received();
114+ stop_wait_handle.result_received(true);
115 }
116
117 char const* MirPromptSession::get_error_message()
118@@ -168,5 +168,5 @@
119 fds.push_back(socket_fd_response.fd(i));
120
121 callback(this, size, fds.data(), context);
122- fds_for_prompt_providers_wait_handle.result_received();
123+ fds_for_prompt_providers_wait_handle.result_received(true);
124 }
125
126=== modified file 'src/client/mir_screencast.cpp'
127--- src/client/mir_screencast.cpp 2015-02-18 05:27:28 +0000
128+++ src/client/mir_screencast.cpp 2015-03-10 08:02:21 +0000
129@@ -104,21 +104,22 @@
130 void MirScreencast::screencast_created(
131 mir_screencast_callback callback, void* context)
132 {
133- if (!protobuf_screencast.has_error())
134+ bool ok = !protobuf_screencast.has_error();
135+ if (ok)
136 {
137 buffer_stream = buffer_stream_factory->make_consumer_stream(server,
138 protobuf_screencast.buffer_stream(), "MirScreencast");
139 }
140
141 callback(this, context);
142- create_screencast_wait_handle.result_received();
143+ create_screencast_wait_handle.result_received(ok);
144 }
145
146 void MirScreencast::released(
147 mir_screencast_callback callback, void* context)
148 {
149 callback(this, context);
150- release_wait_handle.result_received();
151+ release_wait_handle.result_received(true);
152 }
153
154 mir::client::ClientBufferStream* MirScreencast::get_buffer_stream()
155
156=== modified file 'src/client/mir_surface.cpp'
157--- src/client/mir_surface.cpp 2015-03-05 05:47:28 +0000
158+++ src/client/mir_surface.cpp 2015-03-10 08:02:21 +0000
159@@ -206,6 +206,7 @@
160
161 void MirSurface::created(mir_surface_callback callback, void * context)
162 {
163+ bool ok = true;
164 {
165 std::lock_guard<decltype(mutex)> lock(mutex);
166 if (!surface.has_id())
167@@ -214,7 +215,7 @@
168 surface.set_error("Error processing surface create response, no ID (disconnected?)");
169
170 callback(this, context);
171- create_wait_handle.result_received();
172+ create_wait_handle.result_received(false);
173 return;
174 }
175 }
176@@ -237,12 +238,13 @@
177 }
178 catch (std::exception const& error)
179 {
180+ ok = false;
181 surface.set_error(std::string{"Error processing Surface creating response:"} +
182 boost::diagnostic_information(error));
183 }
184
185 callback(this, context);
186- create_wait_handle.result_received();
187+ create_wait_handle.result_received(ok);
188 }
189
190 MirWaitHandle* MirSurface::release_surface(
191@@ -313,7 +315,9 @@
192
193 configure_wait_handle.expect_result();
194 server->configure_surface(0, &setting, &configure_result,
195- google::protobuf::NewCallback(this, &MirSurface::on_configured));
196+ google::protobuf::NewCallback(this,
197+ &MirSurface::on_configured,
198+ value));
199
200 return &configure_wait_handle;
201 }
202@@ -322,7 +326,7 @@
203 {
204 void signal_response_received(MirWaitHandle* handle)
205 {
206- handle->result_received();
207+ handle->result_received(true);
208 }
209 }
210
211@@ -361,7 +365,7 @@
212 return !response.has_error();
213 }
214
215-void MirSurface::on_configured()
216+void MirSurface::on_configured(int requested_value)
217 {
218 std::lock_guard<decltype(mutex)> lock(mutex);
219
220@@ -370,6 +374,8 @@
221 configure_result.has_attrib())
222 {
223 int a = configure_result.attrib();
224+ bool ok = !configure_result.has_error() &&
225+ configure_result.ivalue() == requested_value;
226
227 switch (a)
228 {
229@@ -384,17 +390,18 @@
230 assert(configure_result.has_error());
231 break;
232 default:
233+ ok = false;
234 assert(false);
235 break;
236 }
237
238- configure_wait_handle.result_received();
239+ configure_wait_handle.result_received(ok);
240 }
241 }
242
243 void MirSurface::on_cursor_configured()
244 {
245- configure_cursor_wait_handle.result_received();
246+ configure_cursor_wait_handle.result_received(true);
247 }
248
249
250
251=== modified file 'src/client/mir_surface.h'
252--- src/client/mir_surface.h 2015-03-05 05:47:28 +0000
253+++ src/client/mir_surface.h 2015-03-10 08:02:21 +0000
254@@ -141,7 +141,7 @@
255 private:
256 mutable std::mutex mutex; // Protects all members of *this
257
258- void on_configured();
259+ void on_configured(int requested_value);
260 void on_cursor_configured();
261 void created(mir_surface_callback callback, void* context);
262 MirPixelFormat convert_ipc_pf_to_geometry(google::protobuf::int32 pf) const;
263
264=== modified file 'src/client/mir_wait_api.cpp'
265--- src/client/mir_wait_api.cpp 2014-03-31 14:36:08 +0000
266+++ src/client/mir_wait_api.cpp 2015-03-10 08:02:21 +0000
267@@ -30,3 +30,16 @@
268 if (wait_handle)
269 wait_handle->wait_for_one();
270 }
271+
272+bool mir_wait_for_result(MirWaitHandle* wait_handle)
273+{
274+ if (!wait_handle)
275+ return false;
276+
277+ /*
278+ * As a MirWaitHandle may get recycled immediately, the structure cannot
279+ * store a result beyond the lifetime of the mir_wait_for_ call. So any
280+ * result needs to be copied out immediately. Even if it's ignored.
281+ */
282+ return wait_handle->wait_for_one().success;
283+}
284
285=== modified file 'src/client/mir_wait_handle.cpp'
286--- src/client/mir_wait_handle.cpp 2015-01-21 07:34:50 +0000
287+++ src/client/mir_wait_handle.cpp 2015-03-10 08:02:21 +0000
288@@ -19,11 +19,14 @@
289
290 #include "mir_wait_handle.h"
291
292+MirResult::MirResult(bool ok) : success{ok}
293+{
294+}
295+
296 MirWaitHandle::MirWaitHandle() :
297 guard(),
298 wait_condition(),
299- expecting(0),
300- received(0)
301+ expecting(0)
302 {
303 }
304
305@@ -38,11 +41,11 @@
306 expecting++;
307 }
308
309-void MirWaitHandle::result_received()
310+void MirWaitHandle::result_received(MirResult const& r)
311 {
312 std::lock_guard<std::mutex> lock(guard);
313
314- received++;
315+ received.push_back(r);
316 wait_condition.notify_all();
317 }
318
319@@ -50,9 +53,9 @@
320 {
321 std::unique_lock<std::mutex> lock(guard);
322
323- wait_condition.wait(lock, [&]{ return received == expecting; });
324+ wait_condition.wait(lock, [&]{ return received.size() == expecting; });
325
326- received = 0;
327+ received.clear();
328 expecting = 0;
329 }
330
331@@ -60,17 +63,18 @@
332 {
333 std::unique_lock<std::mutex> lock(guard);
334
335- wait_condition.wait_for(lock, limit, [&]{ return received == expecting; });
336+ wait_condition.wait_for(lock, limit, [&]{ return received.size() == expecting; });
337 }
338
339-
340-void MirWaitHandle::wait_for_one() // wait for any single result
341+MirResult MirWaitHandle::wait_for_one() // wait for any single result
342 {
343 std::unique_lock<std::mutex> lock(guard);
344
345- wait_condition.wait(lock, [&]{ return received != 0; });
346+ wait_condition.wait(lock, [&]{ return !received.empty(); });
347
348- --received;
349+ auto head = received.front();
350+ received.pop_front();
351 --expecting;
352+
353+ return head;
354 }
355-
356
357=== modified file 'src/client/mir_wait_handle.h'
358--- src/client/mir_wait_handle.h 2013-10-03 03:44:08 +0000
359+++ src/client/mir_wait_handle.h 2015-03-10 08:02:21 +0000
360@@ -23,11 +23,24 @@
361 #include <chrono>
362 #include <condition_variable>
363 #include <mutex>
364+#include <deque>
365
366 /**
367 * \addtogroup mir_toolkit
368 * @{
369 */
370+
371+struct MirResult
372+{
373+ MirResult(bool ok);
374+
375+ bool success;
376+ // Suggested future uses:
377+ // const char* error;
378+ // struct MirSurface* surface;
379+ // struct MirConnection* connection;
380+};
381+
382 struct MirWaitHandle
383 {
384 public:
385@@ -36,16 +49,18 @@
386
387 void expect_result();
388 void result_received();
389+ void result_received(MirResult const&);
390 void wait_for_all();
391- void wait_for_one();
392+ MirResult wait_for_one();
393 void wait_for_pending(std::chrono::milliseconds limit);
394
395 private:
396 std::mutex guard;
397 std::condition_variable wait_condition;
398
399- int expecting;
400- int received;
401+ typedef std::deque<MirResult> Queue;
402+ Queue::size_type expecting;
403+ Queue received;
404 };
405 /**@}*/
406
407
408=== modified file 'src/client/symbols.map'
409--- src/client/symbols.map 2015-02-18 05:27:28 +0000
410+++ src/client/symbols.map 2015-03-10 08:02:21 +0000
411@@ -37,10 +37,15 @@
412 mir_surface_spec_set_preferred_orientation;
413 } MIR_CLIENT_8.1;
414
415-MIR_CLIENT_8.3 {
416+MIR_CLIENT_8.3 { # Mir 0.12
417 global:
418 mir_connection_create_spec_for_menu;
419 mir_connection_create_spec_for_tooltip;
420 mir_connection_create_spec_for_dialog;
421 mir_connection_platform_operation;
422 } MIR_CLIENT_8.2;
423+
424+MIR_CLIENT_8.4 { # Mir 0.13
425+ global:
426+ mir_wait_for_result;
427+} MIR_CLIENT_8.3;
428
429=== modified file 'tests/acceptance-tests/test_client_library.cpp'
430--- tests/acceptance-tests/test_client_library.cpp 2015-02-18 05:27:28 +0000
431+++ tests/acceptance-tests/test_client_library.cpp 2015-03-10 08:02:21 +0000
432@@ -152,7 +152,7 @@
433 {
434 MirWaitHandle* wh = mir_connect(new_connection().c_str(), __PRETTY_FUNCTION__, connection_callback, this);
435 EXPECT_THAT(wh, NotNull());
436- mir_wait_for(wh);
437+ EXPECT_TRUE(mir_wait_for_result(wh));
438
439 ASSERT_THAT(connection, NotNull());
440 EXPECT_TRUE(mir_connection_is_valid(connection));
441@@ -161,6 +161,19 @@
442 mir_connection_release(connection);
443 }
444
445+TEST_F(ClientLibrary, client_library_cant_connect)
446+{
447+ MirWaitHandle* wh = mir_connect("/dev/null", __PRETTY_FUNCTION__, connection_callback, this);
448+ EXPECT_THAT(wh, IsNull());
449+ EXPECT_FALSE(mir_wait_for_result(wh));
450+
451+ ASSERT_THAT(connection, NotNull());
452+ EXPECT_FALSE(mir_connection_is_valid(connection));
453+ EXPECT_THAT(mir_connection_get_error_message(connection), StrNe(""));
454+
455+ mir_connection_release(connection);
456+}
457+
458 TEST_F(ClientLibrary, synchronous_connection)
459 {
460 connection = mir_connect_sync(new_connection().c_str(), __PRETTY_FUNCTION__);
461@@ -267,16 +280,16 @@
462
463 EXPECT_THAT(mir_surface_get_state(surface), Eq(mir_surface_state_restored));
464
465- mir_wait_for(mir_surface_set_state(surface, mir_surface_state_fullscreen));
466- EXPECT_THAT(mir_surface_get_state(surface), Eq(mir_surface_state_fullscreen));
467-
468- mir_wait_for(mir_surface_set_state(surface, static_cast<MirSurfaceState>(999)));
469- EXPECT_THAT(mir_surface_get_state(surface), Eq(mir_surface_state_fullscreen));
470-
471- mir_wait_for(mir_surface_set_state(surface, mir_surface_state_minimized));
472+ EXPECT_TRUE(mir_wait_for_result(mir_surface_set_state(surface, mir_surface_state_fullscreen)));
473+ EXPECT_THAT(mir_surface_get_state(surface), Eq(mir_surface_state_fullscreen));
474+
475+ EXPECT_FALSE(mir_wait_for_result(mir_surface_set_state(surface, static_cast<MirSurfaceState>(999))));
476+ EXPECT_THAT(mir_surface_get_state(surface), Eq(mir_surface_state_fullscreen));
477+
478+ EXPECT_TRUE(mir_wait_for_result(mir_surface_set_state(surface, mir_surface_state_minimized)));
479 EXPECT_THAT(mir_surface_get_state(surface), Eq(mir_surface_state_minimized));
480
481- mir_wait_for(mir_surface_set_state(surface, static_cast<MirSurfaceState>(888)));
482+ EXPECT_FALSE(mir_wait_for_result(mir_surface_set_state(surface, static_cast<MirSurfaceState>(888))));
483 EXPECT_THAT(mir_surface_get_state(surface), Eq(mir_surface_state_minimized));
484
485 // Stress-test synchronization logic with some flooding
486
487=== modified file 'tests/unit-tests/client/test_wait_handle.cpp'
488--- tests/unit-tests/client/test_wait_handle.cpp 2015-01-21 07:34:50 +0000
489+++ tests/unit-tests/client/test_wait_handle.cpp 2015-03-10 08:02:21 +0000
490@@ -33,7 +33,7 @@
491
492 for (int i = 0; i < arbitrary_number_of_events; i++)
493 {
494- w.result_received();
495+ w.result_received(true);
496 }
497
498 w.wait_for_all();
499@@ -51,7 +51,7 @@
500 w.expect_result();
501
502 for (int j = 1; j <= i; j++)
503- w.result_received();
504+ w.result_received(true);
505
506 w.wait_for_all();
507 }
508@@ -70,7 +70,7 @@
509 in->expect_result();
510 in->wait_for_all();
511 symmetric_result = i;
512- out->result_received();
513+ out->result_received(true);
514 }
515 }
516 }
517@@ -84,7 +84,7 @@
518 for (int i = 1; i <= max; i++)
519 {
520 out.expect_result();
521- in.result_received();
522+ in.result_received(true);
523 out.wait_for_all();
524 ASSERT_EQ(symmetric_result, i);
525 }
526@@ -103,7 +103,7 @@
527 in->wait_for_all();
528 asymmetric_result = i;
529 for (int j = 1; j <= i; j++)
530- out->result_received();
531+ out->result_received(true);
532 }
533 }
534 }
535@@ -116,7 +116,7 @@
536
537 for (int i = 1; i <= max; i++)
538 {
539- in.result_received();
540+ in.result_received(true);
541 for (int j = 1; j <= i; j++)
542 out.expect_result();
543 out.wait_for_all();
544@@ -153,10 +153,30 @@
545 std::thread c(crowded_thread, &w, max);
546
547 for (int n = 0; n < max * 3; n++)
548- w.result_received();
549+ w.result_received(true);
550
551 a.join();
552 b.join();
553 c.join();
554 }
555
556+TEST(WaitHandle, returns_result_value)
557+{
558+ MirWaitHandle w;
559+
560+ w.result_received(true);
561+ EXPECT_TRUE(w.wait_for_one().success);
562+
563+ w.result_received(false);
564+ EXPECT_FALSE(w.wait_for_one().success);
565+
566+ for (int i = 0; i < 10; ++i)
567+ w.result_received(true);
568+ for (int i = 0; i < 10; ++i)
569+ w.result_received(false);
570+
571+ for (int i = 0; i < 10; ++i)
572+ EXPECT_TRUE(w.wait_for_one().success);
573+ for (int i = 0; i < 10; ++i)
574+ EXPECT_FALSE(w.wait_for_one().success);
575+}

Subscribers

People subscribed via source and target branches