Skip to content

Do not permit UpdateOptions in CANCEL_REQUESTED or RESET_REQUESTED - #11394

Open
dandavison wants to merge 1 commit into
mainfrom
dan/update-options-strict
Open

Do not permit UpdateOptions in CANCEL_REQUESTED or RESET_REQUESTED#11394
dandavison wants to merge 1 commit into
mainfrom
dan/update-options-strict

Conversation

@dandavison

@dandavison dandavison commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

What changed?

Do not permit UpdateOptions in CANCEL_REQUESTED or RESET_REQUESTED

Why?

  • It is unclear whether these transitions should be allowed.
  • Hard to define semantics: if UpdateOptions lands after Reset(restore_original_options) then should the update be silently overridden when honoring the reset on attempt end?
  • We opt to simplify the combinatorial possibilities now and retain the possibility of evolving the API to allow it in the future.

How did you test it?

  • covered by existing tests

Note

Medium Risk
Changes public API semantics for activities with pending cancel/reset; clients that relied on mid-flight option updates will now get FailedPrecondition.

Overview
UpdateActivityExecutionOptions is no longer allowed while an activity is in CANCEL_REQUESTED or RESET_REQUESTED. Those statuses are now treated like other non-updatable states and return FailedPrecondition with the same message pattern as terminal statuses.

This replaces the prior behavior where options could still be updated on a running attempt with a pending cancel, and where updates during RESET_REQUESTED could bump the attempt stamp and re-issue timeout tasks. Standalone activity tests were flipped to expect refusal and to assert timeouts and run state stay unchanged.

Reviewed by Cursor Bugbot for commit b4c607a. Bugbot is set up for automated code reviews on this repo. Configure here.

Assert the refusal of UpdateOptions in CANCEL_REQUESTED and RESET_REQUESTED

Both tests asserted that an update applies in those states.

UpdateWhileCancelRequestedFails now requires FailedPrecondition, an
unchanged heartbeat timeout, and an undisturbed cancellation.

UpdateOptionsPreservesTimeoutsWhileResetRequested existed to pin that
the handler re-emits attempt-scoped timeout tasks for a RESET_REQUESTED
activity, which no longer happens at all. It becomes
UpdateOptionsWhileResetRequestedFails: the refusal, the surviving
StartToCloseTimeout, and the still-pending reset.

go test -count=1 -tags test_dep -run TestStandaloneActivityTestSuite ./tests/
@dandavison
dandavison requested review from a team as code owners August 2, 2026 11:23
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Claude finished @dandavison's task in 4m 7s —— View job


Review complete

  • Read review skill (.claude/skills/review/SKILL.md.github/copilot-instructions.md)
  • Gather PR diff and surrounding context
  • Analyze changes
  • Post inline findings

The core change is sound and narrowly scoped. Four inline comments posted (2 on the test, 2 on activity.go) — all non-blocking: a vacuous assertion, a comment/assertion mismatch, a suggestion to invert the status switch into an explicit allowlist, and a now-stale doc comment on reissueRunningAttemptTimers.

Things I checked and found fine:

  • No other call site validates UpdateActivityExecutionOptions state; frontend.go/validator.go only do request-shape validation, so this is the single choke point.
  • The dedup-before-validation path (activity.go:692) is unaffected: a retry of a request that already succeeded in STARTED still returns the current options rather than a new FailedPrecondition, which is the desired idempotency.
  • reissueRunningAttemptTimers re-emission stays covered by UpdateOptionsPreservesTimeoutsWhilePauseRequested (tests/activity_standalone_test.go:11748), so removing the RESET_REQUESTED variant of that test doesn't drop the behavior from coverage.
  • No unit tests in chasm/lib/activity drive UpdateActivityExecutionOptions from CANCEL_REQUESTED/RESET_REQUESTED, and no docs encode the old permitted-state list.

Not done: go build / go test are outside the allowed tool set in this environment, so I did not compile the branch or run the two modified subtests.
• branch dan/update-options-strict

