When closing the read-end side of the pipe, we need to notify the writer
only if it was blocked in write() on a full pipe - because if it wasn't
blocked, it will get the broken pipe on the next write() attempt.
We used queue::abort() for that, which is fine, because this function
won't do anything if nobody is waiting on a full buffer. But we still
call std::make_exception_ptr() unconditionally, which is slow (involves
throwing an exception and catching it) and an annoying false-alarm when
trying to debug with gdb's "catch throw" (which stops on any throw).
So this patch does
if (_buf.full()) {
_buf.abort(std::make_exception_ptr(broken_pipe_exception()));
}
So that in the typical case when the buffer was not full (hopefully it
is empty), we don't do anything.
Signed-off-by: Nadav Har'El <nyh@cloudius-systems.com>
Our queue<T> is a convenient mechanism for passing data between a producer
fiber (a set of consecutive continuations) and a consumer fiber.
However, queue<T> is difficult to use *correctly*. The biggest problem is
how to handle premature stopping: What if one of the two fibers (the reader
or the writer) stops prematurely, and will never read or write any more?
When queue<T> is used naively, the other fiber will just hang indefinitely
while it waits to read from the empty queue, or write to the full queue.
The solution proposed in this patch is a new pipe mechanism, implemented
internally over a queue. pipe<T>() returns two separate objects - a pipe
reader, and a pipe writer. Typically each object is std::move()ed into a
different fiber. When a fiber stops and its captured variables are destroyed,
one end of the pipe is destroyed, and that causes the other end's operations
to return immediately (if the other end was already blocked, it will resume
immediately, and return an exceptions). This behavior is analogous to
Unix's EOF or broken-pipe behavior.
Signed-off-by: Nadav Har'El <nyh@cloudius-systems.com>