Skip to content

[RayJob] Don't fail a running Ray job when the submitter exits - #5099

Open
1fanwang wants to merge 1 commit into
ray-project:masterfrom
1fanwang:fix/submitter-timeout-consult-job-status
Open

[RayJob] Don't fail a running Ray job when the submitter exits#5099
1fanwang wants to merge 1 commit into
ray-project:masterfrom
1fanwang:fix/submitter-timeout-consult-job-status

Conversation

@1fanwang

@1fanwang 1fanwang commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Why are these changes needed?

KubeRay treats the submitter finishing as evidence the Ray job is terminal. It isn't. ray job logs --follow, the submitter's last command in K8sJobMode and SidecarMode, returns 0 on any non-abnormal WebSocket close (sdk.py):

elif msg.type == aiohttp.WSMsgType.CLOSED:
    if ws.close_code == aiohttp.WSCloseCode.ABNORMAL_CLOSURE:
        raise RuntimeError(...)
    break                                  # normal close: no error
...
return _log_job_status(client, job_id)     # only logs; never exits non-zero

checkSubmitterFinishedTimeoutAndUpdateStatusIfNeeded then waits 30s, overwrites RUNNING with FAILED, and under shutdownAfterJobFinishes deletes the cluster from under the driver. It also produces the inconsistent status pair in #2314.

Ask the cluster instead of guessing

The timeout can't just be removed: #4091 added it for a status that never resolves after a head restart. Both cases arrive with JobStatus: RUNNING.

A job status is only current while the node running its driver is alive. Ray already reports driver_node_id on the job; this deserializes it and adds an IsNodeAlive check against the dashboard's node list:

if rayJobIsActive(rayJob) {
    alive, err := rayJobDriverIsAlive(ctx, dashboardClient, jobInfo)
    ...
    if alive { return false, nil }   // job is genuinely running; leave it
}

The check moves after the job-info poll, where jobInfo and the client are already available. It compares no timestamps and reads no Pod lifecycle, so it is unaffected by readiness probes flapping, by a sidecar submitter dying, and by whether the head restarted before or after the submitter exited, earlier revisions got each of those wrong, and Bugbot caught them.

Scoped to RUNNING on purpose: Ray assigns driver_node_id when it schedules the job's supervisor, so a PENDING job has none to check against. Those keep today's behavior rather than being guessed at. An unreachable dashboard returns an error and requeues instead of counting as a dead driver, and a jobInfo that has already gone terminal is left to the normal handling.

The e2e test RayJob has exceed SubmitterFinishedTimeout asserted the old behaviour; it is rewritten to assert the job survives, and renamed.

Related issue number

Addresses #4921. Related: #2314.

Labels

  • doc-updates-required
  • breaking-change

Checks

  • I've made sure the tests are passing.
  • Testing Strategy
    • Unit tests
    • Manual tests

Manual test instructions

kind, operator built from this branch, rayproject/ray:2.55.1.

The bug, on unpatched master. Stock submitter, nothing simulated, kill the head's DashboardAgent mid-job:

[02:22:08Z] killed DashboardAgent on the head
[02:22:24Z] submitter=Succeeded/0 | js=RUNNING dep=Running
[02:22:54Z] submitter=Succeeded/0 | js=FAILED  dep=Failed
            reason=JobDeploymentStatusTransitionGracePeriodExceeded

Its own last two lines before exiting 0:

INFO cli.py:91 -- Status for job '...': RUNNING
INFO cli.py:93 -- Status message: Job is currently running.

One run died 47s into a 240s job; kept alive for inspection, that job printed its completion marker and GCS reported success while the CR said Failed.

Healthy cluster, submitter exits 0, patched. The job outlives the grace period and finishes:

[07:09:51Z] js=RUNNING   dep=Running
...
[07:13:32Z] js=RUNNING   dep=Running
[07:13:52Z] js=SUCCEEDED dep=Complete

Driver node gone, patched, still fails. Killing the agent degrades the node, so it stops reporting ALIVE and the timeout fires as before:

[07:07:58Z] killed DashboardAgent
[07:08:29Z] submitter=Succeeded/0 | js=RUNNING dep=Running  headRestarts=0
[07:08:59Z] submitter=Succeeded/0 | js=FAILED  dep=Failed
            reason=JobDeploymentStatusTransitionGracePeriodExceeded

Same for a head restart, the driver's node is absent from the live set afterwards:

