WF-IMPL-077: DaprActivityRuntimeClient.cancel_activity adapter#500
Merged
Conversation
Implement the production CancelActivity HTTP adapter against the
local Dapr sidecar's Service Invocation API. Cancellation is
idempotent end-to-end: 404 (ARM has no record) and 409 (already
terminated) collapse to a silent no-op with an INFO-level
breadcrumb so operators can trace the cancel without it looking
like an error.
Source
------
- New constant CANCEL_ACTIVITY_DAPR_METHOD = 'CancelActivity'.
- Module-level _LOGGER (logging.getLogger(__name__)).
- DaprActivityRuntimeClient.cancel_activity replaces the
NotImplementedError placeholder with an async POST that:
* 200 / 204 -> return.
* 404 / 409 -> INFO log + return (idempotent no-op).
* Other 4xx + 5xx -> raise OutboundRpcStatusError with the
observed status_code so RunController can surface it via
RunControllerError.
* httpx.HTTPError -> raise OutboundRpcTransportError, with the
underlying httpx exception preserved on __cause__.
- No envelope mapping: cancel_activity returns None per the
Protocol surface; callers distinguish success from failure by
whether an exception escapes.
- No Idempotency-Key header: cancellation is idempotent on ARM
via 404/409, so retries are byte-identical without keying.
- Updated DaprActivityRuntimeClient docstring (both methods async).
Tests
-----
17 new tests covering the full status-code matrix:
- 200 / 204 success returns None.
- 404 idempotent no-op + INFO breadcrumb with run_id/step_id extras.
- 409 idempotent no-op + INFO breadcrumb.
- 400/401/403/422 raise OutboundRpcStatusError with status_code.
- 500/502/503/504 raise OutboundRpcStatusError with status_code.
- httpx.ConnectTimeout and ConnectError raise
OutboundRpcTransportError; original exception preserved on
__cause__.
- URL targets /v1.0/invoke/<arm-app-id>/method/CancelActivity.
- Body uses camelCase: {runId, stepId}.
- No Idempotency-Key header is attached.
- Per-call timeout propagates to req.extensions.
Quality
-------
- ruff check / format clean.
- mypy --strict clean (158 source files).
- 1802 passed, 1 pre-existing flake
(tests/test_observability.py::test_module_imports_under_noop_providers).
- activity_runtime.py: 100 % coverage; full-suite 98.97 %.
Closes #488.
Contributor
There was a problem hiding this comment.
Pull request overview
Implements the production DaprActivityRuntimeClient.cancel_activity adapter in workflow-service, calling ARM via the local Dapr sidecar’s Service Invocation API and enforcing the idempotent cancellation contract (404/409 treated as no-ops).
Changes:
- Added
CANCEL_ACTIVITY_DAPR_METHODand implementedDaprActivityRuntimeClient.cancel_activityas an async HTTP POST with the specified status/transport error taxonomy and INFO breadcrumbs for idempotent no-ops. - Expanded the
test_dapr_activity_runtimesuite to cover the cancel status-code matrix, wire shape (URL + camelCase body), timeout propagation, and header invariants (noIdempotency-Keyfor cancel).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/services/workflow-service/src/custos_workflow/clients/activity_runtime.py | Adds CancelActivity method constant + logger and implements the cancel adapter with idempotent 404/409 handling and error mapping. |
| src/services/workflow-service/tests/clients/test_dapr_activity_runtime.py | Adds comprehensive unit tests for cancel_activity behavior, including logging breadcrumbs, wire contract, and error taxonomy. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Rewrite the misleading 'Any other 2xx is unexpected' inline comment in DaprActivityRuntimeClient.cancel_activity to accurately describe the fallthrough behaviour: every status outside the contracted set (200/204 success + 404/409 idempotent no-op) — including 4xx, 5xx, redirects, and any non-200/204 2xx — is surfaced as OutboundRpcStatusError.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #488. Implements the production
DaprActivityRuntimeClient.cancel_activityHTTP adapter against the local Dapr sidecar's Service Invocation API. Cancellation is idempotent end-to-end: 404 (ARM has no record) and 409 (already terminated) collapse to a silent no-op with an INFO-level breadcrumb so operators can trace the cancel without it looking like an error.Changes
Source —
src/services/workflow-service/src/custos_workflow/clients/activity_runtime.pyCANCEL_ACTIVITY_DAPR_METHOD = "CancelActivity"(exported via__all__)._LOGGER = logging.getLogger(__name__)for the cancel-path INFO breadcrumbs.DaprActivityRuntimeClient.cancel_activityreplaces the WF-IMPL-076NotImplementedErrorplaceholder with anasyncPOST that:200/204→ return.404→ INFO log + return (ARM has no record of the step).409→ INFO log + return (ARM reports step already terminated).4xxor5xx→ raiseOutboundRpcStatusErrorwith the observedstatus_codesoRunControllercan surface it viaRunControllerError.httpx.HTTPError→ raiseOutboundRpcTransportError, with the underlyinghttpxexception preserved on__cause__.cancel_activityreturnsNoneper the Protocol surface; callers distinguish success from failure by whether an exception escapes.Idempotency-Keyheader: cancellation is idempotent on ARM via 404/409, so retries are byte-identical without keying. This is asserted in the test suite.DaprActivityRuntimeClientdocstring to reflect that both methods are now async.Tests —
tests/clients/test_dapr_activity_runtime.py17 new tests covering the full status-code matrix:
None.caplog-asserted INFO breadcrumb withrun_id/step_idextras.caplog-asserted INFO breadcrumb.OutboundRpcStatusErrorwithstatus_codeechoed.OutboundRpcStatusErrorwithstatus_codeechoed.httpx.ConnectTimeoutandhttpx.ConnectErrorraiseOutboundRpcTransportError; original exception preserved on__cause__./v1.0/invoke/<arm-app-id>/method/CancelActivity(notScheduleActivity).{"runId": ..., "stepId": ...}.Idempotency-Keyheader is attached (asserted via case-insensitive header set diff).req.extensions["timeout"].Quality
ruff check/ruff format --checkclean.mypy --strictclean (158 source files).tests/test_observability.py::test_module_imports_under_noop_providers).activity_runtime.py: 100 % coverage; full-suite 98.97 %.Design references
design/components/workflow-service/design.md§ Internal RPC outbound — CancelActivity.design/components/activity-runtime-manager/design.md§ Internal RPCs.ActivityRuntimeClient.cancel_activity(idempotency contract).Tracker