Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(98)

Side by Side Diff: net/docs/code-patterns.md

Issue 1320933003: Writeup summary of common netstack coding patterns. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Incorporated all comments, reflowed, and added examples. Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | net/net.gypi » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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
OLDNEW
« no previous file with comments | « no previous file | net/net.gypi » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698