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
=== modified file 'include/client/mir_toolkit/mir_wait.h'
--- include/client/mir_toolkit/mir_wait.h 2014-03-31 14:36:08 +0000
+++ include/client/mir_toolkit/mir_wait.h 2015-03-10 08:02:21 +0000
@@ -46,6 +46,14 @@
46 */46 */
47void mir_wait_for_one(MirWaitHandle *wait_handle);47void mir_wait_for_one(MirWaitHandle *wait_handle);
4848
49/**
50 * Wait on the supplied handle until one instance of the associated request
51 * has completed. This is the same as mir_wait_for_one with the extra
52 * feature of returning a result.
53 * \param [in] wait_handle Handle returned by an asynchronous request
54 * \returns Whether the request succeeded.
55 */
56bool mir_wait_for_result(MirWaitHandle* wait_handle);
4957
50#ifdef __cplusplus58#ifdef __cplusplus
51}59}
5260
=== modified file 'src/client/buffer_stream.cpp'
--- src/client/buffer_stream.cpp 2015-02-18 05:27:28 +0000
+++ src/client/buffer_stream.cpp 2015-03-10 08:02:21 +0000
@@ -226,7 +226,7 @@
226226
227 done();227 done();
228228
229 next_buffer_wait_handle.result_received();229 next_buffer_wait_handle.result_received(true);
230}230}
231231
232/* mcl::EGLNativeSurface interface for EGLNativeWindow integration */232/* mcl::EGLNativeSurface interface for EGLNativeWindow integration */
@@ -250,7 +250,7 @@
250void mcl::BufferStream::on_configured()250void mcl::BufferStream::on_configured()
251{251{
252 std::unique_lock<decltype(mutex)> lock(mutex);252 std::unique_lock<decltype(mutex)> lock(mutex);
253 configure_wait_handle.result_received();253 configure_wait_handle.result_received(true);
254}254}
255255
256// TODO: It's a little strange that we use SurfaceAttrib here for the swap interval256// TODO: It's a little strange that we use SurfaceAttrib here for the swap interval
257257
=== modified file 'src/client/mir_connection.cpp'
--- src/client/mir_connection.cpp 2015-02-18 05:27:28 +0000
+++ src/client/mir_connection.cpp 2015-03-10 08:02:21 +0000
@@ -191,7 +191,7 @@
191 data.surface->handle_event(*unfocus);191 data.surface->handle_event(*unfocus);
192 }192 }
193 data.callback(data.surface, data.context);193 data.callback(data.surface, data.context);
194 data.handle->result_received();194 data.handle->result_received(true);
195 delete data.surface;195 delete data.surface;
196}196}
197197
@@ -282,7 +282,7 @@
282 }282 }
283283
284 if (safe_to_callback) callback(this, context);284 if (safe_to_callback) callback(this, context);
285 connect_wait_handle.result_received();285 connect_wait_handle.result_received(!connect_result.has_error());
286}286}
287287
288MirWaitHandle* MirConnection::connect(288MirWaitHandle* MirConnection::connect(
@@ -318,7 +318,7 @@
318318
319 // Ensure no racy lifecycle notifications can happen after disconnect completes319 // Ensure no racy lifecycle notifications can happen after disconnect completes
320 lifecycle_control->set_lifecycle_event_handler([](MirLifecycleState){});320 lifecycle_control->set_lifecycle_event_handler([](MirLifecycleState){});
321 disconnect_wait_handle.result_received();321 disconnect_wait_handle.result_received(true);
322}322}
323323
324MirWaitHandle* MirConnection::disconnect()324MirWaitHandle* MirConnection::disconnect()
@@ -353,7 +353,8 @@
353353
354 callback(this, reply, context);354 callback(this, reply, context);
355355
356 platform_operation_wait_handle.result_received();356 platform_operation_wait_handle.result_received(
357 !platform_operation_reply.has_error());
357}358}
358359
359MirWaitHandle* MirConnection::platform_operation(360MirWaitHandle* MirConnection::platform_operation(
@@ -529,10 +530,11 @@
529530
530 set_error_message(display_configuration_response.error());531 set_error_message(display_configuration_response.error());
531532
532 if (!display_configuration_response.has_error())533 bool ok = !display_configuration_response.has_error();
534 if (ok)
533 display_configuration->set_configuration(display_configuration_response);535 display_configuration->set_configuration(display_configuration_response);
534536
535 return configure_display_wait_handle.result_received();537 return configure_display_wait_handle.result_received(ok);
536}538}
537539
538MirWaitHandle* MirConnection::configure_display(MirDisplayConfiguration* config)540MirWaitHandle* MirConnection::configure_display(MirDisplayConfiguration* config)
539541
=== modified file 'src/client/mir_prompt_session.cpp'
--- src/client/mir_prompt_session.cpp 2015-02-18 05:27:28 +0000
+++ src/client/mir_prompt_session.cpp 2015-03-10 08:02:21 +0000
@@ -114,7 +114,7 @@
114 }114 }
115115
116 callback(this, context);116 callback(this, context);
117 start_wait_handle.result_received();117 start_wait_handle.result_received(!session.has_error());
118}118}
119119
120void MirPromptSession::done_stop(mir_prompt_session_callback callback, void* context)120void MirPromptSession::done_stop(mir_prompt_session_callback callback, void* context)
@@ -122,7 +122,7 @@
122 set_state(mir_prompt_session_state_stopped);122 set_state(mir_prompt_session_state_stopped);
123123
124 callback(this, context);124 callback(this, context);
125 stop_wait_handle.result_received();125 stop_wait_handle.result_received(true);
126}126}
127127
128char const* MirPromptSession::get_error_message()128char const* MirPromptSession::get_error_message()
@@ -168,5 +168,5 @@
168 fds.push_back(socket_fd_response.fd(i));168 fds.push_back(socket_fd_response.fd(i));
169169
170 callback(this, size, fds.data(), context);170 callback(this, size, fds.data(), context);
171 fds_for_prompt_providers_wait_handle.result_received();171 fds_for_prompt_providers_wait_handle.result_received(true);
172}172}
173173
=== modified file 'src/client/mir_screencast.cpp'
--- src/client/mir_screencast.cpp 2015-02-18 05:27:28 +0000
+++ src/client/mir_screencast.cpp 2015-03-10 08:02:21 +0000
@@ -104,21 +104,22 @@
104void MirScreencast::screencast_created(104void MirScreencast::screencast_created(
105 mir_screencast_callback callback, void* context)105 mir_screencast_callback callback, void* context)
106{106{
107 if (!protobuf_screencast.has_error())107 bool ok = !protobuf_screencast.has_error();
108 if (ok)
108 {109 {
109 buffer_stream = buffer_stream_factory->make_consumer_stream(server,110 buffer_stream = buffer_stream_factory->make_consumer_stream(server,
110 protobuf_screencast.buffer_stream(), "MirScreencast");111 protobuf_screencast.buffer_stream(), "MirScreencast");
111 }112 }
112113
113 callback(this, context);114 callback(this, context);
114 create_screencast_wait_handle.result_received();115 create_screencast_wait_handle.result_received(ok);
115}116}
116117
117void MirScreencast::released(118void MirScreencast::released(
118 mir_screencast_callback callback, void* context)119 mir_screencast_callback callback, void* context)
119{120{
120 callback(this, context);121 callback(this, context);
121 release_wait_handle.result_received();122 release_wait_handle.result_received(true);
122}123}
123124
124mir::client::ClientBufferStream* MirScreencast::get_buffer_stream()125mir::client::ClientBufferStream* MirScreencast::get_buffer_stream()
125126
=== modified file 'src/client/mir_surface.cpp'
--- src/client/mir_surface.cpp 2015-03-05 05:47:28 +0000
+++ src/client/mir_surface.cpp 2015-03-10 08:02:21 +0000
@@ -206,6 +206,7 @@
206206
207void MirSurface::created(mir_surface_callback callback, void * context)207void MirSurface::created(mir_surface_callback callback, void * context)
208{208{
209 bool ok = true;
209 {210 {
210 std::lock_guard<decltype(mutex)> lock(mutex);211 std::lock_guard<decltype(mutex)> lock(mutex);
211 if (!surface.has_id())212 if (!surface.has_id())
@@ -214,7 +215,7 @@
214 surface.set_error("Error processing surface create response, no ID (disconnected?)");215 surface.set_error("Error processing surface create response, no ID (disconnected?)");
215216
216 callback(this, context);217 callback(this, context);
217 create_wait_handle.result_received();218 create_wait_handle.result_received(false);
218 return;219 return;
219 }220 }
220 }221 }
@@ -237,12 +238,13 @@
237 }238 }
238 catch (std::exception const& error)239 catch (std::exception const& error)
239 {240 {
241 ok = false;
240 surface.set_error(std::string{"Error processing Surface creating response:"} +242 surface.set_error(std::string{"Error processing Surface creating response:"} +
241 boost::diagnostic_information(error));243 boost::diagnostic_information(error));
242 }244 }
243245
244 callback(this, context);246 callback(this, context);
245 create_wait_handle.result_received();247 create_wait_handle.result_received(ok);
246}248}
247249
248MirWaitHandle* MirSurface::release_surface(250MirWaitHandle* MirSurface::release_surface(
@@ -313,7 +315,9 @@
313315
314 configure_wait_handle.expect_result();316 configure_wait_handle.expect_result();
315 server->configure_surface(0, &setting, &configure_result,317 server->configure_surface(0, &setting, &configure_result,
316 google::protobuf::NewCallback(this, &MirSurface::on_configured));318 google::protobuf::NewCallback(this,
319 &MirSurface::on_configured,
320 value));
317321
318 return &configure_wait_handle;322 return &configure_wait_handle;
319}323}
@@ -322,7 +326,7 @@
322{326{
323void signal_response_received(MirWaitHandle* handle)327void signal_response_received(MirWaitHandle* handle)
324{328{
325 handle->result_received();329 handle->result_received(true);
326}330}
327}331}
328332
@@ -361,7 +365,7 @@
361 return !response.has_error();365 return !response.has_error();
362}366}
363367
364void MirSurface::on_configured()368void MirSurface::on_configured(int requested_value)
365{369{
366 std::lock_guard<decltype(mutex)> lock(mutex);370 std::lock_guard<decltype(mutex)> lock(mutex);
367371
@@ -370,6 +374,8 @@
370 configure_result.has_attrib())374 configure_result.has_attrib())
371 {375 {
372 int a = configure_result.attrib();376 int a = configure_result.attrib();
377 bool ok = !configure_result.has_error() &&
378 configure_result.ivalue() == requested_value;
373379
374 switch (a)380 switch (a)
375 {381 {
@@ -384,17 +390,18 @@
384 assert(configure_result.has_error());390 assert(configure_result.has_error());
385 break;391 break;
386 default:392 default:
393 ok = false;
387 assert(false);394 assert(false);
388 break;395 break;
389 }396 }
390397
391 configure_wait_handle.result_received();398 configure_wait_handle.result_received(ok);
392 }399 }
393}400}
394401
395void MirSurface::on_cursor_configured()402void MirSurface::on_cursor_configured()
396{403{
397 configure_cursor_wait_handle.result_received();404 configure_cursor_wait_handle.result_received(true);
398}405}
399406
400407
401408
=== modified file 'src/client/mir_surface.h'
--- src/client/mir_surface.h 2015-03-05 05:47:28 +0000
+++ src/client/mir_surface.h 2015-03-10 08:02:21 +0000
@@ -141,7 +141,7 @@
141private:141private:
142 mutable std::mutex mutex; // Protects all members of *this142 mutable std::mutex mutex; // Protects all members of *this
143143
144 void on_configured();144 void on_configured(int requested_value);
145 void on_cursor_configured();145 void on_cursor_configured();
146 void created(mir_surface_callback callback, void* context);146 void created(mir_surface_callback callback, void* context);
147 MirPixelFormat convert_ipc_pf_to_geometry(google::protobuf::int32 pf) const;147 MirPixelFormat convert_ipc_pf_to_geometry(google::protobuf::int32 pf) const;
148148
=== modified file 'src/client/mir_wait_api.cpp'
--- src/client/mir_wait_api.cpp 2014-03-31 14:36:08 +0000
+++ src/client/mir_wait_api.cpp 2015-03-10 08:02:21 +0000
@@ -30,3 +30,16 @@
30 if (wait_handle)30 if (wait_handle)
31 wait_handle->wait_for_one();31 wait_handle->wait_for_one();
32}32}
33
34bool mir_wait_for_result(MirWaitHandle* wait_handle)
35{
36 if (!wait_handle)
37 return false;
38
39 /*
40 * As a MirWaitHandle may get recycled immediately, the structure cannot
41 * store a result beyond the lifetime of the mir_wait_for_ call. So any
42 * result needs to be copied out immediately. Even if it's ignored.
43 */
44 return wait_handle->wait_for_one().success;
45}
3346
=== modified file 'src/client/mir_wait_handle.cpp'
--- src/client/mir_wait_handle.cpp 2015-01-21 07:34:50 +0000
+++ src/client/mir_wait_handle.cpp 2015-03-10 08:02:21 +0000
@@ -19,11 +19,14 @@
1919
20#include "mir_wait_handle.h"20#include "mir_wait_handle.h"
2121
22MirResult::MirResult(bool ok) : success{ok}
23{
24}
25
22MirWaitHandle::MirWaitHandle() :26MirWaitHandle::MirWaitHandle() :
23 guard(),27 guard(),
24 wait_condition(),28 wait_condition(),
25 expecting(0),29 expecting(0)
26 received(0)
27{30{
28}31}
2932
@@ -38,11 +41,11 @@
38 expecting++;41 expecting++;
39}42}
4043
41void MirWaitHandle::result_received()44void MirWaitHandle::result_received(MirResult const& r)
42{45{
43 std::lock_guard<std::mutex> lock(guard);46 std::lock_guard<std::mutex> lock(guard);
4447
45 received++;48 received.push_back(r);
46 wait_condition.notify_all();49 wait_condition.notify_all();
47}50}
4851
@@ -50,9 +53,9 @@
50{53{
51 std::unique_lock<std::mutex> lock(guard);54 std::unique_lock<std::mutex> lock(guard);
5255
53 wait_condition.wait(lock, [&]{ return received == expecting; });56 wait_condition.wait(lock, [&]{ return received.size() == expecting; });
5457
55 received = 0;58 received.clear();
56 expecting = 0;59 expecting = 0;
57}60}
5861
@@ -60,17 +63,18 @@
60{63{
61 std::unique_lock<std::mutex> lock(guard);64 std::unique_lock<std::mutex> lock(guard);
6265
63 wait_condition.wait_for(lock, limit, [&]{ return received == expecting; });66 wait_condition.wait_for(lock, limit, [&]{ return received.size() == expecting; });
64}67}
6568
6669MirResult MirWaitHandle::wait_for_one() // wait for any single result
67void MirWaitHandle::wait_for_one() // wait for any single result
68{70{
69 std::unique_lock<std::mutex> lock(guard);71 std::unique_lock<std::mutex> lock(guard);
7072
71 wait_condition.wait(lock, [&]{ return received != 0; });73 wait_condition.wait(lock, [&]{ return !received.empty(); });
7274
73 --received;75 auto head = received.front();
76 received.pop_front();
74 --expecting;77 --expecting;
78
79 return head;
75}80}
76
7781
=== modified file 'src/client/mir_wait_handle.h'
--- src/client/mir_wait_handle.h 2013-10-03 03:44:08 +0000
+++ src/client/mir_wait_handle.h 2015-03-10 08:02:21 +0000
@@ -23,11 +23,24 @@
23#include <chrono>23#include <chrono>
24#include <condition_variable>24#include <condition_variable>
25#include <mutex>25#include <mutex>
26#include <deque>
2627
27/**28/**
28 * \addtogroup mir_toolkit29 * \addtogroup mir_toolkit
29 * @{30 * @{
30 */31 */
32
33struct MirResult
34{
35 MirResult(bool ok);
36
37 bool success;
38 // Suggested future uses:
39 // const char* error;
40 // struct MirSurface* surface;
41 // struct MirConnection* connection;
42};
43
31struct MirWaitHandle44struct MirWaitHandle
32{45{
33public:46public:
@@ -36,16 +49,18 @@
3649
37 void expect_result();50 void expect_result();
38 void result_received();51 void result_received();
52 void result_received(MirResult const&);
39 void wait_for_all();53 void wait_for_all();
40 void wait_for_one();54 MirResult wait_for_one();
41 void wait_for_pending(std::chrono::milliseconds limit);55 void wait_for_pending(std::chrono::milliseconds limit);
4256
43private:57private:
44 std::mutex guard;58 std::mutex guard;
45 std::condition_variable wait_condition;59 std::condition_variable wait_condition;
4660
47 int expecting;61 typedef std::deque<MirResult> Queue;
48 int received;62 Queue::size_type expecting;
63 Queue received;
49};64};
50/**@}*/65/**@}*/
5166
5267
=== modified file 'src/client/symbols.map'
--- src/client/symbols.map 2015-02-18 05:27:28 +0000
+++ src/client/symbols.map 2015-03-10 08:02:21 +0000
@@ -37,10 +37,15 @@
37 mir_surface_spec_set_preferred_orientation;37 mir_surface_spec_set_preferred_orientation;
38} MIR_CLIENT_8.1;38} MIR_CLIENT_8.1;
3939
40MIR_CLIENT_8.3 {40MIR_CLIENT_8.3 { # Mir 0.12
41 global:41 global:
42 mir_connection_create_spec_for_menu;42 mir_connection_create_spec_for_menu;
43 mir_connection_create_spec_for_tooltip;43 mir_connection_create_spec_for_tooltip;
44 mir_connection_create_spec_for_dialog;44 mir_connection_create_spec_for_dialog;
45 mir_connection_platform_operation;45 mir_connection_platform_operation;
46} MIR_CLIENT_8.2;46} MIR_CLIENT_8.2;
47
48MIR_CLIENT_8.4 { # Mir 0.13
49 global:
50 mir_wait_for_result;
51} MIR_CLIENT_8.3;
4752
=== modified file 'tests/acceptance-tests/test_client_library.cpp'
--- tests/acceptance-tests/test_client_library.cpp 2015-02-18 05:27:28 +0000
+++ tests/acceptance-tests/test_client_library.cpp 2015-03-10 08:02:21 +0000
@@ -152,7 +152,7 @@
152{152{
153 MirWaitHandle* wh = mir_connect(new_connection().c_str(), __PRETTY_FUNCTION__, connection_callback, this);153 MirWaitHandle* wh = mir_connect(new_connection().c_str(), __PRETTY_FUNCTION__, connection_callback, this);
154 EXPECT_THAT(wh, NotNull());154 EXPECT_THAT(wh, NotNull());
155 mir_wait_for(wh);155 EXPECT_TRUE(mir_wait_for_result(wh));
156156
157 ASSERT_THAT(connection, NotNull());157 ASSERT_THAT(connection, NotNull());
158 EXPECT_TRUE(mir_connection_is_valid(connection));158 EXPECT_TRUE(mir_connection_is_valid(connection));
@@ -161,6 +161,19 @@
161 mir_connection_release(connection);161 mir_connection_release(connection);
162}162}
163163
164TEST_F(ClientLibrary, client_library_cant_connect)
165{
166 MirWaitHandle* wh = mir_connect("/dev/null", __PRETTY_FUNCTION__, connection_callback, this);
167 EXPECT_THAT(wh, IsNull());
168 EXPECT_FALSE(mir_wait_for_result(wh));
169
170 ASSERT_THAT(connection, NotNull());
171 EXPECT_FALSE(mir_connection_is_valid(connection));
172 EXPECT_THAT(mir_connection_get_error_message(connection), StrNe(""));
173
174 mir_connection_release(connection);
175}
176
164TEST_F(ClientLibrary, synchronous_connection)177TEST_F(ClientLibrary, synchronous_connection)
165{178{
166 connection = mir_connect_sync(new_connection().c_str(), __PRETTY_FUNCTION__);179 connection = mir_connect_sync(new_connection().c_str(), __PRETTY_FUNCTION__);
@@ -267,16 +280,16 @@
267280
268 EXPECT_THAT(mir_surface_get_state(surface), Eq(mir_surface_state_restored));281 EXPECT_THAT(mir_surface_get_state(surface), Eq(mir_surface_state_restored));
269282
270 mir_wait_for(mir_surface_set_state(surface, mir_surface_state_fullscreen));283 EXPECT_TRUE(mir_wait_for_result(mir_surface_set_state(surface, mir_surface_state_fullscreen)));
271 EXPECT_THAT(mir_surface_get_state(surface), Eq(mir_surface_state_fullscreen));284 EXPECT_THAT(mir_surface_get_state(surface), Eq(mir_surface_state_fullscreen));
272285
273 mir_wait_for(mir_surface_set_state(surface, static_cast<MirSurfaceState>(999)));286 EXPECT_FALSE(mir_wait_for_result(mir_surface_set_state(surface, static_cast<MirSurfaceState>(999))));
274 EXPECT_THAT(mir_surface_get_state(surface), Eq(mir_surface_state_fullscreen));287 EXPECT_THAT(mir_surface_get_state(surface), Eq(mir_surface_state_fullscreen));
275288
276 mir_wait_for(mir_surface_set_state(surface, mir_surface_state_minimized));289 EXPECT_TRUE(mir_wait_for_result(mir_surface_set_state(surface, mir_surface_state_minimized)));
277 EXPECT_THAT(mir_surface_get_state(surface), Eq(mir_surface_state_minimized));290 EXPECT_THAT(mir_surface_get_state(surface), Eq(mir_surface_state_minimized));
278291
279 mir_wait_for(mir_surface_set_state(surface, static_cast<MirSurfaceState>(888)));292 EXPECT_FALSE(mir_wait_for_result(mir_surface_set_state(surface, static_cast<MirSurfaceState>(888))));
280 EXPECT_THAT(mir_surface_get_state(surface), Eq(mir_surface_state_minimized));293 EXPECT_THAT(mir_surface_get_state(surface), Eq(mir_surface_state_minimized));
281294
282 // Stress-test synchronization logic with some flooding295 // Stress-test synchronization logic with some flooding
283296
=== modified file 'tests/unit-tests/client/test_wait_handle.cpp'
--- tests/unit-tests/client/test_wait_handle.cpp 2015-01-21 07:34:50 +0000
+++ tests/unit-tests/client/test_wait_handle.cpp 2015-03-10 08:02:21 +0000
@@ -33,7 +33,7 @@
3333
34 for (int i = 0; i < arbitrary_number_of_events; i++)34 for (int i = 0; i < arbitrary_number_of_events; i++)
35 {35 {
36 w.result_received();36 w.result_received(true);
37 }37 }
3838
39 w.wait_for_all();39 w.wait_for_all();
@@ -51,7 +51,7 @@
51 w.expect_result();51 w.expect_result();
5252
53 for (int j = 1; j <= i; j++)53 for (int j = 1; j <= i; j++)
54 w.result_received();54 w.result_received(true);
5555
56 w.wait_for_all();56 w.wait_for_all();
57 }57 }
@@ -70,7 +70,7 @@
70 in->expect_result();70 in->expect_result();
71 in->wait_for_all();71 in->wait_for_all();
72 symmetric_result = i;72 symmetric_result = i;
73 out->result_received();73 out->result_received(true);
74 }74 }
75 }75 }
76}76}
@@ -84,7 +84,7 @@
84 for (int i = 1; i <= max; i++)84 for (int i = 1; i <= max; i++)
85 {85 {
86 out.expect_result();86 out.expect_result();
87 in.result_received();87 in.result_received(true);
88 out.wait_for_all();88 out.wait_for_all();
89 ASSERT_EQ(symmetric_result, i);89 ASSERT_EQ(symmetric_result, i);
90 }90 }
@@ -103,7 +103,7 @@
103 in->wait_for_all();103 in->wait_for_all();
104 asymmetric_result = i;104 asymmetric_result = i;
105 for (int j = 1; j <= i; j++)105 for (int j = 1; j <= i; j++)
106 out->result_received();106 out->result_received(true);
107 }107 }
108 }108 }
109}109}
@@ -116,7 +116,7 @@
116116
117 for (int i = 1; i <= max; i++)117 for (int i = 1; i <= max; i++)
118 {118 {
119 in.result_received();119 in.result_received(true);
120 for (int j = 1; j <= i; j++)120 for (int j = 1; j <= i; j++)
121 out.expect_result();121 out.expect_result();
122 out.wait_for_all();122 out.wait_for_all();
@@ -153,10 +153,30 @@
153 std::thread c(crowded_thread, &w, max);153 std::thread c(crowded_thread, &w, max);
154154
155 for (int n = 0; n < max * 3; n++)155 for (int n = 0; n < max * 3; n++)
156 w.result_received();156 w.result_received(true);
157157
158 a.join();158 a.join();
159 b.join();159 b.join();
160 c.join();160 c.join();
161}161}
162162
163TEST(WaitHandle, returns_result_value)
164{
165 MirWaitHandle w;
166
167 w.result_received(true);
168 EXPECT_TRUE(w.wait_for_one().success);
169
170 w.result_received(false);
171 EXPECT_FALSE(w.wait_for_one().success);
172
173 for (int i = 0; i < 10; ++i)
174 w.result_received(true);
175 for (int i = 0; i < 10; ++i)
176 w.result_received(false);
177
178 for (int i = 0; i < 10; ++i)
179 EXPECT_TRUE(w.wait_for_one().success);
180 for (int i = 0; i < 10; ++i)
181 EXPECT_FALSE(w.wait_for_one().success);
182}

Subscribers

People subscribed via source and target branches