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] [net_error_list.h]). | |
asanka
2015/09/21 20:31:01
You can shorten this to [net_error_list.h][] where
Randy Smith (Not in Mondays)
2015/09/22 01:38:21
Done.
| |
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 | |
mmenke
2015/09/21 19:46:28
Maybe `result` or `rv`?
Randy Smith (Not in Mondays)
2015/09/22 01:38:21
Done.
| |
14 such value; this is especially common in the [DoLoop](#DoLoop) pattern | |
mmenke
2015/09/21 19:46:28
such value -> such a value
Randy Smith (Not in Mondays)
2015/09/22 01:38:21
Done.
| |
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 | |
mmenke
2015/09/21 19:46:28
Should we mention void + taking a callback implies
Randy Smith (Not in Mondays)
2015/09/22 01:38:21
Hmmm. I'm inclined against, but I may not have a
| |
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. | |
mmenke
2015/09/21 19:46:28
and -> either...or ?
Randy Smith (Not in Mondays)
2015/09/22 01:38:21
Done.
| |
26 * If the value is negative and != `net::ERR_IO_PENDING`, it is an error | |
27 code specifying a synchronous failing return. | |
mmenke
2015/09/21 19:46:28
"failing return" sounds a little weird. Maybe syn
Randy Smith (Not in Mondays)
2015/09/22 01:38:21
Done.
| |
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 | |
mmenke
2015/09/21 19:46:28
"An IOBuffer provided" -> "A reference to any prov
Randy Smith (Not in Mondays)
2015/09/22 01:38:21
Done.
| |
31 be written into or read from as required. Other pointers must be kept | |
32 alive manually until asynchronous completion is signaled. | |
mmenke
2015/09/21 19:46:28
Other pointers? You mean pointers for the called
Randy Smith (Not in Mondays)
2015/09/22 01:38:21
On reflection, agreed; this isn't worth mentioning
| |
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 | |
mmenke
2015/09/21 19:46:28
" a callback is not provided" -> "there is no call
Randy Smith (Not in Mondays)
2015/09/22 01:38:21
Done.
| |
35 usually means that some known callback mechanism will be employed. | |
36 | |
37 ## DoLoop | |
38 | |
39 The DoLoop pattern is 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, | |
mmenke
2015/09/21 19:46:28
nit: single-threaded
Randy Smith (Not in Mondays)
2015/09/22 01:38:21
Done.
| |
42 if that function could block waiting for input. Generally initiation | |
mmenke
2015/09/21 19:46:28
nit: I'd suggest a comma after Generally.
Randy Smith (Not in Mondays)
2015/09/22 01:38:21
Done.
| |
43 of a state machine is triggerred by some method invocation by a class | |
mmenke
2015/09/21 19:46:28
nit: triggered
Randy Smith (Not in Mondays)
2015/09/22 01:38:21
Done.
| |
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 if completed and the consumer notified. | |
mmenke
2015/09/21 19:46:28
"the state machine is reset" is not entirely accur
Randy Smith (Not in Mondays)
2015/09/22 01:38:21
Hmmm. This is tricky. I'm making a distinction b
| |
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`_<`STATE_NAME`>` (upper case separated by | |
68 underscores), and the routine is named Do`<`StateName`>` (CamelCase). | |
69 For example: | |
70 | |
71 enum State { | |
72 STATE_NONE, | |
73 STATE_INIT, | |
74 STATE_FOO, | |
75 STATE_FOO_COMPLETE, | |
76 }; | |
77 int DoInit(); | |
78 int DoFoo(); | |
79 int DoFooComplete(); | |
asanka
2015/09/21 20:31:01
int DoFooComplete(int result);
esp since it's inv
Randy Smith (Not in Mondays)
2015/09/22 01:38:21
Ooops. Thank you. Done.
| |
80 | |
81 * Each state handling function has two basic responsibilities in | |
82 addition to state specific handling: Setting the data member | |
83 (named `next_state_` or something similar) | |
84 to specify the next state, and returning a `net::Error` (or combined | |
85 error and byte count, as above). | |
86 | |
87 * On each `DoLoop()` iteration, the function saves the next state to a local | |
88 variable and resets to a default state (`STATE_NONE`), | |
89 and then calls the appropriate state handling based on the | |
90 original value of the next state. This looks like: | |
91 | |
92 do { | |
93 State state = io_state_; | |
94 next_state_ = STATE_NONE; | |
95 switch (state) { | |
96 case STATE_INIT: | |
97 result = DoInit(); | |
98 break; | |
99 ... | |
100 | |
101 This pattern is followed primarily to ensure that in the event of | |
102 a bug where the next state isn't set, the loop terminates rather | |
103 than loops infinitely. It's not a perfect mitigation, but works | |
104 well as a defensive measure. | |
105 | |
106 * If a given state may complete synchronously or asynchronously (for example, | |
mmenke
2015/09/21 19:46:28
This is also true if a given state always complete
Randy Smith (Not in Mondays)
2015/09/22 01:38:21
I captured this by shortening the sentence to "If
| |
107 writing to an underlying transport socket), then there will often | |
108 be split states, such as `STATE_WRITE` and | |
109 `STATE_WRITE_COMPLETE`. The first state is responsible for | |
110 starting/continuing the original operation, while the second state | |
111 is responsible for handling completion (e.g. success vs error, | |
112 complete vs. incomplete writes), and determining the next state to | |
113 transition to. | |
114 | |
115 * While the return value from each call is propagated through the loop | |
116 to the next state, it is expected that for most state transitions the | |
117 return value will be `net::OK`, and that an error return will also | |
118 set the state to `STATE_NONE` or fail to override the default | |
119 assignment to `STATE_DONE` to exit the loop and return that | |
120 error to the caller. This is often asserted with a DCHECK, e.g. | |
121 | |
122 case STATE_FOO: | |
123 DCHECK_EQ(result, OK); | |
124 result = DoFoo(); | |
125 break; | |
126 | |
127 The exception to this pattern is split states, where an IO | |
128 operation has been dispatched, and the second state is handling | |
129 the result. In that case, the second state's function takes the | |
130 result code: | |
131 | |
132 case STATE_FOO_COMPLETE: | |
133 result = DoFooComplete(result); | |
134 break; | |
135 | |
136 * If the return value from the state handling function is | |
137 `net::ERR_IO_PENDING`, that indicates that the function has arranged | |
138 for `DoLoop()` to be called at some point in the future, when further | |
139 progress can be made on the state transitions. The `next_state_` variable | |
140 will have been set to the value proper for handling that incoming | |
asanka
2015/09/21 20:31:01
value proper -> proper value
Randy Smith (Not in Mondays)
2015/09/22 01:38:21
Done.
| |
141 call. In this case, `DoLoop()` will exit. This often occurs between | |
142 split states, as described above. | |
143 | |
144 * The DoLoop mechanism is generally invoked in response to a consumer | |
mmenke
2015/09/21 19:46:28
nit: Remove extra space
Randy Smith (Not in Mondays)
2015/09/22 01:38:21
Done.
| |
145 calling one of its methods. While the operation that method | |
146 requested is occuring, the state machine stays active, possibly | |
147 over multiple asynchronous operations and state transitions. When | |
148 that operation is complete, the state machine transitions to | |
149 `STATE_NONE` (by a `DoLoop()` callee not setting `next_state_`) or | |
150 explicitly to `STATE_DONE` (indicating that the operation is | |
151 complete *and* the state machine is not amenable to further | |
152 driving). At this point the consumer is notified of the completion | |
153 of the operation (by synchronous return or asynchronous callback). | |
154 | |
155 Note that this implies that when `DoLoop()` returns, one of two | |
156 things will be true: | |
157 | |
158 * The return value will be `net::ERR_IO_PENDING`, indicating that the | |
159 caller should take no action and instead wait for asynchronous | |
160 notification. | |
161 * The state of the machine will be either `STATE_DONE` or `STATE_NONE`, | |
162 indicating that the operation that first initiated the `DoLoop()` has | |
163 completed. | |
164 | |
165 This invariant reflects and enforces the single-threaded (though | |
166 possibly asynchronous) nature of the driven state machine--the | |
167 machine is always executing one requested operation. | |
168 | |
169 * `DoLoop()` is called from two places: a) methods exposed to the consumer | |
170 for specific operations (e.g. `ReadHeaders`), and b) an IO completion | |
mmenke
2015/09/21 19:46:28
ReadHeaders() to match DoLoop()?
Randy Smith (Not in Mondays)
2015/09/22 01:38:21
Done.
| |
171 callbacks called asynchronously by spawned IO operations. | |
172 | |
173 In the first case, the return value from `DoLoop()` is returned directly | |
174 to the caller; if the operation completed synchronously, that will | |
175 contain the operation result, and if it completed asynchronously, it | |
176 will be `net::ERR_IO_PENDING`. For example (from | |
177 `HttpStreamParser`, abridged for clarity): | |
178 | |
179 int HttpStreamParser::ReadResponseHeaders( | |
180 const CompletionCallback& callback) { | |
181 DCHECK(io_state_ == STATE_NONE || io_state_ == STATE_DONE); | |
182 DCHECK(callback_.is_null()); | |
183 DCHECK(!callback.is_null()); | |
184 | |
185 int result = OK; | |
186 io_state_ = STATE_READ_HEADERS; | |
187 | |
188 result = DoLoop(result); | |
189 | |
190 if (result == ERR_IO_PENDING) | |
191 callback_ = callback; | |
192 | |
193 return result > 0 ? OK : result; | |
194 } | |
195 | |
196 In the second case, the IO completion callback will examine the | |
197 return value from `DoLoop()`. If it is `net::ERR_IO_PENDING`, no | |
198 further action will be taken, and the IO completion callback will be | |
199 called again at some future point. If it is not | |
200 `net::ERR_IO_PENDING`, that is a signal that the operation has | |
201 completed, and the IO completion callback will call the appropriate | |
202 consumer callback to notify the consumer that the operation has | |
203 completed. Note that it is important that this callback be done | |
204 from the IO completion callback and not from `DoLoop()` or a | |
205 `DoLoop()` callee, both to support the sync/async error return | |
206 (DoLoop and its callees don't know the difference) and to avoid | |
207 consumer callbacks deleting the object out from under `DoLoop()`. | |
208 Example: | |
209 | |
210 void HttpStreamParser::OnIOComplete(int result) { | |
211 result = DoLoop(result); | |
212 | |
213 if (result != ERR_IO_PENDING && !callback_.is_null()) { | |
214 CompletionCallback c = callback_; | |
asanka
2015/09/21 20:31:01
base::ResetAndReturn() appears to be the canonical
Randy Smith (Not in Mondays)
2015/09/22 01:38:21
Done.
| |
215 callback_.Reset(); | |
216 c.Run(result); | |
217 } | |
218 } | |
219 | |
220 * The DoLoop pattern has no concept of different events arriving for | |
221 a single state; each state, if waiting, is waiting for one | |
222 particular event, and when `DoLoop()` is invoked when the machine is | |
223 in that state, it will handle that event. This reflects the | |
224 single-threaded model for operations spawned by the state machine. | |
225 | |
226 Public class methods generally have very little processing, primarily wrapping | |
227 `DoLoop()`. For `DoLoop()` entry this involves setting the `next_state_` | |
228 variable, and possibly making copies of arguments into class members. For | |
229 `DoLoop()` exit, it involves inspecting the return and passing it back to | |
230 the caller, and in the asynchronous case, saving any passed completion callback | |
231 for executing by a future subsidiary IO completion (see above example). | |
232 | |
233 This idiom allows synchronous and asynchronous logic to be written in | |
234 the same fashion; it's all just state transition handling. For mostly | |
235 linear state diagrams, the handling code can be very easy to | |
236 comprehend, as such code is usually written linearly (in different | |
237 handling functions) in the order it's executed. | |
238 | |
239 For examples of this idiom, see | |
240 | |
241 * [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). | |
242 * [HttpNetworkTransaction::DoLoop](https://code.google.com/p/chromium/codesearch #chromium/src/net/http/http_network_transaction.cc&q=HttpNetworkTransaction::DoL oop&sq=package:chromium) | |
243 | |
244 [net_error_list.h]: https://chromium.googlesource.com/chromium/src/+/master/net/ base/net_error_list.h#1 | |
OLD | NEW |