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

FrankChen021 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 760f3cdfae3 fix: avoid sleeping while holding lifecycle locks (#19810)
760f3cdfae3 is described below

commit 760f3cdfae34b1a7027b778b866db8800faba687
Author: Frank Chen <[email protected]>
AuthorDate: Tue Sep 1 20:40:00 2026 +0800

    fix: avoid sleeping while holding lifecycle locks (#19810)
    
    * Avoid sleeping while holding lifecycle locks
    
    * test: cover executor lock retry paths
---
 .../overlord/autoscaling/gce/GceAutoScaler.java    | 10 ++-
 .../worker/executor/ExecutorLifecycle.java         | 43 ++++++++----
 .../worker/executor/ExecutorLifecycleTest.java     | 82 ++++++++++++++++++++++
 3 files changed, 118 insertions(+), 17 deletions(-)

diff --git 
a/extensions-contrib/gce-extensions/src/main/java/org/apache/druid/indexing/overlord/autoscaling/gce/GceAutoScaler.java
 
b/extensions-contrib/gce-extensions/src/main/java/org/apache/druid/indexing/overlord/autoscaling/gce/GceAutoScaler.java
index e307b3217f7..35fdceb57a5 100644
--- 
a/extensions-contrib/gce-extensions/src/main/java/org/apache/druid/indexing/overlord/autoscaling/gce/GceAutoScaler.java
+++ 
b/extensions-contrib/gce-extensions/src/main/java/org/apache/druid/indexing/overlord/autoscaling/gce/GceAutoScaler.java
@@ -67,7 +67,7 @@ public class GceAutoScaler implements 
AutoScaler<GceEnvironmentConfig>
   private final int minNumWorkers;
   private final int maxNumWorkers;
 
-  private Compute cachedComputeService = null;
+  private volatile Compute cachedComputeService = null;
 
   private static final long POLL_INTERVAL_MS = 5 * 1000;  // 5 sec
   private static final int RUNNING_INSTANCES_MAX_RETRIES = 10;
@@ -138,7 +138,7 @@ public class GceAutoScaler implements 
AutoScaler<GceEnvironmentConfig>
         .build();
   }
 
-  private synchronized Compute createComputeService()
+  private Compute createComputeService()
       throws IOException, GeneralSecurityException, InterruptedException, 
GceServiceException
   {
     final int maxRetries = 5;
@@ -155,7 +155,11 @@ public class GceAutoScaler implements 
AutoScaler<GceEnvironmentConfig>
       log.info("Creating new ComputeService [%d/%d]", retries + 1, maxRetries);
 
       try {
-        cachedComputeService = createComputeServiceImpl();
+        synchronized (this) {
+          if (cachedComputeService == null) {
+            cachedComputeService = createComputeServiceImpl();
+          }
+        }
         retries++;
       }
       catch (Throwable e) {
diff --git 
a/indexing-service/src/main/java/org/apache/druid/indexing/worker/executor/ExecutorLifecycle.java
 
b/indexing-service/src/main/java/org/apache/druid/indexing/worker/executor/ExecutorLifecycle.java
index 44a2d5508ca..9ec02c66394 100644
--- 
a/indexing-service/src/main/java/org/apache/druid/indexing/worker/executor/ExecutorLifecycle.java
+++ 
b/indexing-service/src/main/java/org/apache/druid/indexing/worker/executor/ExecutorLifecycle.java
@@ -120,24 +120,12 @@ public class ExecutorLifecycle
             );
 
             log.info("Attempting to lock file[%s].", taskLockFile);
-            final long startLocking = System.currentTimeMillis();
-            final long timeout = 
DateTimes.utc(startLocking).plus(taskConfig.getDirectoryLockTimeout()).getMillis();
-            while (taskLockFileLock == null && System.currentTimeMillis() < 
timeout) {
-              taskLockFileLock = taskLockChannel.tryLock();
-              if (taskLockFileLock == null) {
-                Thread.sleep(100);
-              }
-            }
-
-            if (taskLockFileLock == null) {
-              throw new ISE("Could not acquire lock file[%s] within %,dms.", 
taskLockFile, timeout - startLocking);
-            } else {
-              log.info("Acquired lock file[%s] in %,dms.", taskLockFile, 
System.currentTimeMillis() - startLocking);
-            }
           } else {
             throw new ISE("Already started!");
           }
         }
