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]