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