Comment on lines +11426 to +11427
require.NotEqual(t, 15*time.Second, descResp.GetInfo().GetHeartbeatTimeout().AsDuration(),
"a refused update must not apply")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assertion is vacuous. startAndValidateActivitystartActivityWithType never sets HeartbeatTimeout, so the field is nil and AsDuration() is 0. NotEqual(15s, 0) passes no matter what the handler did — it would also pass if the refused update had somehow written 7 * time.Second. Assert the actual expected value instead:

Suggested change
require.NotEqual(t, 15*time.Second, descResp.GetInfo().GetHeartbeatTimeout().AsDuration(),
"a refused update must not apply")
require.Zero(t, descResp.GetInfo().GetHeartbeatTimeout().AsDuration(),
"a refused update must not apply")

Comment on lines +14545 to +14546
// The refused update leaves the original 1-minute StartToCloseTimeout and the pending reset in
// place, so the activity stays RESET_REQUESTED until the worker yields.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says the activity stays RESET_REQUESTED, but line 14553 asserts PENDING_ACTIVITY_STATE_STARTED. Both are correct — RESET_REQUESTED has no PendingActivityState variant (see activity.go:1724-1727) — but a reader hits an apparent contradiction. Worth stating the mapping:

Suggested change
// The refused update leaves the original 1-minute StartToCloseTimeout and the pending reset in
// place, so the activity stays RESET_REQUESTED until the worker yields.
// The refused update leaves the original 1-minute StartToCloseTimeout and the pending reset in
// place. RESET_REQUESTED has no PendingActivityState variant, so the run state remains STARTED.

Comment on lines +701 to +702
activitypb.ACTIVITY_EXECUTION_STATUS_CANCEL_REQUESTED,
activitypb.ACTIVITY_EXECUTION_STATUS_RESET_REQUESTED,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With these two added, the permitted set is now exactly SCHEDULED, STARTED, PAUSED, PAUSE_REQUESTED — 4 of 12 statuses. Since the point of the PR is to narrow the surface, consider inverting to an explicit allowlist so a newly added status is rejected by default rather than silently permitted by the default: arm:

Suggested change
activitypb.ACTIVITY_EXECUTION_STATUS_CANCEL_REQUESTED,
activitypb.ACTIVITY_EXECUTION_STATUS_RESET_REQUESTED,
switch a.Status {
case activitypb.ACTIVITY_EXECUTION_STATUS_SCHEDULED,
activitypb.ACTIVITY_EXECUTION_STATUS_STARTED,
activitypb.ACTIVITY_EXECUTION_STATUS_PAUSED,
activitypb.ACTIVITY_EXECUTION_STATUS_PAUSE_REQUESTED:
default:
return nil, serviceerror.NewFailedPreconditionf("Cannot update options for activity in state %s", a.Status.String())
}

(the suggestion replaces lines 697-708)

Also, is the PAUSE_REQUESTED / CANCEL_REQUESTED asymmetry intended? Both are "worker still holds the task token" states; the restore-original ambiguity argued in the PR description applies to RESET_REQUESTED but not to either of those. Not blocking — just worth a sentence in the code or the description.

activitypb.ACTIVITY_EXECUTION_STATUS_COMPLETED,
activitypb.ACTIVITY_EXECUTION_STATUS_FAILED,
activitypb.ACTIVITY_EXECUTION_STATUS_CANCEL_REQUESTED,
activitypb.ACTIVITY_EXECUTION_STATUS_RESET_REQUESTED,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-on: reissueRunningAttemptTimers (line 775) is called only from this function, and its doc comment at line 1511-1515 ends with:

No-op unless the activity is in a status where a worker holds the task token (STARTED / CANCEL_REQUESTED / PAUSE_REQUESTED / RESET_REQUESTED).

After this change two of those four statuses can never reach the call site, so the comment now describes states that are unreachable. Suggest trimming it to (STARTED / PAUSE_REQUESTED).

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.

1 participant