Skip to content

[core][taskEvents out of GCS][10/n] Fix bugs found after migration - #65247

Open
karticam wants to merge 5 commits into
ray-project:karticam/reroute-ray-timelinefrom
karticam:karticam/fix-task-event-migration-bugs
Open

[core][taskEvents out of GCS][10/n] Fix bugs found after migration #65247
karticam wants to merge 5 commits into
ray-project:karticam/reroute-ray-timelinefrom
karticam:karticam/fix-task-event-migration-bugs

Conversation

@karticam

@karticam karticam commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

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

To test the entire feature, I took changes from PR 2/n to 9/n, changed the flags so that task_event_buffer to GCS is stopped, ray_task_event_recorder is used, task events are sent to task events head via aggregator agent, and state APIs and ray timeline consume task events from task event head.

essentially these flags were configured:
enable_ray_event: true
enable_ray_task_event_recorder: true
enable_task_events_to_dashboard_head: true
enable_core_worker_task_event_to_gcs: false

Some issues in tests were found. This PR resolves those issues, so that premerge tests pass even even after we switch the flags ON default

@karticam
karticam requested a review from a team as a code owner August 6, 2026 03:29
@karticam karticam added core Issues that should be addressed in Ray Core go add ONLY when ready to merge, run all tests labels Aug 6, 2026

@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 migrates task events from GCS to the dashboard head, introducing a new TaskEventsHead subprocess module to store and query task events in memory, and a TaskEventManager to handle background reconciliation against GCS worker and job events. The state API and timeline queries are updated to read from this new dashboard-head store when enabled. Feedback on the changes suggests several robustness improvements: handling missing worker info and wrapping gRPC calls in try-except blocks in TaskEventManager, implementing retry loops with delays for pubsub subscriptions to avoid CPU-burning loops, using an insertion-ordered dict for FIFO eviction of dropped task attempts in TaskEventStorage, and wrapping the entire request handler in TaskEventsHead to prevent unhandled 500 errors.

Comment thread python/ray/dashboard/modules/task_events/task_event_manager.py
Comment on lines +101 to +113
async def _get_worker_info(
self, worker_id: bytes
) -> Optional[gcs_pb2.WorkerTableData]:
if self._worker_info_stub is None:
self._worker_info_stub = gcs_service_pb2_grpc.WorkerInfoGcsServiceStub(
self._gcs_aio_channel
)
reply = await self._worker_info_stub.GetWorkerInfo(
gcs_service_pb2.GetWorkerInfoRequest(worker_id=worker_id)
)
if not reply.HasField("worker_table_data"):
return None
return reply.worker_table_data

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

Wrap the gRPC call to GetWorkerInfo in a try-except block to handle transient network or gRPC errors gracefully. Otherwise, any gRPC exception will propagate and crash the _on_worker_dead task, preventing task failure marking.

Suggested change
async def _get_worker_info(
self, worker_id: bytes
) -> Optional[gcs_pb2.WorkerTableData]:
if self._worker_info_stub is None:
self._worker_info_stub = gcs_service_pb2_grpc.WorkerInfoGcsServiceStub(
self._gcs_aio_channel
)
reply = await self._worker_info_stub.GetWorkerInfo(
gcs_service_pb2.GetWorkerInfoRequest(worker_id=worker_id)
)
if not reply.HasField("worker_table_data"):
return None
return reply.worker_table_data
async def _get_worker_info(
self, worker_id: bytes
) -> Optional[gcs_pb2.WorkerTableData]:
if self._worker_info_stub is None:
self._worker_info_stub = gcs_service_pb2_grpc.WorkerInfoGcsServiceStub(
self._gcs_aio_channel
)
try:
reply = await self._worker_info_stub.GetWorkerInfo(
gcs_service_pb2.GetWorkerInfoRequest(worker_id=worker_id)
)
if not reply.HasField("worker_table_data"):
return None
return reply.worker_table_data
except Exception as e:
logger.warning(f"Failed to get worker info for {worker_id.hex()}: {e}")
return None

