Skip to content

Extract supervisor heartbeat logic into a standalone Heartbeater - #71102

Draft
jason810496 wants to merge 1 commit into
apache:mainfrom
jason810496:refactor/task-sdk/extract-heartbeater
Draft

Extract supervisor heartbeat logic into a standalone Heartbeater#71102
jason810496 wants to merge 1 commit into
apache:mainfrom
jason810496:refactor/task-sdk/extract-heartbeater

Conversation

@jason810496

@jason810496 jason810496 commented Aug 4, 2026

Copy link
Copy Markdown
Member

Why

A planned follow-up will let subprocess coordinators heartbeat while materializing a Dag bundle, before the task subprocess exists. That requires the heartbeat state to live outside ActivitySubprocess, especially the pid presented to the server: it must stay identical for the whole task instance lifetime, otherwise the server rejects the heartbeat with 409 running_elsewhere and the task is killed. This PR is the purely mechanical foundation with no behavior change.

What

  • Add a Heartbeater class in supervisor.py owning the heartbeat state (_last_successful_heartbeat, _last_heartbeat_attempt, failed_heartbeats) and a fixed pid, with the moved methods:
    • send_heartbeat_if_needed() and _handle_heartbeat_failures() (moved from ActivitySubprocess)
    • send_heartbeat(): the ungated primitive send_heartbeat_if_needed() wraps, for callers that own their own pacing (the coordinator startup thread in the follow-up)
    • compute_max_wait_time() (the select-timeout formula moved from _monitor_subprocess)
    • record_successful_heartbeat() (the task-start call already counts as a beat on the server)
  • Rewire ActivitySubprocess to compose a Heartbeater, injecting its reactions as callbacks:
    • on_server_terminated: terminate the process and record SERVER_TERMINATED
    • on_fatal_failures: kill the process after MAX_FAILED_HEARTBEATS; optional (None disables the cap and retries indefinitely, for callers with no process to kill)
    • _send_heartbeat_if_needed stays as a thin wrapper keeping the overtime guard
  • Constants (HEARTBEAT_TIMEOUT, MIN_HEARTBEAT_INTERVAL, MAX_FAILED_HEARTBEATS) stay module level, so existing config handling and test monkeypatching are unaffected
  • Export Heartbeater in the module __all__
  • Update test_supervisor.py references to the moved attributes; the behavior tests (test_regular_heartbeat, test_no_heartbeat_in_overtime, test_state_conflict_on_heartbeat) pass unchanged

Was generative AI tooling used to co-author this PR?

A follow-up will let subprocess coordinators heartbeat while materializing a
Dag bundle, before the task subprocess exists. That requires the heartbeat
state to live outside ActivitySubprocess — especially the pid presented to
the server, which must stay identical for the task instance's lifetime or
the server rejects the heartbeat as "running elsewhere" and the task is
killed. Reactions to fatal heartbeat outcomes (killing the process,
recording SERVER_TERMINATED) remain in ActivitySubprocess, injected as
callbacks. No behavior change.
@jason810496
jason810496 force-pushed the refactor/task-sdk/extract-heartbeater branch from d0c29c2 to 65fd39e Compare August 5, 2026 07:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant