Skip to content

Report language-SDK tasks running while the runtime starts up - #71075

Draft
jason810496 wants to merge 2 commits into
apache:mainfrom
jason810496:fix/coordinator/mark-running-before-startup
Draft

Report language-SDK tasks running while the runtime starts up#71075
jason810496 wants to merge 2 commits into
apache:mainfrom
jason810496:fix/coordinator/mark-running-before-startup

Conversation

@jason810496

@jason810496 jason810496 commented Aug 4, 2026

Copy link
Copy Markdown
Member

Why

The language-SDK subprocess coordinator left the task QUEUED until the runtime had connected back over the comm/logs sockets, so JVM boot, artifact discovery and the whole task_startup_timeout window were charged to [scheduler] task_queued_timeout, and a runtime that never started looked like a task nothing had ever picked up, with its stdout/stderr discarded.

How

Bring the coordinator path in line with the Python path: report RUNNING at the top of execute_task, before any worker-side preparation.

  • The reported pid is the supervisor's own, because the server 409s any heartbeat or second start whose pid differs. execute_task computes it once and threads it into the RUNNING transition, the startup heartbeats, and the supervised process, where it seeds Heartbeater.pid: the one value every later heartbeat reads, so they cannot drift.
  • The preparation window now heartbeats (new _heartbeat_until_monitored): a small thread paces the Heartbeater from Extract supervisor heartbeat logic into a standalone Heartbeater #71102, reusing the monitor loop's send and stop policy, so a slow launch is not reaped by [scheduler] task_instance_heartbeat_timeout. With no process to kill yet, the failure cap is left off and transient failures simply retry; a disowned run stops beating and is terminated by the monitor loop moments later.
  • running_since is threaded from the transition into StartupDetails, so the task sees the start date the server recorded.
  • When the runtime never starts, execute_task reports FAILED (or UP_FOR_RETRY per the run context) and returns a non-zero ExecutionResult, taking the ordinary failed-task path. Warm-shutdown handlers are held across the RUNNING window.
  • Nothing changes on the wire, so no language SDK needs a change.

What

  • SubprocessCoordinator.execute_task: report RUNNING before _build_execute_task_command, heartbeat through the launch, report the terminal state when the runtime never starts.
  • ActivitySubprocess: extract the RUNNING transition into _report_running and accept an optional reported_pid, seeded into Heartbeater.pid for the transition and every heartbeat to read.
  • _accept_connections: raise the new SubprocessStartupError (carrying the runtime's exit code) and write pre-handshake stdout/stderr to the task log instead of discarding it.
  • Document in the Java / Go / TypeScript pages that the startup wait happens with the task already running and that the recorded pid is the supervisor's.

Not addressed here: _ResourceTracker.__exit__ still terminates only the direct Popen, so a launcher that forks the real runtime can orphan it on startup failure. A robust fix needs start_new_session plus a process-group kill and belongs in its own PR; this change reduces the exposure from up to three launch attempts to one.

No newsfragment: the coordinator feature is itself unreleased on main.


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

@boring-cyborg boring-cyborg Bot added area:coordinator Coordinator: The interface to spawn Lang-SDK subprocesses area:task-sdk kind:documentation labels Aug 4, 2026
@jason810496
jason810496 force-pushed the fix/coordinator/mark-running-before-startup branch 2 times, most recently from eb8c17c to bd0b93d Compare August 4, 2026 13:46
@jason810496
jason810496 force-pushed the fix/coordinator/mark-running-before-startup branch 2 times, most recently from ae0f66f to 105bf39 Compare August 4, 2026 16:26
@jason810496 jason810496 self-assigned this Aug 4, 2026
@jason810496
jason810496 force-pushed the fix/coordinator/mark-running-before-startup branch from 105bf39 to bbbcb30 Compare August 4, 2026 16:31
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.
The coordinator path left the task queued until the language runtime had
connected back to the supervisor. Locating artifacts and waiting out
task_startup_timeout were therefore charged to
[scheduler] task_queued_timeout, whose handler revokes and requeues the
task, and none of it was visible in the UI. A runtime that never started
looked like a task that had never been picked up at all, and the stdout
and stderr it produced while failing were discarded instead of written to
the task log -- for a missing main class or a runtime that is not
installed, that output is the whole explanation.

The Python path reports the forked child's pid before handing it any work
and materializes its Dag bundle inside that window, so this brings the two
paths in line. The pid reported here is the supervisor's own because the
server rejects a heartbeat whose pid differs from the one it was told at
the start, and that is what lets the transition happen before the runtime
exists.
@jason810496
jason810496 force-pushed the fix/coordinator/mark-running-before-startup branch from bbbcb30 to 03368a2 Compare August 5, 2026 07:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:coordinator Coordinator: The interface to spawn Lang-SDK subprocesses area:task-sdk kind:documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant