Repository navigation
[YUNIKORN-3410] Fix self-deadlock on ClusterContext lock during partition removal - #1145
hedger9487 wants to merge 1 commit into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1145 +/- ##
==========================================
+ Coverage 82.89% 82.98% +0.09%
==========================================
Files 104 104
Lines 14467 14462 -5
==========================================
+ Hits 11992 12001 +9
+ Misses 2185 2176 -9
+ Partials 290 285 -5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
416b70b to
eff4d6b
Compare
2dac277 to
9b37668
Compare
| } | ||
| cc.Lock() | ||
| defer cc.Unlock() | ||
| cc.partitions = make(map[string]*PartitionContext) |
There was a problem hiding this comment.
Is this required for unit tests?
There was a problem hiding this comment.
Hi @manirajv06, thanks for pointing this out! To provide some context on why this was added:
In the original master code, ClusterContext.Stop() called part.partitionManager.Stop(), which called manager.remove(), which indirectly invoked manager.cc.removePartition(name) to delete each partition from cc.partitions.
When we removed the cc.removePartition() callback to fix the self-deadlock in updateSchedulerConfig and removePartitionsByRMID, ClusterContext.Stop() also lost that indirect cleanup side-effect. To prevent a behavioral regression where cc.partitions would remain populated after ClusterContext.Stop(), we explicitly cleared cc.partitions under cc.Lock(), and added the test to verify shutdown behavior was preserved.
However, if the community prefers ClusterContext.Stop() to only stop background services without resetting cc.partitions, I'm completely happy to drop it. What do you think?
There was a problem hiding this comment.
I'm completely happy to drop it.
Yes, please
There was a problem hiding this comment.
Dropped ClusterContext.Stop() changes and restored it back to original master.
| assert.Assert(t, checked, "Failed to find metric") | ||
| } | ||
|
|
||
| func TestUpdateSchedulerConfigRemovePartition(t *testing.T) { |
There was a problem hiding this comment.
Is it possible to wrap this, next and next to next and even last one into single test method using t.run() ?
Agree that these are the different situations needs to be tested but not each one requires a separate test method.
There was a problem hiding this comment.
Done! Consolidated all 4 test scenarios into TestContext_RemovePartition using t.Run() subtests:
remove via updateSchedulerConfigremove via removePartitionsByRMIDremove with active apps and nodesstop clears partitions
All subtests pass cleanly under the race detector.
There was a problem hiding this comment.
Nice. Can we avoid repeating the same code for each run?
Have only single run and execute all code and finally assert each case expected output. There are lot of references out there in the codebase. Please take a look.
t.run() {
}
There was a problem hiding this comment.
Updated! Refactored TestContext_RemovePartition to use a single shared setup at the top, followed by t.Run() subtests for each case:
remove via updateSchedulerConfig: tests partition removal and active app/node teardown without deadlock.remove via removePartitionsByRMID: tests remaining partition cleanup via RM event without deadlock.
9b37668 to
2f7a2cd
Compare
2f7a2cd to
06f5646
Compare
|
Hi @manirajv06, I have updated the PR to address all review comments:
All unit tests pass cleanly under |
06f5646 to
ed5ef83
Compare
ed5ef83 to
e741b76
Compare
There was a problem hiding this comment.
Sorry for the delay. Review took more time than expected as it raised a question "Is Partition draining?" and so on..After looking at the old history of changes and further exploration, we had decided to raise #1174 to reinstate the partition state transitions which got missed somewhere along the way. Combining that PR with this should make partition transitions to look better. Once that PR goes in, please rebase and address the comments.
| if err == nil { | ||
| pc.Lock() | ||
| pc.stateTime = time.Now() | ||
| pc.Unlock() |
There was a problem hiding this comment.
Can we file a follow up for Queue as well?
There was a problem hiding this comment.
Filed follow-up Jira YUNIKORN-3508 for Queue.
| }) | ||
|
|
||
| result := <-c | ||
| assert.Assert(t, result.Succeeded) |
There was a problem hiding this comment.
You might need to assert the state after #1174 goes in
There was a problem hiding this comment.
Done! Rebased onto master and added isDraining() / IsRunning() state assertions in both subtests.
…tion removal During a configuration reload that drops a partition, or during RM partition-removal events (removePartitionsByRMID), the caller stops the partition manager while holding the ClusterContext write lock. partitionManager.Stop() calls remove() synchronously, which was calling cc.removePartition(name). Because cc.removePartition() attempts to acquire the non-reentrant cc.Lock(), the infrastructure event goroutine deadlocked on itself, freezing the scheduling loop, health checks, and RM callbacks. Fixes: 1. Drop manager.cc.removePartition() from partitionManager.remove(). 2. Remove the unused private removePartition() callback from ClusterContext. 3. In updateSchedulerConfig() and removePartitionsByRMID(), delete removed partitions directly from cc.partitions during iteration under cc.Lock(). 4. Protect pc.stateTime write in handlePartitionEvent() with pc.Lock(). 5. Add unit test covering deadlock reproduction (config update and RM removal) and active app/node cleanup.
e741b76 to
0889f41
Compare
|
Hi @manirajv06, Thanks for reinstating the partition state transitions in #1174! I have updated the PR:
|
Description
Fixes a self-deadlock on the
ClusterContextwrite lock during partition removal:updateSchedulerConfig), and during RM partition removal events (removePartitionsByRMID), the caller stops the partition manager while holding theClusterContextwrite lock (cc.Lock()).partitionManager.Stop()callsmanager.remove()synchronously (since YUNIKORN-2233). In the previous code,manager.remove()ended withmanager.cc.removePartition(name), which attempted to acquire the non-reentrantcc.Lock()again, resulting in an immediate self-deadlock that hung the scheduling loop, REST endpoints, and RM callbacks.This PR:
manager.cc.removePartition()call frompartitionManager.remove(), keepingremove()focused solely on cleaning up internal partition resources (queues, applications, nodes).removePartition()callback fromClusterContext.updateSchedulerConfig()andremovePartitionsByRMID(), deletes removed partitions directly fromcc.partitionsduring iteration under the already-heldcc.Lock(), eliminating intermediate slice/map allocations (per maintainer feedback).pc.stateTimewrite inhandlePartitionEvent()withpc.Lock()/pc.Unlock()to eliminate potential unprotected concurrent writes.TestContext_RemovePartitionincontext_test.gocovering config reload partition removal, RM partition removal events, and active partition removal with running applications and nodes without duplicate fixtures.Type of change
Jira issue
Jira ID : https://issues.apache.org/jira/browse/YUNIKORN-3410
AI Tooling
If an AI tool was used:
Check https://www.apache.org/legal/generative-tooling.html for details.
How has this been tested?
TestContext_RemovePartition.-race(go test -race ./pkg/scheduler/...), 100% passed.golangci-lint run pkg/scheduler/...).go test ./...), 100% passed with zero regressions.Questions:
release-noteslabel.Screenshots or other details
Generated by hedger9487 with assistance from Antigravity.