Skip to content

fix(scheduler): copy node info into nodeManager instead of storing the caller's - #2504

Merged
hami-robot[bot] merged 1 commit into
Project-HAMi:masterfrom
Lakshya77089:fix/nodemanager-copy-on-write
Aug 11, 2026
Merged

fix(scheduler): copy node info into nodeManager instead of storing the caller's#2504
hami-robot[bot] merged 1 commit into
Project-HAMi:masterfrom
Lakshya77089:fix/nodemanager-copy-on-write

Conversation

@Lakshya77089

@Lakshya77089 Lakshya77089 commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

What type of PR is this?

/kind bug

What this PR does / why we need it:

nodeManager.addNode stored the *device.NodeInfo it was handed, including the
*corev1.Node inside it:

} else {
    m.nodes[nodeID] = nodeInfo
}

and on the update path assigned the caller's slices and node straight in:

m.nodes[nodeID].Devices[vendor] = nodeInfo.Devices[vendor]
m.nodes[nodeID].Node = nodeInfo.Node

The only caller is RegisterFromNodeAnnotations, which builds nodeInfo around a
node taken from s.nodeLister.List(...) — an object owned by the shared informer.

That would be harmless if nothing wrote to it, but something does. Bind fetches
the same node out of the same cache, and on the release path cambricon's
ReleaseNodeLock deletes an annotation from it:

delete(n.Annotations, DsmluLockTime)   // pkg/device/cambricon/device.go:178

Meanwhile GetNode and ListNodes deep copy the stored node on the Filter and
Bind goroutines. go test ./pkg/scheduler/ --race reports the read inside
(*Node).DeepCopyInto against that delete.

#2333 fixed the read side of this cache by making GetNode return a deep copy.
The write side kept a reference to the shared object, so the copy it makes can
still run while the object is being modified.

The fix

Copy Node and the device lists when storing, on both the insert and the update
path, reusing device.DeepCopyDeviceInfos which GetNode and ListNodes
already use.

Tests

TestAddNodeCopiesSharedNodeObject drives the annotation delete on one goroutine
while another calls GetNode/ListNodes, which is the shape of the real
interleaving. TestAddNodeStoresDetachedCopy mutates everything the caller still
holds, on both the insert and the update path, and asserts the manager is
unaffected. Both fail on master with DATA RACE, both pass here.

Which issue(s) this PR fixes:

None filed — raising the fix directly.

Special notes for your reviewer:

Worth being explicit about one thing, since it changes how the bug reads: shared
informers replace objects in their store rather than mutating them in place, so
holding a lister pointer is not automatically a race. What makes this one real is
that HAMi mutates the object itself, in ReleaseNodeLock. #2329 is open against
that mutation. If it lands, this race goes quiet, but the manager would still be
holding a reference it does not own, and the next writer brings it back. The two
changes are worth having independently.

addNode now costs a deep copy per node per registration cycle. That is the same
cost GetNode already pays per call, and registration is far less frequent than
scheduling, so it should not be noticeable.

Not validated on real hardware. The change is confined to the scheduler's
in-memory node cache and is covered by unit tests, which CONTRIBUTING allows for
scheduler-scoped changes.

Does this PR introduce a user-facing change?:

NONE

AI assistance disclosure: this change was developed with Claude Code — finding
the race, the fix, and the tests. Flagging the extent up front per CONTRIBUTING.

Summary by CodeRabbit

  • Bug Fixes

    • Improved reliability when registering and updating nodes by preventing external changes to node metadata and device information from unexpectedly altering stored state.
    • Added safe handling for missing node data.
  • Tests

    • Added coverage for concurrent registration and updates, including protection against shared-data mutations.

…e caller's

RegisterFromNodeAnnotations builds nodeInfo around a *corev1.Node taken
from nodeLister.List and hands it to addNode, which stored the pointer.
Those objects belong to the shared informer, and cambricon's
ReleaseNodeLock deletes an annotation from the node Bind fetched out of
the same cache (pkg/device/cambricon/device.go:178).

GetNode and ListNodes were already changed to deep copy on the way out
(Project-HAMi#2333), but the write side kept a reference to the shared object, so
their DeepCopy could run while that delete was in progress. go test
--race reports it on master.

Copy Node and the device lists when storing, on both the insert and the
update path.

Signed-off-by: Lakshya77089 <lakshyasharma7708@gmail.com>
@hami-robot hami-robot Bot added kind/bug Something isn't working dco-signoff: yes labels Aug 9, 2026
@hami-robot hami-robot Bot added the size/L label Aug 9, 2026
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

nodeManager.addNode now stores detached copies of node metadata and device data. New tests cover initial registration, updates, caller mutations, and concurrent access through GetNode and ListNodes.

Changes

Node state detachment

Layer / File(s) Summary
Copy node state during registration
pkg/scheduler/nodes.go
addNode deep-copies node objects and device data for new and existing nodes. It preserves nil node values.
Validate mutation isolation
pkg/scheduler/nodes_test.go
Tests verify that caller mutations do not change stored state and that concurrent reads remain safe.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: ayushsrivastava1818

Poem

A rabbit copied nodes with care,
Detaching each device pair.
Mutations hopped, but stayed outside,
While safe reads crossed the data tide.
“No shared state!” the rabbit cried.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: copying caller-owned node data before storing it in nodeManager.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/scheduler/nodes_test.go`:
- Around line 517-541: Replace the legacy cambricon.com/dsmlu.lock annotation
key with the corresponding hami.io/ key in the nodes_test concurrency setup and
the Cambricon device implementation. Ensure both locations use the same updated
key; if legacy compatibility requires retaining the old key, define and document
that exception at the key’s declaration.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d1519808-0f5f-4937-afb4-32251d75f93b

📥 Commits

Reviewing files that changed from the base of the PR and between 3616313 and a5265fa.

📒 Files selected for processing (2)
  • pkg/scheduler/nodes.go
  • pkg/scheduler/nodes_test.go

Comment thread pkg/scheduler/nodes_test.go
@codecov

codecov Bot commented Aug 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.71429% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
pkg/scheduler/nodes.go 85.71% 1 Missing and 1 partial ⚠️
Flag Coverage Δ
unittests 64.27% <85.71%> (+0.06%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
pkg/scheduler/nodes.go 91.95% <85.71%> (-1.64%) ⬇️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@mesutoezdil

Copy link
Copy Markdown
Contributor

/lgtm

@FouoF

FouoF commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

/lgtm

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

Re-approve to refresh GitHub review:approved search index for Tide.

@hami-robot

hami-robot Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: FouoF, Lakshya77089

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@hami-robot
hami-robot Bot merged commit 51a6b1e into Project-HAMi:master Aug 11, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants