dcontext-dactor 0.2.1

Automatic dcontext propagation through dactor actor messages
Documentation

dcontext-dactor

Automatic dcontext propagation through dactor actor messages.

Crates.io Docs.rs

When actors send messages to each other — locally or across the network — distributed context (request IDs, tenant info, feature flags, etc.) needs to travel with those messages. dcontext-dactor makes this transparent by providing inbound and outbound interceptors that handle serialization, deserialization, and scope restoration automatically.

Quick Start

[dependencies]

dcontext = "0.2"

dcontext-dactor = "0.2"

dactor = "0.3.1"

use dcontext_dactor::{ContextInboundInterceptor, ContextOutboundInterceptor};

// Register interceptors with your dactor runtime
runtime.add_outbound_interceptor(Box::new(ContextOutboundInterceptor::default()));
runtime.add_inbound_interceptor(Box::new(ContextInboundInterceptor::default()));

// That's it! Context now flows automatically between actors.
// Inside any actor handler:
let rid: RequestId = dcontext::get_context("request_id");

How It Works

Two-Stage Pipeline

The crate uses a two-stage pipeline that separates header normalization from context restoration:

Sender                              Receiver
──────                              ────────
OutboundInterceptor                 InboundInterceptor
  ├─ local target?                    Stage 1: on_receive()
  │   └─ attach ContextSnapshot         ├─ wire header? deserialize → snapshot
  └─ remote target?                     └─ normalize to ContextSnapshotHeader
      └─ serialize → ContextHeader    Stage 2: wrap_handler()
                                         └─ wrap future with dcontext::with_context()

Outbound — The ContextOutboundInterceptor captures the current context and attaches it to the message headers. For local targets, it uses a zero-copy snapshot (preserving local-only values). For remote targets, it serializes to wire bytes.

Inbound — The ContextInboundInterceptor works in two stages:

  1. on_receive normalizes incoming headers (deserializes wire bytes into a snapshot)
  2. wrap_handler wraps the handler future with dcontext::with_context(), so context is automatically available inside the handler — no manual restoration needed.

Local vs Remote Propagation

Scenario Header Type Serialization Local-only values
Same process ContextSnapshotHeader None ✅ Preserved
Cross-network ContextHeader Bincode bytes ❌ Excluded

Error Handling

Both interceptors accept an ErrorPolicy:

use dcontext_dactor::{ContextOutboundInterceptor, ContextInboundInterceptor, ErrorPolicy};

// Log warnings and continue (default) — messages are delivered even if
// context serialization/deserialization fails
let outbound = ContextOutboundInterceptor::default();
let inbound = ContextInboundInterceptor::default();

// Reject — message is dropped if context cannot be propagated
let outbound = ContextOutboundInterceptor::new(ErrorPolicy::Reject);
let inbound = ContextInboundInterceptor::new(ErrorPolicy::Reject);

Wire Transport Registration

If your actors communicate over the network, register the context header deserializer with dactor's HeaderRegistry:

use dcontext_dactor::register_context_headers;

let mut header_registry = dactor::HeaderRegistry::new();
register_context_headers(&mut header_registry);
// Pass header_registry to your dactor transport configuration

Manually Extracting Context

If you need the propagated context snapshot for spawning sub-tasks or other manual use, use extract_context:

use dcontext_dactor::extract_context;

async fn my_handler(ctx: &ActorContext, msg: MyMessage) {
    // Get the propagated snapshot (if any)
    if let Some(snapshot) = extract_context(ctx) {
        // Use it to propagate context to a spawned task
        dcontext::spawn_with_context_async(snapshot, async {
            // sub-task has the same context
        }).await;
    }
}

Full Example

See samples/src/bin/dactor_propagation.rs for a complete working example.

API Reference

Type Purpose
ContextOutboundInterceptor Captures context on message send
ContextInboundInterceptor Restores context on message receive (two-stage)
ContextHeader Serialized wire header ("dcontext.wire")
ContextSnapshotHeader Local snapshot header ("dcontext.snapshot")
ErrorPolicy LogAndContinue (default) or Reject
extract_context() Manually extract propagated context
register_context_headers() Register wire deserializer with HeaderRegistry

Related

License

MIT