xref: /aosp_15_r20/external/cronet/net/docs/code-patterns.md (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1*6777b538SAndroid Build Coastguard Worker# Chrome Network Stack Common Coding Patterns
2*6777b538SAndroid Build Coastguard Worker
3*6777b538SAndroid Build Coastguard Worker## Combined error and byte count into a single value
4*6777b538SAndroid Build Coastguard Worker
5*6777b538SAndroid Build Coastguard WorkerAt many places in the network stack, functions return a value that, if
6*6777b538SAndroid Build Coastguard Workerpositive, indicate a count of bytes that the the function read or
7*6777b538SAndroid Build Coastguard Workerwrote, and if negative, indicates a network stack error code (see
8*6777b538SAndroid Build Coastguard Worker[net_error_list.h][]).
9*6777b538SAndroid Build Coastguard WorkerZero indicates either `net::OK` or zero bytes read (usually EOF)
10*6777b538SAndroid Build Coastguard Workerdepending on the context. This pattern is generally specified by
11*6777b538SAndroid Build Coastguard Workeran `int` return type.
12*6777b538SAndroid Build Coastguard Worker
13*6777b538SAndroid Build Coastguard WorkerMany functions also have variables (often named `result` or `rv`) containing
14*6777b538SAndroid Build Coastguard Workersuch a value; this is especially common in the [DoLoop](#DoLoop) pattern
15*6777b538SAndroid Build Coastguard Workerdescribed below.
16*6777b538SAndroid Build Coastguard Worker
17*6777b538SAndroid Build Coastguard Worker## Sync/Async Return
18*6777b538SAndroid Build Coastguard Worker
19*6777b538SAndroid Build Coastguard WorkerMany network stack routines may return synchronously or
20*6777b538SAndroid Build Coastguard Workerasynchronously. These functions generally return an int as described
21*6777b538SAndroid Build Coastguard Workerabove. There are three cases:
22*6777b538SAndroid Build Coastguard Worker
23*6777b538SAndroid Build Coastguard Worker* If the value is positive or zero, that indicates a synchronous
24*6777b538SAndroid Build Coastguard Worker  successful return, with a zero return value indicating either zero
25*6777b538SAndroid Build Coastguard Worker  bytes/EOF or indicating `net::OK`, depending on context. If there
26*6777b538SAndroid Build Coastguard Worker  is a callback argument, it is not invoked.
27*6777b538SAndroid Build Coastguard Worker* If the value is negative and != `net::ERR_IO_PENDING`, it is an error
28*6777b538SAndroid Build Coastguard Worker  code specifying a synchronous failure. If there is a callback argument,
29*6777b538SAndroid Build Coastguard Worker  it is not invoked.
30*6777b538SAndroid Build Coastguard Worker* If the return value is the special value `net::ERR_IO_PENDING`, it
31*6777b538SAndroid Build Coastguard Worker  indicates that the routine will complete asynchronously. A reference to
32*6777b538SAndroid Build Coastguard Worker  any provided IOBuffer will be retained by the called entity until
33*6777b538SAndroid Build Coastguard Worker  completion, to be written into or read from as required.
34*6777b538SAndroid Build Coastguard Worker  If there is a callback argument, that callback will be called upon
35*6777b538SAndroid Build Coastguard Worker  completion with the return value; if there is no callback argument, it
36*6777b538SAndroid Build Coastguard Worker  usually means that some known callback mechanism will be employed.
37*6777b538SAndroid Build Coastguard Worker
38*6777b538SAndroid Build Coastguard Worker## DoLoop
39*6777b538SAndroid Build Coastguard Worker
40*6777b538SAndroid Build Coastguard WorkerThe DoLoop pattern is used in the network stack to construct simple
41*6777b538SAndroid Build Coastguard Workerstate machines. It is used for cases in which processing is basically
42*6777b538SAndroid Build Coastguard Workersingle-threaded and could be written in a single function, if that
43*6777b538SAndroid Build Coastguard Workerfunction could block waiting for input. Generally, initiation of a
44*6777b538SAndroid Build Coastguard Workerstate machine is triggered by some method invocation by a class
45*6777b538SAndroid Build Coastguard Workerconsumer, and that state machine is driven (possibly across
46*6777b538SAndroid Build Coastguard Workerasynchronous IO initiated by the class) until the operation requested
47*6777b538SAndroid Build Coastguard Workerby the method invocation completes, at which point the state variable is
48*6777b538SAndroid Build Coastguard Workerset to `STATE_NONE` and the consumer notified.
49*6777b538SAndroid Build Coastguard Worker
50*6777b538SAndroid Build Coastguard WorkerCases which do not fit into this single-threaded, single consumer
51*6777b538SAndroid Build Coastguard Workeroperation model are generally adapted in some way to fit the model,
52*6777b538SAndroid Build Coastguard Workereither by multiple state machines (e.g. independent state machines for
53*6777b538SAndroid Build Coastguard Workerreading and writing, if each can be initiated while the other is
54*6777b538SAndroid Build Coastguard Workeroutstanding) or by storing information across consumer invocations and
55*6777b538SAndroid Build Coastguard Workerreturns that can be used to restart the state machine in the proper
56*6777b538SAndroid Build Coastguard Workerstate.
57*6777b538SAndroid Build Coastguard Worker
58*6777b538SAndroid Build Coastguard WorkerAny class using this pattern will contain an enum listing all states
59*6777b538SAndroid Build Coastguard Workerof that machine, and define a function, `DoLoop()`, to drive that state
60*6777b538SAndroid Build Coastguard Workermachine. If a class has multiple state machines (as above) it will
61*6777b538SAndroid Build Coastguard Workerhave multiple methods (e.g. `DoReadLoop()` and `DoWriteLoop()`) to drive
62*6777b538SAndroid Build Coastguard Workerthose different machines.
63*6777b538SAndroid Build Coastguard Worker
64*6777b538SAndroid Build Coastguard WorkerThe characteristics of the DoLoop pattern are:
65*6777b538SAndroid Build Coastguard Worker
66*6777b538SAndroid Build Coastguard Worker*   Each state has a corresponding function which is called by `DoLoop()`
67*6777b538SAndroid Build Coastguard Worker    for handling when the state machine is in that state. Generally the
68*6777b538SAndroid Build Coastguard Worker    states are named STATE`_<`STATE_NAME`>` (upper case separated by
69*6777b538SAndroid Build Coastguard Worker    underscores), and the routine is named Do`<`StateName`>` (CamelCase).
70*6777b538SAndroid Build Coastguard Worker    For example:
71*6777b538SAndroid Build Coastguard Worker
72*6777b538SAndroid Build Coastguard Worker         enum State {
73*6777b538SAndroid Build Coastguard Worker             STATE_NONE,
74*6777b538SAndroid Build Coastguard Worker             STATE_INIT,
75*6777b538SAndroid Build Coastguard Worker             STATE_FOO,
76*6777b538SAndroid Build Coastguard Worker             STATE_FOO_COMPLETE,
77*6777b538SAndroid Build Coastguard Worker         };
78*6777b538SAndroid Build Coastguard Worker         int DoInit();
79*6777b538SAndroid Build Coastguard Worker         int DoFoo();
80*6777b538SAndroid Build Coastguard Worker         int DoFooComplete(int result);
81*6777b538SAndroid Build Coastguard Worker
82*6777b538SAndroid Build Coastguard Worker*   Each state handling function has two basic responsibilities in
83*6777b538SAndroid Build Coastguard Worker    addition to state specific handling: Setting the data member
84*6777b538SAndroid Build Coastguard Worker    (named `next_state_` or something similar)
85*6777b538SAndroid Build Coastguard Worker    to specify the next state, and returning a `net::Error` (or combined
86*6777b538SAndroid Build Coastguard Worker    error and byte count, as above).
87*6777b538SAndroid Build Coastguard Worker
88*6777b538SAndroid Build Coastguard Worker*   On each `DoLoop()` iteration, the function saves the next state to a local
89*6777b538SAndroid Build Coastguard Worker    variable and resets to a default state (`STATE_NONE`),
90*6777b538SAndroid Build Coastguard Worker    and then calls the appropriate state handling based on the
91*6777b538SAndroid Build Coastguard Worker    original value of the next state. This looks like:
92*6777b538SAndroid Build Coastguard Worker
93*6777b538SAndroid Build Coastguard Worker           do {
94*6777b538SAndroid Build Coastguard Worker             State state = io_state_;
95*6777b538SAndroid Build Coastguard Worker             next_state_ = STATE_NONE;
96*6777b538SAndroid Build Coastguard Worker             switch (state) {
97*6777b538SAndroid Build Coastguard Worker               case STATE_INIT:
98*6777b538SAndroid Build Coastguard Worker                 result = DoInit();
99*6777b538SAndroid Build Coastguard Worker                 break;
100*6777b538SAndroid Build Coastguard Worker               ...
101*6777b538SAndroid Build Coastguard Worker
102*6777b538SAndroid Build Coastguard Worker    This pattern is followed primarily to ensure that in the event of
103*6777b538SAndroid Build Coastguard Worker    a bug where the next state isn't set, the loop terminates rather
104*6777b538SAndroid Build Coastguard Worker    than loops infinitely. It's not a perfect mitigation, but works
105*6777b538SAndroid Build Coastguard Worker    well as a defensive measure.
106*6777b538SAndroid Build Coastguard Worker
107*6777b538SAndroid Build Coastguard Worker*   If a given state may complete asynchronously (for example,
108*6777b538SAndroid Build Coastguard Worker    writing to an underlying transport socket), then there will often
109*6777b538SAndroid Build Coastguard Worker    be split states, such as `STATE_WRITE` and
110*6777b538SAndroid Build Coastguard Worker    `STATE_WRITE_COMPLETE`. The first state is responsible for
111*6777b538SAndroid Build Coastguard Worker    starting/continuing the original operation, while the second state
112*6777b538SAndroid Build Coastguard Worker    is responsible for handling completion (e.g. success vs error,
113*6777b538SAndroid Build Coastguard Worker    complete vs. incomplete writes), and determining the next state to
114*6777b538SAndroid Build Coastguard Worker    transition to.
115*6777b538SAndroid Build Coastguard Worker
116*6777b538SAndroid Build Coastguard Worker*   While the return value from each call is propagated through the loop
117*6777b538SAndroid Build Coastguard Worker    to the next state, it is expected that for most state transitions the
118*6777b538SAndroid Build Coastguard Worker    return value will be `net::OK`, and that an error return will also
119*6777b538SAndroid Build Coastguard Worker    set the state to `STATE_NONE` or fail to override the default
120*6777b538SAndroid Build Coastguard Worker    assignment to `STATE_DONE` to exit the loop and return that
121*6777b538SAndroid Build Coastguard Worker    error to the caller. This is often asserted with a DCHECK, e.g.
122*6777b538SAndroid Build Coastguard Worker
123*6777b538SAndroid Build Coastguard Worker            case STATE_FOO:
124*6777b538SAndroid Build Coastguard Worker                DCHECK_EQ(result, OK);
125*6777b538SAndroid Build Coastguard Worker                result = DoFoo();
126*6777b538SAndroid Build Coastguard Worker                break;
127*6777b538SAndroid Build Coastguard Worker
128*6777b538SAndroid Build Coastguard Worker    The exception to this pattern is split states, where an IO
129*6777b538SAndroid Build Coastguard Worker    operation has been dispatched, and the second state is handling
130*6777b538SAndroid Build Coastguard Worker    the result. In that case, the second state's function takes the
131*6777b538SAndroid Build Coastguard Worker    result code:
132*6777b538SAndroid Build Coastguard Worker
133*6777b538SAndroid Build Coastguard Worker            case STATE_FOO_COMPLETE:
134*6777b538SAndroid Build Coastguard Worker                result = DoFooComplete(result);
135*6777b538SAndroid Build Coastguard Worker                break;
136*6777b538SAndroid Build Coastguard Worker
137*6777b538SAndroid Build Coastguard Worker*   If the return value from the state handling function is
138*6777b538SAndroid Build Coastguard Worker    `net::ERR_IO_PENDING`, that indicates that the function has arranged
139*6777b538SAndroid Build Coastguard Worker    for `DoLoop()` to be called at some point in the future, when further
140*6777b538SAndroid Build Coastguard Worker    progress can be made on the state transitions. The `next_state_` variable
141*6777b538SAndroid Build Coastguard Worker    will have been set to the proper value for handling that incoming
142*6777b538SAndroid Build Coastguard Worker    call. In this case, `DoLoop()` will exit. This often occurs between
143*6777b538SAndroid Build Coastguard Worker    split states, as described above.
144*6777b538SAndroid Build Coastguard Worker
145*6777b538SAndroid Build Coastguard Worker*   The DoLoop mechanism is generally invoked in response to a consumer
146*6777b538SAndroid Build Coastguard Worker    calling one of its methods. While the operation that method
147*6777b538SAndroid Build Coastguard Worker    requested is occuring, the state machine stays active, possibly
148*6777b538SAndroid Build Coastguard Worker    over multiple asynchronous operations and state transitions. When
149*6777b538SAndroid Build Coastguard Worker    that operation is complete, the state machine transitions to
150*6777b538SAndroid Build Coastguard Worker    `STATE_NONE` (by a `DoLoop()` callee not setting `next_state_`) or
151*6777b538SAndroid Build Coastguard Worker    explicitly to `STATE_DONE` (indicating that the operation is
152*6777b538SAndroid Build Coastguard Worker    complete *and* the state machine is not amenable to further
153*6777b538SAndroid Build Coastguard Worker    driving). At this point the consumer is notified of the completion
154*6777b538SAndroid Build Coastguard Worker    of the operation (by synchronous return or asynchronous callback).
155*6777b538SAndroid Build Coastguard Worker
156*6777b538SAndroid Build Coastguard Worker    Note that this implies that when `DoLoop()` returns, one of two
157*6777b538SAndroid Build Coastguard Worker    things will be true:
158*6777b538SAndroid Build Coastguard Worker
159*6777b538SAndroid Build Coastguard Worker    * The return value will be `net::ERR_IO_PENDING`, indicating that the
160*6777b538SAndroid Build Coastguard Worker      caller should take no action and instead wait for asynchronous
161*6777b538SAndroid Build Coastguard Worker      notification.
162*6777b538SAndroid Build Coastguard Worker    * The state of the machine will be either `STATE_DONE` or `STATE_NONE`,
163*6777b538SAndroid Build Coastguard Worker      indicating that the operation that first initiated the `DoLoop()` has
164*6777b538SAndroid Build Coastguard Worker      completed.
165*6777b538SAndroid Build Coastguard Worker
166*6777b538SAndroid Build Coastguard Worker    This invariant reflects and enforces the single-threaded (though
167*6777b538SAndroid Build Coastguard Worker    possibly asynchronous) nature of the driven state machine--the
168*6777b538SAndroid Build Coastguard Worker    machine is always executing one requested operation.
169*6777b538SAndroid Build Coastguard Worker
170*6777b538SAndroid Build Coastguard Worker*   `DoLoop()` is called from two places: a) methods exposed to the consumer
171*6777b538SAndroid Build Coastguard Worker    for specific operations (e.g. `ReadHeaders()`), and b) an IO completion
172*6777b538SAndroid Build Coastguard Worker    callbacks called asynchronously by spawned IO operations.
173*6777b538SAndroid Build Coastguard Worker
174*6777b538SAndroid Build Coastguard Worker    In the first case, the return value from `DoLoop()` is returned directly
175*6777b538SAndroid Build Coastguard Worker    to the caller; if the operation completed synchronously, that will
176*6777b538SAndroid Build Coastguard Worker    contain the operation result, and if it completed asynchronously, it
177*6777b538SAndroid Build Coastguard Worker    will be `net::ERR_IO_PENDING`. For example (from
178*6777b538SAndroid Build Coastguard Worker    `HttpStreamParser`, abridged for clarity):
179*6777b538SAndroid Build Coastguard Worker
180*6777b538SAndroid Build Coastguard Worker             int HttpStreamParser::ReadResponseHeaders(
181*6777b538SAndroid Build Coastguard Worker                 CompletionOnceCallback callback) {
182*6777b538SAndroid Build Coastguard Worker               DCHECK(io_state_ == STATE_NONE || io_state_ == STATE_DONE);
183*6777b538SAndroid Build Coastguard Worker               DCHECK(callback_.is_null());
184*6777b538SAndroid Build Coastguard Worker               DCHECK(!callback.is_null());
185*6777b538SAndroid Build Coastguard Worker
186*6777b538SAndroid Build Coastguard Worker               int result = OK;
187*6777b538SAndroid Build Coastguard Worker               io_state_ = STATE_READ_HEADERS;
188*6777b538SAndroid Build Coastguard Worker
189*6777b538SAndroid Build Coastguard Worker               result = DoLoop(result);
190*6777b538SAndroid Build Coastguard Worker
191*6777b538SAndroid Build Coastguard Worker               if (result == ERR_IO_PENDING)
192*6777b538SAndroid Build Coastguard Worker                 callback_ = std::move(callback);
193*6777b538SAndroid Build Coastguard Worker
194*6777b538SAndroid Build Coastguard Worker               return result > 0 ? OK : result;
195*6777b538SAndroid Build Coastguard Worker             }
196*6777b538SAndroid Build Coastguard Worker
197*6777b538SAndroid Build Coastguard Worker    In the second case, the IO completion callback will examine the
198*6777b538SAndroid Build Coastguard Worker    return value from `DoLoop()`. If it is `net::ERR_IO_PENDING`, no
199*6777b538SAndroid Build Coastguard Worker    further action will be taken, and the IO completion callback will be
200*6777b538SAndroid Build Coastguard Worker    called again at some future point. If it is not
201*6777b538SAndroid Build Coastguard Worker    `net::ERR_IO_PENDING`, that is a signal that the operation has
202*6777b538SAndroid Build Coastguard Worker    completed, and the IO completion callback will call the appropriate
203*6777b538SAndroid Build Coastguard Worker    consumer callback to notify the consumer that the operation has
204*6777b538SAndroid Build Coastguard Worker    completed. Note that it is important that this callback be done
205*6777b538SAndroid Build Coastguard Worker    from the IO completion callback and not from `DoLoop()` or a
206*6777b538SAndroid Build Coastguard Worker    `DoLoop()` callee, both to support the sync/async error return
207*6777b538SAndroid Build Coastguard Worker    (DoLoop and its callees don't know the difference) and to avoid
208*6777b538SAndroid Build Coastguard Worker    consumer callbacks deleting the object out from under `DoLoop()`.
209*6777b538SAndroid Build Coastguard Worker    Example:
210*6777b538SAndroid Build Coastguard Worker
211*6777b538SAndroid Build Coastguard Worker             void HttpStreamParser::OnIOComplete(int result) {
212*6777b538SAndroid Build Coastguard Worker               result = DoLoop(result);
213*6777b538SAndroid Build Coastguard Worker
214*6777b538SAndroid Build Coastguard Worker               if (result != ERR_IO_PENDING && !callback_.is_null())
215*6777b538SAndroid Build Coastguard Worker                 std::move(callback_).Run(result);
216*6777b538SAndroid Build Coastguard Worker             }
217*6777b538SAndroid Build Coastguard Worker
218*6777b538SAndroid Build Coastguard Worker*   The DoLoop pattern has no concept of different events arriving for
219*6777b538SAndroid Build Coastguard Worker    a single state; each state, if waiting, is waiting for one
220*6777b538SAndroid Build Coastguard Worker    particular event, and when `DoLoop()` is invoked when the machine is
221*6777b538SAndroid Build Coastguard Worker    in that state, it will handle that event. This reflects the
222*6777b538SAndroid Build Coastguard Worker    single-threaded model for operations spawned by the state machine.
223*6777b538SAndroid Build Coastguard Worker
224*6777b538SAndroid Build Coastguard WorkerPublic class methods generally have very little processing, primarily wrapping
225*6777b538SAndroid Build Coastguard Worker`DoLoop()`. For `DoLoop()` entry this involves setting the `next_state_`
226*6777b538SAndroid Build Coastguard Workervariable, and possibly making copies of arguments into class members. For
227*6777b538SAndroid Build Coastguard Worker`DoLoop()` exit, it involves inspecting the return and passing it back to
228*6777b538SAndroid Build Coastguard Workerthe caller, and in the asynchronous case, saving any passed completion callback
229*6777b538SAndroid Build Coastguard Workerfor executing by a future subsidiary IO completion (see above example).
230*6777b538SAndroid Build Coastguard Worker
231*6777b538SAndroid Build Coastguard WorkerThis idiom allows synchronous and asynchronous logic to be written in
232*6777b538SAndroid Build Coastguard Workerthe same fashion; it's all just state transition handling. For mostly
233*6777b538SAndroid Build Coastguard Workerlinear state diagrams, the handling code can be very easy to
234*6777b538SAndroid Build Coastguard Workercomprehend, as such code is usually written linearly (in different
235*6777b538SAndroid Build Coastguard Workerhandling functions) in the order it's executed.
236*6777b538SAndroid Build Coastguard Worker
237*6777b538SAndroid Build Coastguard WorkerFor examples of this idiom, see
238*6777b538SAndroid Build Coastguard Worker
239*6777b538SAndroid Build Coastguard Worker* [HttpStreamParser::DoLoop](https://source.chromium.org/chromium/chromium/src/+/HEAD:net/http/http_stream_parser.cc).
240*6777b538SAndroid Build Coastguard Worker* [HttpNetworkTransaction::DoLoop](https://source.chromium.org/chromium/chromium/src/+/HEAD:net/http/http_network_transaction.cc)
241*6777b538SAndroid Build Coastguard Worker
242*6777b538SAndroid Build Coastguard Worker[net_error_list.h]: https://chromium.googlesource.com/chromium/src/+/main/net/base/net_error_list.h#1
243