+
+        acquireTaskFileLock(taskLockChannel, taskLockFile);
       }
       catch (IOException e) {
         throw new RuntimeException(e);
@@ -204,6 +192,33 @@ public class ExecutorLifecycle
     );
   }
 
+  FileLock acquireTaskFileLock(FileChannel lockChannel, File taskLockFile)
+      throws IOException, InterruptedException
+  {
+    final long startLocking = System.currentTimeMillis();
+    final long timeout = 
DateTimes.utc(startLocking).plus(taskConfig.getDirectoryLockTimeout()).getMillis();
+    FileLock acquiredLock = null;
+    while (acquiredLock == null && System.currentTimeMillis() < timeout) {
+      synchronized (this) {
+        acquiredLock = lockChannel.tryLock();
+        if (acquiredLock != null) {
+          taskLockFileLock = acquiredLock;
+        }
+      }
+
+      if (acquiredLock == null) {
+        Thread.sleep(100);
+      }
+    }
+
+    if (acquiredLock == null) {
+      throw new ISE("Could not acquire lock file[%s] within %,dms.", 
taskLockFile, timeout - startLocking);
+    } else {
+      log.info("Acquired lock file[%s] in %,dms.", taskLockFile, 
System.currentTimeMillis() - startLocking);
+      return acquiredLock;
+    }
+  }
+
   public void join()
   {
     try {
diff --git 
a/indexing-service/src/test/java/org/apache/druid/indexing/worker/executor/ExecutorLifecycleTest.java
 
b/indexing-service/src/test/java/org/apache/druid/indexing/worker/executor/ExecutorLifecycleTest.java
new file mode 100644
index 00000000000..1419186c1c5
--- /dev/null
+++ 
b/indexing-service/src/test/java/org/apache/druid/indexing/worker/executor/ExecutorLifecycleTest.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.indexing.worker.executor;
+
+import org.apache.druid.indexing.common.actions.TaskActionClientFactory;
+import org.apache.druid.indexing.common.config.TaskConfig;
+import org.apache.druid.indexing.overlord.TaskRunner;
+import org.apache.druid.jackson.DefaultObjectMapper;
+import org.apache.druid.java.util.common.ISE;
+import org.joda.time.Period;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.io.File;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+
+public class ExecutorLifecycleTest
+{
+  @Test
+  public void testAcquireTaskFileLockRetriesUntilSuccess() throws Exception
+  {
+    final ExecutorLifecycle lifecycle = createLifecycle(Period.seconds(1));
+    final FileChannel channel = Mockito.mock(FileChannel.class);
+    final FileLock lock = Mockito.mock(FileLock.class);
+    Mockito.when(channel.tryLock()).thenReturn(null, lock);
+
+    Assert.assertSame(lock, lifecycle.acquireTaskFileLock(channel, new 
File("task.lock")));
+    Mockito.verify(channel, Mockito.times(2)).tryLock();
+
+    lifecycle.stop();
+    Mockito.verify(lock).release();
+  }
+
+  @Test
+  public void testAcquireTaskFileLockTimesOut() throws Exception
+  {
+    final ExecutorLifecycle lifecycle = createLifecycle(Period.ZERO);
+    final FileChannel channel = Mockito.mock(FileChannel.class);
+    final File lockFile = new File("task.lock");
+
+    final ISE exception = Assert.assertThrows(
+        ISE.class,
+        () -> lifecycle.acquireTaskFileLock(channel, lockFile)
+    );
+
+    Assert.assertEquals("Could not acquire lock file[task.lock] within 0ms.", 
exception.getMessage());
+    Mockito.verifyNoInteractions(channel);
+    lifecycle.stop();
+  }
+
+  private static ExecutorLifecycle createLifecycle(Period directoryLockTimeout)
+  {
+    final TaskConfig taskConfig = Mockito.mock(TaskConfig.class);
+    
Mockito.when(taskConfig.getDirectoryLockTimeout()).thenReturn(directoryLockTimeout);
+    return new ExecutorLifecycle(
+        new ExecutorLifecycleConfig(),
+        taskConfig,
+        Mockito.mock(TaskActionClientFactory.class),
+        Mockito.mock(TaskRunner.class),
+        new DefaultObjectMapper()
+    );
+  }
+}


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

Reply via email to