1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
|
// 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>
#include <cstdio>
/*****************************************************************************
* example nonblocking API
*****************************************************************************/
//[NonblockingAPI
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( std::string const& 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( std::string const& 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
*****************************************************************************/
//[nonblocking_read_chunk
// 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;
}
//]
//[nonblocking_read_desired
// 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;
}
//]
//[nonblocking_IncompleteRead
// 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_;
};
//]
//[nonblocking_read
// 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);
}
//]
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 ( IncompleteRead const& e) {
thrown = e.get_errorcode();
}
assert(thrown == 1);
std::cout << "done." << std::endl;
return EXIT_SUCCESS;
}
|