Skip to content

scheduler: prioritize workloads pending preemption to prevent thrashing - #13797

Merged
kubernetes-prow[bot] merged 53 commits into
kubernetes-sigs:mainfrom
Nilsachy:fix-preemption-loop-due-to-eviction-timeout
Aug 20, 2026
Merged

kubernetes-prow[bot] merged 53 commits into
kubernetes-sigs:mainfrom
Nilsachy:fix-preemption-loop-due-to-eviction-timeout

Conversation

@Nilsachy

@Nilsachy Nilsachy commented Aug 4, 2026 •

Copy link
Copy Markdown
Contributor

What type of PR is this?

/kind bug

What this PR does / why we need it:

When a workload requires preemption/reclamation of multiple target workloads across a cohort to acquire their combined quota, desynchronized eviction completion times can cause preemption thrashing and infinite preemption loops.

Specifically, if one target workload (wlA) finishes evicting and releases its quota early, the preempting workload (preemptor-wl) cannot yet be admitted because it is still waiting for remaining target workloads (wlB) to finish evicting. During this window, because the pending quota for preemptor-wl is not reserved or locked in the persistent cache across scheduling cycles:

  1. Target workloads (or other workloads) can immediately reclaim or steal the newly freed quota if their queue has sufficient nominal capacity or under fair sharing allocation.
  2. When wlA is re-admitted, preemptor-wl remains blocked and must re-issue a preemption request against wlA.
  3. When wlB subsequently completes eviction, it similarly steals the freed quota and gets re-admitted, causing preemptor-wl to issue another preemption against wlB.
  4. If eviction completions are desynchronized, target workloads alternate between evicted and re-admitted states, creating a preemption thrashing loop that repeatedly evicts workloads without ever allowing preemptor-wl to acquire the combined quota it needs.

To resolve this, this PR introduces a prioritization mechanism guarded by the PrioritizePreemptorWorkloads feature gate that leverages ClusterQueue preemptor tracking (preemptorWorkload):

  • Preemptor Tracking at Queue Head: When preemption is initiated, the ClusterQueue records the active preemptor workload (preemptorWorkload) and its evaluated .metadata.generation. When popping queue heads in manager.Heads(), we query cq.IsPreemptor(wl) (verifying strict generation matching) and stamp IsPreemptor: true directly onto the scheduler's Head struct.
  • Iterator Prioritization: Under the PrioritizePreemptorWorkloads feature gate, scheduler iterators (makeClassicalIterator in Classical and entryComparer.less in Fair Sharing) prioritize candidate workloads with IsPreemptor == true at the top of the evaluation order.
  • Snapshot Quota Reservation: When evaluated first, active preemptors reserve their required capacity in the scheduling cycle's snapshot (cq.AddUsage), even when ReclaimWithinCohort: Any (CanAlwaysReclaim) is configured. This effectively locks the quota freed during desynchronized target evictions and prevents lower-priority or competitor workloads from stealing it mid-eviction.

Which issue(s) this PR fixes:

Fixes #13662

Does this PR introduce a user-facing change?

Scheduling: Fix preemption thrashing/loops caused by desynchronized eviction completion times by prioritizing preemptor workloads at the head of the scheduling queue. This is guarded by the PrioritizePreemptorWorkloads Alpha feature gate, disabled by default.

Summary by CodeRabbit

  • Bug Fixes
    • Improved scheduling so workloads awaiting preemption are prioritized appropriately during quota allocation.
    • Prevented lower-priority workloads from acquiring temporarily freed quota while required evictions are still in progress.
    • Preserved ordering based on reservations, borrowing, priority, and FIFO behavior after preemption handling.
  • Reliability
    • Strengthened scheduling consistency and concurrency behavior across supported queueing strategies.

@kubernetes-prow kubernetes-prow Bot added release-note Denotes a PR that will be considered when it comes time to generate release notes. do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. kind/bug Categorizes issue or PR as related to a bug. labels Aug 4, 2026
@netlify

netlify Bot commented Aug 4, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for kubernetes-sigs-kueue ready!

Name Link
🔨 Latest commit d397216
🔍 Latest deploy log https://app.netlify.com/projects/kubernetes-sigs-kueue/deploys/6a86ddf146a3c0000834fa7e
😎 Deploy Preview https://deploy-preview-13797--kubernetes-sigs-kueue.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Added pending-preemption detection and scheduler ordering. Integration coverage verifies admission waits for all evictions. Queue concurrency tests cover both supported strategies.

Changes

Pending preemption ordering

