dcontext_dactor/propagation.rs
1use dactor::ActorContext;
2use dcontext::ContextSnapshot;
3
4use crate::header::{ContextHeader, ContextSnapshotHeader};
5
6/// Extract the propagated context snapshot from actor message headers.
7///
8/// Checks for a [`ContextSnapshotHeader`] (local hop) first, then falls
9/// back to [`ContextHeader`] (remote wire bytes). Returns `None` if no
10/// propagated context is present.
11///
12/// Useful for advanced scenarios such as spawning sub-tasks that need the
13/// propagated context, or inspecting context values without relying on
14/// the task-local scope.
15pub fn extract_context(ctx: &ActorContext) -> Option<ContextSnapshot> {
16 // Prefer local snapshot (preserves local-only values).
17 if let Some(h) = ctx.headers.get::<ContextSnapshotHeader>() {
18 return Some(h.snapshot.clone());
19 }
20
21 // Fall back to wire bytes.
22 if let Some(h) = ctx.headers.get::<ContextHeader>() {
23 return bytes_to_snapshot(&h.bytes);
24 }
25
26 None
27}
28
29/// Run an async handler body with the propagated dcontext from actor headers.
30///
31/// **Deprecated since dactor 0.3**: The [`ContextInboundInterceptor`](crate::ContextInboundInterceptor)
32/// now implements `wrap_handler` which automatically restores context into the
33/// handler's async task-local scope. This function is no longer needed when
34/// using the interceptor pipeline.
35///
36/// Retained for backward compatibility and for use cases outside the
37/// interceptor pipeline (e.g., manual context restoration in tests or
38/// one-off async blocks).
39///
40/// If no propagated context is found in the headers, the future runs without
41/// any dcontext scope (a no-op passthrough).
42#[deprecated(
43 since = "0.2.0",
44 note = "ContextInboundInterceptor now restores context automatically via wrap_handler. \
45 Use this only for manual context restoration outside the interceptor pipeline."
46)]
47pub async fn with_propagated_context<F, R>(ctx: &ActorContext, f: F) -> R
48where
49 F: std::future::Future<Output = R>,
50{
51 match extract_context(ctx) {
52 Some(snap) => dcontext::async_ctx::with_context(snap, f).await,
53 None => f.await,
54 }
55}
56
57/// Convert serialized wire bytes into a `ContextSnapshot`.
58///
59/// Temporarily deserializes into sync (thread-local) storage, captures a
60/// snapshot, then reverts. This avoids interfering with any active task-local
61/// context.
62pub(crate) fn bytes_to_snapshot(bytes: &[u8]) -> Option<ContextSnapshot> {
63 // Push a temporary scope, deserialize wire values into it,
64 // capture as snapshot, then let guards revert everything.
65 let _outer = dcontext::sync_ctx::enter_scope();
66 let _wire_guard = dcontext::sync_ctx::deserialize_context(bytes).ok()?;
67 Some(dcontext::sync_ctx::snapshot())
68 // _wire_guard drops → pops deserialized scope
69 // _outer drops → pops isolation scope
70}