Crate io_context [−] [src]
This crate defines a Context
, which carries deadlines, cancelations
signals and request scoped values across API boundaries.
Incoming server requests should create a request specific Context
from the
background context
. While outgoing requests to servers should accept a
Context
in there methods to allow for cancelation and deadlines. A chain
of funtions calls for handling a request should propagate the Context
,
optionally adding their own deadlines or cancelation signals. As demostrated
in the example below.
fn main() { // First create our background context. To this context we could add // signal handling, e.g. when the user pressed ctrl-c. let mut ctx = Context::background(); // This signal should be canceled once ctrl-c is pressed. let cancel_signal = ctx.add_cancel_signal(); let ctx = ctx.freeze(); loop { // Create a context for our request. This will copy any deadlines // and cancelation signals from the background context into the // request specific one. // // However adding a deadline or cancelation signal to the client // will not after the background context. let request_ctx = Context::create_child(&ctx); // Read a request. let request = read_request(); // Pass the request context along with the request to the request // handler. let response = handle_request(request_ctx, request).unwrap(); println!("got response: {:?}", response); } } fn handle_request(ctx: Context, request: Request) -> io::Result<Response> { // Check if the context's deadline was exceeded, or if the context was // canceled. if let Some(reason) = ctx.done() { // For convience `DoneReason`, returned by `Context.done`, can be // converted into an `io::Error`. return Err(reason.into()); } // Make request to an external server, passing our request context. make_http_request(ctx, "https://api.example.com".to_owned()) } // An outgoing request should accept a `Context` as first parameter. fn make_http_request(ctx: Context, url: String) -> io::Result<Response> { // Again checking the context is done. if let Some(reason) = ctx.done() { return Err(reason.into()); } // Here any deadlines should be added to the HTTP request, using // `context.deadline` method to retrieve it. // But we'll fake the response here, for the sake of simplicity. Ok(Response) }
Contexts should not stored in structs. Instead functions and methods that
need a context should accept it as first parameter. For more conventions see
the Context convention
documentation. A possible exception for this is a
struct that implements a Future
, which represents the state of that
future.
Usage
In the main
function of the program a background context should be
created, which is used in creating all child (request specific) contexts.
Request deadlines can be added using the add_deadline
and add_timeout
methods defined on Context
. While cancelation signals can be added using
the add_cancel_signal
method. Request scoped values, e.g. a request id,
can be added using the add_value
method. For examples see below and the
documentation of different linked methods.
fn main() { // Create a background context. let mut ctx = Context::background(); // Add cancelation to the context. We can use this to respond to a // kill signal from the user, e.g. when pressing ctrl-c. let cancel_signal = ctx.add_cancel_signal(); // ... Some time later when we received a kill signal. cancel_signal.cancel(); // Now the context and it's children will be canceled. }
In libraries
Libraries should accept contexts in their API, the convention is to accept
it as first parameter in all methods and functions that need it. Do not
store a context in a struct. For more conventions see the Context convention
documentation.
use std::io; use std::time::Duration; struct Connection { /* Some fields. */ } impl Connection { // This executes some long runnning operation. fn execute_operation(&self, ctx: Context, input: &[u8]) -> io::Result<()> { // Do step 1 of the work. work_step_1(input); // Check if the context is done, e.g. if the deadline has exceeded. if let Some(reason) = ctx.done() { return Err(reason.into()); } // Do some more work. work_step_2(); // Check the context again. if let Some(reason) = ctx.done() { rollback(); return Err(reason.into()); } // More work, etc. work_step_3(); // Of course we can have even more preciese control if we pass the // context to our work functions as well, that is omitted in this // example. Ok(()) } } fn main() { let mut ctx = Context::background(); ctx.add_timeout(Duration::from_secs(5)); let ctx = ctx.freeze(); // We create a new child context just for the specific operation. Any // deadlines or cancelation signals added to the parent will be added // to the child context as well. However any deadlines or cancelation // signals added to the child will not affect the parent. let mut child_ctx = Context::create_child(&ctx); child_ctx.add_timeout(Duration::from_secs(1)); // Now we execute the operation, while controlling it's deadline. We // could also add a cancelation signal so we could stop it from another // thread (or future), see `Context.add_cancel_signal`. let connection = Connection{}; connection.execute_operation(child_ctx, b"some input").unwrap(); }
When should a Context be used?
A Context
is mainly useful if an operation is long running. As a rule of
thumb; if an operation is less then 10 milliseconds it is not worth it to
add a Context
to the operation.
Examples of when adding a Context
is worth it:
- Any operation which involve any kind of I/O (network, disk or otherwise).
- Any operation which support cancelation or timeouts already.
- Long looping operations which process input in batches.
The common theme in the example operations above is the fact that they all
could be or are long running. If you, both as a developer and user of your
application, would like more control over these kind of operations the use
of a Context
is a good fit.
Structs
CancelSignal |
A cancelation signal, see |
Context |
A context that carries a deadline, cancelation signals and request scoped values across API boundaries and between processes. |
Enums
DoneReason |
The reason why a context was stopped, see |