dcontext-dactor
Automatic dcontext propagation through dactor actor messages.
This crate bridges [dcontext] (distributed context propagation) with
[dactor] (actor framework) by providing interceptors that automatically
carry context across actor message boundaries — no boilerplate needed
in handlers.
How It Works
Context propagation is a two-stage interceptor pipeline:
-
Outbound interceptor (sender side) — [
ContextOutboundInterceptor] captures the current dcontext and attaches it as message headers.- Local targets → [
ContextSnapshotHeader] (preserves local-only values) - Remote targets → [
ContextHeader] (serialized wire bytes)
- Local targets → [
-
Inbound interceptor (receiver side) — [
ContextInboundInterceptor] performs two actions:on_receive: normalizes headers — deserializes wire bytes into a [ContextSnapshotHeader] if needed.wrap_handler: wraps the handler future with [dcontext::with_context], restoring the propagated snapshot into the async task-local scope automatically.
This uses dactor 0.3's wrap_handler feature to wrap the handler future
with a task-local context scope. dcontext::get_context /
dcontext::set_context work transparently inside the handler.
Error Handling
Both interceptors accept an [ErrorPolicy] that controls behavior when
serialization or deserialization fails:
- [
ErrorPolicy::LogAndContinue] (default) — log a warning, deliver the message without context. - [
ErrorPolicy::Reject] — reject the message viaDisposition::Reject.
Local vs. Remote
-
Local (same process): Context is propagated via
ContextSnapshot, preserving local-only values that cannot be serialized. No serialization is performed. -
Remote (cross-process): Context is serialized to bytes via [
dcontext::serialize_context] and transmitted as a wire header. Local-only values are excluded.
Quick Start
use dcontext_dactor::{ContextOutboundInterceptor, ContextInboundInterceptor};
// Register interceptors on your runtime — that's it!
runtime.add_outbound_interceptor(Box::new(ContextOutboundInterceptor::default()));
runtime.add_inbound_interceptor(Box::new(ContextInboundInterceptor::default()));
// Handlers automatically have dcontext available — no boilerplate
#[async_trait]
impl Handler<MyMessage> for MyActor {
async fn handle(&mut self, msg: MyMessage, ctx: &mut ActorContext) -> () {
// dcontext is automatically restored by the interceptor's wrap_handler
let rid: RequestId = dcontext::get_context("request_id");
// ... handle message with context available ...
}
}
Manual Extraction
For advanced use cases (e.g., spawning sub-tasks that need context),
[extract_context] is still available to pull the snapshot from headers.
[with_propagated_context] is retained for backward compatibility but is
no longer needed when using the interceptor pipeline.