[Bro-Dev] Rethinking Broker's blocking API

Robin Sommer robin at icir.org
Wed Jan 4 12:48:50 PST 2017


On Mon, Jan 02, 2017 at 13:07 -0800, you wrote:

> Ultimately, I think it's important to have consistent API of blocking
> and non-blocking endpoints. Any thoughts on how to move forwards would
> be appreciated.

Nice summary of the challenge! I agree that none of the options you
list sound really appealing. Here's an alternative idea: could we
change your option 1 (the variant) into always returning *both*, i.e.,
tuple<status, message>? To make that work, we'd add an additional
(say) GOT_MESSAGE status. Each time receive() gets called, it returns
whatever's internally next on deck: either a message (with status set
to GOT_MESSAGE), or a "real" status (with the tuple's message set to
null). The caller would then check the status first:

    next = ev.receive()

    if ( next.status() == GOT_MESSAGE )
        process_message(next.msg());
    else {
        // Status/error handling here.
    }

This would actually align pretty nicely with how blocking APIs
normally operate: returning potential errors directly with the call.
And having the client code check for errors before using the result
feels natural to me. (And it's actually same approach we discussed for
the Bro-side API if we had tuples there. :)

What do you think?

Robin

> Broker's current API to receive messages is as follows:
> 
>     context ctx;
>     auto ep = ctx.spawn<blocking>();
>     ep.receive([&](const topic& t, const data& x) { .. });
>     ep.receive([&](const status& s) { .. });
> 
> or the last two in one call:
> 
>     ep.receive(
>       [&](const topic& t, const data& x) { .. },
>       [&](const status& s) { .. }
>     );
> 
> The idea behind this API is that it's similar to the non-blocking
> endpoint API:
> 
>     auto ep = ctx.spawn<nonblocking>();
>     ep.subscribe([=](const topic& t, const data& x) { .. });
>     ep.subscribe([=](const status& s) { .. });
> 
> Non-blocking endpoints should be the default, because they are more
> efficient due to the absence of blocking. For simplicity, the current
> API also provides a non-lambda overload of receive:
> 
>     auto ep = ctx.spawn<blocking>();
>     auto msg = ep.receive();
>     std::cout << msg.topic() << " -> " << msg.data() << std::endl;
> 
> Users can also check the mailbox of the blocking endpoint whether it
> contains a message:
> 
>     // Only block if know that we have a message.
>     if (!ep.mailbox().empty())
>       auto msg = ep.receive();
> 
> What I haven't considered up to now is the interaction of data and
> status messages in the blocking API. Both broker::message and
> broker::status are messages that linger the endpoint's mailbox. I find
> the terminology confusing, because a status instance is technically also
> a message. I'd rather speak of "data messages" and "status messages" as
> opposed to "messages" and "statuses". But more about the terminology
> later.
> 
> There's a problem with the snippet above. If the mailbox is non-empty
> because it contains a status message, the following call to receive()
> would hang, because it expects a data message. The only safe solution
> would be to use this form:
> 
>     if (!ep.mailbox().empty())
>       ep.receive(
>         [&](const topic& t, const data& x) { .. },
>         [&](const status& s) { .. }
>       );
> 
> The problem lies in the receive() function that returns a message. It
> doesn't match the current architecture (a blocking endpoint has a single
> mailbox) and is not a safe API for users.
> 
> Here are some solutions I could think of:
> 
>     (1) Let receive() return a variant<message, status> instead, because
>         the caller cannot know a priori what to expect. While simple to
>         call, it burdens the user with type-based dispatching afterwards.
>     
>     (2) Specify the type of message a user wants to receive, e.g.,
> 
>           auto x = ep.receive<message>();
>           auto y = ep.receive<status>();
> 
>         Here, don't like the terminology issues I mentioned above. More
>         reasonable could be 
> 
>           auto x = ep.receive<data>();
>           auto y = ep.receive<status>();
> 
>         where x could have type data_message with .topic() and .data(),
>         and y be a direct instance of type status.
> 
>         But because callers don't know whether they'll receive a status
>         or data message, this solution is only an incremental
>         improvement.
> 
>     (3) Equip blocking endpoints with separate mailboxes for data and
>         status messages. In combination with (2), this could lead to
>         something like:
> 
>             if (!ep.mailbox<data>().empty())
>               auto msg = ep.receive<data>();
> 
>             if (!ep.mailbox<status>().empty())
>               auto s = ep.receive<status>();
> 
>         But now users have to keep track of two mailboxes, which is more
>         error-prone and verbose.
> 
>     (4) Drop status messages unless the user explicitly asks for them.
>         Do not consider them when working with an endpoint's mailbox,
>         which only covers data messages.
> 
>         While keeping the semantics of ep.receive() simple, it's not
>         clear how to poll for status messages. Should they just
>         accumulate in the endpoint and be queryable? E.g.,:
> 
>             // Bounded? Infinite?
>             const std::vector<status>& xs = ep.statuses();
> 
> 
>     Matthias
> _______________________________________________
> bro-dev mailing list
> bro-dev at bro.org
> http://mailman.icsi.berkeley.edu/mailman/listinfo/bro-dev
> 


-- 
Robin Sommer * ICSI/LBNL * robin at icir.org * www.icir.org/robin


More information about the bro-dev mailing list