blob: 77effab41d87bd879e129926433c1d6001ee6fab [file] [log] [blame] [view]
# Control Flow
This document describes how control flow works in the netstack.
Control flow in the netstack is event-driven. The netstack runs in a single
thread, which runs the event loop. The event loop blocks waiting for an event.
When an event occurs, control passes to the code responsible for processing that
event. That code is responsible for processing the event in its entirety,
including updating any state and emitting any other events in response to the
incoming event.
There are three types of events in the system:
- Incoming packets from the network
- Incoming requests from local application clients (open new connection, read on
an existing connection, etc)
- Timers firing (a timer fire event may include associated data, the type of the
timer (e.g. "TCP ACK timeout"), etc)
The netstack may emit three types of events:
- Outgoing packets to the network
- Responses to requests from local application clients (return newly-created
connection, return data for a read request on an existing connection, etc)
- Installing timers
Once an event has been fully processed, the code responsible for its processing
returns, and the event loop goes back to blocking for a future event.
Consider the following example of an event being handled:
1. The netstack receives an Ethernet frame from the Ethernet driver. This causes
the event loop to become unblocked.
2. The event loop executes the function responsible for processing incoming
Ethernet frames.
3. This function parses the frame, and discovers that it contains an IPv4
packet. It passes control to the function responsible for processing incoming
IPv4 packets.
4. This function parses the packet, and discovers that it contains a TCP
segment. It passes control to the function responsible for processing
incoming TCP segments.
5. This function parses the segment, and discovers that it is a SYN from a
remote host attempting to initiate a new TCP connection. It finds the
appropriate local TCP listener, and creates a new TCP connection object to
track the new connection. It emits the following events:
- It sends a SYN/ACK segment back to the sender of the SYN segment.
- It sends a message to the local application client which created the TCP
listener to inform it that there's a new connection.
- It installs a timer which will fire if the remote side doesn't respond with
an ACK within a certain period of time.
6. This function is done, so it returns. None of the parent functions (the one
for processing incoming IPv4 packets or the one for processing incoming
Ethernet frames) have any more work to do, so they return as well.
7. The event loop returns to its steady state of waiting for further events to
process.
This control flow design has a number of advantages:
- Since it is entirely single-threaded, there's no need for synchronization
of any common data structures.
- Since each event results in a single function call, the flow of control
within the core of the netstack is easy to follow, and follows normal
function call flow. There's no indirect flow, for example in the form
of scheduling and later executing callbacks or other event processing.
- Since data is never shared between threads, the design is quite
cache-friendly.
- From a development perspective, the simplicity of control flow makes it easy
to iterate and add features without having to think carefully about which
logic is performed on which threads, having to do complex refactors to
reorganize how logic is assigned to threads, etc.
Of course, being single-threaded has its downsides as well. For a discussion of
those, see [`IMPROVEMENTS.md`](./IMPROVEMENTS.md#single-threaded-execution).
While a single-threaded architecture is the rgith one for us during early
development, we will likely move away from it eventually as we focus more on
performance.