Skip to content

[serve] Fix a potential UnboundLocalError in `ActorReplicaWrapper.check_stopped()#63339

Merged
abrarsheikh merged 1 commit into
ray-project:masterfrom
chenshi5012:fix/serve-check-stopped-unbound-local-error
May 15, 2026
Merged

[serve] Fix a potential UnboundLocalError in `ActorReplicaWrapper.check_stopped()#63339
abrarsheikh merged 1 commit into
ray-project:masterfrom
chenshi5012:fix/serve-check-stopped-unbound-local-error

Conversation

@chenshi5012

Copy link
Copy Markdown
Contributor

Description

Fix a potential UnboundLocalError in ActorReplicaWrapper.check_stopped() (python/ray/serve/_private/deployment_state.py).

Related issues

none

Additional information

The fix is a single-line defensive initialization. It does not change any behavior in the normal code path — stopped will still be correctly set to True by either the try body or the except ValueError handler when those paths execute successfully.

def check_stopped(self) -> bool:
    """Check if the actor has exited."""
    stopped = False  # Ensure 'stopped' is always defined for finally/return
    try:
        handle = ray.get_actor(self._actor_name, namespace=SERVE_NAMESPACE)
        stopped = check_obj_ref_ready_nowait(self._graceful_shutdown_ref)
        ...
    except ValueError:
        stopped = True
    finally:
        if stopped and self._placement_group is not None:
            ...
    return stopped
@chenshi5012 chenshi5012 requested a review from a team as a code owner May 14, 2026 07:55

@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 initializes the "stopped" variable in the check_stopped method to prevent an UnboundLocalError. The reviewer identified a potential robustness issue where self._graceful_shutdown_ref is not checked for None before being passed to check_obj_ref_ready_nowait, which could cause a TypeError and crash the controller loop.

Comment thread python/ray/serve/_private/deployment_state.py
…k_stopped()

Fix two related issues in ActorReplicaWrapper.check_stopped():

1. The local variable 'stopped' was only assigned inside the try block or
   the except ValueError handler. If any other exception was raised (e.g.,
   TypeError), 'stopped' would be undefined in the finally block and return
   statement, causing an UnboundLocalError that masks the root cause.

2. check_obj_ref_ready_nowait() was called without guarding against
   self._graceful_shutdown_ref being None. If graceful_stop() failed to set
   the reference (e.g., ray.get_actor raised ValueError at that time), the
   subsequent call would pass None to ray.wait(), raising TypeError and
   potentially crashing the controller reconcile loop.

Fix: Initialize 'stopped = False' before the try block, and add a None
check for _graceful_shutdown_ref before calling check_obj_ref_ready_nowait,
consistent with the guard pattern used in check_ready() and
_check_active_health_check().

Signed-off-by: chenshi5012 <chenshi5012@163.com>
@chenshi5012 chenshi5012 force-pushed the fix/serve-check-stopped-unbound-local-error branch from d290304 to ee4fc0b Compare May 14, 2026 08:01
@ray-gardener ray-gardener Bot added serve Ray Serve Related Issue community-contribution Contributed by the community labels May 14, 2026
@abrarsheikh abrarsheikh added the go add ONLY when ready to merge, run all tests label May 14, 2026

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

thank you

@abrarsheikh abrarsheikh merged commit bd99d2a into ray-project:master May 15, 2026
10 checks passed
TruongQuangPhat pushed a commit to cyhapun/ray-fix-issue that referenced this pull request May 27, 2026
…heck_stopped() (ray-project#63339)

## Description
Fix a potential `UnboundLocalError` in
`ActorReplicaWrapper.check_stopped()`
(`python/ray/serve/_private/deployment_state.py`).

## Related issues
none

## Additional information
The fix is a single-line defensive initialization. It does not change
any behavior in the normal code path — `stopped` will still be correctly
set to `True` by either the `try` body or the `except ValueError`
handler when those paths execute successfully.

```python
def check_stopped(self) -> bool:
    """Check if the actor has exited."""
    stopped = False  # Ensure 'stopped' is always defined for finally/return
    try:
        handle = ray.get_actor(self._actor_name, namespace=SERVE_NAMESPACE)
        stopped = check_obj_ref_ready_nowait(self._graceful_shutdown_ref)
        ...
    except ValueError:
        stopped = True
    finally:
        if stopped and self._placement_group is not None:
            ...
    return stopped
```

Signed-off-by: chenshi5012 <chenshi5012@163.com>
Signed-off-by: phattruong <23120318@student.hcmus.edu.vn>
@andrewsykim andrewsykim added the backport-candidate Label to identify PRs that should be considered for backport to older versions. label May 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-candidate Label to identify PRs that should be considered for backport to older versions. community-contribution Contributed by the community go add ONLY when ready to merge, run all tests serve Ray Serve Related Issue

3 participants