Skip to content

fix(nodelock): handle empty lock annotations#1727

Closed
maishivamhoo123 wants to merge 2 commits into
Project-HAMi:masterfrom
maishivamhoo123:fix/nodelock-time-parse-panic
Closed

fix(nodelock): handle empty lock annotations#1727
maishivamhoo123 wants to merge 2 commits into
Project-HAMi:masterfrom
maishivamhoo123:fix/nodelock-time-parse-panic

Conversation

@maishivamhoo123

Copy link
Copy Markdown
Member

Description:
This PR resolves the 15-30 minute scheduling delays during high-concurrency pod submissions and fixes time.Parse panics during node locking.

Key Code Changes:

In-Memory Queuing (scheduler.go & nodelock.go): Added LockNodeMemory() and applied it directly inside the scheduler's Bind function. This queues concurrent pods locally instead of instantly rejecting them, bypassing the kube-scheduler's 5-minute exponential backoff trap.

Blind API Patching (nodelock.go): Removed resourceVersion from the JSON patch payloads in SetNodeLock and ReleaseNodeLock. This safely updates the Node annotation for the Device Plugin handshake while eliminating HTTP 409 Resource Conflict storms.

Empty Annotation Fix (nodelock.go): Added a safeguard (if !ok || lockVal == "") in LockNode to prevent panics when attempting to parse an empty string as a time value.

Test Fix (nodelock.go): Corrected the timeout error message string in SetNodeLock to satisfy existing unit test assertions.

New Concurrency Test (nodelock_test.go): Added TestLockNode_ConcurrentPatchSimulation to explicitly verify that a burst of 40+ pods processes successfully without triggering K8s API conflicts or backoff errors.

Signed-off-by: maishivamhoo123 <maishivamhoo@gmail.com>
@hami-robot

hami-robot Bot commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: maishivamhoo123
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 requested review from DSFans2014 and wawa0210 April 4, 2026 05:36
@github-actions github-actions Bot added the kind/bug Something isn't working label Apr 4, 2026
@hami-robot hami-robot Bot added the size/L label Apr 4, 2026

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request implements in-memory node locking within the scheduler's Bind function and transitions to "blind patches" to mitigate Kubernetes API 409 conflicts during high-concurrency pod submissions. Feedback highlights a critical deadlock risk where the same mutex is acquired multiple times during the binding process, as well as redundant package imports. Furthermore, the LockNode logic was found to be inefficient by performing unnecessary API calls when a node is already locked, and the new concurrency test requires updates to properly validate node state transitions.

Comment on lines +655 to +656
nodelock.LockNodeMemory(args.Node)
defer nodelock.UnlockNodeMemory(args.Node) // Safely releases even on goto ReleaseNodeLocks

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.

critical

This call to nodelock.LockNodeMemory(args.Node) acquires a per-node mutex. However, the subsequent call to val.LockNode (line 687) eventually calls nodelock.SetNodeLock, which attempts to acquire the same mutex. Since Go's sync.Mutex is not reentrant, this will result in a deadlock for every pod binding attempt. To implement in-memory queuing safely, you should either use a separate mutex for the queuing phase or refactor the nodelock package to allow internal calls without re-acquiring the lock.

Comment on lines +50 to 51
"github.com/Project-HAMi/HAMi/pkg/util/nodelock"
nodelockutil "github.com/Project-HAMi/HAMi/pkg/util/nodelock"

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.

medium

The package github.com/Project-HAMi/HAMi/pkg/util/nodelock is now imported twice: once with the default name and once with the alias nodelockutil. This is redundant and inconsistent. Please remove the new import and use the existing nodelockutil alias for the new calls in the Bind function.

}

return fmt.Errorf("node %s has been locked within %v", nodeName, NodeLockTimeout)
return SetNodeLock(nodeName, lockname, 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.

medium

This change causes LockNode to call SetNodeLock even when the node is already known to be locked and the lock has not expired. SetNodeLock will perform an unnecessary API Get call and attempt to acquire the mutex (which causes a deadlock if called from Bind) only to return the same error message. It is more efficient to return the error directly as in the previous version.

Suggested change
return SetNodeLock(nodeName, lockname, pods)
return fmt.Errorf("node %s has been locked within %v", nodeName, NodeLockTimeout)

Comment on lines +583 to +591
fakeClient.PrependReactor("patch", "nodes", func(action core.Action) (bool, k8sruntime.Object, error) {
atomic.AddInt32(&patchCount, 1)

// Simulate network latency. If the in-memory sync.Mutex isn't working,
// these concurrent overlapping calls would normally fail or race.
time.Sleep(5 * time.Millisecond)

return true, nil, nil // Return true to indicate we handled the patch successfully
})

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.

medium

The patch reactor returns true, nil, nil without updating the node's state in the fake client's object tracker. Consequently, every concurrent call to LockNode in the test will see the node as unlocked because the annotation is never actually added to the node object. This means the test is not verifying the logic's ability to block pods when a lock exists; it only verifies that the Patch calls are serialized by the mutex.

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

@DSFans2014

DSFans2014 commented Apr 7, 2026

Copy link
Copy Markdown
Member

In-Memory Queuing (scheduler.go & nodelock.go): Added LockNodeMemory() and applied it directly inside the scheduler's Bind function. This queues concurrent pods locally instead of instantly rejecting them, bypassing the kube-scheduler's 5-minute exponential backoff trap.

hi @maishivamhoo123 Have you tested whether adding mutex lock in the bind phase can effectively mitigate this problem?

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

@maishivamhoo123 Please solve the golang-ci lint issues first

@maishivamhoo123

Copy link
Copy Markdown
Member Author

@Shouren please see #1773 i solved everything there . I am closing this pull request.
Thank you!

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.

3 participants