rdsmith | 6c8f819 | 2015-09-22 16:52:06 | [diff] [blame] | 1 | # Chrome Network Stack Common Coding Patterns |
| 2 | |
| 3 | ## Combined error and byte count into a single value |
| 4 | |
| 5 | At many places in the network stack, functions return a value that, if |
| 6 | positive, indicate a count of bytes that the the function read or |
| 7 | wrote, and if negative, indicates a network stack error code (see |
| 8 | [net_error_list.h][]). |
| 9 | Zero indicates either `net::OK` or zero bytes read (usually EOF) |
| 10 | depending on the context. This pattern is generally specified by |
| 11 | an `int` return type. |
| 12 | |
| 13 | Many functions also have variables (often named `result` or `rv`) containing |
| 14 | such a value; this is especially common in the [DoLoop](#DoLoop) pattern |
| 15 | described below. |
| 16 | |
| 17 | ## Sync/Async Return |
| 18 | |
| 19 | Many network stack routines may return synchronously or |
| 20 | asynchronously. These functions generally return an int as described |
| 21 | above. 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 | |
| 38 | The DoLoop pattern is used in the network stack to construct simple |
| 39 | state machines. It is used for cases in which processing is basically |
| 40 | single-threaded and could be written in a single function, if that |
| 41 | function could block waiting for input. Generally, initiation of a |
| 42 | state machine is triggered by some method invocation by a class |
| 43 | consumer, and that state machine is driven (possibly across |
| 44 | asynchronous IO initiated by the class) until the operation requested |
| 45 | by the method invocation completes, at which point the state variable is |
| 46 | set to `STATE_NONE` and the consumer notified. |
| 47 | |
| 48 | Cases which do not fit into this single-threaded, single consumer |
| 49 | operation model are generally adapted in some way to fit the model, |
| 50 | either by multiple state machines (e.g. independent state machines for |
| 51 | reading and writing, if each can be initiated while the other is |
| 52 | outstanding) or by storing information across consumer invocations and |
| 53 | returns that can be used to restart the state machine in the proper |
| 54 | state. |
| 55 | |
| 56 | Any class using this pattern will contain an enum listing all states |
| 57 | of that machine, and define a function, `DoLoop()`, to drive that state |
| 58 | machine. If a class has multiple state machines (as above) it will |
| 59 | have multiple methods (e.g. `DoReadLoop()` and `DoWriteLoop()`) to drive |
| 60 | those different machines. |
| 61 | |
| 62 | The 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 | const CompletionCallback& 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_ = 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 | base::ResetAndReturn(&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 | |
| 222 | Public class methods generally have very little processing, primarily wrapping |
| 223 | `DoLoop()`. For `DoLoop()` entry this involves setting the `next_state_` |
| 224 | variable, and possibly making copies of arguments into class members. For |
| 225 | `DoLoop()` exit, it involves inspecting the return and passing it back to |
| 226 | the caller, and in the asynchronous case, saving any passed completion callback |
| 227 | for executing by a future subsidiary IO completion (see above example). |
| 228 | |
| 229 | This idiom allows synchronous and asynchronous logic to be written in |
| 230 | the same fashion; it's all just state transition handling. For mostly |
| 231 | linear state diagrams, the handling code can be very easy to |
| 232 | comprehend, as such code is usually written linearly (in different |
| 233 | handling functions) in the order it's executed. |
| 234 | |
| 235 | For examples of this idiom, see |
| 236 | |
| 237 | * [HttpStreamParser::DoLoop](https://code.google.com/p/chromium/codesearch#chromium/src/net/http/http_stream_parser.cc&q=HttpStreamParser::DoLoop&sq=package:chromium). |
| 238 | * [HttpNetworkTransaction::DoLoop](https://code.google.com/p/chromium/codesearch#chromium/src/net/http/http_network_transaction.cc&q=HttpNetworkTransaction::DoLoop&sq=package:chromium) |
| 239 | |
| 240 | [net_error_list.h]: https://chromium.googlesource.com/chromium/src/+/master/net/base/net_error_list.h#1 |