Skip to content

fix(scheduler): serialize pod quota updates to avoid double counting - #2487

Closed
Lcos-000 wants to merge 1 commit into
Project-HAMi:masterfrom
Lcos-000:fix/scheduler-pod-quota-race
Closed

fix(scheduler): serialize pod quota updates to avoid double counting#2487
Lcos-000 wants to merge 1 commit into
Project-HAMi:masterfrom
Lcos-000:fix/scheduler-pod-quota-race

Conversation

@Lcos-000

@Lcos-000 Lcos-000 commented Aug 8, 2026

Copy link
Copy Markdown

What does this PR do?

Fixes #2478.

Synchronizes pod device/quota accounting between Scheduler.Filter() and the pod informer callbacks. A dedicated podMu mutex now serializes the TakeAndDeletePod → recompute → AddPod cycle in Filter() and all informer handlers (onAddPod, onDelPod, cleanupStalePodAllocation) that mutate PodManager + QuotaManager.

Why is this fix needed?

Without the lock, onAddPod could fire between Filter()'s TakeAndDeletePod (line 956) and its subsequent AddPod (line 998). Since the PodManager keys pods by UID:

  1. Filter deletes the pod from the cache.
  2. onAddPod re-adds it with stale devices and calls AddUsage(stale_devices).
  3. Filter's later AddPod(new_devices) returns added=false, so the new usage is never recorded while the stale usage stays counted.

This causes double-counted / leaked quota usage that is never cleaned up.

Description

  • Add Scheduler.podMu sync.Mutex.
  • Hold it across the whole Filter() live-path usage window.
  • Acquire it in onAddPod, onDelPod, and cleanupStalePodAllocation.

Validation

  • go test ./pkg/scheduler/... -short -count=1 passes.
  • go test -race ./pkg/scheduler/... -short -count=1 passes.

AI assistance disclosure

This PR was written primarily by opencode (an AI coding assistant). Per CONTRIBUTING.md, AI assistance is disclosed here.

Summary by CodeRabbit

  • Bug Fixes
    • Improved scheduling consistency when pods are added, deleted, or cleaned up.
    • Prevented stale resource allocations from affecting quota and usage calculations.
    • Reduced the risk of conflicting updates during concurrent pod events.

@hami-robot
hami-robot Bot requested a review from archlitchi August 8, 2026 15:58
@hami-robot

hami-robot Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: Lcos-000
Once this PR has been reviewed and has the lgtm label, please assign dsfans2014 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 requested a review from mesutoezdil August 8, 2026 15:59
@github-actions github-actions Bot added the kind/bug Something isn't working label Aug 8, 2026
@hami-robot

hami-robot Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

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

@hami-robot hami-robot Bot added the size/S label Aug 8, 2026
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The scheduler adds a mutex that serializes pod cache and quota updates across pod callbacks, stale allocation cleanup, and the live Filter removal, calculation, and restoration cycle.

Changes

Pod State Synchronization

Layer / File(s) Summary
Synchronize pod callbacks and cleanup
pkg/scheduler/scheduler.go
Adds podMu and uses it for pod add, pod delete, and stale allocation cleanup operations.
Protect the live filtering cycle
pkg/scheduler/scheduler.go
Holds podMu while Filter removes the existing allocation, recalculates usage, scores, and restores the allocation.

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

Possibly related PRs

Suggested reviewers: mesutoezdil, wawa0210

Poem

A rabbit guards the pod cache tight,
No stale counts escape tonight.
Add and delete now move in line,
Filter’s steps stay safe and fine.
The quota ledger thumps: “All right!”

🚥 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 describes serializing scheduler pod quota updates to prevent double counting.
Linked Issues check ✅ Passed The PR adds podMu to serialize Filter() recalculation with informer callbacks, addressing the double-counting race in [#2478].
Out of Scope Changes check ✅ Passed The changes are limited to scheduler synchronization and directly support the linked issue objective.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 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 wawa0210 August 8, 2026 15:59
Signed-off-by: Lcos <3349888061@qq.com>
@Lcos-000
Lcos-000 force-pushed the fix/scheduler-pod-quota-race branch from 9394e30 to f4a847e Compare August 8, 2026 16:03

@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/scheduler.go`:
- Around line 969-973: The podMu lock currently spans external event recording
through Filter return; narrow it to the in-memory TakeAndDeletePod,
getNodesUsage/calcScore, AddPod, quota, annotation-failure, and rollback
transaction. Replace deferred unlocking with explicit unlock before
recordScheduleFilterResultEvent, while preserving lock coverage for annotation
failures and rollback.
🪄 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: 5d75e2bb-7a8d-4eda-9f50-3460af34e4da

📥 Commits

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

📒 Files selected for processing (1)
  • pkg/scheduler/scheduler.go

Comment on lines +969 to +973

// Keep the pod absent from the manager for the complete usage calculation.
// Informer callbacks must not re-add it with stale devices in this window.
s.podMu.Lock()
defer s.podMu.Unlock()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

if command -v ast-grep >/dev/null 2>&1; then
  ast-grep outline pkg/scheduler/scheduler.go \
    --items all \
    --type function \
    --match 'Filter|onAddPod|onDelPod|cleanupStalePodAllocation'
fi

rg -n -C 8 'PatchPodAnnotations|recordScheduleFilterResultEvent' .
rg -n -C 5 'podMu\.(Lock|Unlock)|getNodesUsage\s*\(' \
  pkg/scheduler --glob '*.go'

Repository: Project-HAMi/HAMi

Length of output: 25350


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "scheduler.Filter and surrounding handler:"
sed -n '924,1036p' pkg/scheduler/scheduler.go

echo
echo "PatchPodAnnotations implementation:"
sed -n '163,190p' pkg/util/util.go

echo
echo "recordScheduleFilterResultEvent implementation:"
sed -n '58,82p' pkg/scheduler/event.go

echo
echo "informer callbacks using podMu:"
sed -n '138,170p' pkg/scheduler/scheduler.go
sed -n '192,214p' pkg/scheduler/scheduler.go
sed -n '806,818p' pkg/scheduler/scheduler.go

Repository: Project-HAMi/HAMi

Length of output: 8139


Shorten the podMu critical section before external calls.

defer s.podMu.Unlock() holds the scheduler-wide mutex until Filter returns. Because Filter calls event recording while still holding the lock, slow Kubernetes event writes can block pod callbacks and live filter requests. Keep podMu only around the in-memory TakeAndDeletePod → getNodesUsage/calcScore → AddPod/quota transaction. Move recordScheduleFilterResultEvent outside the lock; handle annotation failures and rollback under the lock.

🤖 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/scheduler/scheduler.go` around lines 969 - 973, The podMu lock currently
spans external event recording through Filter return; narrow it to the in-memory
TakeAndDeletePod, getNodesUsage/calcScore, AddPod, quota, annotation-failure,
and rollback transaction. Replace deferred unlocking with explicit unlock before
recordScheduleFilterResultEvent, while preserving lock coverage for annotation
failures and rollback.

@mesutoezdil

Copy link
Copy Markdown
Contributor

duplicated

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.

Race Condition Between Filter() and onAddPod() Causes Double Counting

2 participants