This is an automated email from the ASF dual-hosted git repository.

HuangTing-Yao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/yunikorn-core.git


The following commit(s) were added to refs/heads/master by this push:
     new 4b35f0d3 [YUNIKORN-3379] Fix scheduler panic on PLACEHOLDER_REPLACED 
release without linked replacement (#1150)
4b35f0d3 is described below

commit 4b35f0d39d8424a0415431c1e05d1b32dc827f48
Author: hedger9487 <[email protected]>
AuthorDate: Mon Sep 7 20:58:39 2026 +0800

    [YUNIKORN-3379] Fix scheduler panic on PLACEHOLDER_REPLACED release without 
linked replacement (#1150)
    
    When an AllocationRelease of type PLACEHOLDER_REPLACED is processed for a
    placeholder that has no linked replacement allocation (ph.GetRelease() == 
nil),
    replacePlaceholderOnNode dereferences confirmed.GetAllocatedResource() 
without
    a nil check, causing a SIGSEGV panic and taking down the scheduler.
    
    This fix:
    1. Adds a defensive nil check for confirmed in replacePlaceholderOnNode.
    2. Emits a descriptive WARN log and cleans up the placeholder from the node 
via
       node.RemoveAllocation(), accumulating the freed resource into total so 
queue
       allocated resources are correctly decremented.
    3. Safely returns nil to caller, allowing removeAllocation to decrement the 
cluster
       allocation count and notify the RM of the released placeholder.
    4. Adds TestRemoveAllocationPlaceholderReplacedWithoutReplacement in 
partition_test.go,
       reproducing the defect and asserting that node, queue, and partition 
states are
       cleanly restored.
    
    Closes: #1150
    
    Signed-off-by: HuangTing-Yao <[email protected]>
---
 pkg/scheduler/partition.go      |  9 +++++++++
 pkg/scheduler/partition_test.go | 40 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 49 insertions(+)

diff --git a/pkg/scheduler/partition.go b/pkg/scheduler/partition.go
index 3c3c9f20..c162e530 100644
--- a/pkg/scheduler/partition.go
+++ b/pkg/scheduler/partition.go
@@ -1598,6 +1598,15 @@ func (pc *PartitionContext) removeAllocation(release 
*si.AllocationRelease) ([]*
 // real allocation.
 func (pc *PartitionContext) replacePlaceholderOnNode(alloc 
*objects.Allocation, node *objects.Node, total *resources.Resource) 
*objects.Allocation {
        confirmed := alloc.GetRelease()
+       if confirmed == nil {
+               log.Log(log.SchedPartition).Warn("unexpected placeholder 
replacement without replacement allocation, removing placeholder from node",
+                       zap.String("nodeID", alloc.GetNodeID()),
+                       zap.String("allocationKey", alloc.GetAllocationKey()))
+               if node.RemoveAllocation(alloc.GetAllocationKey()) != nil {
+                       total.AddTo(alloc.GetAllocatedResource())
+               }
+               return nil
+       }
        // we need to check the resources equality
        delta := resources.Sub(confirmed.GetAllocatedResource(), 
alloc.GetAllocatedResource())
        // Any negative value in the delta means that at least one of the 
requested resource in the
diff --git a/pkg/scheduler/partition_test.go b/pkg/scheduler/partition_test.go
index eb67e11d..40dcfbd1 100644
--- a/pkg/scheduler/partition_test.go
+++ b/pkg/scheduler/partition_test.go
@@ -5718,3 +5718,43 @@ func TestRemoveAppWithReservations(t *testing.T) {
        partition.removeApplication(appID1)
        assert.Equal(t, 0, partition.getReservationCount())
 }
+
+func TestRemoveAllocationPlaceholderReplacedWithoutReplacement(t *testing.T) {
+       setupUGM()
+       partition, err := newBasePartition()
+       assert.NilError(t, err, "partition create failed")
+       defer partition.userGroupCache.Stop()
+
+       // add a new app
+       app := newApplication(appID1, "default", defQueue)
+       err = partition.AddApplication(app)
+       assert.NilError(t, err, "add application to partition should not have 
failed")
+
+       // add a node with placeholder allocation
+       nodeRes := 
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 10})
+       node1 := newNodeMaxResource(nodeID1, nodeRes)
+       appRes := 
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 1})
+       ph := newAllocationTG("placeholder", appID1, nodeID1, taskGroup, 
appRes, true)
+       err = partition.AddNode(node1)
+       assert.NilError(t, err)
+       _, allocCreated, err := partition.UpdateAllocation(ph)
+       assert.NilError(t, err)
+       assert.Check(t, allocCreated)
+       assert.Equal(t, 1, partition.GetTotalAllocationCount())
+       assert.Assert(t, 
resources.Equals(partition.GetQueue(defQueue).GetAllocatedResource(), appRes))
+
+       // Release with PLACEHOLDER_REPLACED when no replacement was ever 
linked (ph.GetRelease() == nil)
+       release := &si.AllocationRelease{
+               PartitionName:   partition.Name,
+               ApplicationID:   appID1,
+               AllocationKey:   "placeholder",
+               TerminationType: si.TerminationType_PLACEHOLDER_REPLACED,
+       }
+
+       released, confirmed := partition.removeAllocation(release)
+       assert.Assert(t, confirmed == nil, "confirmed allocation should be nil 
when no replacement exists")
+       assert.Equal(t, 1, len(released), "placeholder should be in released 
list")
+       assert.Equal(t, 0, partition.GetTotalAllocationCount(), "allocation 
count should be 0 after placeholder removed")
+       assert.Assert(t, node1.GetAllocation("placeholder") == nil, 
"placeholder should be removed from node")
+       assert.Assert(t, 
resources.IsZero(partition.GetQueue(defQueue).GetAllocatedResource()), "queue 
resource should be zero after placeholder removed")
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to