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

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