Layer / File(s) Summary
Detect and prioritize pending preemption
pkg/workload/workload.go, pkg/scheduler/...
Added IsPendingPreemption. Fair-sharing and classical scheduling now prioritize pending-preemption workloads before later ordering criteria.
Validate multi-eviction admission
test/integration/singlecluster/scheduler/preemption_test.go
Configured lower-priority cohort borrowing and verified that a preemptor waits until all required evictions complete.
Exercise queue strategies concurrently
pkg/cache/queue/cluster_queue_test.go
Extended snapshot and sticky-workload concurrency tests to BestEffortFIFO and StrictFIFO.

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

Possibly related PRs

Suggested reviewers: mimowo, pajakd, tenzen-y

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The scheduling changes and integration test address issue #13662 by keeping pending-preemption quota unavailable to lower-priority workloads.
Out of Scope Changes check ✅ Passed The implementation and test updates support the preemption-ordering fix and contain no unrelated code changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main scheduler change and its purpose: prioritizing workloads pending preemption to prevent thrashing.
✨ 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.

@kubernetes-prow kubernetes-prow Bot added cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. labels Aug 4, 2026
@kubernetes-prow

Copy link
Copy Markdown

Hi @Nilsachy. Thanks for your PR.

I'm waiting for a kubernetes-sigs member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work.

Tip

We noticed you've done this a few times! Consider joining the org to skip this step and gain /lgtm and other bot rights. We recommend asking approvers on your previous PRs to sponsor you.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@kubernetes-prow
kubernetes-prow Bot requested review from PBundyra and pajakd August 4, 2026 08:29
@kubernetes-prow kubernetes-prow Bot added the size/M Denotes a PR that changes 30-99 lines, ignoring generated files. label Aug 4, 2026
@Nilsachy Nilsachy changed the title PoC scheduler: prioritize workloads pending preemption to prevent thrashing Aug 4, 2026
@Nilsachy

Nilsachy commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

/assign kshalot

@Nilsachy

Nilsachy commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

/assign mimowo

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

/ok-to-test

@kubernetes-prow kubernetes-prow Bot added ok-to-test Indicates a non-member PR verified by an org member that is safe to test. and removed needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. labels Aug 4, 2026
Comment thread pkg/workload/workload.go Outdated
@j-skiba

j-skiba commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

let's also add an integration test for the scenario from the PR description

@kubernetes-prow kubernetes-prow Bot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. and removed size/M Denotes a PR that changes 30-99 lines, ignoring generated files. labels Aug 4, 2026
@Nilsachy
Nilsachy marked this pull request as ready for review August 4, 2026 12:03
@kubernetes-prow kubernetes-prow Bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Aug 4, 2026
@kubernetes-prow
kubernetes-prow Bot requested a review from mimowo August 4, 2026 12:03
@tenzen-y

Copy link
Copy Markdown
Member

Thanks, I'm checking this for today's patch release

