mirror of
https://github.com/boostorg/fiber.git
synced 2026-02-10 23:32:28 +00:00
This illustrates how Fiber can ease the problem of wrapping retries for nonblocking I/O in an event-driven program.
209 lines
5.4 KiB
C++
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;
|
|
}
|