summaryrefslogtreecommitdiffstats
path: root/src/boost/libs/fiber/examples/adapt_nonblocking.cpp
blob: 879e178927ab54c81e79e731a3c53152f1c59644 (plain)
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;
}