var result []Head
for _, v := range q.queued {
result = append(result, *v)
result = append(result, Head{Info: *v})

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.

Isn't there a problem here? We lose IsPreemptor state. So we either have to store Heads in q.queued or recompute IsPreemptor here (or revert the Head addition and add IsPreemptor to workload.Info and revisit this in a followup. It would reduce the diff in this PR.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

yeah, right. each entry already has Head. So, I think we can easily replace workload.Info with Head for q.queued.

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.

sgtm

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

So, I meant we can easily replace workload.Info with Head for q.queued in this PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I might be wrong but doesn't a workload enter secondPassQueue (workload.NeedsSecondPass) only if it already has its quota reserved? https://github.com/kubernetes-sigs/kueue/blob/main/pkg/workload/workload.go#L974

So in that case it will never have the IsPreemptor set to true anyway

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah, it looks like a really good point. We have !HasQuotaReservation(w) as requirment for second pass queue.

if workloadfinish.IsFinished(w) || workloadevict.IsEvicted(w) || !HasQuotaReservation(w) || IsOnHold(w) {
return false
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Although I agree that from a semantics perspective it would be cleaner. I think it is still worth doing. Wdyt?

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.

I think it makes sense and it would be more consistent. And also I think it would save us a few memory allocations.

But I don't see this is a blocker for this PR, leaving it up to you and Yuki

@tenzen-y tenzen-y Aug 20, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes, we can do that, but can you work on it in follow-up PR? Because this PR now is blocking our today's patch release...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@alien1403 PTAL at this discussion for more context on #14681

workloads = append(workloads, wlCopy)
heads = append(heads, Head{
Info: wlCopy,
IsPreemptor: cq.IsPreemptor(wl),

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.

I think technically there is a small chance of races here, because we are calculating cq.IsPreemptor before taking the CQ Snapshot. So in theory they could be misaligned.

I think the risk is small though, it will just treat the WL as preemptor in one more cycle. Could be a follow-up fix maybe

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point, maybe this can be also part of follow up PR (together with semantics improvement mentioned above)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I checked @kshalot 's comment carefully, and you're right since pw.set will be executed concurrently by the scheduler goroutine.
But, this can be a follow-up since it needs just one more cycle.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@alien1403 PTAL at this discussion for more context on #14681

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

So we have this structure: Heads -> Snapshot -> Nominate -> Requeue
In Heads, we read pw once and save IsPreemptor as a boolean. Later steps only read that boolean, and pw.set is only called at the very end in Requeue. As everything runs in order on one thread, nothing can touch pw between Heads and Snapshot
WDYT? cc: @kshalot @tenzen-y

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.

pw.clear() can be called in a separate goroutine in the Workload Reconciler:

r.queues.DeleteWorkload(log, wlKey)

But I think this race condition is not worth considering anyway - if the workload is deleted after we already took it from the queue with Heads(), it doesn't really matter how it will be sorted in the iterator because the cycle will fail with a 404 at some point anyway 🤷 I'd disregard this

@tenzen-y

Copy link
Copy Markdown
Member

@Nilsachy we have to rebased onto the main branch.

@kubernetes-prow kubernetes-prow Bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Aug 20, 2026
@kubernetes-prow kubernetes-prow Bot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Aug 20, 2026

@tenzen-y tenzen-y left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you for this great improvement!
/lgtm
/approve

@kubernetes-prow kubernetes-prow Bot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Aug 20, 2026
@kubernetes-prow

Copy link
Copy Markdown

LGTM label has been added.

DetailsGit tree hash: b4fd4d8418d564525263f4951301fdf1c0cfa2a1

@kubernetes-prow

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: Nilsachy, tenzen-y

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

@kubernetes-prow kubernetes-prow Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Aug 20, 2026
@tenzen-y

Copy link
Copy Markdown
Member

@Nilsachy can you manually prepare cherry pick PRs for 0.18 and 0.19?
Those cherry picks should have "0.18, Alpha, disabled by default" and "0.19, Alpha, disabled by default" changes.

@tenzen-y

Copy link
Copy Markdown
Member

@Nilsachy can you manually prepare cherry pick PRs for 0.18 and 0.19? Those cherry picks should have "0.18, Alpha, disabled by default" and "0.19, Alpha, disabled by default" changes.

You can leverage hack/cherry_pick_pull.sh

@kubernetes-prow
kubernetes-prow Bot merged commit 9129946 into kubernetes-sigs:main Aug 20, 2026
58 checks passed
@kubernetes-prow kubernetes-prow Bot added this to the v0.20 milestone Aug 20, 2026
@Nilsachy

Copy link
Copy Markdown
Contributor Author

@Nilsachy can you manually prepare cherry pick PRs for 0.18 and 0.19?
Those cherry picks should have "0.18, Alpha, disabled by default" and "0.19, Alpha, disabled by default" changes.

Sure I'll do it now

@tenzen-y

Copy link
Copy Markdown
Member

@coderabbitai can you open a follow-up issue for #13797 (comment) and #13797 (comment) w/ kind/cleanup label, and assign @Nilsachy to the issue?

@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="kubernetes-sigs/kueue"
pr_number="13797"

echo "== Requested review comments =="
for comment_id in 3820759470 3820718664; do
  gh api \
    -H "Accept: application/vnd.github+json" \
    "/repos/${repo}/pulls/comments/${comment_id}" \
    --jq '{id, html_url, path, line, original_line, body, user: .user.login}'
done

echo
echo "== Cleanup label =="
gh label list --repo "$repo" --limit 200 \
  --json name,color,description \
  --jq '.[] | select(.name == "kind/cleanup")'

Length of output: 1832


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="kubernetes-sigs/kueue"
pr_url="https://github.com/kubernetes-sigs/kueue/pull/13797"
comment_1="https://github.com/kubernetes-sigs/kueue/pull/13797#discussion_r3820759470"
comment_2="https://github.com/kubernetes-sigs/kueue/pull/13797#discussion_r3820718664"

body=$(cat <<EOF
## Summary

Ensure that preemptor state remains consistent between the queue state and the ClusterQueue scheduling snapshot.

## Rationale

The current implementation has two related follow-up concerns:

1. \`pkg/cache/queue/manager.go\` calculates \`cq.IsPreemptor\` before it takes the ClusterQueue snapshot. Concurrent state changes can make the calculated value and the snapshot state differ for one scheduling cycle.
2. \`pkg/cache/queue/second_pass_queue.go\` can lose \`IsPreemptor\` state when it stores queued workload data. The implementation needs a durable representation or a correct recomputation point.

## Affected areas

- \`pkg/cache/queue/manager.go\`
- \`pkg/cache/queue/second_pass_queue.go\`
- Preemptor-state propagation in queue snapshots and second-pass scheduling

## Acceptance criteria

- Preemptor state and the ClusterQueue snapshot use a consistent view of queue state.
- The second-pass queue preserves or correctly recomputes preemptor state.
- Tests cover concurrent queue-state changes and second-pass scheduling behavior.
- The change does not regress sticky pending-preemption scheduling.

## References

- PR: ${pr_url}
- Review comment on snapshot consistency: ${comment_1}
- Review comment on second-pass state preservation: ${comment_2}

Requested by `@tenzen-y`.
EOF
)

issue_url=$(gh issue create \
  --repo "$repo" \
  --title "Ensure consistent preemptor state in queue snapshots and second-pass scheduling" \
  --body "$body" \
  --label "kind/cleanup" \
  --assignee "Nilsachy")

echo "Created issue: ${issue_url}"

Length of output: 2080


@tenzen-y, created #14681: Ensure consistent preemptor state in queue snapshots and second-pass scheduling.

It has the kind/cleanup label and is assigned to @Nilsachy.


🧠 Learnings used
Learnt from: mimowo
Repo: kubernetes-sigs/kueue PR: 0
File: :0-0
Timestamp: 2026-07-02T09:39:34.007Z
Learning: Repo: kubernetes-sigs/kueue. The CodeRabbit GitHub App currently lacks "Issues: Write" permission on this repo, so it cannot post comments (e.g., "/kind flake") or add/edit labels on issues directly — both attempts fail with "GraphQL: Resource not accessible by integration". To apply labels like `kind/flake`, post a comment starting with "/kind flake" (k8s-ci-robot will add the label), but this requires the CodeRabbit GitHub App to have write access to issue comments first.

Learnt from: mimowo
Repo: kubernetes-sigs/kueue PR: 0
File: :0-0
Timestamp: 2026-07-02T09:18:17.453Z
Learning: In kubernetes-sigs/kueue, issue templates live under `.github/ISSUE_TEMPLATE/` as legacy Markdown files (BUG_REPORT.md, CLEANUP.md, DOCUMENTATION.md, ENHANCEMENT.md, FLAKE.md, NEW_RELEASE.md, SUPPORT.md), not GitHub Issue Forms (YAML). Maintainer mimowo asked about preventing duplicate issue reports (e.g., issue `#12709` duplicating `#12708`); suggested mitigations include migrating to YAML issue forms with a "search first" checkbox, adding a search reminder to existing templates, using a similar-issues bot/Action, and leaning on Prow triage commands (`/duplicate`, `/close`).

You are interacting with an AI system.

@tenzen-y

Copy link
Copy Markdown
Member

/release-note-edit

Scheduling: Fix preemption thrashing/loops caused by desynchronized eviction completion times by prioritizing preemptor workloads at the head of the scheduling queue. This is guarded by the PrioritizePreemptorWorkloads Alpha feature gate, disabled by default.

kubernetes-prow Bot pushed a commit that referenced this pull request Aug 20, 2026
…ing preemption to prevent thrashing (#14682)

* Automated cherry pick of #13797: scheduler: prioritize workloads pending preemption to prevent thrashing

* chore: downgrade PrioritizePreemptorWorkloads feature version to 0.19

* refactor: remove redundant local queue weight tracking from ClusterQueue heap comparator

* fix: update newClusterQueue constructor call in TestClusterQueueIsPreemptor to match signature
kubernetes-prow Bot pushed a commit that referenced this pull request Aug 20, 2026
…ing preemption to prevent thrashing (#14683)

* Automated cherry pick of #13797: scheduler: prioritize workloads pending preemption to prevent thrashing

* chore: downgrade PrioritizePreemptorWorkloads feature gate version to 0.18

* update newClusterQueue call in ClusterQueueIsPreemptor test to match signature change
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. kind/bug Categorizes issue or PR as related to a bug. lgtm "Looks good to me", indicates that a PR is ready to be merged. ok-to-test Indicates a non-member PR verified by an org member that is safe to test. release-note Denotes a PR that will be considered when it comes time to generate release notes. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Preemption Thrashing Loop in Cohort Reclaim Due to Partial Eviction and Re-Admission

8 participants