old driver node: 3bbbda71075d7929d056881d8921592b969874dd65ea3ee2d2067ca8
ALIVE nodes:    ['cb015fd19db8268abf47d287acf4c588ae1bba9f63bb971e1a06272d']
old driver node still ALIVE? False

Unit tests

=== RUN   TestCheckSubmitterFinishedTimeoutAndUpdateStatusIfNeeded
    --- PASS: .../submitter_still_running
    --- PASS: .../grace_period_not_yet_elapsed
    --- PASS: .../job_RUNNING_on_a_live_driver_node_is_left_alone
    --- PASS: .../job_PENDING_on_a_live_driver_node_is_left_alone
    --- PASS: .../job_RUNNING_but_the_driver_node_is_gone_times_out
    --- PASS: .../job_RUNNING_with_no_driver_node_reported_times_out
    --- PASS: .../job_never_observed_times_out
--- PASS: TestCheckSubmitterFinishedTimeoutAndUpdateStatusIfNeeded (0.00s)

@1fanwang
1fanwang force-pushed the fix/submitter-timeout-consult-job-status branch 2 times, most recently from 7c42080 to 50c57a2 Compare August 7, 2026 01:19
Comment thread ray-operator/controllers/ray/rayjob_controller.go
@1fanwang
1fanwang force-pushed the fix/submitter-timeout-consult-job-status branch from 50c57a2 to 34a7ed6 Compare August 7, 2026 02:25
@1fanwang
1fanwang marked this pull request as draft August 7, 2026 03:21
@1fanwang
1fanwang marked this pull request as ready for review August 7, 2026 05:38
@1fanwang
1fanwang force-pushed the fix/submitter-timeout-consult-job-status branch from 34a7ed6 to d31576d Compare August 7, 2026 05:58
Comment thread ray-operator/controllers/ray/rayjob_controller.go
@1fanwang
1fanwang marked this pull request as draft August 7, 2026 06:39
@1fanwang
1fanwang force-pushed the fix/submitter-timeout-consult-job-status branch from d31576d to 247d6fb Compare August 7, 2026 07:14
@1fanwang
1fanwang marked this pull request as ready for review August 7, 2026 07:15
Comment thread ray-operator/controllers/ray/rayjob_controller.go Outdated
@1fanwang
1fanwang marked this pull request as draft August 7, 2026 07:45
@1fanwang
1fanwang force-pushed the fix/submitter-timeout-consult-job-status branch from 247d6fb to ddf3116 Compare August 7, 2026 08:04
@1fanwang
1fanwang marked this pull request as ready for review August 7, 2026 09:25
Comment thread ray-operator/controllers/ray/rayjob_controller.go
Comment thread ray-operator/controllers/ray/rayjob_controller_unit_test.go Outdated
@1fanwang
1fanwang force-pushed the fix/submitter-timeout-consult-job-status branch from ddf3116 to f01abb6 Compare August 7, 2026 09:47

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit f01abb6. Configure here.

Comment thread ray-operator/controllers/ray/rayjob_controller.go Outdated
The submitter's exit is treated as evidence that the Ray job should be
terminal, but it isn't. `ray job logs --follow` returns 0 whenever the log
WebSocket closes with a non-abnormal code, so the submitter can finish under
a perfectly healthy job — after printing that the job is still RUNNING. When
that happens KubeRay waits 30s, overwrites the accurate RUNNING with a
fabricated FAILED, and with shutdownAfterJobFinishes deletes the cluster out
from under the driver.

The grace period was added for the opposite problem: after a head restart the
dashboard reports a status that never resolves, so the RayJob would hang.
Both cases reach this code with JobStatus RUNNING, which is why the timeout
cannot simply be removed.

Ask the cluster instead of guessing. A job status is only current while the
node running its driver is alive, so check that node against the dashboard's
node list before giving up. Ray already reports driver_node_id on the job;
this deserializes it and adds an IsNodeAlive call for the check. The check
runs after the job-info poll, where both are available.

This compares no timestamps and reads no Pod lifecycle, so it is unaffected
by readiness probes flapping, by a sidecar submitter dying, and by whether
the head restarted before or after the submitter exited.

Addresses ray-project#4921. Related: ray-project#2314.

Signed-off-by: 1fanwang <1fannnw@gmail.com>
@1fanwang
1fanwang force-pushed the fix/submitter-timeout-consult-job-status branch from f01abb6 to 9f11cf2 Compare August 7, 2026 10:08
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