Context Propagation
Overview
Remote actors and clustered grains often need more than just the message payload. They also need the context the message was sent with:
Correlation IDs (for log stitching)
Authentication and authorization tokens
Tenant / request / locale information
Other per-request metadata
Inside a single process, this metadata usually lives in context.Context.
As soon as a message crosses a process boundary (HTTP, gRPC, gateway, cluster hop, etc.), that data is lost unless we explicitly propagate it.
ContextPropagator exists to solve that problem in a controlled, pluggable way.
// ContextPropagator defines how Go context values travel across remoting and cluster
// boundaries by injecting them into outbound HTTP headers and extracting them on the
// receiving side (trace IDs, auth tokens, correlation IDs, and similar metadata).
//
// Implementations should be stateless, safe for concurrent use, favor stable header keys,
// and avoid leaking sensitive data unless explicitly required. Validate inputs to guard
// against header injection or oversized header sets. Go-Akt relies on a ContextPropagator
// so that context-derived metadata survives hops to remote actors or cluster peers and
// can be read safely during messages handling via ReceiveContext.Context() or GrainContext.Context().
//
// Error handling:
// - Inject should fail only when headers cannot be written.
// - Extract should return a derived context and report parse issues via the error,
// letting callers choose log-and-continue vs fail-fast policies.
type ContextPropagator interface {
// Inject writes context values into headers for an outgoing request.
// Implementations should not mutate ctx and must be safe for concurrent use.
Inject(ctx context.Context, headers nethttp.Header) error
// Extract reads headers from an incoming request and returns a new context
// containing any propagated values. The returned context should derive from
// the provided ctx to preserve cancellations and deadlines.
Extract(ctx context.Context, headers nethttp.Header) (context.Context, error)
}Purpose
Preserving intent across hops Without a propagator, each remote hop starts with a “fresh” context: deadlines and cancellations are lost, logs lose correlation IDs, and downstream services can’t make auth decisions based on upstream user info.
ContextPropagatorgives GoAkt a framework hook to carry that metadata from inbound HTTP → actors, between cluster nodes, and back out over HTTP, soReceiveContext.Context()andGrainContext.Context()see a single logical request context end-to-end.Decoupling headers from logic Environments encode metadata differently (
X-Request-IDvsX-Correlation-ID, W3Ctraceparentvs B3, different auth schemes). Baking these into GoAkt would lock in tracing/auth strategies and tangle protocol details with application code. Instead, GoAkt depends only on the abstract contract: Inject (“fromcontext.Contextto headers”) and Extract (“from headers to a derivedcontext.Context”). Each deployment supplies its ownContextPropagatorthat matches its header conventions and infrastructure.Safety & observability Propagation can leak secrets, inflate headers, or break traces if misused. Centralising it in
ContextPropagatorgives a single, auditable place to decide which context keys may leave the process, cap header sizes, validate IDs (logging or failing when invalid), and enforce consistent header naming. This is far safer and more observable than ad-hoc header handling scattered across handlers and actors.
Configuration
Defining a ContextPropagator is only the first step.
For it to actually be used, it must be plugged into remoting in two places:
When enabling remoting (via the remote config option)
When sending remote messages (via remoting options)
Guidelines
When writing your own ContextPropagator:
Stateless & concurrent-safe
No mutable fields that change per call
No caches that require locking
If you must cache, use safe concurrency primitives and keep it small
Stable header keys
Choose header names once (e.g.
X-Correlation-ID,traceparent) and keep them stableDocument them so other services can interoperate
Minimal, explicit data set
Only propagate values you explicitly intend to share
Do not blindly dump all context values into headers
Security considerations
Treat auth tokens and user identifiers as sensitive
Only propagate them to trusted peers
Prefer references (IDs) over raw secrets when possible
Validation & limits
Validate header values before trusting them
Consider applying size limits:
Truncate overly long IDs
Ignore overly large headers
Guard against header injection (e.g. reject values with line breaks)
Integration
At a high level, GoAkt integrates ContextPropagator like this:
Inbound HTTP → Actor/Grain
An HTTP handler receives a request with headers
Extractis called to build a derivedcontext.ContextThat context is attached to the
ReceiveContextorGrainContextYour handlers access it via
ReceiveContext.Context()/GrainContext.Context()
Actor/Grain → Outbound HTTP / cluster hop
Before sending a remote message or HTTP call, GoAkt calls
InjectThe context from the actor or grain is encoded into headers
The remote side’s
ContextPropagator.Extractrestores it
The result is a consistent request-context story across the entire system: from the edge, through the actor system runtime, and across cluster peers.
Applicability
You should implement or configure a ContextPropagator if:
You rely heavily on correlation IDs in logs
You enforce auth/tenant rules even on internal calls
You’re integrating Go-Akt into an existing HTTP or gRPC ecosystem with specific header conventions
You’re operating a multi-tenant or security-sensitive cluster where context leakage is a concern
If none of the above apply, you can start with a very basic propagator (or a no-op one) and evolve it as your observability and security needs grow.
Last updated