Skip to content

[core][move semantics][7/n] Owner frees the previous primary copy after a move - #67158

Open
karticam wants to merge 5 commits into
karticam/consumer-side-edge-casesfrom
karticam/owner-side-flow
Open

karticam wants to merge 5 commits into
karticam/consumer-side-edge-casesfrom
karticam/owner-side-flow

Conversation

@karticam

@karticam karticam commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

This is PR 7 of a series of PRs to implement move semantics for plasma objects in core.

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

After #67093 and #67133, the consumer's node pins a moved object as its new primary copy and reports it to the owner. With this PR the owner frees the previous primary's copy when it handles that report, so a move ends with one copy.

Changes

  1. The owner frees the previous primary's copy. The new ReferenceCounter::UpdateObjectPrimaryMoved records the new primary and returns the previous primary's node. HandleUpdateObjectLocationBatch passes that node to FreeObjectOnNodesAsync, which sends a FreeLocalObjects request to its raylet, and that raylet unpins the copy and deletes it, spill file included. Nothing is freed when:

    • there is no previous primary: recovery is in flight;
    • the previous primary is the new node: the owner handles the same update twice, which happens when the raylet resends a location batch after a lost reply;
    • the new node is already dead: the previous primary was never freed, so it still pins the object and stays the primary.
  2. Recovery skips a copy that is being freed. If the consumer's node dies while the free is on its way to the producer's node, recovery could pin the producer's copy just before the free deletes it. The owner would then record a primary that no longer exists, and the object would be lost without an error. Each Reference now has locations_being_freed, and the new GetObjectLocationsForRecovery returns the locations minus those nodes. Only recovery's lookup uses it; every other reader still sees all locations. A node leaves the set when it reports the copy removed or when it dies, and the set is cleared when the object goes out of scope. The batched free has no per-object reply and fails only when the node is dead, so node death also covers a failed free.

  3. Spill reports of a moved object. For a while both the producer's and the consumer's nodes pin a moved object, and both may spill it, but the owner keeps one spill location. If the producer's node spills the object just before the free reaches it, its spill report can reach the owner after the move. The owner would then record a spill file that the free is about to delete. So:

    • HandleObjectSpilled takes the reporting node, which the owner knows from the batch even for a spill to external storage, and records a moved object's spill only from the node that holds its primary copy. While recovery is in flight, it ignores only a node whose copy is being freed.
    • The primary-moved update is handled before a spill report in the same update, since the consumer's node can spill the object before its update leaves.
    • When the previous primary is freed, its recorded spill is dropped, since the free deletes the spill file too.

    Objects that are never moved are unaffected.

@karticam
karticam added this pull request to stack #66768 October 19, 2026 09:01
@karticam
karticam marked this pull request as ready for review October 20, 2026 23:14
@karticam
karticam requested a review from a team as a code owner October 20, 2026 23:14

@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 implements a mechanism to free the previous primary copy of an object once its primary copy has moved to a consumer node. It introduces tracking of locations_being_freed to prevent recovery from pinning copies that are being deleted, and ensures that object spilling is only recorded from the node holding the primary copy. The review feedback identifies a valid issue where a node might remain in locations_being_freed if an object is moved back to a previous primary node before its removal report is processed, and suggests explicitly erasing the node from locations_being_freed when it becomes the primary copy again.

Comment on lines +995 to +998
const std::optional<NodeID> previous_primary = it->second.pinned_at_node_id_;
UpdateOwnedObjectCounters(object_id, it->second, /*decrement=*/true);
it->second.pinned_at_node_id_ = node_id;
std::optional<NodeID> node_to_free;

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

If an object is moved back to a node that was previously a primary (e.g., in a sequence of moves like A -> B -> A), the node node_id might still reside in locations_being_freed if the removal report from the first move was delayed or lost. If node_id remains in locations_being_freed while it is the active primary, GetObjectLocationsForRecovery will incorrectly filter it out, potentially leaving recovery with no valid locations.\n\nTo prevent this, we should explicitly erase node_id from locations_being_freed when it becomes the primary copy again.

  const std::optional<NodeID> previous_primary = it->second.pinned_at_node_id_;\n  UpdateOwnedObjectCounters(object_id, it->second, /*decrement=*/true);\n  it->second.pinned_at_node_id_ = node_id;\n  it->second.locations_being_freed.erase(node_id);\n  std::optional<NodeID> node_to_free;

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

Stale Bugbot comment from a previous run.

void AddSpilledObjectLocationOwner(const ObjectID &object_id,
const std::string &spilled_url,
const NodeID &spilled_node_id,
const NodeID &reporter_node_id,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing Doxygen on modified function

Low Severity

⚠️ Document functions and classes with Doxygen /** ... */ block comments using @ tags (@brief, and @param/@return where applicable).

Fix in Cursor Fix in Web

Triggered by project rule: Bugbot Rules

Reviewed by Cursor Bugbot for commit 200a5a9. Configure here.

@karticam karticam added core Issues that should be addressed in Ray Core go add ONLY when ready to merge, run all tests unstale A PR that has been marked unstale. It will not get marked stale again if this label is on it. labels Oct 20, 2026
@karticam
karticam force-pushed the karticam/owner-side-flow branch from 58ec095 to c7a2396 Compare October 21, 2026 05:14

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

Stale Bugbot comment from a previous run.

@karticam
karticam force-pushed the karticam/owner-side-flow branch 2 times, most recently from a4558be to ca92ca6 Compare November 3, 2026 03:07
When the consumer's raylet reports that it now pins the primary copy, the
owner frees the copy on the previous primary's node. Nothing is freed while
recovery is in flight, for a repeated update, or when the consumer's node is
already dead, in which case the previous primary stays the primary. The copy
being freed is left out of recovery's locations until its node reports it
removed or dies, so recovery cannot pin it just before the free deletes it.
A spilled previous primary's location is dropped with the free.

Signed-off-by: kartica.modi <kartica.modi@anyscale.com>
Both the producer's and the consumer's nodes pin a moved object for a
while, and both may spill it, but the owner keeps one spill location. A
spill report for a moved object is now recorded only from the node that
holds its primary copy, or, while recovery is in flight, from any node
whose copy is not being freed. The primary-moved update is handled before
a spill report in the same update, since the consumer's node can spill the
object before its update leaves, and the previous primary's recorded spill
is always dropped with its free.

Signed-off-by: kartica.modi <kartica.modi@anyscale.com>
Signed-off-by: kartica.modi <kartica.modi@anyscale.com>
Signed-off-by: kartica.modi <kartica.modi@anyscale.com>
… and move the cross-node test to the large shard

Signed-off-by: kartica.modi <kartica.modi@anyscale.com>
@karticam
karticam force-pushed the karticam/owner-side-flow branch from ca92ca6 to 7bdb8ca Compare November 14, 2026 02:36

@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 2 total unresolved issues (including 1 from previous review).

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

// batch whose reply was lost.
if (previous_primary.has_value() && *previous_primary != node_id) {
node_to_free = previous_primary;
it->second.locations_being_freed.insert(*node_to_free);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Freed spill node stuck forever

Medium Severity

A node enters locations_being_freed on move and leaves only on a later plasma REMOVED, node death, or the object going out of scope. A producer copy that was already spilled has already reported REMOVED, and deleting the spill file does not send another one. That node then stays excluded from GetObjectLocationsForRecovery for the rest of the object's life, so a later copy there cannot be pinned if the new primary is lost.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7bdb8ca. Configure here.

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 unstale A PR that has been marked unstale. It will not get marked stale again if this label is on it.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants