Skip to content

[core][__on_object_freed__][1/n] Detect the __on_object_freed__ hook on actor classes - #67232

Open
karticam wants to merge 2 commits into
karticam/streaming-generator-consume-oncefrom
karticam/on-object-freed-detect
Open

karticam wants to merge 2 commits into
karticam/streaming-generator-consume-oncefrom
karticam/on-object-freed-detect

Conversation

@karticam

@karticam karticam commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

This is PR 1 of a series of PRs that add the __on_object_freed__ hook: an actor that defines the hook will be told when an object it created is freed. This can be used to implement backpressure policies, for example, by identifying that back pressure can be released since the object on this actor is freed.

The complete design doc is here: https://docs.google.com/document/d/1ZJhgRmwVyKfNCR21CWf6xMcc0B6HJwSPQsfebTRvnnE/edit?tab=t.0#heading=h.7wcod1f9afpg

This PR only detects the hook and records it on the actor's worker. Nothing reads the record yet: a later PR stamps it on the objects the actor creates, and another one calls the hook. The hook is private and experimental for now, like _consume_once.

Changes

  1. The worker records the hook. On the actor creation path in _raylet.pyx, before the actor class constructor runs, the worker looks up __on_object_freed__ on the actor class. If it is there, it calls the new CoreWorker::SetProducerFreedCallback to record that the hook exists and its concurrency group (empty means the default group). Both values are set before any actor method or RPC for the actor runs, so they are read later without a lock.
  2. The hook's signature is checked when the class is decorated. @ray.remote raises a TypeError if the hook isn't a method, is a generator, or can't be called with exactly one argument (it takes the freed object's ID as a hex string). async def, static and class methods are accepted. An async def hook makes the actor an asyncio actor, as any async method does, and a later PR runs it on its concurrency group's event loop.
  3. The hook can't be called through an actor handle. It is left out of the handle's methods, and handle.__on_object_freed__ raises an AttributeError. It stays in the class metadata, so its concurrency group is still checked when the actor is created.

@karticam
karticam added this pull request to stack #66768 October 21, 2026 08:35
@karticam karticam added core Issues that should be addressed in Ray Core go add ONLY when ready to merge, run all tests labels Oct 21, 2026
@karticam karticam changed the title [core][move semantics/oof][9/n] Detect the __on_object_freed__ hook on actor classes [core][on_object_freed][1/n] Detect the __on_object_freed__ hook on actor classes Oct 21, 2026
@karticam karticam changed the title [core][on_object_freed][1/n] Detect the __on_object_freed__ hook on actor classes [core][__on_object_freed__][1/n] Detect the __on_object_freed__ hook on actor classes Oct 21, 2026
@karticam
karticam marked this pull request as ready for review October 22, 2026 00:05
@karticam
karticam requested a review from a team as a code owner October 22, 2026 00:05

@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 and found 2 potential issues.

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 a2d0bca. Configure here.

Comment thread src/ray/core_worker/core_worker.h Resolved
)
def test_valid_hook_signatures_accepted(cls):
ray.remote(cls)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Signature tests belong in unit

Low Severity

💡 This test appears to only test pure Python logic. Consider moving it to tests/unit/.

Additional Locations (1)
Fix in Cursor Fix in Web

Triggered by project rule: Bugbot Rules

Reviewed by Cursor Bugbot for commit a2d0bca. Configure here.

@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 private, experimental __on_object_freed__ hook for Ray actors, which is triggered when an object created by the actor is freed. The changes include hook validation in Python, registration of the callback and its concurrency group with the core worker via Cython, and C++ core worker updates to track this state. The review feedback highlights an issue where @classmethod hooks might fail to resolve concurrency group attributes because they are bound methods; it suggests unwrapping them using __func__ and adding corresponding test coverage.

Comment thread python/ray/_raylet.pyx
Comment on lines +2541 to +2542
hook_concurrency_group = getattr(
on_object_freed_hook, "__ray_concurrency_group__", None) or ""

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

If __on_object_freed__ is defined as a @classmethod, getattr(actor_class, "__on_object_freed__") returns a bound method. In Python, bound methods do not delegate attribute lookup to their underlying functions, so any attributes set by decorators like @ray.method(concurrency_group=...) on the underlying function will not be found directly on the bound method.\n\nTo fix this, we should unwrap the bound method to its underlying function using getattr(on_object_freed_hook, "__func__", on_object_freed_hook) before looking up __ray_concurrency_group__.

            unwrapped_hook = getattr(on_object_freed_hook, "__func__", on_object_freed_hook)\n            hook_concurrency_group = getattr(\n                unwrapped_hook, "__ray_concurrency_group__", None) or ""

Comment on lines +38 to +48
@ray.remote(concurrency_groups={"freed": 1})
class GroupHook:
@ray.method(concurrency_group="freed")
def __on_object_freed__(self, object_id):
pass

def callback_state(self):
return (
ray._private.worker.global_worker.core_worker.get_producer_freed_callback()
)

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

Let's add a test case for a @classmethod hook with a concurrency group to ensure that the concurrency group is correctly detected and registered for class methods as well.

@ray.remote(concurrency_groups={"freed": 1})\nclass GroupHook:\n    @ray.method(concurrency_group="freed")\n    def __on_object_freed__(self, object_id):\n        pass\n\n    def callback_state(self):\n        return (\n            ray._private.worker.global_worker.core_worker.get_producer_freed_callback()\n        )\n\n\n@ray.remote(concurrency_groups={"freed": 1})\nclass GroupClassHook:\n    @classmethod\n    @ray.method(concurrency_group="freed")\n    def __on_object_freed__(cls, object_id):\n        pass\n\n    def callback_state(self):\n        return (\n            ray._private.worker.global_worker.core_worker.get_producer_freed_callback()\n        )

Comment on lines +63 to +72
@pytest.mark.parametrize(
"actor_cls, expected",
[
(NoHook, (False, "")),
(SyncHook, (True, "")),
(AsyncHook, (True, "")),
(GroupHook, (True, "freed")),
(InheritedHook, (True, "")),
],
)

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

Include GroupClassHook in the parameterized test cases to verify that its concurrency group is correctly detected.

@pytest.mark.parametrize(\n    "actor_cls, expected",\n    [\n        (NoHook, (False, "")),\n        (SyncHook, (True, "")),\n        (AsyncHook, (True, "")),\n        (GroupHook, (True, "freed")),\n        (GroupClassHook, (True, "freed")),\n        (InheritedHook, (True, "")),\n    ],\n)

Signed-off-by: kartica.modi <kartica.modi@anyscale.com>
Signed-off-by: kartica.modi <kartica.modi@anyscale.com>
@karticam
karticam force-pushed the karticam/on-object-freed-detect branch from 0140916 to 12f072a Compare November 14, 2026 02:36

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

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