| Home | Libraries | People | FAQ | More

## Integrating Fibers with Nonblocking I/O

#### Overview

Nonblocking I/O is distinct from asynchronous I/O. A true async I/O operation promises to initiate the operation and notify the caller on completion, usually via some sort of callback (as described in Integrating Fibers with Asynchronous Callbacks).

In contrast, a nonblocking I/O operation refuses to start at all if it would be necessary to block, returning an error code such as `EWOULDBLOCK`. The operation is performed only when it can complete immediately. In effect, the caller must repeatedly retry the operation until it stops returning `EWOULDBLOCK`.

In a classic event-driven program, it can be something of a headache to use nonblocking I/O. At the point where the nonblocking I/O is attempted, a return value of `EWOULDBLOCK` requires the caller to pass control back to the main event loop, arranging to retry again on the next iteration.

Worse, a nonblocking I/O operation might partially succeed. That means that the relevant business logic must continue receiving control on every main loop iteration until all required data have been processed: a doubly-nested loop, implemented as a callback-driven state machine.

Boost.Fiber can simplify this problem immensely. Once you have integrated with the application's main loop as described in Sharing a Thread with Another Main Loop, waiting for the next main-loop iteration is as simple as calling `this_fiber::yield()`.

#### Example Nonblocking API

For purposes of illustration, consider this API:

    class NonblockingAPI {
    public:
        NonblockingAPI();

        // nonblocking operation: may return EWOULDBLOCK
        int read( std::string & data, std::size_t desired);

        ...
    };

#### Polling for Completion

We can build a low-level wrapper around `NonblockingAPI::read()` that shields its caller from ever having to deal with `EWOULDBLOCK`:

    // guaranteed not to return EWOULDBLOCK
    int read_chunk( NonblockingAPI & api, std::string & data, std::size_t desired) {
        int error;
        while ( EWOULDBLOCK == ( error = api.read( data, desired) ) ) {
            // not ready yet -- try again on the next iteration of the
            // application's main loop
            boost::this_fiber::yield();
        }
        return error;
    }

#### Filling All Desired Data

Given `read_chunk()`, we can straightforwardly iterate until we have all desired data:

    // keep reading until desired length, EOF or error
    // may return both partial data and nonzero error
    int read_desired( NonblockingAPI & api, std::string & data, std::size_t desired) {
        // we're going to accumulate results into 'data'
        data.clear();
        std::string chunk;
        int error = 0;
        while ( data.length() < desired &&
               ( error = read_chunk( api, chunk, desired - data.length() ) ) == 0) {
            data.append( chunk);
        }
        return error;
    }

(Of course there are more efficient ways to accumulate string data. That's not the point of this example.)

#### Wrapping it Up

Finally, we can define a relevant exception:

    // exception class augmented with both partially-read data and errorcode
    class IncompleteRead : public std::runtime_error {
    public:
        IncompleteRead( std::string const& what, std::string const& partial, int ec) :
            std::runtime_error( what),
            partial_( partial),
            ec_( ec) {
        }

        std::string get_partial() const {
            return partial_;
        }

        int get_errorcode() const {
            return ec_;
        }

    private:
        std::string partial_;
        int         ec_;
    };

and write a simple `read()` function that either returns all desired data or throws `IncompleteRead`:

    // read all desired data or throw IncompleteRead
    std::string read( NonblockingAPI & api, std::size_t desired) {
        std::string data;
        int ec( read_desired( api, data, desired) );

        // for present purposes, EOF isn't a failure
        if ( 0 == ec || EOF == ec) {
            return data;
        }

        // oh oh, partial read
        std::ostringstream msg;
        msg << "NonblockingAPI::read() error " << ec << " after "
            << data.length() << " of " << desired << " characters";
        throw IncompleteRead( msg.str(), data, ec);
    }

Once we can transparently wait for the next main-loop iteration using `this_fiber::yield()`, ordinary encapsulation Just Works.

The source code above is found in adapt_nonblocking.cpp.