adapt_nonblocking.cpp 5.6 KB

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