Skip to content

Add support for updating start_delay via UpdateActivityOptions#10745

Open
fretz12 wants to merge 1 commit into
feature/activity-operator-cmdsfrom
fredtzeng/saa-start-delay-update
Open

Add support for updating start_delay via UpdateActivityOptions#10745
fretz12 wants to merge 1 commit into
feature/activity-operator-cmdsfrom
fredtzeng/saa-start-delay-update

Conversation

@fretz12

@fretz12 fretz12 commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

What changed?

start_delay is now updatable via UpdateActivityOptions for standalone activities. The new value is anchored to the original schedule_time, so users can shorten it (including to 0, which dispatches immediately if the target is already past), extend it, or restore the original via RestoreOriginal. Updates once the activity has left its delay window (first dispatch passed, or no longer SCHEDULED) are rejected with InvalidArgument. The merge, validation, and namespace dynamic-config check from activity creation are reused, so create and update stay consistent.

This also adds the shared infrastructure for the downstream PRs:

  • A persisted first_attempt_started_time on ActivityState, set once on the first SCHEDULED → STARTED and never updated on retries/resets. It is the canonical "has the first dispatch happened" discriminator.
  • A respectStartDelay helper that lifts a candidate dispatch time to scheduleTime + start_delay until first pickup, and no-ops afterward. It drives ActivityDispatchTask re-issuance so an unrelated update in the delay window no longer dispatches early, and gates the RestoreOriginal restore so it never shows a stale value post-dispatch.
  • Timer re-issuance split into reissueRunningAttemptTimers (StartToClose/Heartbeat) and reissueScheduledDispatch (Dispatch/ScheduleToStart).

Also changes a pre-existing behavior where the update path anchored ScheduleToClose to scheduleTime + timeout instead of firstDispatchTime + timeout; it now uses the canonical scheduleToCloseDeadline(), matching TransitionScheduled and hasEnoughTimeForRetry.

Why?

Run-now (cancel-delay / kickstart) is table-stakes in the job queues SAA is replacing (BullMQ, Sidekiq, Cloud Tasks, Asynq) and a GA recommendation. Exposing start_delay through UpdateActivityOptions covers both run-now (set 0) and rescheduling with one primitive, using the same anchored-to-schedule-time semantics as creation. start_delay is a first-attempt parameter, so first_attempt_started_time keeps every re-scheduling path from re-applying or restoring it after dispatch, where it would only mislead. The ScheduleToClose change prevents an unrelated update during the delay window from shrinking the deadline (sometimes into the past) and timing the activity out before it dispatches.

How did you test it?

  • built
  • run locally and tested manually
  • covered by existing tests
  • added new unit test(s)
  • added new functional test(s)

@fretz12 fretz12 marked this pull request as ready for review June 16, 2026 22:31
@fretz12 fretz12 requested review from a team as code owners June 16, 2026 22:31
@fretz12 fretz12 marked this pull request as draft June 17, 2026 15:57
@fretz12 fretz12 force-pushed the fredtzeng/saa-start-delay-update branch from e6ff106 to a73e9e7 Compare June 17, 2026 16:44
@fretz12 fretz12 force-pushed the fredtzeng/saa-start-delay-update branch from a73e9e7 to 2bfa39e Compare June 17, 2026 17:07
@fretz12 fretz12 force-pushed the fredtzeng/saa-start-delay-update branch from 2bfa39e to 8b0c9ab Compare June 18, 2026 20:03
@fretz12 fretz12 force-pushed the fredtzeng/saa-start-delay-update branch from 8b0c9ab to 1dd4571 Compare June 18, 2026 21:23
// respectStartDelay lifts a candidate dispatch time up to scheduleTime + start_delay when the
// activity has not yet been picked up by a worker, so pre-dispatch re-scheduling (unpause, Reset+
// RestoreOriginalOptions, options update) honors start_delay. No-op once dispatched.
func (a *Activity) respectStartDelay(scheduleTime time.Time) time.Time {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

important helper reused in downstream PRs

// reissueScheduledDispatch re-emits the ActivityDispatchTask and ScheduleToStart timeout task for
// a SCHEDULED activity. Retries fire at the retry time; first attempts dispatch now, lifted to
// honor any pending start_delay.
func (a *Activity) reissueScheduledDispatch(ctx chasm.MutableContext, attempt *activitypb.ActivityAttemptState) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Refactoring due to lint complaining code complexity

// paths after stamp bump so the old tasks are invalidated and replaced with the (possibly
// updated) timeouts. No-op unless the activity is in a status where a worker holds the task token
// (STARTED / CANCEL_REQUESTED / PAUSE_REQUESTED / RESET_REQUESTED).
func (a *Activity) reissueRunningAttemptTimers(ctx chasm.MutableContext, attempt *activitypb.ActivityAttemptState) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Refactoring due to lint complaining code complexity

@fretz12 fretz12 marked this pull request as ready for review June 18, 2026 22:55
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