Skip to content

[cherry-pick][2.58.0][core][taskEvents out of GCS][8/n] Reroute state head to task events head for state APIs. (#65160) - #65304

Open
elliot-barn wants to merge 5 commits into
releases/2.58.0from
elliot-barn/cherry-pick-2.58.0-reroute-state-head-65160
Open

[cherry-pick][2.58.0][core][taskEvents out of GCS][8/n] Reroute state head to task events head for state APIs. (#65160)#65304
elliot-barn wants to merge 5 commits into
releases/2.58.0from
elliot-barn/cherry-pick-2.58.0-reroute-state-head-65160

Conversation

@elliot-barn

Copy link
Copy Markdown
Collaborator

Cherry-pick of #65160 (merge commit 12436ea) into releases/2.58.0.

This PR builds on #65141, so this branch is stacked on the #65141 cherry-pick (#65303) and must merge after it — until then the diff shown here includes both commits. The pick applied cleanly on top of that branch, and the patch-id matches the original merge commit exactly.

The intermediate 7/n (#65158) that this PR builds on needs no separate cherry-pick — it was merged into the #65141 PR branch rather than into master, so its content already arrives via #65303.

Not a duplicate: no existing open PR against releases/2.58.0 contains this change. Cherry-picked with AI assistance (Claude Code); pre-commit hooks ran on the changed files and passed.

🤖 Generated with Claude Code

karticam and others added 5 commits August 7, 2026 01:41
… agent to dashboard head (#65028)

Part of the effort to move task-event observability data out of GCS and
onto the
dashboard head. This PR adds support for moving task events from
aggregator agent to dashboard head. Changes are as follows:

1. Adds a new module `task_events_head` that hosts a POST
`/api/task_events` endpoint to receive task events from the aggregator
agent. For now it just buffers received events in memory. More stuff to
be done in upcoming PRs.
2. Adds a new publisher client `AsyncDashboardHeadPublisherClient` and a
corresponding flag to `PUBLISH_EVENTS_TO_DASHBOARD_HEAD`.
3. The existing publisher `AsyncGCSTaskEventsPublisherClient` is still
kept since some tests depend on it. This will be removed later after the
entire migration completes.
4. The new publisher client is similar to
`AsyncGCSTaskEventsPublisherClient` in that it has the same event
selection to export and has the same proto building, but sends the
request over HTTP POST instead of a gRPC call.
5. Each of the two publishers gets its own dropped-task-attempts
metadata buffer, since`TaskEventsMetadataBuffer.get()` is destructive
and a shared buffer would split the metadata across publishers.
6. The publisher resolves the dashboard head's address lazily from
InternalKV (DASHBOARD_ADDRESS) via `gcs_client`, then makes the POST
request. If the address isn't registered yet the publish is marked
unsuccessful and the publisher retries on its next cycle; once resolved,
the endpoint is cached.

---------

Signed-off-by: Kartica Modi <karticamodi@gmail.com>
(cherry picked from commit 3ca1915)
Signed-off-by: elliot-barn <elliot.barnwell@anyscale.com>
…death and job finished (#65057)

This PR builds on top of: #65028
and therefore has some changes from that PR as well.

GcsTaskManager does task state management based on worker death and job
finished notifications. Since as part of the project to move task events
out of GCS, we will be putting all this logic on dashboard head
(specifically, task_events_head), we need these notifications on
task_events_head as well.

This PR adds `GcsAioWorkerDeltaSubscriber` and `GcsAioJobSubscriber` and
makes `task_events_head` subscribe to GCS for worker delta notifications
and job notifications from GCS.

For workers delta notifications, we get notifications only on worker
deaths. For jobs, any action is taken only when the job is finished.
Right now, we just append them to a list. More logic (similar to what is
being done in `GcsTaskManager`) will be added in subsequent PRs.

---------

Signed-off-by: Kartica Modi <karticamodi@gmail.com>
Co-authored-by: Elliot Barnwell <elliot.barnwell@anyscale.com>
(cherry picked from commit dc13372)
Signed-off-by: elliot-barn <elliot.barnwell@anyscale.com>
…ntation on task events head (#65123)

Part of the effort to move task events out of GCS.
This PR builds on top of #65057

This is the first part of implementing `GcsTaskManager` inside
`task_events_head`
Changes done:
1. `ray_event_converter.py`: the incoming requests are
`AddEventsRequest` but the storage stores `TaskEvents`, and
`dropped_task_attempts`. This layer converts the incoming requests to
storage friendly format.
2. `gc_policy.py`: `FinishedTaskActorTaskGcPolicy`: 3 priority tiers
(finished tasks, actor, others) that decide eviction order of task
events when needed. Mimics the existing policy in `GcsTaskManager`.
3. `task_event_storage.py`: Based on `TaskEventStorage` in
`GcsTaskManager`.
- The entire storage is organized in three tiered lists (actually
implemented using insertion ordered-dicts keyed by taskAttempt in
python). For every task attempt, we hold one task event entry, which
goes to one of the tiers based on whether the task attempt is finished,
is actor task or none of this.
- When more task events for an attempt arrive, they are merged with an
existing entry and moved across tiers if required.
- Task attempts for which events are dropped (either at the source level
i.e. core_worker level, or due to eviction here) are maintained in
`dropped_task_attempts` per job.
- Priority-tiered eviction once over MAX_NUM_TASK_EVENTS (100k),
evicting the oldest in the lowest-priority tier first.
- `dropped_task_attempts` maintained per job which garbage when the job
finishes. If per job list grows past a threshold, its pruned every 5
seconds.
- Tracks the counters (stored / reported / attempts-dropped /
profile-dropped / per-type). These will be emitted in a followup PR.

 Followup PRs:
1. Changing task states based on worker death and job finished
notification. Receiving those notifications is already wired up.
2. Metric emissions.
3. Serving state API from task events head.

---------

Signed-off-by: Kartica Modi <karticamodi@gmail.com>
(cherry picked from commit 471d3cc)
Signed-off-by: elliot-barn <elliot.barnwell@anyscale.com>
…ath and job notifications (#65141)

Part of the effort to move task events out of GCS.
This PR builds on top of #65123
which adds the in-memory store on the task events head.

Changes done:
1. Adds functions to mark task failed if needed and calls it when a
worker is dead or job is finished.
2. When a job update is received, checks if the update is for job
finished. If yes, triggers `_on_job_finished` which calls the function
to mark tasks failed when job ends, and a function to GC dropped
attempts for a finished job. This is done after a 15 second timeout (the
same as existing timeout on GcsTaskManager).
3. When a worker update is received, its guaranteed to a worker death.
However, we don't have the entire worker table information which is used
for observability purposes. To get that, we query GCS one more time, and
then mark apt tasks as failed. Also we mark the tasks failed after 1
second. Therefore, this wait is overlapped wth the GCS trip cost. A
slight caveat is that if we don't get the worker table info due to some
error, we still wait for 1 second before bailing out.

NOTE: This PR also contains changes of
#65158 squash merged into it.

---------

Signed-off-by: Kartica Modi <karticamodi@gmail.com>
(cherry picked from commit 328e6b7)
Signed-off-by: elliot-barn <elliot.barnwell@anyscale.com>
…head for state APIs. (#65160)

Part of the effort to move task events out of GCS.
This PR builds on top of #65158

#65158 adds logic on task events
head to serve queries for task events. This PR adds the logic to route
the task related state APIs (`ray list tasks`) to task events head
rather than GCS.

Changes done:
1. Adds a flag `RAY_task_events_read_from_dashboard_head` which
determines whether state head sends the request to GCS or dashboard head
(specifically task events head).
2. If the request is send to dashboard head, it makes an HTTP client to
task events head. Since both of them are subprocesses on the same node,
they just communicate through unix sockets without the need for any
auth.
3. The HTTP client is made lazily on the first request.

---------

Signed-off-by: Kartica Modi <karticamodi@gmail.com>
(cherry picked from commit 12436ea)
Signed-off-by: elliot-barn <elliot.barnwell@anyscale.com>
@elliot-barn
elliot-barn requested a review from a team as a code owner August 8, 2026 22:49

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request implements the migration of task events from GCS to the dashboard head. It introduces a new TaskEventsHead subprocess module, an in-memory TaskEventStorage with priority-based garbage collection, and a TaskEventManager to reconcile task states against GCS worker and job lifecycle events. Additionally, it adds an HTTP publisher client to send events from the aggregator agent to the dashboard head and updates the State API to query from this new source when enabled. The review feedback highlights a few critical issues: a missing aiohttp import in the publisher client that would cause a NameError on connection failures, potential CPU-exhausting infinite loops in the GCS subscription polling error paths, and a double-counting bug in the dropped task attempts metric.

@@ -1,3 +1,4 @@
import asyncio

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.

high

The module aiohttp is referenced in this file (e.g., aiohttp.ClientConnectionError on line 398) but is never imported. This will lead to a NameError when a connection error or timeout occurs. Please import aiohttp at the top of the file.

Suggested change
import asyncio
import asyncio
import aiohttp

Comment on lines +197 to +198
except Exception:
logger.exception("Failed handling worker-death notifications.")

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.

medium

If subscriber.poll raises an exception repeatedly (e.g., due to a persistent GCS connection issue), this loop will spin infinitely without any delay, leading to 100% CPU exhaustion and log flooding. Adding a short sleep (e.g., await asyncio.sleep(1)) in the except block will prevent this.

Suggested change
except Exception:
logger.exception("Failed handling worker-death notifications.")
except Exception:
logger.exception("Failed handling worker-death notifications.")
await asyncio.sleep(1)

Comment on lines +213 to +214
except Exception:
logger.exception("Failed handling job-finished notifications.")

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.

medium

If subscriber.poll raises an exception repeatedly (e.g., due to a persistent GCS connection issue), this loop will spin infinitely without any delay, leading to 100% CPU exhaustion and log flooding. Adding a short sleep (e.g., await asyncio.sleep(1)) in the except block will prevent this.

Suggested change
except Exception:
logger.exception("Failed handling job-finished notifications.")
except Exception:
logger.exception("Failed handling job-finished notifications.")
await asyncio.sleep(1)

Comment on lines +223 to +225
self._stats[STAT_TOTAL_ATTEMPTS_DROPPED] += 1
if attempt in self._primary_index:
self._remove_task_attempt(attempt)

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.

medium

The metric STAT_TOTAL_ATTEMPTS_DROPPED is incremented here, but if attempt in self._primary_index is true, _remove_task_attempt(attempt) is called, which increments the same metric again. This leads to double-counting of dropped task attempts. We should only increment it here if the attempt is not already in the index.

Suggested change
self._stats[STAT_TOTAL_ATTEMPTS_DROPPED] += 1
if attempt in self._primary_index:
self._remove_task_attempt(attempt)
if attempt in self._primary_index:
self._remove_task_attempt(attempt)
else:
self._stats[STAT_TOTAL_ATTEMPTS_DROPPED] += 1

@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

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 5a90b44. Configure here.

@@ -1,3 +1,4 @@
import asyncio

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unclear PR description

Low Severity

⚠️ This PR needs a clearer title and/or description.

To help reviewers, please ensure your PR includes:

  • Title: A concise summary of the change
  • Description:
    • What problem does this solve?
    • How does this PR solve it?
    • Any relevant context for reviewers such as:
      • Why is the problem important to solve?
      • Why was this approach chosen over others?

See this list of PRs as examples for PRs that have gone above and beyond:

This violates the Clear PR Descriptions and Titles rule: the description covers cherry-pick stacking logistics but does not explain the problem being solved or how the change fixes it.

Fix in Cursor Fix in Web

Triggered by project rule: Bugbot Rules

Reviewed by Cursor Bugbot for commit 5a90b44. Configure here.

@ray-gardener ray-gardener Bot added core Issues that should be addressed in Ray Core release-test release test labels Aug 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core Issues that should be addressed in Ray Core release-test release test

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants