Workflow waits get CANCELLED "context canceled" when daprd's own runtime shuts down; return UNAVAILABLE instead
In what area(s)?
/area runtime
What version of Dapr?
Reproduced on 1.18.0. The same code is on master (e170747).
Expected Behavior
When daprd shuts down or restarts (rollout, pod restart, hot-reload, a config change that restarts the runtime) while a client is blocked in WaitForInstanceCompletion or WaitForInstanceStart, the call should end with a status that tells the client the sidecar went away and it should retry, such as UNAVAILABLE. The workflow is still running, and a re-issued wait on the new daprd would succeed.
Actual Behavior
The wait fails with CANCELLED and the message context canceled. SDKs treat CANCELLED as final, so the caller sees a failed wait for a workflow that then completes normally.
How it happens:
WaitForInstanceCompletiongoes toWatchWorkflowRuntimeStatus(actors.go#L801). That callsrouter.CallStream(#L824), and the orchestrator actor holds the stream open untilcase <-ctx.Done(): return true, ctx.Err()(stream.go#L131-L132).CallStreamwraps its context withr.withContext(router.go#L165-L170). That registers a cancel function for every in-flight call (router.go#L454-L476).- When the runtime context ends,
router.Runmarks the router closed and calls every registered cancel (router.go#L105-L119). The caller's context is still alive, but the wait returnscontext.Canceled. - grpc-go turns that into
codes.Canceled"context canceled". The API server stops withGracefulStop, so the client receives that status instead of a connection reset.
Call and CallReminder go through the same withContext wrapper, so any in-flight actor call is affected the same way. The workflow wait matters most, because it is held open for minutes or hours.
Steps to Reproduce the Problem
- Run placement, scheduler and daprd 1.18.0 natively, with an in-memory actor state store.
- Register a workflow whose single activity sleeps for 60 s. Schedule it, and call
wait_for_workflow_completion(instance_id)from the Python SDK with no timeout. - About 10 s in, send daprd
SIGTERM.
The client fails with StatusCode.CANCELLED and details context canceled, and daprd logs Daprd shutdown gracefully. Without the SIGTERM, the same wait returns normally after 60 s.
Suggested fix
In the wait paths (WatchWorkflowRuntimeStatus and the gRPC executor's wait handlers), when the router's own context ended but the caller's context didn't, return codes.Unavailable ("runtime shutting down, retry") instead of ctx.Err(). SDKs already retry UNAVAILABLE on these waits. Also consider draining in-flight actor streams before cancelling them on shutdown.
Related
- #8332 (closed): graceful shutdown cancels in-flight requests, in general
- #10428: status watches on a stalled workflow retry with backoff
- #10408, #10307: nearby workflow wait and cancel fixes
- #10337: daprd not recovering after a control-plane restart
Client-side discussion: dapr/python-sdk#1246
Release Note
RELEASE NOTE: FIX Workflow waits (WaitForInstanceCompletion / WaitForInstanceStart) now return UNAVAILABLE instead of CANCELLED when the sidecar shuts down mid-wait, so clients can retry against the restarted sidecar.
Source: dapr/dapr