diff options
author | Allan Sandfeld Jensen <allan.jensen@theqtcompany.com> | 2015-10-13 13:24:50 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@theqtcompany.com> | 2015-10-14 10:57:25 +0000 |
commit | af3d4809763ef308f08ced947a73b624729ac7ea (patch) | |
tree | 4402b911e30383f6c6dace1e8cf3b8e85355db3a /chromium/net/docs/code-patterns.md | |
parent | 0e8ff63a407fe323e215bb1a2c423c09a4747c8a (diff) | |
download | qtwebengine-chromium-af3d4809763ef308f08ced947a73b624729ac7ea.tar.gz |
BASELINE: Update Chromium to 47.0.2526.14
Also adding in sources needed for spellchecking.
Change-Id: Idd44170fa1616f26315188970a8d5ba7d472b18a
Reviewed-by: Michael BrĂ¼ning <michael.bruning@theqtcompany.com>
Diffstat (limited to 'chromium/net/docs/code-patterns.md')
-rw-r--r-- | chromium/net/docs/code-patterns.md | 240 |
1 files changed, 240 insertions, 0 deletions
diff --git a/chromium/net/docs/code-patterns.md b/chromium/net/docs/code-patterns.md new file mode 100644 index 00000000000..27a37136728 --- /dev/null +++ b/chromium/net/docs/code-patterns.md @@ -0,0 +1,240 @@ +# Chrome Network Stack Common Coding Patterns + +## Combined error and byte count into a single value + +At many places in the network stack, functions return a value that, if +positive, indicate a count of bytes that the the function read or +wrote, and if negative, indicates a network stack error code (see +[net_error_list.h][]). +Zero indicates either `net::OK` or zero bytes read (usually EOF) +depending on the context. This pattern is generally specified by +an `int` return type. + +Many functions also have variables (often named `result` or `rv`) containing +such a value; this is especially common in the [DoLoop](#DoLoop) pattern +described below. + +## Sync/Async Return + +Many network stack routines may return synchronously or +asynchronously. These functions generally return an int as described +above. There are three cases: + +* If the value is positive or zero, that indicates a synchronous + successful return, with a zero return value indicating either zero + bytes/EOF or indicating `net::OK`, depending on context. +* If the value is negative and != `net::ERR_IO_PENDING`, it is an error + code specifying a synchronous failure. +* If the return value is the special value `net::ERR_IO_PENDING`, it + indicates that the routine will complete asynchronously. A reference to + any provided IOBuffer will be retained by the called entity until + completion, to be written into or read from as required. + If there is a callback argument, that callback will be called upon + completion with the return value; if there is no callback argument, it + usually means that some known callback mechanism will be employed. + +## DoLoop + +The DoLoop pattern is used in the network stack to construct simple +state machines. It is used for cases in which processing is basically +single-threaded and could be written in a single function, if that +function could block waiting for input. Generally, initiation of a +state machine is triggered by some method invocation by a class +consumer, and that state machine is driven (possibly across +asynchronous IO initiated by the class) until the operation requested +by the method invocation completes, at which point the state variable is +set to `STATE_NONE` and the consumer notified. + +Cases which do not fit into this single-threaded, single consumer +operation model are generally adapted in some way to fit the model, +either by multiple state machines (e.g. independent state machines for +reading and writing, if each can be initiated while the other is +outstanding) or by storing information across consumer invocations and +returns that can be used to restart the state machine in the proper +state. + +Any class using this pattern will contain an enum listing all states +of that machine, and define a function, `DoLoop()`, to drive that state +machine. If a class has multiple state machines (as above) it will +have multiple methods (e.g. `DoReadLoop()` and `DoWriteLoop()`) to drive +those different machines. + +The characteristics of the DoLoop pattern are: + +* Each state has a corresponding function which is called by `DoLoop()` + for handling when the state machine is in that state. Generally the + states are named STATE`_<`STATE_NAME`>` (upper case separated by + underscores), and the routine is named Do`<`StateName`>` (CamelCase). + For example: + + enum State { + STATE_NONE, + STATE_INIT, + STATE_FOO, + STATE_FOO_COMPLETE, + }; + int DoInit(); + int DoFoo(); + int DoFooComplete(int result); + +* Each state handling function has two basic responsibilities in + addition to state specific handling: Setting the data member + (named `next_state_` or something similar) + to specify the next state, and returning a `net::Error` (or combined + error and byte count, as above). + +* On each `DoLoop()` iteration, the function saves the next state to a local + variable and resets to a default state (`STATE_NONE`), + and then calls the appropriate state handling based on the + original value of the next state. This looks like: + + do { + State state = io_state_; + next_state_ = STATE_NONE; + switch (state) { + case STATE_INIT: + result = DoInit(); + break; + ... + + This pattern is followed primarily to ensure that in the event of + a bug where the next state isn't set, the loop terminates rather + than loops infinitely. It's not a perfect mitigation, but works + well as a defensive measure. + +* If a given state may complete asynchronously (for example, + writing to an underlying transport socket), then there will often + be split states, such as `STATE_WRITE` and + `STATE_WRITE_COMPLETE`. The first state is responsible for + starting/continuing the original operation, while the second state + is responsible for handling completion (e.g. success vs error, + complete vs. incomplete writes), and determining the next state to + transition to. + +* While the return value from each call is propagated through the loop + to the next state, it is expected that for most state transitions the + return value will be `net::OK`, and that an error return will also + set the state to `STATE_NONE` or fail to override the default + assignment to `STATE_DONE` to exit the loop and return that + error to the caller. This is often asserted with a DCHECK, e.g. + + case STATE_FOO: + DCHECK_EQ(result, OK); + result = DoFoo(); + break; + + The exception to this pattern is split states, where an IO + operation has been dispatched, and the second state is handling + the result. In that case, the second state's function takes the + result code: + + case STATE_FOO_COMPLETE: + result = DoFooComplete(result); + break; + +* If the return value from the state handling function is + `net::ERR_IO_PENDING`, that indicates that the function has arranged + for `DoLoop()` to be called at some point in the future, when further + progress can be made on the state transitions. The `next_state_` variable + will have been set to the proper value for handling that incoming + call. In this case, `DoLoop()` will exit. This often occurs between + split states, as described above. + +* The DoLoop mechanism is generally invoked in response to a consumer + calling one of its methods. While the operation that method + requested is occuring, the state machine stays active, possibly + over multiple asynchronous operations and state transitions. When + that operation is complete, the state machine transitions to + `STATE_NONE` (by a `DoLoop()` callee not setting `next_state_`) or + explicitly to `STATE_DONE` (indicating that the operation is + complete *and* the state machine is not amenable to further + driving). At this point the consumer is notified of the completion + of the operation (by synchronous return or asynchronous callback). + + Note that this implies that when `DoLoop()` returns, one of two + things will be true: + + * The return value will be `net::ERR_IO_PENDING`, indicating that the + caller should take no action and instead wait for asynchronous + notification. + * The state of the machine will be either `STATE_DONE` or `STATE_NONE`, + indicating that the operation that first initiated the `DoLoop()` has + completed. + + This invariant reflects and enforces the single-threaded (though + possibly asynchronous) nature of the driven state machine--the + machine is always executing one requested operation. + +* `DoLoop()` is called from two places: a) methods exposed to the consumer + for specific operations (e.g. `ReadHeaders()`), and b) an IO completion + callbacks called asynchronously by spawned IO operations. + + In the first case, the return value from `DoLoop()` is returned directly + to the caller; if the operation completed synchronously, that will + contain the operation result, and if it completed asynchronously, it + will be `net::ERR_IO_PENDING`. For example (from + `HttpStreamParser`, abridged for clarity): + + int HttpStreamParser::ReadResponseHeaders( + const CompletionCallback& callback) { + DCHECK(io_state_ == STATE_NONE || io_state_ == STATE_DONE); + DCHECK(callback_.is_null()); + DCHECK(!callback.is_null()); + + int result = OK; + io_state_ = STATE_READ_HEADERS; + + result = DoLoop(result); + + if (result == ERR_IO_PENDING) + callback_ = callback; + + return result > 0 ? OK : result; + } + + In the second case, the IO completion callback will examine the + return value from `DoLoop()`. If it is `net::ERR_IO_PENDING`, no + further action will be taken, and the IO completion callback will be + called again at some future point. If it is not + `net::ERR_IO_PENDING`, that is a signal that the operation has + completed, and the IO completion callback will call the appropriate + consumer callback to notify the consumer that the operation has + completed. Note that it is important that this callback be done + from the IO completion callback and not from `DoLoop()` or a + `DoLoop()` callee, both to support the sync/async error return + (DoLoop and its callees don't know the difference) and to avoid + consumer callbacks deleting the object out from under `DoLoop()`. + Example: + + void HttpStreamParser::OnIOComplete(int result) { + result = DoLoop(result); + + if (result != ERR_IO_PENDING && !callback_.is_null()) + base::ResetAndReturn(&callback_).Run(result); + } + +* The DoLoop pattern has no concept of different events arriving for + a single state; each state, if waiting, is waiting for one + particular event, and when `DoLoop()` is invoked when the machine is + in that state, it will handle that event. This reflects the + single-threaded model for operations spawned by the state machine. + +Public class methods generally have very little processing, primarily wrapping +`DoLoop()`. For `DoLoop()` entry this involves setting the `next_state_` +variable, and possibly making copies of arguments into class members. For +`DoLoop()` exit, it involves inspecting the return and passing it back to +the caller, and in the asynchronous case, saving any passed completion callback +for executing by a future subsidiary IO completion (see above example). + +This idiom allows synchronous and asynchronous logic to be written in +the same fashion; it's all just state transition handling. For mostly +linear state diagrams, the handling code can be very easy to +comprehend, as such code is usually written linearly (in different +handling functions) in the order it's executed. + +For examples of this idiom, see + +* [HttpStreamParser::DoLoop](https://code.google.com/p/chromium/codesearch#chromium/src/net/http/http_stream_parser.cc&q=HttpStreamParser::DoLoop&sq=package:chromium). +* [HttpNetworkTransaction::DoLoop](https://code.google.com/p/chromium/codesearch#chromium/src/net/http/http_network_transaction.cc&q=HttpNetworkTransaction::DoLoop&sq=package:chromium) + +[net_error_list.h]: https://chromium.googlesource.com/chromium/src/+/master/net/base/net_error_list.h#1 |