Skip to content

[Core] Store an error when the owner reports an object as FREED - #65270

Open
LuciferYang wants to merge 2 commits into
ray-project:masterfrom
LuciferYang:fix-future-resolver-freed
Open

[Core] Store an error when the owner reports an object as FREED#65270
LuciferYang wants to merge 2 commits into
ray-project:masterfrom
LuciferYang:fix-future-resolver-freed

Conversation

@LuciferYang

Copy link
Copy Markdown
Contributor

Description

FutureResolver::ProcessResolvedObject handles three outcomes of a borrower's GetObjectStatus RPC: an unreachable owner stores OWNER_DIED, OUT_OF_SCOPE stores OBJECT_DELETED, and CREATED stores the value. GetObjectStatusReply::ObjectStatus also has FREED, which the owner sends when the object's value was freed while the borrower's reference was still in scope. That status matches no branch, so nothing is put into the memory store and nothing is logged. A ray.get on that reference blocks forever with no error.

No other path fills the store either. ResolveFutureAsync sends one RPC and does not retry. GetObjects only falls through to plasma for entries that already exist carrying IsInPlasmaError, so a missing entry never gets there. The OWNER_DIED put in this function is the only one in the tree, so a later owner death does not help.

The pre-ownership raylet code did handle this, treating FREED alongside OUT_OF_SCOPE: if (!status.ok() || reply.status() == OUT_OF_SCOPE || reply.status() == FREED) { MarkObjectsAsFailed(OBJECT_UNRECONSTRUCTABLE, ...) }. That block was deleted wholesale in #14184 and the FREED case was never ported to FutureResolver.

The fix stores OBJECT_FREED, which is what the sibling branches do for their statuses. That is also the error the owner already stores locally in DeleteImpl, and the one Python maps to ObjectFreedError, so the owner and the borrower raise the same exception for the same event. OBJECT_DELETED (what the OUT_OF_SCOPE sibling uses) would be wrong here, because it maps to ReferenceCountingAssertionError, whose message says "This should not happen"; that would report a legitimate user action as an internal Ray invariant violation.

Related issues

Fixes #65269

Additional information

Adds future_resolver_test.cc; this class had no test file. The FREED case fails against the unfixed code (nothing is stored, GetIfExists returns null) and passes with the fix. I re-verified that by reverting only the production change and re-running. The other two statuses are covered as regression anchors. bazel test //src/ray/core_worker/tests:future_resolver_test passes, including under --config=asan.

Two notes for reviewers. The most familiar trigger, ray.internal.free, is deprecated, but the DeleteObjects RPC handler and SealExisting(pin_object=false) reach the same owner-side freed state and are not. I also left the if/else chain as a chain instead of converting it to a switch on the enum, to keep this to one concern; a switch would stop a future fourth value from being dropped the same way.

I used AI assistance to investigate and draft this change. I reviewed every changed line and ran the build and tests myself.

FutureResolver::ProcessResolvedObject handles three of the four cases a GetObjectStatus reply can carry: an unreachable owner, OUT_OF_SCOPE, and CREATED. GetObjectStatusReply::ObjectStatus also has FREED, which the owner sends when the object's value was freed while our reference was still in scope. That case matches no branch, so nothing is put into the memory store and a ray.get() on the reference blocks forever with no error.

Put an OBJECT_FREED error, mirroring the sibling branches. This is the same error the owner already stores locally when it frees the object, and the one the Python layer maps to ObjectFreedError. The pre-ownership raylet code handled FREED alongside OUT_OF_SCOPE; the branch was dropped when that code was removed in ray-project#14184 and never ported to FutureResolver.

Adds future_resolver_test.cc, which had no test file before. The FREED case fails against the unfixed code (nothing is stored) and passes with the fix; the other two statuses are covered as regression anchors.

Signed-off-by: yangjie01 <yangjie01@baidu.com>
Include the headers the test uses directly instead of relying on test_utils.h to pull them in transitively, and swap the now-unused test_utils dep for the status dep the test actually needs.

Hold the two FakeGauge metrics as members. Passing *std::make_shared<FakeGauge>() left ReferenceCounter holding references to temporaries that died at the end of the mem-initializer. The sibling tests do the same thing, but there is no reason to carry it into a new file.

Stop the io thread in TearDown, matching object_recovery_manager_test. io_context_ is declared first and so destroyed last, which means without this the thread outlives the members whose callbacks it could run.

Signed-off-by: yangjie01 <yangjie01@baidu.com>
@LuciferYang
LuciferYang requested a review from a team as a code owner August 6, 2026 19:53

@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 adds handling for the FREED object status in FutureResolver::ProcessResolvedObject, storing an OBJECT_FREED error in the in-memory store to prevent ray.get() from blocking indefinitely when an object's value is freed while its reference is still in scope. It also introduces a new test suite future_resolver_test to verify this behavior along with other resolution statuses. There are no review comments, so I have no feedback to provide.

@Yicheng-Lu-llll Yicheng-Lu-llll self-assigned this Aug 6, 2026
@ray-gardener ray-gardener Bot added core Issues that should be addressed in Ray Core community-contribution Contributed by the community labels Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community-contribution Contributed by the community core Issues that should be addressed in Ray Core

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FutureResolver drops the FREED object status, so a borrower's ray.get hangs forever

2 participants