I have a client which connects to a named pipe as follows:


CreateFile(pipeName.c_str(),GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr);


The result of this call is assigned to a stream_handle. I want that io service shuts down orderly by run() returning due to running out of work. To achieve this, I post a lambda that effectively calls cancel() on the handle (hidden inside Kill()):


    _io.post([this]() {
        for (auto& w : _workers) w->Kill();
    });


However, cancel() has no effect: the callback for async_read continues to return with error code of zero and data read from the pipe. For reference, this is the read call with its handler:


    template<typename T>
    void read(T& ioh, const asio::mutable_buffer& buf)
    {
        ioh.async_read_some(asio::buffer(buf), [this, self = shared_from_this()](const boost::system::error_code& ec, size_t sz) {
            if (ec == boost::system::errc::operation_canceled)
                return;
            if (ec)
                QUINE_THROW(Error(self->_ownersId, ec, "async_read_some"));
            self->ReadCompleted(sz);
        });
    }


ReadCompleted() processes the received data and loops by calling read() again.


If I call close() the callback gets an error code and everything works out correctly, *except* that I get an error code [invalid handle] that gets logged as an error (though it's not).


Am I correct in assuming that cancel() is an apparent noop in this case because of the race-condition where an I/O request completes successfully before cancel is invoked?


If so, can you suggest a more elegant way  (i.e., a way that doesn't induce a hard error) of exiting a loop as described here? Setting a member variable instead of calling cancel?


Given the existence of the race-condition, what are use-cases for cancel? How to use it correctly, if at all possible?


-- Stian