Comment thread python/ray/dashboard/modules/task_events/task_event_manager.py
Comment thread python/ray/dashboard/modules/task_events/task_event_manager.py
Comment on lines +75 to +83
def __init__(self):
self._num_profile_events_dropped = 0
self._num_task_attempts_dropped_tracked = 0
self._num_dropped_task_attempts_evicted = 0
self._dropped_task_attempts: Set[TaskAttempt] = set()

def record_task_attempt_dropped(self, task_attempt: TaskAttempt) -> None:
self._dropped_task_attempts.add(task_attempt)
self._num_task_attempts_dropped_tracked = len(self._dropped_task_attempts)

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

Using a set for self._dropped_task_attempts results in arbitrary eviction of tracked dropped attempts when the cap is reached. If a recently dropped attempt is arbitrarily evicted, late events for it could be partially re-stored. Using a dict as an ordered set (since Python 3.7+ dicts preserve insertion order) allows us to perform FIFO eviction, ensuring we keep the most recently dropped attempts tracked.

Suggested change
def __init__(self):
self._num_profile_events_dropped = 0
self._num_task_attempts_dropped_tracked = 0
self._num_dropped_task_attempts_evicted = 0
self._dropped_task_attempts: Set[TaskAttempt] = set()
def record_task_attempt_dropped(self, task_attempt: TaskAttempt) -> None:
self._dropped_task_attempts.add(task_attempt)
self._num_task_attempts_dropped_tracked = len(self._dropped_task_attempts)
def __init__(self):
self._num_profile_events_dropped = 0
self._num_task_attempts_dropped_tracked = 0
self._num_dropped_task_attempts_evicted = 0
self._dropped_task_attempts: Dict[TaskAttempt, None] = {}
def record_task_attempt_dropped(self, task_attempt: TaskAttempt) -> None:
self._dropped_task_attempts[task_attempt] = None
self._num_task_attempts_dropped_tracked = len(self._dropped_task_attempts)

Comment on lines +122 to +131
num_to_evict = min(
len(self._dropped_task_attempts), num_to_evict + int(0.1 * num_to_evict)
)
self._num_task_attempts_dropped_tracked = len(self._dropped_task_attempts)
if num_to_evict == 0:
return
self._num_dropped_task_attempts_evicted += num_to_evict
to_evict = list(self._dropped_task_attempts)[:num_to_evict]
self._dropped_task_attempts.difference_update(to_evict)
self._num_task_attempts_dropped_tracked = len(self._dropped_task_attempts)

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

Evict the oldest dropped task attempts first using FIFO order by leveraging the insertion-ordered dict keys.

Suggested change
num_to_evict = min(
len(self._dropped_task_attempts), num_to_evict + int(0.1 * num_to_evict)
)
self._num_task_attempts_dropped_tracked = len(self._dropped_task_attempts)
if num_to_evict == 0:
return
self._num_dropped_task_attempts_evicted += num_to_evict
to_evict = list(self._dropped_task_attempts)[:num_to_evict]
self._dropped_task_attempts.difference_update(to_evict)
self._num_task_attempts_dropped_tracked = len(self._dropped_task_attempts)
num_to_evict = min(
len(self._dropped_task_attempts), num_to_evict + int(0.1 * num_to_evict)
)
self._num_task_attempts_dropped_tracked = len(self._dropped_task_attempts)
if num_to_evict == 0:
return
self._num_dropped_task_attempts_evicted += num_to_evict
to_evict = list(self._dropped_task_attempts.keys())[:num_to_evict]
for k in to_evict:
del self._dropped_task_attempts[k]
self._num_task_attempts_dropped_tracked = len(self._dropped_task_attempts)

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.

in same parity as GcsTaskManager

