Skip to content

pull_from and push_to -- reporting failure #396

Description

@akrzemi1

This is to report my findings. It belongs in some rationale document, but I do not know where to put one.

In Capy, operations on streams throw exceptions upon resource acquisition failures (the usage of allocator), but for reporting "irregular" situations on streams themselves (loss of connection, TLS truncation) it is signal as "status" using the error_code type. EOF is another such irregular situation.

There is also a tricky situation whith canceling an operation with a stop_token, which is also reported via error_code as if it was a status of the stream. This seems coherent, but is actually a conflation of two things.

This is a departure from what other libraries do. They either treat stream irregular operations as errors and throw exceptions, or use things like error_code to say, "this is an error (failure to meet postcondition) but I cannot afford to throw an exception". Some of these libraries count EOF as error (like ASIO), and some use a separate channel for communicating it. When such an irregular situation is treated as error, it is usually of little importance what actually happened. The main information is binary: "is the postcondition satisfied or not", because theonly thing that the program does then is to branch. The only exception is EOF: when it is communicated as other errors, then there has to be a way to distinguish it from other conditions.

Now, the particular choice in Capy ("not error, simply irregular, but successful, stream operation result") breaks when we need to deal with a function that operates on two streams, like pull_from and push_to. Two stream ops ma have two irregular states, but you have only one error_code to return.

The survey of other IO libraries (Boost.Asio, C++ Networking TS (N4771), stdexec (P2300), libunifex, Folly.coro, Boost.Beast, Boost.Cobalt, cppcoro, Seastar, Qt, POCO, libuv) shows that they approach the same problem basically in two ways:

  1. Do not provide a function like this: have the user write the loop explicitly and then push the status reporting responsibility on the user.
  2. Just report error, and do not communicate the statuses of the streams or partial results. Th user should treat it as a binary information: full success or not.

Note that even if we used two error_codes to report status from two streams, we have to answer the question what happens when the task is canceled? Which of the two status codes should communicate the cancellation? Cancellation is not the result of any of the streams, but of the entire function that happens to use them.

Recommendation

The API should be driven by the use cases. We have a couple in Http, Burl and Best2. All places but one boil down to pattern:

auto [ec, n] = co_await http::push_to(rp.req_body, js);
if (ec)
  co_return http::route_error(ec);
json::value jv = js.release();
co_return http::route_next;

That is: n is discarded, ec is only chacked for non-zero status. At this level, ec becomes a genuine error. We no longer talk about the status of a stream but about success-or-failure.

There is also the use case in Burl: https://github.com/cppalliance/burl/blob/develop/src/file.cpp#L79.

This use case to me looks very fishy. If nything goes wrong in any stream, a partially written, incomplete file is left on disk. It looks like Burl never bothers to clean this up. I am not even sure if it should. If Burl wanted to clean this up, knowing which stream it was might be essential. At this point, I am inclined to think that this use case has to be fixed, before any design conclusions could be drawn from it.

Thus, we are left with the first use case, where a high-level operation doesn't want to bother the user with low-level stream states, and gives a binary success-or-failure information. We need a success-or-failure API. This means that the "stream status" narration need to be abandoned in favour of error reporting sematics.

However, for the implementation to reflect this intention clearly, the value of error_code shall not be directly copied from the stream operation, but a dedicated value should be produced for this, informing about "partial pumping". EOF from the target stream should be considered a success.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    • Status
      Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions