• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //          Copyright Nat Goodspeed 2015.
2 // Distributed under the Boost Software License, Version 1.0.
3 //    (See accompanying file LICENSE_1_0.txt or copy at
4 //          http://www.boost.org/LICENSE_1_0.txt)
5 
6 #include <boost/fiber/all.hpp>
7 #include <iostream>
8 #include <sstream>
9 #include <exception>
10 #include <string>
11 #include <algorithm>                // std::min()
12 #include <errno.h>                  // EWOULDBLOCK
13 #include <cassert>
14 #include <cstdio>
15 
16 /*****************************************************************************
17 *   example nonblocking API
18 *****************************************************************************/
19 //[NonblockingAPI
20 class NonblockingAPI {
21 public:
22     NonblockingAPI();
23 
24     // nonblocking operation: may return EWOULDBLOCK
25     int read( std::string & data, std::size_t desired);
26 
27 /*=    ...*/
28 //<-
29     // for simulating a real nonblocking API
30     void set_data( std::string const& data, std::size_t chunksize);
31     void inject_error( int ec);
32 
33 private:
34     std::string data_;
35     int         injected_;
36     unsigned    tries_;
37     std::size_t chunksize_;
38 //->
39 };
40 //]
41 
42 /*****************************************************************************
43 *   fake NonblockingAPI implementation... pay no attention to the little man
44 *   behind the curtain...
45 *****************************************************************************/
NonblockingAPI()46 NonblockingAPI::NonblockingAPI() :
47     injected_( 0),
48     tries_( 0),
49     chunksize_( 9999) {
50 }
51 
set_data(std::string const & data,std::size_t chunksize)52 void NonblockingAPI::set_data( std::string const& data, std::size_t chunksize) {
53     data_ = data;
54     chunksize_ = chunksize;
55     // This delimits the start of a new test. Reset state.
56     injected_ = 0;
57     tries_ = 0;
58 }
59 
inject_error(int ec)60 void NonblockingAPI::inject_error( int ec) {
61     injected_ = ec;
62 }
63 
read(std::string & data,std::size_t desired)64 int NonblockingAPI::read( std::string & data, std::size_t desired) {
65     // in case of error
66     data.clear();
67 
68     if ( injected_) {
69         // copy injected_ because we're about to reset it
70         auto injected( injected_);
71         injected_ = 0;
72         // after an error situation, restart success count
73         tries_ = 0;
74         return injected;
75     }
76 
77     if ( ++tries_ < 5) {
78         // no injected error, but the resource isn't yet ready
79         return EWOULDBLOCK;
80     }
81 
82     // tell caller there's nothing left
83     if ( data_.empty() ) {
84         return EOF;
85     }
86 
87     // okay, finally have some data
88     // but return minimum of desired and chunksize_
89     std::size_t size( ( std::min)( desired, chunksize_) );
90     data = data_.substr( 0, size);
91     // strip off what we just returned
92     data_ = data_.substr( size);
93     // reset I/O retries count for next time
94     tries_ = 0;
95     // success
96     return 0;
97 }
98 
99 /*****************************************************************************
100 *   adapters
101 *****************************************************************************/
102 //[nonblocking_read_chunk
103 // guaranteed not to return EWOULDBLOCK
read_chunk(NonblockingAPI & api,std::string & data,std::size_t desired)104 int read_chunk( NonblockingAPI & api, std::string & data, std::size_t desired) {
105     int error;
106     while ( EWOULDBLOCK == ( error = api.read( data, desired) ) ) {
107         // not ready yet -- try again on the next iteration of the
108         // application's main loop
109         boost::this_fiber::yield();
110     }
111     return error;
112 }
113 //]
114 
115 //[nonblocking_read_desired
116 // keep reading until desired length, EOF or error
117 // may return both partial data and nonzero error
read_desired(NonblockingAPI & api,std::string & data,std::size_t desired)118 int read_desired( NonblockingAPI & api, std::string & data, std::size_t desired) {
119     // we're going to accumulate results into 'data'
120     data.clear();
121     std::string chunk;
122     int error = 0;
123     while ( data.length() < desired &&
124            ( error = read_chunk( api, chunk, desired - data.length() ) ) == 0) {
125         data.append( chunk);
126     }
127     return error;
128 }
129 //]
130 
131 //[nonblocking_IncompleteRead
132 // exception class augmented with both partially-read data and errorcode
133 class IncompleteRead : public std::runtime_error {
134 public:
IncompleteRead(std::string const & what,std::string const & partial,int ec)135     IncompleteRead( std::string const& what, std::string const& partial, int ec) :
136         std::runtime_error( what),
137         partial_( partial),
138         ec_( ec) {
139     }
140 
get_partial() const141     std::string get_partial() const {
142         return partial_;
143     }
144 
get_errorcode() const145     int get_errorcode() const {
146         return ec_;
147     }
148 
149 private:
150     std::string partial_;
151     int         ec_;
152 };
153 //]
154 
155 //[nonblocking_read
156 // read all desired data or throw IncompleteRead
read(NonblockingAPI & api,std::size_t desired)157 std::string read( NonblockingAPI & api, std::size_t desired) {
158     std::string data;
159     int ec( read_desired( api, data, desired) );
160 
161     // for present purposes, EOF isn't a failure
162     if ( 0 == ec || EOF == ec) {
163         return data;
164     }
165 
166     // oh oh, partial read
167     std::ostringstream msg;
168     msg << "NonblockingAPI::read() error " << ec << " after "
169         << data.length() << " of " << desired << " characters";
170     throw IncompleteRead( msg.str(), data, ec);
171 }
172 //]
173 
main(int argc,char * argv[])174 int main( int argc, char *argv[]) {
175     NonblockingAPI api;
176     const std::string sample_data("abcdefghijklmnopqrstuvwxyz");
177 
178     // Try just reading directly from NonblockingAPI
179     api.set_data( sample_data, 5);
180     std::string data;
181     int ec = api.read( data, 13);
182     // whoops, underlying resource not ready
183     assert(ec == EWOULDBLOCK);
184     assert(data.empty());
185 
186     // successful read()
187     api.set_data( sample_data, 5);
188     data = read( api, 13);
189     assert(data == "abcdefghijklm");
190 
191     // read() with error
192     api.set_data( sample_data, 5);
193     // don't accidentally pick either EOF or EWOULDBLOCK
194     assert(EOF != 1);
195     assert(EWOULDBLOCK != 1);
196     api.inject_error(1);
197     int thrown = 0;
198     try {
199         data = read( api, 13);
200     } catch ( IncompleteRead const& e) {
201         thrown = e.get_errorcode();
202     }
203     assert(thrown == 1);
204 
205     std::cout << "done." << std::endl;
206 
207     return EXIT_SUCCESS;
208 }
209