Comment thread python/ray/dashboard/modules/task_events/task_event_storage.py
Comment thread python/ray/dashboard/modules/task_events/task_events_head.py
Comment thread python/ray/_private/state.py
Comment thread python/ray/dashboard/modules/task_events/task_event_storage.py
@karticam
karticam changed the base branch from master to karticam/reroute-ray-timeline August 6, 2026 05:39
@karticam
karticam requested a review from edoakes as a code owner August 6, 2026 09:53

@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.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

Reviewed by Cursor Bugbot for commit 73a8272. Configure here.

Comment thread src/ray/protobuf/gcs.proto

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.

Wondering should we add tests to verify the added log task log info conversion?

@karticam karticam Aug 9, 2026

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.

I moved this piece of code to #65123, and added the test there

Commit: 464a0b5

)


def _convert_task_log_info(src, dst) -> None:

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.

nit: the src and dst can be confusing. Better to add some descriptions to talk about the conversion is from where to where.

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.

Wondering is the task_log_info field covered in any of the test state api tests?

# These delays are read by the dashboard-head store from ray._config; set them via
# env vars so the head subprocess picks them up even if _system_config does not
# propagate to the head's ray._config.
monkeypatch.setenv("RAY_gcs_mark_task_failed_on_job_done_delay_ms", "1000")

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.

Nit: if we've already set the env vars through monkeypatch, should we remove the settings to the _system_config?

# These make list_nodes, list_workers, list_actors never return in 20secs
# Pin the read path to GCS so list_tasks stays a delayable GCS
# GetTaskEvents query.
m.setenv("RAY_enable_task_events_to_dashboard_head", "0")

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.

Could be address in a separate PR but wondering is the behavior worth testing with the new setup?

@karticam
karticam force-pushed the karticam/reroute-ray-timeline branch from 031279b to 30afc48 Compare August 7, 2026 21:04
@karticam
karticam force-pushed the karticam/fix-task-event-migration-bugs branch from dc8e355 to b93cf23 Compare August 7, 2026 21:20
@karticam
karticam force-pushed the karticam/reroute-ray-timeline branch from 9387c98 to 20138e3 Compare August 7, 2026 22:59
@karticam
karticam force-pushed the karticam/fix-task-event-migration-bugs branch from b93cf23 to 5551877 Compare August 7, 2026 22:59
@karticam
karticam force-pushed the karticam/reroute-ray-timeline branch from 20138e3 to e477ec8 Compare August 8, 2026 01:08
@karticam
karticam force-pushed the karticam/fix-task-event-migration-bugs branch from 5551877 to 94e50b4 Compare August 8, 2026 01:09
@karticam
karticam force-pushed the karticam/reroute-ray-timeline branch from 8a6583a to f170291 Compare August 8, 2026 07:09
@karticam
karticam force-pushed the karticam/fix-task-event-migration-bugs branch from 94e50b4 to 76a7522 Compare August 8, 2026 07:09
@karticam
karticam force-pushed the karticam/reroute-ray-timeline branch 3 times, most recently from 97f0d17 to f5c37be Compare August 8, 2026 21:50
@karticam
karticam force-pushed the karticam/fix-task-event-migration-bugs branch from 76a7522 to 3c1efde Compare August 8, 2026 22:28
Signed-off-by: Kartica Modi <karticamodi@gmail.com>
…g constant

Signed-off-by: Kartica Modi <karticamodi@gmail.com>
…m the migration

Signed-off-by: Kartica Modi <karticamodi@gmail.com>
…s enabled

Signed-off-by: Kartica Modi <karticamodi@gmail.com>
…s them up

Signed-off-by: Kartica Modi <karticamodi@gmail.com>
@karticam
karticam force-pushed the karticam/fix-task-event-migration-bugs branch from 3c1efde to 7522e44 Compare August 8, 2026 22:30
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 go add ONLY when ready to merge, run all tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants