Skip to content

Internals ​

Read from the source at commit 9f2dcfd9. Every claim here points at a file and line.

Code map ​

PathResponsibility
cmd/daprdThe sidecar binary; main.go:21 calls app.Run()
cmd/operator, cmd/injector, cmd/sentry, cmd/placement, cmd/schedulerControl-plane binaries
pkg/runtimeThe sidecar core; DaprRuntime holds every subsystem (runtime.go:102)
pkg/runtime/compstoreThe in-memory registry of loaded components (compstore.go:42)
pkg/api/http, pkg/api/grpc, pkg/api/universalPublic app-facing APIs (HTTP, gRPC, shared logic)
pkg/messagingService invocation and the internal gRPC proxy
pkg/resiliencyRetry, circuit-breaker, and timeout policy engine
pkg/actors, pkg/runtime/wfengineActor runtime and workflow engine
pkg/security, pkg/sentrymTLS and SPIFFE-based identity

Core data structures ​

DaprRuntime (pkg/runtime/runtime.go:102) is the object that owns the sidecar's state. It carries the app channels, directMessaging, actors, wfengine, the compStore, resiliency, the security handler sec, and two gRPC servers: grpcAPIServer for the app-facing API and grpcInternalServer for sidecar-to-sidecar traffic (pkg/runtime/runtime.go:139).

ComponentStore (pkg/runtime/compstore/compstore.go:42) is the component registry. Under a single sync.RWMutex it keeps a map[string]... per component type: states, pubSubs, secrets, inputBindings and outputBindings, locks, cryptoProviders, workflowComponents, conversations, and more. The building-block list shows up directly as fields here. Hot reload swaps entries in this store.

InvokeMethodRequest (pkg/messaging/v1/invoke_method_request.go:37) is the internal representation of a service-invocation call. It embeds replayableRequest to handle body replay on retry, and carries a streamingRequest flag (pkg/messaging/v1/invoke_method_request.go:48) that disables buffering when the body cannot be replayed.

A path worth tracing ​

The method-name normalizer sits on the service-invocation edge. directMessaging.Invoke calls it before resolving the destination (pkg/messaging/direct_messaging.go:168):

go
normalized, normErr := method.NormalizeMethod(msg.GetMethod())
if normErr != nil {
    return nil, status.Errorf(codes.InvalidArgument, "invalid method: %v", normErr)
}
msg.Method = normalized

NormalizeMethod (pkg/messaging/method/normalize.go:46) rejects forbidden characters, then resolves traversal with path.Clean:

go
if strings.ContainsAny(method, "#?\x00") {
    return "", fmt.Errorf("method contains forbidden character: %q", method)
}
// Reject control characters (0x01-0x1f and 0x7f DEL).
for i := range method {
    b := method[i]
    if b < 0x20 || b == 0x7f {
        return "", fmt.Errorf("method contains control character at position %d: %q", i, method)
    }
}
// Resolve path traversal sequences.
cleaned := path.Clean(method)

The normalized string is what reaches both the ACL check and the dispatch, so the two never disagree about what method is being called.

Things that surprised me ​

Replay safety is decided per request, not globally. The HTTP handler inspects r.ContentLength and, when it is negative (chunked transfer or absent Content-Length), calls req.SetStreamingRequest() (pkg/api/http/directmessaging.go:148). The flag's comment is explicit that WithReplay becomes a no-op in that state to avoid buffering the entire body into memory (pkg/messaging/v1/invoke_method_request.go:48). The runtime trades retry-ability for memory safety based on the actual shape of each request.

The same handler then guards against retrying a drained body. When a streaming request hits a transport error, it wraps the error with backoff.Permanent so the resiliency policy will not retry with a consumed, empty body (pkg/api/http/directmessaging.go:182):

go
if req.IsStreamingRequest() {
    return rResp, backoff.Permanent(invokeErr)
}

The remote send path defaults to streaming. invokeRemote builds the internal client and calls invokeRemoteStream rather than a unary RPC (pkg/messaging/direct_messaging.go:311), and increments ServiceInvocationRequestSent before the call and ServiceInvocationResponseReceived after. The receiving side has a matching CallLocalStream variant that uses an io.Pipe to forward large bodies in chunks (pkg/api/grpc/daprinternal.go:85).