2
0
mirror of https://github.com/boostorg/fiber.git synced 2026-02-10 23:32:28 +00:00
Files
fiber/examples/adapt_nonblocking.cpp
Nat Goodspeed 685ba8f16f Add examples/adapt_nonblocking.cpp.
This illustrates how Fiber can ease the problem of wrapping retries for
nonblocking I/O in an event-driven program.
2015-08-23 17:20:49 -04:00

209 lines
5.4 KiB
C++

// Copyright Nat Goodspeed 2015.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
#include <boost/fiber/all.hpp>
#include <iostream>
#include <sstream>
#include <exception>
#include <string>
#include <algorithm> // std::min()
#include <errno.h> // EWOULDBLOCK
#include <cassert>
/*****************************************************************************
* example nonblocking API
*****************************************************************************/
class NonblockingAPI
{
public:
NonblockingAPI();
// nonblocking operation: may return EWOULDBLOCK
int read(std::string& data, std::size_t desired);
// for simulating a real nonblocking API
void set_data(const std::string& data, std::size_t chunksize);
void inject_error(int ec);
private:
std::string data_;
int injected_;
unsigned tries_;
std::size_t chunksize_;
};
/*****************************************************************************
* fake NonblockingAPI implementation... pay no attention to the little man
* behind the curtain...
*****************************************************************************/
NonblockingAPI::NonblockingAPI():
injected_(0),
tries_(0),
chunksize_(9999)
{}
void NonblockingAPI::set_data(const std::string& data, std::size_t chunksize)
{
data_ = data;
chunksize_ = chunksize;
// This delimits the start of a new test. Reset state.
injected_ = 0;
tries_ = 0;
}
void NonblockingAPI::inject_error(int ec)
{
injected_ = ec;
}
int NonblockingAPI::read(std::string& data, std::size_t desired)
{
// in case of error
data.clear();
if (injected_)
{
// copy injected_ because we're about to reset it
auto injected(injected_);
injected_ = 0;
// after an error situation, restart success count
tries_ = 0;
return injected;
}
if (++tries_ < 5)
{
// no injected error, but the resource isn't yet ready
return EWOULDBLOCK;
}
// tell caller there's nothing left
if (data_.empty())
return EOF;
// okay, finally have some data
// but return minimum of desired and chunksize_
std::size_t size((std::min)(desired, chunksize_));
data = data_.substr(0, size);
// strip off what we just returned
data_ = data_.substr(size);
// reset I/O retries count for next time
tries_ = 0;
// success
return 0;
}
/*****************************************************************************
* adapters
*****************************************************************************/
// guaranteed not to return EWOULDBLOCK
int read_chunk(NonblockingAPI& api, std::string& data, std::size_t desired)
{
int error;
while ((error = api.read(data, desired)) == EWOULDBLOCK)
{
// not ready yet -- try again on the next iteration of the
// application's main loop
boost::this_fiber::yield();
}
return error;
}
// 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;
}
// exception class augmented with both partially-read data and errorcode
class IncompleteRead: public std::runtime_error
{
public:
IncompleteRead(const std::string& what, const std::string& 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_;
};
// 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 (ec == 0 || ec == EOF)
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);
}
int main(int argc, char *argv[])
{
NonblockingAPI api;
const std::string sample_data("abcdefghijklmnopqrstuvwxyz");
// Try just reading directly from NonblockingAPI
api.set_data(sample_data, 5);
std::string data;
int ec = api.read(data, 13);
// whoops, underlying resource not ready
assert(ec == EWOULDBLOCK);
assert(data.empty());
// successful read()
api.set_data(sample_data, 5);
data = read(api, 13);
assert(data == "abcdefghijklm");
// read() with error
api.set_data(sample_data, 5);
// don't accidentally pick either EOF or EWOULDBLOCK
assert(EOF != 1);
assert(EWOULDBLOCK != 1);
api.inject_error(1);
int thrown = 0;
try
{
data = read(api, 13);
}
catch (const IncompleteRead& e)
{
thrown = e.get_errorcode();
}
assert(thrown == 1);
return EXIT_SUCCESS;
}