// 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 #include #include #include #include #include // std::min() #include // EWOULDBLOCK #include /***************************************************************************** * 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; }