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

kfaraz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git


The following commit(s) were added to refs/heads/master by this push:
     new 2a644b5482f fix: guard `dutiesRunnables` with lock in DruidCoordinator 
(#20408)
2a644b5482f is described below

commit 2a644b5482f8037554f30de70399397562eda42a
Author: Andreas Maechler <[email protected]>
AuthorDate: Tue Sep 29 02:38:16 2026 -0600

    fix: guard `dutiesRunnables` with lock in DruidCoordinator (#20408)
    
    getStatusOfDuties() read dutiesRunnables without holding lock while
    becomeLeader() and stopAllDutyGroups() mutates it under lock. A request to
    /druid/coordinator/v1/duties racing a leadership change could observe a
    half-cleared list and throw an NPE.
    
    Annotate the field with @GuardedBy and take the lock in getStatusOfDuties().
---
 .../org/apache/druid/server/coordinator/DruidCoordinator.java | 11 ++++++++++-
 .../apache/druid/server/coordinator/DruidCoordinatorTest.java |  9 ++++++---
 2 files changed, 16 insertions(+), 4 deletions(-)

diff --git 
a/server/src/main/java/org/apache/druid/server/coordinator/DruidCoordinator.java
 
b/server/src/main/java/org/apache/druid/server/coordinator/DruidCoordinator.java
index 3467cd8c2b0..d385cad797a 100644
--- 
a/server/src/main/java/org/apache/druid/server/coordinator/DruidCoordinator.java
+++ 
b/server/src/main/java/org/apache/druid/server/coordinator/DruidCoordinator.java
@@ -127,7 +127,10 @@ public class DruidCoordinator
   private final ServiceEmitter emitter;
   private final OverlordClient overlordClient;
   private final ScheduledExecutorFactory executorFactory;
+
+  @GuardedBy("lock")
   private final List<DutiesRunnable> dutiesRunnables = new ArrayList<>();
+
   private final LoadQueueTaskMaster taskMaster;
   private final SegmentLoadQueueManager loadQueueManager;
   private final CoordinatorCustomDutyGroups customDutyGroups;
@@ -352,7 +355,13 @@ public class DruidCoordinator
 
   public List<DutyGroupStatus> getStatusOfDuties()
   {
-    return dutiesRunnables.stream().map(r -> 
r.dutyGroup.getStatus()).collect(Collectors.toList());
+    // Only the copy needs the coordinator lock. Each getStatus() call 
synchronizes on its own duty group,
+    // so keep that work outside the lock to avoid delaying leadership changes 
or duty runs.
+    final List<DutiesRunnable> runnables;
+    synchronized (lock) {
+      runnables = new ArrayList<>(dutiesRunnables);
+    }
+    return runnables.stream().map(r -> 
r.dutyGroup.getStatus()).collect(Collectors.toList());
   }
 
   @LifecycleStart
diff --git 
a/server/src/test/java/org/apache/druid/server/coordinator/DruidCoordinatorTest.java
 
b/server/src/test/java/org/apache/druid/server/coordinator/DruidCoordinatorTest.java
index ad6b9390b55..c3203df0a93 100644
--- 
a/server/src/test/java/org/apache/druid/server/coordinator/DruidCoordinatorTest.java
+++ 
b/server/src/test/java/org/apache/druid/server/coordinator/DruidCoordinatorTest.java
@@ -492,7 +492,8 @@ public class DruidCoordinatorTest
   {
     EasyMock.expect(segmentsMetadataManager.isPollingDatabasePeriodically())
             .andReturn(true).anyTimes();
-    EasyMock.replay(segmentsMetadataManager, metadataRuleManager);
+    
EasyMock.expect(serverInventoryView.isStarted()).andReturn(true).anyTimes();
+    EasyMock.replay(segmentsMetadataManager, metadataRuleManager, 
serverInventoryView);
 
     CoordinatorCustomDutyGroups emptyCustomDutyGroups = new 
CoordinatorCustomDutyGroups(ImmutableSet.of());
     coordinator = new DruidCoordinator(
@@ -539,7 +540,8 @@ public class DruidCoordinatorTest
   {
     EasyMock.expect(segmentsMetadataManager.isPollingDatabasePeriodically())
             .andReturn(true).anyTimes();
-    EasyMock.replay(segmentsMetadataManager, metadataRuleManager);
+    
EasyMock.expect(serverInventoryView.isStarted()).andReturn(true).anyTimes();
+    EasyMock.replay(segmentsMetadataManager, metadataRuleManager, 
serverInventoryView);
     CoordinatorCustomDutyGroup group = new CoordinatorCustomDutyGroup(
         "group1",
         Duration.standardSeconds(1),
@@ -590,7 +592,8 @@ public class DruidCoordinatorTest
   {
     EasyMock.expect(segmentsMetadataManager.isPollingDatabasePeriodically())
             .andReturn(true).anyTimes();
-    EasyMock.replay(segmentsMetadataManager, metadataRuleManager);
+    
EasyMock.expect(serverInventoryView.isStarted()).andReturn(true).anyTimes();
+    EasyMock.replay(segmentsMetadataManager, metadataRuleManager, 
serverInventoryView);
     CoordinatorCustomDutyGroup compactSegmentCustomGroup = new 
CoordinatorCustomDutyGroup(
         "group1",
         Duration.standardSeconds(1),


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

Reply via email to