Re: Design question: asynchronous API in C++

Discussion in 'C++' started by Adam Skutt, Jan 1, 2011.

  1. Adam Skutt

    Adam Skutt Guest

    On Saturday, January 1, 2011 5:45:11 AM UTC-5, Marcel Müller wrote:
    >
    > The interface is already there. It is a C API (pulseaudio port to OS/2).


    Which does not specify the complicated interface you outlined at all.

    > But it requires a callback function to be passed and returns a request
    > object pointer. However these two things are not related directly which
    > is very error prone.


    How are they not related directly?

    > E.g. one cannot call a wait function on the request
    > object pointer, because this object does not receive the callback.


    Huh? You can't call a wait function on a pa_operation because it's nonsensical: depending on the mainloop structure you'd never return from the wait. But I don't see what "because this object does not receive the callback" has to do with anything. To be honest, I'm not even entirely sure what it means, but I fail to see the relevance. wait() is no difference from cancel() from an encapsulation perspective. pa_operation doesn't support wait() not because it lacks the information to do so, but because doing so would be _dangerous and nonsensical_.

    I really think you need to go back and figure out exactly what you're trying to do, because what you've explained to us sounds dangerous, though perhaps only out of inadequate explanation.

    Adam
     
    Adam Skutt, Jan 1, 2011
    #1
    1. Advertising

  2. Adam Skutt wrote:
    >> But it requires a callback function to be passed and returns a request
    >> object pointer. However these two things are not related directly which
    >> is very error prone.

    >
    > How are they not related directly?


    To wait for the command to complete the application has to wait for the
    callback. This implies providing a callback function and dealing with an
    event semaphore. Very inconvenient.

    To poll the completion state the application has to query the
    pa_operation object.

    If the callback calls a member function of some command tracking object
    of the application, and if the command has to be canceled for some
    reason it is up to the application to ensure to cleanup it's own
    tracking object together with the pa_operation object. This is error
    prone and can't be easily supported by a smart C++ object. In fact the
    applications request object and the pa_operation object belong to the
    same thing and therefore should be intrinsically tied to each other.


    >> E.g. one cannot call a wait function on the request
    >> object pointer, because this object does not receive the callback.

    >
    > Huh? You can't call a wait function on a pa_operation because it's nonsensical:
    > depending on the mainloop structure you'd never return from the wait.


    AFAIK all requests should either complete or fail sooner or later. In
    case of need they should fail by a time out.

    > But I don't see what "because this object does not receive the callback" has to do with anything.
    > To be honest, I'm not even entirely sure what it means, but I fail to

    see the relevance.
    > wait() is no difference from cancel() from an encapsulation perspective.
    > pa_operation doesn't support wait() not because it lacks the

    information to do so,

    In fact the operations state is not sufficient, because there is a need
    for some kind of thread synchronization.

    > but because doing so would be _dangerous and nonsensical_.


    From the applications point of view it would be helpful to have e
    receive function that waits for completion and returns the result. And
    it would be even more helpful if the function also returns (e.g. with an
    exception) if another thread cancels the outstanding operation. It is a
    lot of work to implement this semantics for each request.

    > I really think you need to go back and figure out exactly what you're trying to do,
    > because what you've explained to us sounds dangerous, though perhaps

    only out of inadequate explanation.

    Maybe.

    I am currently writing a plug-in that acts as pulseaudio client. This
    plug-in is written in C++. And because I want the compiler to detect as
    many bugs as possible (debugging plug-ins is a pain) I want to make the
    API as safe as possible. I expect from a C++ API that it is impossible
    or at least not that easy to run into undefined behavior. That means:
    - All objects should manage the pulseaudio reference counters
    automatically and exception safe (no big deal).
    - All methods should belong to class types that really support this kind
    of call. E.g. only a sink input stream could be written to, not any
    stream, like the API suggests.
    - The C++ objects should be thread safe and manage the synchronization
    with the mainloop as far as it makes sense.
    - The state of asynchronous calls should be tracked safely. If the C++
    request object goes out of scope the callback should no longer be
    called. If the callback arrives the results should be stored at a
    predefined location, handled synchronously or discarded, its the
    applications choice.
    - And last but not least, using the C++ API should result in small and
    readable code.

    The pulseaudio developers were also be happy to have some
    libpulseaudio++, since there are more C++ pulseaudio applications
    around. So I thought it would be a good idea to look for a sophisticated
    solution. And because the asynchronous nature does not fit well into the
    C++ language, I want to do some brainstorming.


    Marcel
     
    Marcel Müller, Jan 1, 2011
    #2
    1. Advertising

  3. Adam Skutt

    Adam Skutt Guest

    On Jan 1, 12:26 pm, Marcel Müller <>
    wrote:
    >
    > To wait for the command to complete the application has to wait for the
    > callback. This implies providing a callback function and dealing with an
    > event semaphore. Very inconvenient.
    >

    First, this doesn't make them "not directly related". Second, I fail
    to see how this is ever not the case: in most asynchronous APIs[1] you
    schedule an operation then eventually call some sort of function that
    says: "allow asynchronous callbacks on this thread". What you term
    "very inconvenient" is something that must happen all of the time,
    AFAICT; it is fundamental to asynchronous processing.

    Unless you're talking about some else entirely, which seems entirely
    likely to me. In which case, again, I don't see how it remotely fits
    in with how pulseaudio works.

    > To poll the completion state the application has to query the
    > pa_operation object.
    >
    > If the callback calls a member function of some command tracking object
    > of the application, and if the command has to be canceled for some
    > reason it is up to the application to ensure to cleanup it's own
    > tracking object together with the pa_operation object.


    I don't see why cancellation ipso facto means that the "command
    tracking object" (if such a thing even exists) must be destroyed as
    well. It certainly can be this way, but that's not something you
    should force on your users.

    But even when cancellation means the object should be destroyed, I
    don't see the problem. The callback has a smart pointer to the
    "command tracking object", you have a pa_operation. You cancel the
    pa_operation and decrement its reference count; when the callback is
    destroyed, it destroys the "command tracking object". If your callback
    is anything but a C function, some code you write in your pulseaudio
    wrapper API has to hold a reference of some sort to the callback /
    anyway/. As such, you can rely on destruction of the callback to
    destroy the command tracking object, if necessary.

    I'm not sure why one would even attempt to do it any other way, in
    fact.

    > This is error
    > prone and can't be easily supported by a smart C++ object. In fact the
    > applications request object and the pa_operation object belong to the
    > same thing and therefore should be intrinsically tied to each other.


    No, that's not the case and typically should not be the case. Trivial
    example: a GUI with a 'stop button' (e.g., a web browser). The stop
    button code doesn't need to to have access to any state the callback
    needs (e.g., I/O buffers). All it needs is some way to cancel/abort
    the request.

    > AFAIK all requests should either complete or fail sooner or later. In
    > case of need they should fail by a time out.


    No, again, this is not something you should enforce on your users.
    It's simply not true. If they need to guarantee response, let them do
    it on their own. Cancellation is bad enough without it being forced
    on people.

    > In fact the operations state is not sufficient, because there is a need
    > for some kind of thread synchronization.
    >


    Which is why pa_operation doesn't support wait(). You probably
    shouldn't be assuming number of threads > 1, either, if you intend to
    build a proper pulseaudio wrapper.

    >  From the applications point of view it would be helpful to have e
    > receive function that waits for completion and returns the result.


    Which, in the pulseaudio threaded mainloop API, is what
    pa_threaded_mainloop_signal() / pa_threaded_mainloop_accept() are
    for. Given the lack of thread-safety inside of PA, anything you build
    will necessarily be based
    around that paradigm (or be deadlock prone).

    > And
    > it would be even more helpful if the function also returns (e.g. with an
    > exception) if another thread cancels the outstanding operation. It is a
    > lot of work to implement this semantics for each request.


    You can't make such behavior anything other than best-effort so I'm
    not sure I see the point. If you still decided to implement this, I
    don't see why nor how pa_operation is an impediment.


    > - The state of asynchronous calls should be tracked safely. If the C++
    > request object goes out of scope the callback should no longer be
    > called. If the callback arrives the results should be stored at a
    > predefined location, handled synchronously or discarded, its the
    > applications choice.


    Having a 'pa_operation equivalent' going out of scope equating to
    cancellation is almost certainly a bad idea, FWIW. Letting the client
    users destroy callback objects out from under you, in any fashion, is
    equally awful. Either idea is almost certainly going to lead to
    defined behavior.

    Your wrapper API should hold on to callback objects. The callback
    objects should hold on to any state they need. This is the natural
    way of accomplishing things in C++. Why would you ever even attempt
    anything else? This gives you exactly what you want: well-defined,
    well-behaved resource management.

    > And because the asynchronous nature does not fit well into the C++ language, I want to do some brainstorming.


    It fits just fine. It doesn't fit well when you attempt to
    overcomplicate things, however, which is what you seem set on doing.

    Adam

    [1] Some pick the thread (of execution )for you rather forcibly, like
    VMS ASTs and UNIX signals. The idea is still the same, however.
     
    Adam Skutt, Jan 1, 2011
    #3
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Amir

    asynchronous design

    Amir, Oct 10, 2003, in forum: VHDL
    Replies:
    3
    Views:
    685
  2. dutchgoldtony

    Asynchronous Design

    dutchgoldtony, Apr 23, 2005, in forum: VHDL
    Replies:
    3
    Views:
    508
  3. Marcel Müller
    Replies:
    4
    Views:
    1,116
    Öö Tiib
    Jan 1, 2011
  4. Adam Skutt
    Replies:
    1
    Views:
    538
    Marcel Müller
    Jan 1, 2011
  5. Scott Sauyet

    API for aggregator of asynchronous calls

    Scott Sauyet, Jun 29, 2010, in forum: Javascript
    Replies:
    7
    Views:
    149
    Scott Sauyet
    Jul 1, 2010
Loading...

Share This Page