Skip to content

fix: return deep copies in GetScheduledPods to prevent data races - #2164

Closed
Gaurav-205 wants to merge 2 commits into
Project-HAMi:masterfrom
Gaurav-205:fix/scheduled-pods-deep-copy
Closed

fix: return deep copies in GetScheduledPods to prevent data races#2164
Gaurav-205 wants to merge 2 commits into
Project-HAMi:masterfrom
Gaurav-205:fix/scheduled-pods-deep-copy

Conversation

@Gaurav-205

@Gaurav-205 Gaurav-205 commented Jul 28, 2026

Copy link
Copy Markdown

What type of PR is this?

/kind bug

What this PR does / why we need it:

GetScheduledPods returned pointers to PodManager-owned PodInfo values after
releasing its lock. The metrics collector reads the returned device allocations
outside that lock, so concurrent updates could race with collection.

This PR returns a metrics-focused snapshot: it copies PodInfo allocation data
and scalar device fields while retaining the existing Pod pointer for identity
fields. CustomInfo is intentionally omitted because the metrics collector
does not consume it.

Which issue(s) this PR fixes:

Fixes #2163

Special notes for your reviewer:

The snapshot intentionally avoids Pod.DeepCopy() and avoids copying
CustomInfo; the only caller reads Namespace, Name, NodeID, UUID, Usedmem,
and Usedcores.

Does this PR introduce a user-facing change?:

No.

AI assistance disclosure:

I used Codex to help audit the code path and plan validation. I reviewed,
implemented, and verified the final change myself.

Summary by CodeRabbit

  • Bug Fixes
    • Scheduled pod data is now returned as an isolated snapshot, preventing changes to retrieved results from affecting cached scheduling information.
    • Metrics collection no longer reports potentially inaccurate device memory totals based on legacy node-usage scanning.
    • Metric-safe device copies now exclude custom information that should not be exposed in metrics.

@hami-robot
hami-robot Bot requested review from FouoF and lengrongfu July 28, 2026 09:24
@hami-robot

hami-robot Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: Gaurav-205
Once this PR has been reviewed and has the lgtm label, please assign wawa0210 for approval. For more information see the Kubernetes Code Review Process.

The full list of commands accepted by this bot can be found 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 commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Welcome @Gaurav-205! It looks like this is your first PR to Project-HAMi/HAMi 🎉

@hami-robot hami-robot Bot added the size/S label Jul 28, 2026
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

GetScheduledPods() now returns deep-copied PodInfo snapshots, including metric-safe device copies that omit CustomInfo. Tests verify mutation isolation, and legacy device memory lookup code was removed from metrics collection.

Changes

Scheduled pod isolation

Layer / File(s) Summary
Deep-copy scheduled pod snapshots
pkg/device/pods.go, pkg/device/pod_test.go
GetScheduledPods() constructs independent PodInfo snapshots with copied device data, while tests verify top-level and nested mutations do not alter cached state. CustomInfo is omitted from metric-safe copies.
Remove legacy metrics lookup
cmd/scheduler/metrics.go
Legacy metrics collection no longer scans node usage for device total memory or emits the associated debug log.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: lengrongfu

Poem

A rabbit copied pods with care,
So cached fields stay safely there.
Custom info hops out of sight,
Metrics gather clean and light. 🐇

🚥 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 and concisely describes the main change: returning deep copies from GetScheduledPods to avoid races.
Linked Issues check ✅ Passed GetScheduledPods now snapshots PodInfo/device state instead of returning shared cached values, addressing the race described in #2163.
Out of Scope Changes check ✅ Passed The metric cleanup is directly tied to the GetScheduledPods race fix and does not introduce unrelated functionality.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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 requested a review from mesutoezdil July 28, 2026 09:25

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

🧹 Nitpick comments (1)
pkg/device/pod_test.go (1)

556-561: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Cover the embedded Pod copy as well.

The test verifies NodeID and Devices, but not PodInfo.Pod. Mutate a field such as scheduled["uid-1"].Pod.Name and assert the cached pod remains unchanged; otherwise a regression to shallow-copying the embedded corev1.Pod would go undetected.

