Skip to content

Fix recordHeartbeat: swallowed cancel/reset/pause exceptions - #2990

Open
brainlimitexceeded wants to merge 5 commits into
temporalio:mainfrom
brainlimitexceeded:fix/heartbeat-swallowed-exceptions-and-retry
Open

Fix recordHeartbeat: swallowed cancel/reset/pause exceptions#2990
brainlimitexceeded wants to merge 5 commits into
temporalio:mainfrom
brainlimitexceeded:fix/heartbeat-swallowed-exceptions-and-retry

Conversation

@brainlimitexceeded

@brainlimitexceeded brainlimitexceeded commented Aug 6, 2026

Copy link
Copy Markdown

Summary

Closes #2983. Fixes ManualActivityCompletionClientImpl's recordHeartbeat to raise ActivityCanceledException/ActivityResetException/ActivityPausedException instead of an incorrect ActivityCompletionFailureException.

What changed

  • ActivityHeartbeatResponse: New wrapper class to reduce code duplication. Technically a breaking change, but only to internals that users shouldn't be relying on.
  • Updated documentation for ManualActivityCompletionClient.recordHeartbeat
  • Restructured ManualActivityCompletionClientImpl to propagate exceptions correctly
  • Added regression tests in ManualActivityCompletionClientImplTest for each possible thrown exception

…ons and missing retry

Two related bugs in the same method:

Fixes temporalio#2983: recordHeartbeat wrapped the RPC call and the response-flag
checks in a single try block, so its own ActivityCanceledException /
ActivityResetException / ActivityPausedException were caught by the
generic catch (Exception e) and turned into
ActivityCompletionFailureException by processException. Callers could
not tell a cancelled/reset/paused activity apart from a failed RPC
without unwrapping getCause(). The interface also declared
`throws CanceledFailure`, a type the method has never actually thrown
(CanceledFailure and ActivityCompletionException are unrelated
siblings under TemporalException) -- changed to the type it genuinely
throws, ActivityCompletionException.

Fixes temporalio#2984: recordHeartbeat was the only one of this class's four
RPC methods (complete/fail/reportCancellation/recordHeartbeat) that
didn't go through grpcRetryer.retryWithResult(...). A single transient
error (RESOURCE_EXHAUSTED from namespace rate limiting, DEADLINE_EXCEEDED,
UNAVAILABLE) could fail the heartbeat outright, which for async
completion can cost a long-running activity via heartbeat timeout. Now
wrapped in the same retry helper the sibling methods already use.

Both fixes land in the same restructure: the RPC call is now isolated
in its own try/catch (wrapped in retryWithResult), and the
cancel/reset/paused flag checks happen outside that catch so the
correct exception always reaches the caller.
@brainlimitexceeded
brainlimitexceeded requested a review from a team as a code owner August 6, 2026 21:46
@CLAassistant

CLAassistant commented Aug 6, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ dplyukhin
❌ manaagrawal0812
You have signed the CLA already but the status is still pending? Let us recheck it.

@dplyukhin dplyukhin changed the title Fix recordHeartbeat: swallowed cancel/reset/pause exceptions and missing retry Fix recordHeartbeat: swallowed cancel/reset/pause exceptions Aug 24, 2026
@dplyukhin
dplyukhin force-pushed the fix/heartbeat-swallowed-exceptions-and-retry branch from 7720ba0 to cb0373a Compare August 24, 2026 22:43
@dplyukhin

Copy link
Copy Markdown
Contributor

Thanks for the contrib @brainlimitexceeded! I removed the retry part of the PR because that's complicated logic I want to handle separately. Can you please sign the CLA?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Manual completion client's recordHeartbeat swallows cancellation, reset and pause exceptions

4 participants