Skip to content

fix(nodelock): let a pod re-acquire its own still-valid node lock - #2255

Merged
hami-robot[bot] merged 3 commits into
Project-HAMi:masterfrom
adity1raut:fix/nodelock-reentrant-same-pod
Aug 3, 2026
Merged

fix(nodelock): let a pod re-acquire its own still-valid node lock#2255
hami-robot[bot] merged 3 commits into
Project-HAMi:masterfrom
adity1raut:fix/nodelock-reentrant-same-pod

Conversation

@adity1raut

@adity1raut adity1raut commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

/kind bug

What this PR does / why we need it:

lockAllDevices (pkg/scheduler/scheduler.go) calls nodelock.LockNode once per device vendor backend a pod requests resources from (device.GetDevices() is iterated per registered vendor), and every vendor writes to the same shared node annotation (hami.io/mutex.lock).

LockNode had no case for "this exact pod already holds the lock": the dangling-lock check only ran when the lock's namespace/name differed from the requesting pod, so a same-pod, non-expired lock fell straight through to the contention error. Concretely: a pod requesting resources from two or more HAMi-managed vendors in the same spec (e.g. one container asking for nvidia.com/gpu, another for cambricon.com/vmlu) would have its first LockNode call succeed and its second call immediately contend with its own still-valid lock — making that pod permanently unschedulable.

This PR adds an explicit branch: if the existing lock's namespace/name match the calling pod and it hasn't expired, LockNode treats it as already acquired instead of erroring.

It also fixes Test_LockNode's "node has been locked" case, which had (accidentally) used the exact same pod identity as both the lock holder and the requester — so it was asserting the buggy contention behavior as correct. That case is now a genuine third-party-contention scenario (locked by a different, still-live pod), and a new dedicated test (TestLockNodeReentrantSamePod) reproduces lockAllDevices' actual multi-vendor call pattern.

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

Special notes for your reviewer:
Found this while reviewing other parts of the scheduler/monitor code for unrelated PRs; it's a distinct issue from any of my other open PRs.

Does this PR introduce a user-facing change?:

Fix pods requesting resources from multiple HAMi-managed device vendors becoming permanently unschedulable due to the pod's own node lock contending with itself.

This PR was written with AI assistance (Claude Code), per the AI-assistance disclosure requirement in CONTRIBUTING.md.

Summary by CodeRabbit

  • Bug Fixes

    • Node locks can now be safely reacquired by the same pod without being reported as conflicts.
    • Improved handling of dangling or invalid node locks to avoid unnecessary contention.
  • Tests

    • Added coverage for same-pod lock reacquisition and additional lock-holder scenarios.
    • Expanded validation for invalid and unlocked node setups.

lockAllDevices calls LockNode once per device vendor a pod requests
resources from (device.GetDevices() is iterated per vendor backend),
all writing to the same shared node annotation (NodeLockKey). LockNode
had no case for "this exact pod already holds the lock": the dangling-
lock check only ran for a different pod, so a same-pod, non-expired
lock fell straight through to the contention error. A pod requesting
resources from two or more HAMi-managed vendors (e.g. nvidia.com/gpu
and cambricon.com/vmlu together) would have its first LockNode call
succeed and its second one immediately contend with its own lock,
making it permanently unschedulable.

Add an explicit branch: if the existing lock's namespace/name match
the calling pod and it hasn't expired, treat it as already acquired
instead of erroring.

Test_LockNode's "node has been locked" case had (accidentally) used
the exact same pod identity as both the lock holder and the requester,
so it was asserting the buggy contention behavior as correct. Split it
into a genuine third-party-contention case (locked by a different,
still-live pod) and a new dedicated test reproducing lockAllDevices'
actual multi-vendor call pattern.

Signed-off-by: Aditya Raut <araut7798@gmail.com>
@hami-robot hami-robot Bot added kind/bug Something isn't working dco-signoff: yes labels Aug 1, 2026
@hami-robot
hami-robot Bot requested review from DSFans2014 and lengrongfu August 1, 2026 05:27
@hami-robot hami-robot Bot added the size/M label Aug 1, 2026
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f5d6d766-6e59-4bc3-86de-e3c2617ffeff

📥 Commits

Reviewing files that changed from the base of the PR and between 462ccc1 and 3364efd.

📒 Files selected for processing (1)
  • pkg/util/nodelock/nodelock_test.go

📝 Walkthrough

Walkthrough

LockNode now treats a valid lock owned by the requesting pod as successful. Tests cover conflicting ownership and repeated acquisition across device vendors.

Changes

Node lock reentrancy

Layer / File(s) Summary
Same-pod lock handling
pkg/util/nodelock/nodelock.go
LockNode accepts an existing valid lock owned by the requesting pod. Locks owned by other pods continue through conflict and dangling-lock checks.
Reentrancy validation
pkg/util/nodelock/nodelock_test.go
Tests use live pod fixtures, validate setup errors, identify conflicts from other pods, and verify repeated acquisition by the same pod for nvidia and cambricon.

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

Possibly related issues

  • Project-HAMi/HAMi-core issue 246 — Addresses repeated same-pod LockNode acquisition failure.
  • Project-HAMi/HAMi issue 2243 — Describes same-pod lock contention during multi-backend acquisition.

Possibly related PRs

Suggested reviewers: archlitchi, dsfans2014

Poem

