1# Chrome Network Stack Common Coding Patterns 2 3## Combined error and byte count into a single value 4 5At many places in the network stack, functions return a value that, if 6positive, indicate a count of bytes that the the function read or 7wrote, and if negative, indicates a network stack error code (see 8[net_error_list.h][]). 9Zero indicates either `net::OK` or zero bytes read (usually EOF) 10depending on the context. This pattern is generally specified by 11an `int` return type. 12 13Many functions also have variables (often named `result` or `rv`) containing 14such a value; this is especially common in the [DoLoop](#DoLoop) pattern 15described below. 16 17## Sync/Async Return 18 19Many network stack routines may return synchronously or 20asynchronously. These functions generally return an int as described 21above. There are three cases: 22 23* If the value is positive or zero, that indicates a synchronous 24 successful return, with a zero return value indicating either zero 25 bytes/EOF or indicating `net::OK`, depending on context. 26* If the value is negative and != `net::ERR_IO_PENDING`, it is an error 27 code specifying a synchronous failure. 28* If the return value is the special value `net::ERR_IO_PENDING`, it 29 indicates that the routine will complete asynchronously. A reference to 30 any provided IOBuffer will be retained by the called entity until 31 completion, to be written into or read from as required. 32 If there is a callback argument, that callback will be called upon 33 completion with the return value; if there is no callback argument, it 34 usually means that some known callback mechanism will be employed. 35 36## DoLoop 37 38The DoLoop pattern is used in the network stack to construct simple 39state machines. It is used for cases in which processing is basically 40single-threaded and could be written in a single function, if that 41function could block waiting for input. Generally, initiation of a 42state machine is triggered by some method invocation by a class 43consumer, and that state machine is driven (possibly across 44asynchronous IO initiated by the class) until the operation requested 45by the method invocation completes, at which point the state variable is 46set to `STATE_NONE` and the consumer notified. 47 48Cases which do not fit into this single-threaded, single consumer 49operation model are generally adapted in some way to fit the model, 50either by multiple state machines (e.g. independent state machines for 51reading and writing, if each can be initiated while the other is 52outstanding) or by storing information across consumer invocations and 53returns that can be used to restart the state machine in the proper 54state. 55 56Any class using this pattern will contain an enum listing all states 57of that machine, and define a function, `DoLoop()`, to drive that state 58machine. If a class has multiple state machines (as above) it will 59have multiple methods (e.g. `DoReadLoop()` and `DoWriteLoop()`) to drive 60those different machines. 61 62The characteristics of the DoLoop pattern are: 63 64* Each state has a corresponding function which is called by `DoLoop()` 65 for handling when the state machine is in that state. Generally the 66 states are named STATE`_<`STATE_NAME`>` (upper case separated by 67 underscores), and the routine is named Do`<`StateName`>` (CamelCase). 68 For example: 69 70 enum State { 71 STATE_NONE, 72 STATE_INIT, 73 STATE_FOO, 74 STATE_FOO_COMPLETE, 75 }; 76 int DoInit(); 77 int DoFoo(); 78 int DoFooComplete(int result); 79 80* Each state handling function has two basic responsibilities in 81 addition to state specific handling: Setting the data member 82 (named `next_state_` or something similar) 83 to specify the next state, and returning a `net::Error` (or combined 84 error and byte count, as above). 85 86* On each `DoLoop()` iteration, the function saves the next state to a local 87 variable and resets to a default state (`STATE_NONE`), 88 and then calls the appropriate state handling based on the 89 original value of the next state. This looks like: 90 91 do { 92 State state = io_state_; 93 next_state_ = STATE_NONE; 94 switch (state) { 95 case STATE_INIT: 96 result = DoInit(); 97 break; 98 ... 99 100 This pattern is followed primarily to ensure that in the event of 101 a bug where the next state isn't set, the loop terminates rather 102 than loops infinitely. It's not a perfect mitigation, but works 103 well as a defensive measure. 104 105* If a given state may complete asynchronously (for example, 106 writing to an underlying transport socket), then there will often 107 be split states, such as `STATE_WRITE` and 108 `STATE_WRITE_COMPLETE`. The first state is responsible for 109 starting/continuing the original operation, while the second state 110 is responsible for handling completion (e.g. success vs error, 111 complete vs. incomplete writes), and determining the next state to 112 transition to. 113 114* While the return value from each call is propagated through the loop 115 to the next state, it is expected that for most state transitions the 116 return value will be `net::OK`, and that an error return will also 117 set the state to `STATE_NONE` or fail to override the default 118 assignment to `STATE_DONE` to exit the loop and return that 119 error to the caller. This is often asserted with a DCHECK, e.g. 120 121 case STATE_FOO: 122 DCHECK_EQ(result, OK); 123 result = DoFoo(); 124 break; 125 126 The exception to this pattern is split states, where an IO 127 operation has been dispatched, and the second state is handling 128 the result. In that case, the second state's function takes the 129 result code: 130 131 case STATE_FOO_COMPLETE: 132 result = DoFooComplete(result); 133 break; 134 135* If the return value from the state handling function is 136 `net::ERR_IO_PENDING`, that indicates that the function has arranged 137 for `DoLoop()` to be called at some point in the future, when further 138 progress can be made on the state transitions. The `next_state_` variable 139 will have been set to the proper value for handling that incoming 140 call. In this case, `DoLoop()` will exit. This often occurs between 141 split states, as described above. 142 143* The DoLoop mechanism is generally invoked in response to a consumer 144 calling one of its methods. While the operation that method 145 requested is occuring, the state machine stays active, possibly 146 over multiple asynchronous operations and state transitions. When 147 that operation is complete, the state machine transitions to 148 `STATE_NONE` (by a `DoLoop()` callee not setting `next_state_`) or 149 explicitly to `STATE_DONE` (indicating that the operation is 150 complete *and* the state machine is not amenable to further 151 driving). At this point the consumer is notified of the completion 152 of the operation (by synchronous return or asynchronous callback). 153 154 Note that this implies that when `DoLoop()` returns, one of two 155 things will be true: 156 157 * The return value will be `net::ERR_IO_PENDING`, indicating that the 158 caller should take no action and instead wait for asynchronous 159 notification. 160 * The state of the machine will be either `STATE_DONE` or `STATE_NONE`, 161 indicating that the operation that first initiated the `DoLoop()` has 162 completed. 163 164 This invariant reflects and enforces the single-threaded (though 165 possibly asynchronous) nature of the driven state machine--the 166 machine is always executing one requested operation. 167 168* `DoLoop()` is called from two places: a) methods exposed to the consumer 169 for specific operations (e.g. `ReadHeaders()`), and b) an IO completion 170 callbacks called asynchronously by spawned IO operations. 171 172 In the first case, the return value from `DoLoop()` is returned directly 173 to the caller; if the operation completed synchronously, that will 174 contain the operation result, and if it completed asynchronously, it 175 will be `net::ERR_IO_PENDING`. For example (from 176 `HttpStreamParser`, abridged for clarity): 177 178 int HttpStreamParser::ReadResponseHeaders( 179 CompletionOnceCallback callback) { 180 DCHECK(io_state_ == STATE_NONE || io_state_ == STATE_DONE); 181 DCHECK(callback_.is_null()); 182 DCHECK(!callback.is_null()); 183 184 int result = OK; 185 io_state_ = STATE_READ_HEADERS; 186 187 result = DoLoop(result); 188 189 if (result == ERR_IO_PENDING) 190 callback_ = std::move(callback); 191 192 return result > 0 ? OK : result; 193 } 194 195 In the second case, the IO completion callback will examine the 196 return value from `DoLoop()`. If it is `net::ERR_IO_PENDING`, no 197 further action will be taken, and the IO completion callback will be 198 called again at some future point. If it is not 199 `net::ERR_IO_PENDING`, that is a signal that the operation has 200 completed, and the IO completion callback will call the appropriate 201 consumer callback to notify the consumer that the operation has 202 completed. Note that it is important that this callback be done 203 from the IO completion callback and not from `DoLoop()` or a 204 `DoLoop()` callee, both to support the sync/async error return 205 (DoLoop and its callees don't know the difference) and to avoid 206 consumer callbacks deleting the object out from under `DoLoop()`. 207 Example: 208 209 void HttpStreamParser::OnIOComplete(int result) { 210 result = DoLoop(result); 211 212 if (result != ERR_IO_PENDING && !callback_.is_null()) 213 std::move(callback_).Run(result); 214 } 215 216* The DoLoop pattern has no concept of different events arriving for 217 a single state; each state, if waiting, is waiting for one 218 particular event, and when `DoLoop()` is invoked when the machine is 219 in that state, it will handle that event. This reflects the 220 single-threaded model for operations spawned by the state machine. 221 222Public class methods generally have very little processing, primarily wrapping 223`DoLoop()`. For `DoLoop()` entry this involves setting the `next_state_` 224variable, and possibly making copies of arguments into class members. For 225`DoLoop()` exit, it involves inspecting the return and passing it back to 226the caller, and in the asynchronous case, saving any passed completion callback 227for executing by a future subsidiary IO completion (see above example). 228 229This idiom allows synchronous and asynchronous logic to be written in 230the same fashion; it's all just state transition handling. For mostly 231linear state diagrams, the handling code can be very easy to 232comprehend, as such code is usually written linearly (in different 233handling functions) in the order it's executed. 234 235For examples of this idiom, see 236 237* [HttpStreamParser::DoLoop](https://source.chromium.org/chromium/chromium/src/+/HEAD:net/http/http_stream_parser.cc). 238* [HttpNetworkTransaction::DoLoop](https://source.chromium.org/chromium/chromium/src/+/HEAD:net/http/http_network_transaction.cc) 239 240[net_error_list.h]: https://chromium.googlesource.com/chromium/src/+/main/net/base/net_error_list.h#1 241