mirror of
https://github.com/boostorg/fiber.git
synced 2026-07-21 13:13:32 +00:00
Fill in section on adapting to nonblocking I/O.
This commit is contained in:
@@ -13,6 +13,70 @@
|
||||
|
||||
[heading Overview]
|
||||
|
||||
['Nonblocking] I/O is distinct from ['asynchronous] I/O. A true async I/O
|
||||
operation promises to initiate the operation and notify the caller on
|
||||
completion, usually via some sort of callback (as described in [link callbacks
|
||||
Integrating Fibers with Asynchronous Callbacks]).
|
||||
|
||||
In contrast, a nonblocking I/O operation refuses to start at all if it would
|
||||
be necessary to block, returning an error code such as
|
||||
[@http://man7.org/linux/man-pages/man3/errno.3.html `EWOULDBLOCK`]. The
|
||||
operation is performed only when it can complete immediately. In effect, the
|
||||
caller must repeatedly retry the operation until it stops returning
|
||||
`EWOULDBLOCK`.
|
||||
|
||||
In a classic event-driven program, it can be something of a headache to use
|
||||
nonblocking I/O. At the point where the nonblocking I/O is attempted, a return
|
||||
value of `EWOULDBLOCK` requires the caller to pass control back to the main
|
||||
event loop, arranging to retry again on the next iteration.
|
||||
|
||||
Worse, a nonblocking I/O operation might ['partially] succeed. That means that
|
||||
the relevant business logic must continue receiving control on every main loop
|
||||
iteration until all required data have been processed: a doubly-nested loop,
|
||||
implemented as a callback-driven state machine.
|
||||
|
||||
__boost_fiber__ can simplify this problem immensely. Once you have integrated
|
||||
with the application's main loop as described in [link integration Sharing a
|
||||
Thread with Another Main Loop], waiting for the next main-loop iteration is as
|
||||
simple as calling [ns_function_link this_fiber..yield].
|
||||
|
||||
[heading Example Nonblocking API]
|
||||
|
||||
For purposes of illustration, consider this API:
|
||||
|
||||
[NonblockingAPI]
|
||||
|
||||
[heading Polling for Completion]
|
||||
|
||||
We can build a low-level wrapper around `NonblockingAPI::read()` that
|
||||
shields its caller from ever having to deal with `EWOULDBLOCK`:
|
||||
|
||||
[nonblocking_read_chunk]
|
||||
|
||||
[heading Filling All Desired Data]
|
||||
|
||||
Given `read_chunk()`, we can straightforwardly iterate until we have all
|
||||
desired data:
|
||||
|
||||
[nonblocking_read_desired]
|
||||
|
||||
(Of ['course] there are more efficient ways to accumulate string data. That's
|
||||
not the point of this example.)
|
||||
|
||||
[heading Wrapping it Up]
|
||||
|
||||
Finally, we can define a relevant exception:
|
||||
|
||||
[nonblocking_IncompleteRead]
|
||||
|
||||
and write a simple `read()` function that either returns all desired data or
|
||||
throws `IncompleteRead`:
|
||||
|
||||
[nonblocking_read]
|
||||
|
||||
Once we can transparently wait for the next main-loop iteration using
|
||||
[ns_function_link this_fiber..yield], ordinary encapsulation Just Works.
|
||||
|
||||
[/ @path link is relative to (eventual) doc/html/index.html, hence ../..]
|
||||
The source code above is found in
|
||||
[@../../examples/adapt_nonblocking.cpp adapt_nonblocking.cpp].
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
/*****************************************************************************
|
||||
* example nonblocking API
|
||||
*****************************************************************************/
|
||||
//[NonblockingAPI
|
||||
class NonblockingAPI {
|
||||
public:
|
||||
NonblockingAPI();
|
||||
@@ -22,6 +23,8 @@ public:
|
||||
// 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);
|
||||
@@ -31,7 +34,9 @@ private:
|
||||
int injected_;
|
||||
unsigned tries_;
|
||||
std::size_t chunksize_;
|
||||
//->
|
||||
};
|
||||
//]
|
||||
|
||||
/*****************************************************************************
|
||||
* fake NonblockingAPI implementation... pay no attention to the little man
|
||||
@@ -93,6 +98,7 @@ int NonblockingAPI::read( std::string & data, std::size_t desired) {
|
||||
/*****************************************************************************
|
||||
* adapters
|
||||
*****************************************************************************/
|
||||
//[nonblocking_read_chunk
|
||||
// guaranteed not to return EWOULDBLOCK
|
||||
int read_chunk( NonblockingAPI & api, std::string& data, std::size_t desired) {
|
||||
int error;
|
||||
@@ -103,7 +109,9 @@ int read_chunk( NonblockingAPI & api, std::string& data, std::size_t desired) {
|
||||
}
|
||||
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) {
|
||||
@@ -117,7 +125,9 @@ int read_desired( NonblockingAPI & api, std::string& data, std::size_t desired)
|
||||
}
|
||||
return error;
|
||||
}
|
||||
//]
|
||||
|
||||
//[nonblocking_IncompleteRead
|
||||
// exception class augmented with both partially-read data and errorcode
|
||||
class IncompleteRead: public std::runtime_error {
|
||||
public:
|
||||
@@ -139,7 +149,9 @@ 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;
|
||||
@@ -156,6 +168,7 @@ std::string read( NonblockingAPI & api, std::size_t desired) {
|
||||
<< data.length() << " of " << desired << " characters";
|
||||
throw IncompleteRead( msg.str(), data, ec);
|
||||
}
|
||||
//]
|
||||
|
||||
int main( int argc, char *argv[]) {
|
||||
NonblockingAPI api;
|
||||
|
||||
Reference in New Issue
Block a user