Suggested assertion
 scheduled["uid-1"].NodeID = "mutated"
 scheduled["uid-1"].Devices["dev"][0][0].UUID = "mutated"
+scheduled["uid-1"].Pod.Name = "mutated"

 inner := pm.pods[k8stypes.UID("uid-1")]
 assert.Equal(t, "node-1", inner.NodeID)
 assert.Equal(t, "GPU-0", inner.Devices["dev"][0][0].UUID)
+assert.Equal(t, "p", inner.Pod.Name)
🤖 Prompt for 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.

In `@pkg/device/pod_test.go` around lines 556 - 561, Extend the copy-isolation
test around scheduled["uid-1"] to mutate a field on its embedded Pod, such as
Pod.Name, and assert the cached entry in pm.pods retains the original Pod.Name
alongside the existing NodeID and Devices checks.
🤖 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.

Nitpick comments:
In `@pkg/device/pod_test.go`:
- Around line 556-561: Extend the copy-isolation test around scheduled["uid-1"]
to mutate a field on its embedded Pod, such as Pod.Name, and assert the cached
entry in pm.pods retains the original Pod.Name alongside the existing NodeID and
Devices checks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0ed79512-4e75-49c1-805e-e1ee675b4fc1

📥 Commits

Reviewing files that changed from the base of the PR and between 37730dd and 928e598.

📒 Files selected for processing (2)
  • pkg/device/pod_test.go
  • pkg/device/pods.go

Signed-off-by: Gaurav-205 <gauravkhandelwal205@gmail.com>
@github-actions github-actions Bot added the kind/bug Something isn't working label Jul 28, 2026

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

Ahh... i mean it looks good to me from a code-read perspective! Returning copied PodInfo values here makes sense because the metrics collector reads this data after the pod manager lock is released.

Small follow-up suggestion: CustomInfo is still only copied at the top level, so nested values could remain shared. That does not seem to block this PR since the metrics path does not use CustomInfo.

Now let's see what the maintainers think!

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

also the description doesnt follow the pr template, no "what type of pr is this?" block or /kind line, and no ai disclosure. if any ai tool was used it has to be disclosed, see https://github.com/Project-HAMi/HAMi/blob/master/CONTRIBUTING.md#ai-assistance-notice

