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

frankvicky pushed a commit to branch 4.0
in repository https://gitbox.apache.org/repos/asf/kafka.git


The following commit(s) were added to refs/heads/4.0 by this push:
     new 1cc14f63434 KAFKA-19334 MetadataShell execution unintentionally 
deletes lock file (#19817)
1cc14f63434 is described below

commit 1cc14f6343462716c0add9e358ca99cba74ec37a
Author: Okada Haruki <[email protected]>
AuthorDate: Mon Jun 9 13:24:26 2025 +0900

    KAFKA-19334 MetadataShell execution unintentionally deletes lock file 
(#19817)
    
    ## Summary
    - MetadataShell may deletes lock file unintentionally when it exists or
    fails to acquire lock. If there's running server, this causes unexpected
    result as below:
      * MetadataShell succeeds on 2nd run unexpectedly
      * Even worse, LogManager/RaftManager's lock also no longer work from
    concurrent Kafka process startup
    
    Reviewers: TengYao Chi <[email protected]>
    # Conflicts:
    #       
shell/src/test/java/org/apache/kafka/shell/MetadataShellIntegrationTest.java
---
 .../java/org/apache/kafka/server/util/FileLock.java  |  8 ++++++++
 .../java/org/apache/kafka/shell/MetadataShell.java   |  6 +++---
 .../kafka/shell/MetadataShellIntegrationTest.java    | 20 ++++++++++++--------
 3 files changed, 23 insertions(+), 11 deletions(-)

diff --git 
a/server-common/src/main/java/org/apache/kafka/server/util/FileLock.java 
b/server-common/src/main/java/org/apache/kafka/server/util/FileLock.java
index 4f55b4aebcd..b06f239183a 100644
--- a/server-common/src/main/java/org/apache/kafka/server/util/FileLock.java
+++ b/server-common/src/main/java/org/apache/kafka/server/util/FileLock.java
@@ -91,4 +91,12 @@ public class FileLock {
         }
         channel.close();
     }
+
+    /**
+     * Unlock the file and close the associated FileChannel
+     */
+    public synchronized void unlockAndClose() throws IOException {
+        unlock();
+        channel.close();
+    }
 }
diff --git a/shell/src/main/java/org/apache/kafka/shell/MetadataShell.java 
b/shell/src/main/java/org/apache/kafka/shell/MetadataShell.java
index 0242e349835..5600aa5e5ef 100644
--- a/shell/src/main/java/org/apache/kafka/shell/MetadataShell.java
+++ b/shell/src/main/java/org/apache/kafka/shell/MetadataShell.java
@@ -128,7 +128,7 @@ public final class MetadataShell {
                     "directory before proceeding.");
             }
         } catch (Throwable e) {
-            fileLock.destroy();
+            fileLock.unlockAndClose();
             throw e;
         }
         return fileLock;
@@ -232,9 +232,9 @@ public final class MetadataShell {
         Utils.closeQuietly(snapshotFileReader, "raftManager");
         if (fileLock != null) {
             try {
-                fileLock.destroy();
+                fileLock.unlockAndClose();
             } catch (Exception e) {
-                log.error("Error destroying fileLock", e);
+                log.error("Error cleaning up fileLock", e);
             } finally {
                 fileLock = null;
             }
diff --git 
a/shell/src/test/java/org/apache/kafka/shell/MetadataShellIntegrationTest.java 
b/shell/src/test/java/org/apache/kafka/shell/MetadataShellIntegrationTest.java
index 970075d959c..a81642d7450 100644
--- 
a/shell/src/test/java/org/apache/kafka/shell/MetadataShellIntegrationTest.java
+++ 
b/shell/src/test/java/org/apache/kafka/shell/MetadataShellIntegrationTest.java
@@ -33,7 +33,7 @@ import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.NoSuchFileException;
-import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.ExecutionException;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -91,18 +91,22 @@ public class MetadataShellIntegrationTest {
             if (canLock) {
                 assertEquals(NoSuchFileException.class,
                     assertThrows(ExecutionException.class,
-                        () -> env.shell.run(Collections.emptyList())).
+                        () -> env.shell.run(List.of())).
                             getCause().getClass());
             } else {
                 FileLock fileLock = new FileLock(new File(env.tempDir, 
".lock"));
                 try {
                     fileLock.lock();
-                    assertEquals("Unable to lock " + 
env.tempDir.getAbsolutePath() +
-                        ". Please ensure that no broker or controller process 
is using this " +
-                        "directory before proceeding.",
-                        assertThrows(RuntimeException.class,
-                            () -> env.shell.run(Collections.emptyList())).
-                                getMessage());
+                    // We had a bug where the shell can lock the directory 
unintentionally
+                    // at the 2nd run, so we check that it fails (See 
KAFKA-19334)
+                    for (int i = 0; i < 2; i++) {
+                        assertEquals("Unable to lock " + 
env.tempDir.getAbsolutePath() +
+                                     ". Please ensure that no broker or 
controller process is using this " +
+                                     "directory before proceeding.",
+                                     assertThrows(RuntimeException.class,
+                                                  () -> 
env.shell.run(List.of())).
+                                             getMessage());
+                    }
                 } finally {
                     fileLock.destroy();
                 }

Reply via email to