Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 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](https://chromium.googlesource.com/chromium/src/+/master/net/b ase/net_error_list.h#1)). | |
| 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|) containing | |
| 14 such 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 possibly indicating zero | |
| 25 bytes/EOF and possibly 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 failing return. | |
| 28 * If the return value is the special value `net::ERR_IO_PENDING`, it | |
| 29 indicates that the routine will complete asynchronously. An IOBuffer | |
| 30 provided will be retained by the called entity until completion, to | |
| 31 be written into or read from as required. Other pointers must be kept | |
| 32 alive manually until asynchronous completion is signaled. | |
| 33 If a callback was provided, that callback will be called upon | |
| 34 completion with the return value; if a callback is not provided, it | |
| 35 usually means that some known callback mechanism will be employed. | |
| 36 | |
| 37 ## DoLoop | |
| 38 | |
| 39 The DoLoop pattern is a pattern used in the network stack to construct | |
| 40 simple state machines. It is used for cases in which processing is | |
| 41 basically single threaded and could be written in a single function, | |
| 42 if that function could block waiting for input. Generally initiation | |
| 43 of a state machine is triggerred by some method invocation by a class | |
| 44 consumer, and that state machine is driven (possibly across | |
| 45 asynchronous IO initiated by the class) until the operation requested | |
| 46 by the method invocation completes, at which point the state machine | |
| 47 is reset of completed and the consumer notified. | |
|
mmenke
2015/09/02 16:41:17
reset if completed?
Randy Smith (Not in Mondays)
2015/09/02 20:25:54
Yep, oops, done.
| |
| 48 | |
| 49 Cases which do not fit into this single-threaded, single consumer | |
| 50 operation model are generally adapted in some way to fit the model, | |
| 51 either by multiple state machines (e.g. independent state machines for | |
| 52 reading and writing, if each can be initiated while the other is | |
| 53 outstanding) or by storing information across consumer invocations and | |
| 54 returns that can be used to restart the state machine in the proper | |
| 55 state. | |
| 56 | |
| 57 Any class using this pattern will contain an enum listing all states | |
| 58 of that machine, and define a function, |DoLoop|, to drive that state | |
| 59 machine. If a class has multiple state machines (as above) it will | |
| 60 have multiple methods (e.g. |DoReadLoop| and |DoWriteLoop|) to drive | |
| 61 those different machines. | |
| 62 | |
| 63 The characteristics of the DoLoop pattern are: | |
| 64 | |
| 65 * Each state has a corresponding function which is called by DoLoop | |
| 66 for handling when the state machine is in that state. Generally the | |
| 67 states are named STATE`_<`STATENAME`>` (upper case separated by | |
| 68 underscores), and the routine is named Do`<`StateName`>` (CamelCase). | |
| 69 Those functions both take and return values that are either | |
| 70 net::Errors or the above combined error and byte count value. | |
| 71 * If a given state may complete synchronously or asynchronously (for example, | |
| 72 writing to an underlying transport socket), then there will often | |
| 73 be pairs of related states, such as `STATE_WRITE` and | |
| 74 `STATE_WRITE_COMPLETE`. The first state is responsible for | |
| 75 starting/continuing the original operation, while the second state | |
| 76 is responsible for handling completion (e.g. success vs error, | |
| 77 complete vs. incomplete writes), and determining the next state to | |
| 78 transition to. | |
| 79 * Each state handling function has two basic responsibilities in | |
| 80 addition to state specific handling: Setting the data member | |
| 81 (named |`next_state_`| or something similar) | |
| 82 to specify the next state, and returning a net::Error (or combined | |
| 83 error and byte count, as above). | |
| 84 * On each DoLoop iteration, it saves the next state to a local | |
| 85 variable and resets to the default/terminal state, and then calls | |
| 86 the appropriate state handling based on the original value of the | |
| 87 next state. This pattern is followed primarily to ensure that in | |
| 88 the event of a bug where the next state isn't set, the loop | |
| 89 terminates rather than loops infinitely. It's not a perfect | |
| 90 mitigation, but works well as a defensive measure. | |
| 91 * If the return value from the state handling function is | |
| 92 `net::ERR_IO_PENDING`, that indicates that the function has arranged | |
| 93 for DoLoop() to be called at some point in the future, when further | |
| 94 progress can be made on the state transitions. The `next_state_` variable | |
| 95 will have been set to the value proper for handling that incoming | |
| 96 call. In this case, DoLoop() will exit. | |
| 97 * A class state machine is generally invoked in response to a consumer | |
| 98 calling one of its methods. While the operation that method | |
|
mmenke
2015/09/02 16:41:17
My god! Two spaces after a period.
mmenke
2015/09/02 16:43:05
Worth noting:
1) I'm trying to be silly, not cri
Randy Smith (Not in Mondays)
2015/09/02 20:25:54
Indeed. I will arrange to commit ritual hari-kari
Randy Smith (Not in Mondays)
2015/09/02 20:25:54
No worries on my end; I've basically accepted that
| |
| 99 requested is occuring, the state machine stays active, possibly | |
| 100 over multiple asynchronous operations and state transitions. When | |
| 101 that operation is complete, the state machine transitions to | |
| 102 `STATE_NONE` (by a DoLoop callee not setting `next_state_`) or | |
| 103 `STATE_DONE` (by explicitly setting next_state_ to `STATE_DONE` | |
| 104 indicating that the operation is complete *and* the state machine is | |
| 105 not amenable to further driving). At this point the consumer is | |
| 106 notified of the completion of the operation (by synchronous return | |
| 107 or asynchronous callback). | |
| 108 | |
| 109 Note that this implies that when DoLoop() returns, one of two | |
| 110 things will be true: | |
| 111 | |
| 112 * The return value will be `net::ERR_IO_PENDING`, indicating that the | |
| 113 caller should take no action and instead wait for asynchronous | |
| 114 notification. | |
| 115 * The state of the machine will be either `STATE_DONE` or `STATE_NONE`, | |
| 116 indicating that the operation that first initiated the DoLoop() has | |
| 117 completed. | |
| 118 | |
| 119 This invariant reflects and enforces the single-threaded (though | |
| 120 possibly asynchronous) nature of the driven state machine--the | |
| 121 machine is always executing one requested operation. | |
| 122 * DoLoop is called from two places: a) methods exposed to the consumer | |
| 123 for specific operations (e.g. |ReadHeaders|), and b) an IO completion | |
| 124 callbacks called asynchronously by spawned IO operations. | |
| 125 | |
| 126 In the first case, the return value from DoLoop is returned directly | |
| 127 to the caller; if the operation completed synchronously, that will | |
| 128 contain the operation result, and if it completed asynchronously, it | |
| 129 will be `net::ERR_IO_PENDING`. | |
| 130 | |
| 131 In the second case, the IO completion callback will examine the | |
| 132 return value from DoLoop(). If it is `net::ERR_IO_PENDING`, no | |
| 133 further action will be taken, and the IO completion callback will be | |
| 134 called again at some future point. If it is not | |
| 135 `net::ERR_IO_PENDING`, that is a signal that the operation has | |
| 136 completed, and the IO completion callback will call the appropriate | |
| 137 consumer callback to notify the consumer that the operation has | |
| 138 completed. Note that it is important that this callback be done | |
| 139 from the IO completion callback and not DoLoop or a DoLoop callee, | |
| 140 both to support the sync/async error return (DoLoop and its callees | |
| 141 don't know the difference) and to avoid consumer callbacks deleting | |
| 142 the object out from under DoLoop(). | |
| 143 * The DoLoop pattern has no concept of events; each state, if | |
|
mmenke
2015/09/02 16:41:17
This contradicts itself - you say no concept of ev
Randy Smith (Not in Mondays)
2015/09/02 20:25:54
So what I meant was that when the state machine is
mmenke
2015/09/02 20:31:29
Sorry, I mean HttpNetworkTransaction. Notice it's
| |
| 144 waiting, is waiting for one particular event, and when DoLoop is | |
| 145 invoked when the machine is in that state, it will handle that | |
| 146 event. This reflects the single-threaded model for operations | |
| 147 spawned by the state machine. | |
| 148 | |
| 149 Public class methods should have as little processing as possible, | |
| 150 often simply making copies of arguments into data members, setting the | |
| 151 `next_state_` variable to indicate the section of the state diagram to | |
| 152 process, and calling DoLoop(). | |
| 153 | |
| 154 This idiom allows synchronous and asynchronous logic to be written in | |
| 155 the same fashion; it's all just state transition handling. For mostly | |
| 156 linear state diagrams, the handling code can be very easy to | |
| 157 comprehend, as such code is usually written linearly (in different | |
| 158 handling functions) in the order it's executed. If there can be | |
| 159 multiple different events that complete outstanding IO, the framework | |
| 160 doesn't handle that explicitly; the state handling code for the | |
| 161 receiving state must explicitly distinguish between those events and | |
| 162 do the appropriate state transition. | |
| 163 | |
| 164 For examples of this idiom, see | |
| 165 | |
| 166 * [HttpStreamParser::DoLoop](https://code.google.com/p/chromium/codesearch#chrom ium/src/net/http/http_stream_parser.cc&q=HttpStreamParser::DoLoop&sq=package:chr omium). | |
| 167 * [HttpNetworkTransaction::DoLoop](https://code.google.com/p/chromium/codesearch #chromium/src/net/http/http_network_transaction.cc&q=HttpNetworkTransaction::DoL oop&sq=package:chromium) | |
| 168 | |
| OLD | NEW |