Deep EVM #24: Kontextpropagierung in async Rust — Deadlines, Abbruch und Tracing
Engineering Team
Das fehlende Stueck: context.Context fuer Rust
Go hat context.Context fuer das Weiterreichen von Deadlines, Abbruchsignalen und Metadaten ueber API-Grenzen hinweg. Rust hat kein eingebautes Aequivalent. Dieser Artikel baut einen einheitlichen Kontexttyp fuer async Rust mit Deadline-bewussten Operationen, hierarchischem Abbruch und Tracing-Span-Propagierung.
CancellationToken
tokio_util bietet CancellationToken fuer kooperativen Abbruch:
use tokio_util::sync::CancellationToken;
let token = CancellationToken::new();
let child_token = token.child_token();
// Aufgabe mit Abbruchunterstuetzung
tokio::spawn(async move {
tokio::select! {
result = do_work() => {
println!("Arbeit abgeschlossen: {:?}", result);
}
_ = child_token.cancelled() => {
println!("Abgebrochen!");
}
}
});
// Spaeter: Alles abbrechen
token.cancel();
Deadline-bewusste Operationen
use tokio::time::{timeout, Duration};
struct Context {
cancel: CancellationToken,
deadline: Option<Instant>,
span: tracing::Span,
}
impl Context {
fn with_timeout(parent: &Context, duration: Duration) -> Self {
let deadline = Instant::now() + duration;
Self {
cancel: parent.cancel.child_token(),
deadline: Some(deadline),
span: parent.span.clone(),
}
}
async fn run<F, T>(&self, future: F) -> Result<T, ContextError>
where
F: Future<Output = T>,
{
let _guard = self.span.enter();
tokio::select! {
result = future => Ok(result),
_ = self.cancel.cancelled() => Err(ContextError::Cancelled),
_ = self.sleep_until_deadline() => Err(ContextError::DeadlineExceeded),
}
}
}
Tracing-Span-Propagierung
use tracing::{instrument, Span};
#[instrument(parent = &ctx.span, skip(ctx, db))]
async fn process_block(
ctx: &Context,
db: &Database,
block: &Block,
) -> Result<(), Error> {
let span = tracing::info_span!("process_transactions",
count = block.transactions.len());
for tx in &block.transactions {
let tx_ctx = Context::with_timeout(ctx, Duration::from_secs(5));
tx_ctx.run(process_transaction(db, tx)).await??;
}
Ok(())
}
Hierarchischer Abbruch
Das Schoene an CancellationToken: Kind-Token werden automatisch abgebrochen, wenn der Eltern-Token abgebrochen wird:
Root Token
|- Block-Verarbeitung Token
| |- Transaktion 1 Token
| |- Transaktion 2 Token
|- API-Server Token
|- Request 1 Token
|- Request 2 Token
Wenn der Root-Token abgebrochen wird (z.B. SIGTERM), werden alle Kind-Aufgaben sauber heruntergefahren.
Fazit
Kontextpropagierung in async Rust erfordert etwas mehr Aufwand als in Go, aber CancellationToken, tokio::select! und Tracing-Spans bieten alle Bausteine. Der Schluessel: Definieren Sie einen einheitlichen Context-Typ und verwenden Sie ihn konsistent durch Ihre gesamte Anwendung.