Skip to content

fix(scheduler): serialize Filter device selection to prevent double G… - #2559

Closed
yxxhero wants to merge 2 commits into
Project-HAMi:masterfrom
yxxhero:fix/issue-2232-concurrent-device-double-allocation
Closed

fix(scheduler): serialize Filter device selection to prevent double G…#2559
yxxhero wants to merge 2 commits into
Project-HAMi:masterfrom
yxxhero:fix/issue-2232-concurrent-device-double-allocation

Conversation

@yxxhero

@yxxhero yxxhero commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

What type of PR is this?
/kind bug

What this PR does / why we need it:

Fixes #2232并发创建 pod 情况下,存在 pod 中卡 id 的注解和实际 pod 中使用卡的 id 不一致问题 (under concurrent pod creation, the GPU id annotated on a pod is inconsistent with the GPU id the pod actually uses).

The reported consequence is duplicate card allocation: the shared card gets over-allocated (causing OOM) while other idle cards cannot be handed out.

Root cause

The scheduler extender is served over HTTP with one goroutine per request and provides no cross-request serialization guarantee. In Filter, the device-selection critical section was not atomic:

  1. read node usage (getNodesUsage)
  2. pick the best node/device (calcScore)
  3. commit the reservation to the pod cache (podManager.AddPod)
  4. publish the allocation onto the pod annotation (PatchPodAnnotations)

Under concurrent pod creation (e.g. a multi-replica Deployment), two Filter calls can both read the same free GPU in step 1 before either commits it in step 3, so both pods are annotated with the same card id.

In the normal kube-scheduler flow scheduling cycles are already serialized, which is why the problem only surfaces under concurrent/re-entrant extender requests.

Fix

Add a filterLock and move the in-memory critical section (steps 1–3) into a selectAndCommitDevice helper that owns the lock through a single defer Unlock, so two pods can never reserve the same device. The lock is released before step 4 (PatchPodAnnotations), so concurrent Filters are never blocked on the apiserver round trip; the on-failure rollback runs outside the lock and is safe because the pod/quota managers have their own locks. The simulation path (filterSimulation) does not reserve devices and is unchanged.

