Skip to content

[Serve] Reconnect long-poll clients after a controller replacement - #66511

Draft
johntaylor-cell wants to merge 3 commits into
ray-project:masterfrom
johntaylor-cell:serve-longpoll-reconnect
Draft

johntaylor-cell wants to merge 3 commits into
ray-project:masterfrom
johntaylor-cell:serve-longpoll-reconnect

Conversation

@johntaylor-cell

Copy link
Copy Markdown
Contributor

LongPollClient resolves its host actor once, at construction. When the ServeController is replaced rather than restarted in place, the replacement registers under the same name but with a new actor ID, so every client's next poll fails with ActorDiedError. _process_update treats any RayActorError as an intentional shutdown: it clears is_running, logs at DEBUG, and never polls again. All five production consumers (proxy, HAProxy manager, both routers, capacity queue) then stop receiving updates while serve.status() still reports RUNNING, and only restarting each consumer recovers.

Re-resolve the host by name instead of retiring the client. Resolution is bounded by a deadline so an intentional serve.shutdown() still retires clients, and it runs in an executor because a blocking GCS lookup on the Ray callback thread deadlocks that thread.

Fixes #63784

LongPollClient resolves its host actor once, at construction. When the
ServeController is replaced rather than restarted in place, the
replacement registers under the same name but with a new actor ID, so
every client's next poll fails with ActorDiedError. _process_update
treats any RayActorError as an intentional shutdown: it clears
is_running, logs at DEBUG, and never polls again. All five production
consumers (proxy, HAProxy manager, both routers, capacity queue) then
stop receiving updates while serve.status() still reports RUNNING, and
only restarting each consumer recovers.

Re-resolve the host by name instead of retiring the client. Resolution
is bounded by a deadline so an intentional serve.shutdown() still
retires clients, and it runs in an executor because a blocking GCS
lookup on the Ray callback thread deadlocks that thread.

Fixes ray-project#63784

Signed-off-by: john.taylor <john.taylor@anyscale.com>
@johntaylor-cell
johntaylor-cell requested a review from a team as a code owner September 26, 2026 21:33
@johntaylor-cell johntaylor-cell self-assigned this Sep 26, 2026
@johntaylor-cell johntaylor-cell added serve Ray Serve Related Issue go add ONLY when ready to merge, run all tests labels Sep 26, 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 introduces a reconnection mechanism for LongPollClient in Ray Serve, allowing it to re-resolve and reconnect to a replacement host (such as a restarted controller) when the current host dies. It includes new configuration constants, helper functions, and comprehensive unit tests to verify the reconnection behavior. The review feedback points out that the newly introduced _reconnect_task is not cancelled when stop() is called on the client, which could delay shutdown, and suggests updating the stop() method to cancel this task.

Comment thread python/ray/serve/_private/long_poll.py
stop() cleared is_running but left a running reconnect task to notice it
only after its current backoff sleep, up to 8s later. Nothing awaits the
task, so shutdown was not blocked, but the task could outlive the event
loop and log "Task was destroyed but it is pending".

Cancel the task instead. The cancel is scheduled onto the event loop
because stop() is called from long-poll listener callbacks.

Signed-off-by: john.taylor <john.taylor@anyscale.com>
ray.get_actor is wrapped by Rays auto-init hook, so the reconnect loop
would silently ray.init() a fresh local instance inside a process that
had already called ray.shutdown(). In the serve test suites, which cycle
serve.start() and serve.shutdown() in one driver process, that new
instance bound the metrics export port and the following ray.init()
failed with "Perhaps you called ray.init twice by accident?", breaking
premerge across test_metrics, test_standalone_2, test_telemetry and
others.

Retire the client when Ray is gone instead, and never reach for the
resolver once it is.

Signed-off-by: john.taylor <john.taylor@anyscale.com>
@johntaylor-cell
johntaylor-cell marked this pull request as draft September 27, 2026 17:39

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

go add ONLY when ready to merge, run all tests serve Ray Serve Related Issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[RFC][Serve] LongPoll consumer resilience under ServeController restart

1 participant