Comment thread pkg/device/pods.go Outdated
podsCopy := make(map[k8stypes.UID]*PodInfo, podCount)
maps.Copy(podsCopy, m.pods)
for uid, pod := range m.pods {
podsCopy[uid] = pod.DeepCopy()

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.

ContainerDevice.DeepCopy just does maps.Copy on CustomInfo map[string]any, and metax stores pod.Annotations in there at sdevice.go:465, so what does the copy share w/ the original on a metax node?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

You are right. ContainerDevice.DeepCopy previously only cloned the outer CustomInfo map, leaving nested maps like Metax's Pod.Annotations shared with the original.

In the latest commit, GetScheduledPods uses DeepCopyForMetrics(), which intentionally sets CustomInfo = nil on snapshot devices because cmd/scheduler/metrics.go only consumes scalar allocation fields (UUID, Type, Usedmem, Usedcores). This completely avoids nested map aliasing without introducing an unsafe or complex generic deep-copy implementation.

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.

You are right. ContainerDevice.DeepCopy previously only cloned the outer CustomInfo map, leaving nested maps like Metax's Pod.Annotations shared with the original.

In the latest commit, GetScheduledPods uses DeepCopyForMetrics(), which intentionally sets CustomInfo = nil on snapshot devices because cmd/scheduler/metrics.go only consumes scalar allocation fields (UUID, Type, Usedmem, Usedcores). This completely avoids nested map aliasing without introducing an unsafe or complex generic deep-copy implementation.

Reminder: Answers must be written by human being. You can view the relevant rule here.
https://github.com/Project-HAMi/HAMi/blob/master/CONTRIBUTING.md#contribution-gates
"4. Review replies. The reply you post must be written by you and must address the specific point raised. Verbatim or canned AI replies, or replies that do not engage the comment, lead to the PR being closed."

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Sorry @mesutoezdil. It was literally my first time contributing to an open-source project. I just wanted everything to be perfect. I understand my mistake now, and from now on I'll make sure all my answers and contributions are written by me

Comment thread pkg/device/pods.go
// Return a deep copy of the pods map and its PodInfo values to avoid race conditions.
podsCopy := make(map[k8stypes.UID]*PodInfo, podCount)
maps.Copy(podsCopy, m.pods)
for uid, pod := range m.pods {

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.

the only caller is cmd/scheduler/metrics.go:318 and it reads Namespace, Name, NodeID and Devices only, so cloning the whole corev1.Pod spec and status per pod per scrape is a lot of garbage for nothing, did u consider copying just PodInfo w/ the pod ptr left alone?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Excellent point. I revised GetScheduledPods to construct a metrics-specific PodInfo snapshot: it retains the original Pod pointer for name/namespace identity checks while copying NodeID and cloning device allocations via DeepCopyForMetrics(). This avoids generating garbage by copying the full corev1.Pod spec and status on every Prometheus scrape cycle.

Signed-off-by: Gaurav-205 <gauravkhandelwal205@gmail.com>
@Gaurav-205

Copy link
Copy Markdown
Author

also the description doesnt follow the pr template, no "what type of pr is this?" block or /kind line, and no ai disclosure. if any ai tool was used it has to be disclosed, see https://github.com/Project-HAMi/HAMi/blob/master/CONTRIBUTING.md#ai-assistance-notice

Thanks for pointing this out. I have updated the PR description to follow the official repository template, added the /kind bug line, and included the required AI assistance disclosure notice.

@Gaurav-205
Gaurav-205 force-pushed the fix/scheduled-pods-deep-copy branch from f25cd8b to e62ae08 Compare July 29, 2026 17:20
@hami-robot hami-robot Bot added size/L and removed size/S labels Jul 29, 2026

@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/device/pods.go`:
- Around line 213-220: Update ContainerDevice.DeepCopy in pkg/device/pods.go to
preserve CustomInfo via an isolated copy, while leaving DeepCopyForMetrics
redacted. In pkg/device/pod_test.go lines 537-545, assert generic copies retain
CustomInfo without sharing mutable data, and preserve the existing nil assertion
for metrics snapshots.
🪄 Autofix (Beta)

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: c6a06fe9-4798-4eab-8a7c-a3c1355c0ade

📥 Commits

Reviewing files that changed from the base of the PR and between f25cd8b and e62ae08.

📒 Files selected for processing (3)
  • cmd/scheduler/metrics.go
  • pkg/device/pod_test.go
  • pkg/device/pods.go
💤 Files with no reviewable changes (1)
  • cmd/scheduler/metrics.go

Comment thread pkg/device/pods.go
Comment on lines 213 to 220
func (c ContainerDevice) DeepCopy() ContainerDevice {
dup := ContainerDevice{
return ContainerDevice{
Idx: c.Idx,
UUID: c.UUID,
Type: c.Type,
Usedmem: c.Usedmem,
Usedcores: c.Usedcores,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep CustomInfo omission limited to metrics snapshots.

ContainerDevice.DeepCopy() is also used by PodInfo.DeepCopy() through PodDevices.DeepCopy(), so Line 213 now silently discards metadata for every generic copy, not only GetScheduledPods(). DeepCopyForMetrics() already provides the intended redaction.

  • pkg/device/pods.go#L213-L220: restore CustomInfo preservation/isolation in the generic copy path; retain its omission only in DeepCopyForMetrics().
  • pkg/device/pod_test.go#L537-L545: assert generic copies preserve CustomInfo; keep the nil assertion in the metrics-snapshot test.
📍 Affects 2 files
  • pkg/device/pods.go#L213-L220 (this comment)
  • pkg/device/pod_test.go#L537-L545
🤖 Prompt for 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.

In `@pkg/device/pods.go` around lines 213 - 220, Update ContainerDevice.DeepCopy
in pkg/device/pods.go to preserve CustomInfo via an isolated copy, while leaving
DeepCopyForMetrics redacted. In pkg/device/pod_test.go lines 537-545, assert
generic copies retain CustomInfo without sharing mutable data, and preserve the
existing nil assertion for metrics snapshots.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: GetScheduledPods returns mutable pointers leading to potential data races

3 participants