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;
}
|