A rabbit checks the node lock twice,
The same pod passes without a fight.
Other pods still meet the guard,
Two vendors cross the lockyard.
Reentrant hops complete the bind.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 the primary change: allowing a pod to re-acquire its valid node lock.
Linked Issues check ✅ Passed The change allows same-pod LockNode re-acquisition and adds regression coverage, satisfying issue #2243.
Out of Scope Changes check ✅ Passed The implementation and test updates directly support same-pod lock re-acquisition and regression coverage for issue #2243.
✨ 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 archlitchi August 1, 2026 05:27
@codecov

codecov Bot commented Aug 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.00000% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
pkg/util/nodelock/nodelock.go 75.00% 0 Missing and 1 partial ⚠️
Flag Coverage Δ
unittests 64.21% <75.00%> (-0.01%) ⬇️

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

Files with missing lines Coverage Δ
pkg/util/nodelock/nodelock.go 70.80% <75.00%> (+0.65%) ⬆️

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

The reentrancy check added in the previous commit is a compound
condition, ns == pods.Namespace && previousPodName == pods.Name.
Existing tests only exercised the fully-false case (different
namespace entirely) and the fully-true case (same pod, the reentrant
scenario); nothing exercised same namespace with a different pod name,
leaving codecov's patch coverage partial on that line. Add that case:
a live pod in the same namespace, but a different name, holding the
lock - which must still be treated as third-party contention, not
reentrancy.

Signed-off-by: Aditya Raut <araut7798@gmail.com>

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
pkg/util/nodelock/nodelock_test.go (2)

71-91: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Check all fixture creation errors.

The Nodes().Create and Pods().Create results are ignored in both conflict fixtures. If node creation fails, LockNode returns a not-found error, so wantErr: true can pass without exercising the lock-contention path. Fail the subtest when any fixture creation returns an error.

Also applies to: 92-114

🤖 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/util/nodelock/nodelock_test.go` around lines 71 - 91, Check and handle
the errors returned by every Nodes().Create and Pods().Create call in both
conflict fixtures, including the fixture around the existing and additional
conflict case. Make each subtest fail immediately when fixture creation fails,
so wantErr: true validates lock contention rather than a missing-resource path.

275-300: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Create the requesting pod before acquiring the nodelock.

LockNode checks the previous lock owner pod via the fake client. If test-ns/multi-vendor-pod is not created, the first call succeeds with a dangling lock, and the live ownership branch is not exercised.

🤖 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/util/nodelock/nodelock_test.go` around lines 275 - 300, Update
TestLockNodeReentrantSamePod to create test-ns/multi-vendor-pod in the fake
Kubernetes client before the first LockNode call, and fail the test if creation
fails. Keep the existing two LockNode calls unchanged so they exercise reentrant
ownership by the live requesting pod.
🤖 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.

Outside diff comments:
In `@pkg/util/nodelock/nodelock_test.go`:
- Around line 71-91: Check and handle the errors returned by every
Nodes().Create and Pods().Create call in both conflict fixtures, including the
fixture around the existing and additional conflict case. Make each subtest fail
immediately when fixture creation fails, so wantErr: true validates lock
contention rather than a missing-resource path.
- Around line 275-300: Update TestLockNodeReentrantSamePod to create
test-ns/multi-vendor-pod in the fake Kubernetes client before the first LockNode
call, and fail the test if creation fails. Keep the existing two LockNode calls
unchanged so they exercise reentrant ownership by the live requesting pod.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: af266436-75d2-4f53-a32f-ff799547f7be

📥 Commits

Reviewing files that changed from the base of the PR and between 64eda74 and 462ccc1.

📒 Files selected for processing (1)
  • pkg/util/nodelock/nodelock_test.go

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

pls add "fixes #2243" to the desc so it auto closes.

Comment thread pkg/util/nodelock/nodelock.go
Addresses CodeRabbit review on PR Project-HAMi#2255:

- Test_LockNode's "another pod" and "same namespace, different pod"
  cases ignored the Nodes().Create/Pods().Create fixture errors. If
  node creation silently failed, LockNode would return a not-found
  error instead, and wantErr: true would pass without actually
  exercising the lock-contention path it's meant to test. Fail the
  subtest via t.Fatalf on any fixture-creation error instead.

- TestLockNodeReentrantSamePod never created the requesting pod
  ("multi-vendor-pod"/"test-ns") in the fake client, so the first
  LockNode call succeeded only because it was setting a fresh lock,
  not because anything verified a real, live pod. Create the pod
  before acquiring the lock so the test reflects lockAllDevices'
  actual runtime scenario, where the pod genuinely exists throughout.

Signed-off-by: Aditya Raut <araut7798@gmail.com>
@iemAnshuman

Copy link
Copy Markdown
Contributor

@adity1raut this handles taking the lock, but giving it back is still a problem. whichever device plugin finishes first deletes hami.io/mutex.lock while the other one is still working on the same pod, and the owner check passes for both since it's the same pod either way. wrote it up in #2243 under fix consideration. happy to take the release side as a separate pr if you want to keep this one to the acquire path.

@mesutoezdil

Copy link
Copy Markdown
Contributor

/lgtm

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

/lgtm

@hami-robot

hami-robot Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: adity1raut, Shouren

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 added the approved label Aug 3, 2026
@hami-robot
hami-robot Bot merged commit 3982d89 into Project-HAMi:master Aug 3, 2026
17 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.

[Bug] Multi device pods fail to bind because backends acquire the same node lock

4 participants