Module netstack3_core::context
source · Expand description
Execution contexts.
This module defines “context” traits, which allow code in this crate to be written agnostic to their execution context.
All of the code in this crate operates in terms of “events”. When an event
occurs (for example, a packet is received, an application makes a request,
or a timer fires), a function is called to handle that event. In response to
that event, the code may wish to emit new events (for example, to send a
packet, to respond to an application request, or to install a new timer).
The traits in this module provide the ability to emit new events. For
example, if, in order to handle some event, we need the ability to install
new timers, then the function to handle that event would take a
TimerContext
parameter, which it could use to install new timers.
Structuring code this way allows us to write code which is agnostic to execution context - a test fake or any number of possible “real-world” implementations of these traits all appear as indistinguishable, opaque trait implementations to our code.
The benefits are deeper than this, though. Large units of code can be
subdivided into smaller units that view each other as “contexts”. For
example, the ARP implementation in the [crate::device::arp
] module defines
the ArpContext
trait, which is an execution context for ARP operations.
It is implemented both by the test fakes in that module, and also by the
Ethernet device implementation in the crate::device::ethernet
module.
This subdivision of code into small units in turn enables modularity. If, for example, the IP code sees transport layer protocols as execution contexts, then customizing which transport layer protocols are supported is just a matter of providing a different implementation of the transport layer context traits (this isn’t what we do today, but we may in the future).
Synchronized vs Non-Synchronized Contexts
Since Netstack3 aspires to be multi-threaded in the future, some resources need to be shared between threads, including resources which are accessed via context traits. Sometimes, this resource sharing has implications for how the context’s behavior is exposed via its API, and thus has implications for how the consuming code needs to interact with that API.
For this reason, some modules require two different contexts - a
“synchronized” context and a “non-synchronized” context. Traits implementing
a synchronized context are named FooSyncContext
while traits implementing
a non-synchronized context are named FooContext
. Note that the
implementation of a non-synchronized context trait may still provide access
to shared resources - the distinction is simply that the consumer doesn’t
need to be aware that that’s what’s happening under the hood. As a result,
this shared access is not assumed by the context trait itself. Its API is
designed just as it would be if exclusive access were assumed. For example,
even though a multi-threaded implementation of TimerContext
would need
to synchronize on a shared set of timers, the TimerContext
trait is
designed as though it were providing a vanilla, unsynchronized container.
When synchronized contexts provide access to state, they do it via a
with
-style API:
trait FooSyncContext<S> {
fn with_state<O, F: FnOnce(&mut S) -> O>(&mut self, f: F) -> O;
}
This style is easy to implement when state is shared and mutated via interior mutability (e.g., using mutexes), and is also easy to implement when state is accessed exclusively (e.g., when writing a test fake). It also makes it clear that a critical section is starting and ending, and thus makes it clear to the programmer that they’re performing a potentially expensive operation, and hopefully encourages them to minimize the duration of the critical section.
Since the with_xxx
method operates on &mut self
, it prevents other
operations on the context from happening concurrently. This prevents
deadlocks which occur as a result of a single mutex being locked while it is
held by the locking thread - in other words, it prevents lock reentrance.
Traits
- A context that stores performance counters.
- A context for emitting events.
- A context that provides access to a monotonic clock.
- A context for receiving frames.
- A context that provides a random number generator (RNG).
- A context for sending frames.
- A context that supports scheduling timers.