This keeps the change minimal — it does not move reservation from Filter to Bind (the larger redesign from #2273 is tracked via the #2264 design discussion).

Test

  • Test_Filter_ConcurrentNoDoubleAllocation fires 8 pods at Filter simultaneously and asserts each is annotated with a distinct device id. Without the lock it reliably reports device device-7 double-allocated to pod pod-0 and pod pod-2; with the lock it passes (-race).
  • Test_Filter_RollbackOnPatchFailure verifies the cache reservation is rolled back when the annotation patch fails.

Validation

Scoped to the scheduler extender, so per CONTRIBUTING's hardware-validation gate this is validated with unit tests (including a -race regression test) rather than physical GPU hardware. make verify (license, import-aliases, golangci-lint) and go test -race ./pkg/scheduler/... pass locally.

Which issue(s) this PR fixes:
Fixes #2232

Special notes for your reviewer:

  • A single defer Unlock in the helper means any early return added there in future can never leak the lock.
  • A per-pod-UID lock was considered but not added: kube-scheduler runs one scheduling cycle at a time, so a pod UID is not filtered concurrently; the reproduced race is between different pods, which the global filterLock closes.

Does this PR introduce a user-facing change?

Fixed a race in the scheduler extender's Filter that could assign the same GPU to multiple pods created concurrently (e.g. a multi-replica Deployment), causing duplicate card allocation and OOM. Device allocation in Filter is now serialized.

AI assistance disclosure:
This PR was written with AI assistance for codebase analysis, implementation and test generation. I reviewed, built and tested the change myself (make verify, go test -race) and can explain the locking semantics. Per CONTRIBUTING.md, disclosure belongs in the PR description only — no AI co-author trailers are used.

Summary by CodeRabbit

  • Bug Fixes
    • Improved concurrent device allocation to prevent multiple pods from receiving the same GPU.
    • Added rollback handling when pod annotation updates fail, preventing stale reservations and keeping allocation state consistent.
    • Improved reporting of device selection failures.
    • Initialization failures are now reported clearly instead of terminating without useful error details.
  • Reliability
    • Added validation for successful device initialization and allocation-state consistency.

@hami-robot hami-robot Bot added kind/bug Something isn't working dco-signoff: yes labels Aug 10, 2026
@hami-robot

hami-robot Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: yxxhero
Once this PR has been reviewed and has the lgtm label, please assign archlitchi 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 added the size/L label Aug 10, 2026
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The scheduler now serializes concurrent device selection and cache updates. It performs annotation patching after selection and rolls back reservation state when patching fails. Device initialization errors now propagate through startup.

Changes

Scheduler consistency and initialization

Layer / File(s) Summary
Device initialization error propagation
pkg/scheduler/config/config.go, cmd/scheduler/main.go, pkg/scheduler/config/config_test.go, pkg/scheduler/nodes_test.go
Initialization functions return wrapped errors. Startup and tests now handle initialization results.
Selection and rollback flow
pkg/scheduler/scheduler.go, pkg/scheduler/scheduler_test.go
Filter serializes device selection and reservation updates. Annotation patching occurs afterward. Tests cover unique concurrent allocation and rollback after patch failure.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

  • Project-HAMi/HAMi issue 2522 — The issue concerns atomic Filter allocation, reservation, quota, and rollback handling.

Possibly related PRs

Suggested reviewers: mesutoezdil, dsfans2014

Poem

A rabbit locks each GPU lane,
So pods do not claim cards in vain.
If annotation patches fall,
Reservations hop back from the wall.
Eight pods race, then each one knows:
One device safely goes.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The device initialization API and startup error-handling changes are unrelated to the concurrent allocation fix in [#2232]. Move the InitDevices and InitDefaultDevices error-propagation changes to a separate pull request unless they are required by the allocation fix.
✅ Passed checks (4 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 device selection to prevent duplicate GPU allocation.
Linked Issues check ✅ Passed The locking, allocation, rollback, and concurrency tests address the concurrent GPU annotation mismatch reported in [#2232].
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 FouoF August 10, 2026 22:59

@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

🧹 Nitpick comments (3)
pkg/scheduler/scheduler.go (1)

1001-1019: 🚀 Performance & Scalability | 🔵 Trivial

Consider narrowing the critical section or measuring its cost.

filterLock covers getNodesUsage and calcScore. getNodesUsage iterates every cached pod, every device, and every node on each call. All extender Filter requests now run this stage one at a time.

The serialization is required for the read-select-commit sequence to be atomic, so the current design is correct. For large clusters, add a latency metric around selectAndCommitDevice so the queueing cost stays visible.

🤖 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 1001 - 1019, Add a latency metric
for the complete selectAndCommitDevice operation, including time spent waiting
for filterLock and executing getNodesUsage/calcScore, and record it on every
return path. Use the existing scheduler metrics conventions and label the
measurement consistently with this operation.
pkg/scheduler/scheduler_test.go (2)

1918-1923: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the quota rollback too.

The test verifies only the pod-cache rollback. Filter also calls quotaManager.RmUsage, but only when selection.added is true. A regression in the quota path would pass this test.

Add an assertion on the quota state:

💚 Proposed addition to cover the quota rollback
 	_, inCache := s.podManager.GetPod(pod)
 	require.False(t, inCache, "reservation must be rolled back when the annotation patch fails")
+
+	for _, v := range *s.quotaManager.GetResourceQuota()[pod.Namespace] {
+		require.Equal(t, int64(0), v.Used, "quota usage must be rolled back when the annotation patch fails")
+	}
 }

Test_Filter_EvictsStaleEntry at Line 1763 uses the same accessor, so the helper exists.

🤖 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_test.go` around lines 1918 - 1923, Extend the failure
assertions in the Filter test to verify quota usage is rolled back as well as
pod-cache state. Reuse the existing quota-manager accessor used by
Test_Filter_EvictsStaleEntry, and assert the affected pod’s quota usage is
absent or restored after the annotation patch failure.

1850-1859: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Collect the Filter errors from the goroutines.

The goroutines discard both return values. If Filter fails for one pod, the test fails later at DecodePodDevices or at the empty-device-id check. That failure message hides the real cause.

Store the errors and assert them after wg.Wait:

♻️ Proposed refactor to surface Filter errors
 	start := make(chan struct{})
+	errs := make([]error, numPods)
 	var wg sync.WaitGroup
 	for i := range numPods {
 		wg.Add(1)
-		go func(pod *corev1.Pod) {
+		go func(idx int, pod *corev1.Pod) {
 			defer wg.Done()
 			<-start
-			_, _ = s.Filter(extenderv1.ExtenderArgs{Pod: pod, NodeNames: &nodeNames})
-		}(pods[i])
+			_, errs[idx] = s.Filter(extenderv1.ExtenderArgs{Pod: pod, NodeNames: &nodeNames})
+		}(i, pods[i])
 	}
 	close(start)
 	wg.Wait()
+	for i, err := range errs {
+		require.NoError(t, err, "Filter failed for pod %s", pods[i].Name)
+	}

Each goroutine writes a distinct slice element, so the race detector stays clean.

🤖 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_test.go` around lines 1850 - 1859, Update the
concurrent Filter test around the goroutine loop to capture each call’s error in
a distinct per-pod slice element instead of discarding both return values. After
wg.Wait, assert that every collected error is nil before continuing to
DecodePodDevices or the empty-device-id checks, preserving race-free writes and
surfacing the original Filter failure.
🤖 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 974-982: In pkg/scheduler/scheduler.go lines 974-982, update the
rollback in Filter’s PatchPodAnnotations failure path so
podManager.DelPod(args.Pod) is gated by selection.added, matching
quotaManager.RmUsage and keeping reservation cleanup consistent. In
pkg/scheduler/scheduler_test.go lines 1918-1923, extend the existing inCache
assertion with a quota assertion that verifies both reservation effects are
rolled back.

---

Nitpick comments:
In `@pkg/scheduler/scheduler_test.go`:
- Around line 1918-1923: Extend the failure assertions in the Filter test to
verify quota usage is rolled back as well as pod-cache state. Reuse the existing
quota-manager accessor used by Test_Filter_EvictsStaleEntry, and assert the
affected pod’s quota usage is absent or restored after the annotation patch
failure.
- Around line 1850-1859: Update the concurrent Filter test around the goroutine
loop to capture each call’s error in a distinct per-pod slice element instead of
discarding both return values. After wg.Wait, assert that every collected error
is nil before continuing to DecodePodDevices or the empty-device-id checks,
preserving race-free writes and surfacing the original Filter failure.

In `@pkg/scheduler/scheduler.go`:
- Around line 1001-1019: Add a latency metric for the complete
selectAndCommitDevice operation, including time spent waiting for filterLock and
executing getNodesUsage/calcScore, and record it on every return path. Use the
existing scheduler metrics conventions and label the measurement consistently
with this operation.
🪄 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: f1b6d4d0-3c29-41ce-9a39-73d12e45bb19

📥 Commits

Reviewing files that changed from the base of the PR and between 91f0248 and b0aacdc.

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

Comment on lines +974 to +982
// Patch the annotation outside the lock; roll back the cache on failure.
if err = util.PatchPodAnnotations(args.Pod, selection.annotations); err != nil {
s.recordScheduleFilterResultEvent(args.Pod, EventReasonFilteringFailed, "", err)
if selection.added {
s.quotaManager.RmUsage(args.Pod, m.Devices)
}
s.podManager.DelPod(args.Pod)
return nil, err
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

The rollback path treats the pod cache and the quota inconsistently, and no test binds them together. Filter deletes the pod-cache entry unconditionally but removes the quota usage only when selection.added is true, so the two states can diverge after a patch failure.

  • pkg/scheduler/scheduler.go#L974-L982: gate s.podManager.DelPod(args.Pod) on selection.added, matching the s.quotaManager.RmUsage call, so both reservation effects are undone together.
  • pkg/scheduler/scheduler_test.go#L1918-L1923: add a quota assertion after the inCache check, so the test covers both halves of the rollback.
📍 Affects 2 files
  • pkg/scheduler/scheduler.go#L974-L982 (this comment)
  • pkg/scheduler/scheduler_test.go#L1918-L1923
🤖 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 974 - 982, In
pkg/scheduler/scheduler.go lines 974-982, update the rollback in Filter’s
PatchPodAnnotations failure path so podManager.DelPod(args.Pod) is gated by
selection.added, matching quotaManager.RmUsage and keeping reservation cleanup
consistent. In pkg/scheduler/scheduler_test.go lines 1918-1923, extend the
existing inCache assertion with a quota assertion that verifies both reservation
effects are rolled back.

klog.Fatalf calls os.Exit on transient API errors (informer list
failures, label patch failures, config loading), which kills the
scheduler process instead of allowing retry. Replace with:

- updateSchedulerLabel(): klog.ErrorS + return instead of klog.Fatalf
- InitDevices(): return error instead of klog.Fatalf
- InitDefaultDevices(): return error instead of klog.Fatalf

Update all callers to handle the returned errors.

Signed-off-by: yxxhero <aiopsclub@163.com>
…PU allocation

Under concurrent pod creation (e.g. a multi-replica Deployment), two Filter
extender calls can run in separate goroutines and both observe the same free
GPU before either commits its reservation to the pod cache. Both pods then get
the same card id annotated, so the card id in the pod annotation no longer
matches the card the pod actually uses: the shared card gets over-allocated
(causing OOM) while other idle cards cannot be handed out. This is issue Project-HAMi#2232.

The scheduler extender is served over HTTP with one goroutine per request and
provides no cross-request serialization guarantee, so the read-pick-commit
sequence in Filter (read node usage -> pick devices -> commit to cache) must be
made atomic.

Add a filterLock and move the in-memory critical section into a
selectAndCommitDevice helper that owns the lock through a single deferred
Unlock (so a future early return can never leak it). The lock covers only
device selection and cache commit; the PatchPodAnnotations API call and the
on-failure rollback run outside it, so concurrent Filters are not blocked on
network I/O. The rollback is safe because the pod/quota managers have their own
locks. The simulation path does not reserve devices and is unchanged. In the
normal kube-scheduler flow scheduling cycles are already serialized, so this
lock is uncontended there; it only closes the window for concurrent extender
requests.

Tests:
- Test_Filter_ConcurrentNoDoubleAllocation fires many pods at Filter
  simultaneously and asserts each is annotated with a distinct device id. It
  reliably reproduces the double allocation without the lock and passes with it.
- Test_Filter_RollbackOnPatchFailure verifies the cache reservation is rolled
  back when the annotation patch fails.
Signed-off-by: yxxhero <aiopsclub@163.com>
@yxxhero
yxxhero force-pushed the fix/issue-2232-concurrent-device-double-allocation branch from b0aacdc to 4ac00e1 Compare August 10, 2026 23:11
@coderabbitai
coderabbitai Bot requested a review from mesutoezdil August 10, 2026 23:12
@codecov

codecov Bot commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 74.50980% with 13 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
pkg/scheduler/config/config.go 22.22% 7 Missing ⚠️
pkg/scheduler/scheduler.go 90.00% 3 Missing and 1 partial ⚠️
cmd/scheduler/main.go 0.00% 2 Missing ⚠️
Flag Coverage Δ
unittests 62.43% <74.50%> (+0.12%) ⬆️

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

Files with missing lines Coverage Δ
cmd/scheduler/main.go 22.72% <0.00%> (-0.21%) ⬇️
pkg/scheduler/scheduler.go 69.98% <90.00%> (+2.07%) ⬆️
pkg/scheduler/config/config.go 78.22% <22.22%> (+0.06%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@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/config/config.go`:
- Around line 281-295: Make device initialization atomic across
InitDevicesWithConfig and InitDefaultDevices: prevent partially initialized
backends from remaining in the global device maps when any initializer fails.
Build temporary maps and publish them only after all initialization succeeds, or
clear both global maps on every failure path, so subsequent InitDevices calls
retry instead of incorrectly returning success.
🪄 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: 974cda96-3ce5-414e-9c11-115078389dc2

📥 Commits

Reviewing files that changed from the base of the PR and between b0aacdc and 4ac00e1.

📒 Files selected for processing (6)
  • cmd/scheduler/main.go
  • pkg/scheduler/config/config.go
  • pkg/scheduler/config/config_test.go
  • pkg/scheduler/nodes_test.go
  • pkg/scheduler/scheduler.go
  • pkg/scheduler/scheduler_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • pkg/scheduler/scheduler.go
  • pkg/scheduler/scheduler_test.go

Comment on lines +281 to 295
func InitDevices() error {
if len(device.DevicesMap) > 0 {
klog.Info("Devices are already initialized, skipping initialization")
return
return nil
}
klog.Infof("Loading device configuration from file: %s", configFile)
config, err := LoadConfig(configFile)
if err != nil {
klog.Fatalf("Failed to load device config file %s: %v", configFile, err)
return fmt.Errorf("failed to load device config file %s: %w", configFile, err)
}
klog.Infof("Loaded config: %v", config)
err = InitDevicesWithConfig(config)
if err != nil {
klog.Fatalf("Failed to initialize devices: %v", err)
return fmt.Errorf("failed to initialize devices: %w", err)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make failed device initialization atomic.

InitDevicesWithConfig adds successful backends to the global maps before it returns initialization errors. If a later backend fails, a subsequent InitDevices call can see the non-empty map and return nil. InitDefaultDevices can leave the same partial state after its nested call fails. Build the maps locally and publish them only after all initializers succeed, or clear both global maps on failure.

Also applies to: 461-464

🤖 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/config/config.go` around lines 281 - 295, Make device
initialization atomic across InitDevicesWithConfig and InitDefaultDevices:
prevent partially initialized backends from remaining in the global device maps
when any initializer fails. Build temporary maps and publish them only after all
initialization succeeds, or clear both global maps on every failure path, so
subsequent InitDevices calls retry instead of incorrectly returning success.

@mesutoezdil

Copy link
Copy Markdown
Contributor

I am unable to reproduce the issue described at #2232 using K3s with the hami mock device plugin. The concurrency issue I mentioned at #2470 (comment) is limited solely to concurrent calls to the /filter endpoint; when I create Pods concurrently rather than making concurrent calls to the /filter endpoint, the problem no longer occurs. The kube-scheduler scheduling process is serialized, so concurrent calls to the /filter endpoint are not possible in this scenario. I am now curious about:

  1. where the version boundary threshold for Kubernetes v1.23 came from.
  2. How the 并发创建pod情况下,存在pod 中卡id的注解和实际pod中使用卡的id不一致问题 #2232 issue arose, and
    why I cannot reproduce it.

In fact, although the /filter interface itself has concurrency control issues you mentioned and I tested, in a real-world scheduler extender scenario, as maintainers expected, no problems should arise.

I’d like to know what specific, real-world issue you encountered that led you to create this PR?

Pls write only your own sentences, as specified in the contribution guidelines.

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

this looks like the follow up to closed pr #2470.
left 2 questions inline about this version.

// selectAndCommitDevice selects a device and commits it to the cache under
// filterLock. It does not publish the pod annotation; the caller patches it.
func (s *Scheduler) selectAndCommitDevice(args extenderv1.ExtenderArgs, resourceReqs device.PodDeviceRequests) (*filterSelection, error) {
s.filterLock.Lock()

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.

this lock is global, not per node or per device. a big concurrent deployment create now queues through one lock for every node. was a per node lock considered, not only per pod uid?

Comment thread cmd/scheduler/main.go
)

config.InitDevices()
if err := config.InitDevices(); err != nil {

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.

this initdevices error change is not about the filterlock race. this repo closed a past pr for bundling unrelated changes together. should this be its own pr?

@Shouren

Shouren commented Aug 11, 2026

Copy link
Copy Markdown
Member

@yxxhero I think the root cause for #2232 is that specific Kubernetes versions, kube-scheduler cannot guarantee that the Extender's Filter is called only once during a Pod's scheduling cycle. Due to timing issues between Filter and Bind, the mismatch happens. That is why @spencercjh cannot reproduce it in K3s, I guess him testing with a higher version than v1.23.


// selectAndCommitDevice selects a device and commits it to the cache under
// filterLock. It does not publish the pod annotation; the caller patches it.
func (s *Scheduler) selectAndCommitDevice(args extenderv1.ExtenderArgs, resourceReqs device.PodDeviceRequests) (*filterSelection, error) {

@mesutoezdil mesutoezdil Aug 12, 2026

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.

which k8s version, and where in kube-scheduler's source does that happen? without that this fix may be locking against a race that never occurs in real scheduling.

@archlitchi

Copy link
Copy Markdown
Member

This scenario happens to k8s version < v1.23, and conflicts with prerequisities which requires k8s>=v1.23.

Reopen this PR if encounter the same issue with k8s version >= v1.23

@archlitchi archlitchi closed this Aug 20, 2026
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.

并发创建pod情况下,存在pod 中卡id的注解和实际pod中使用卡的id不一致问题

4 participants