You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@artemis.apache.org by gt...@apache.org on 2026/06/29 13:48:27 UTC

(artemis) branch main updated: ARTEMIS-6117 allow configuration of journal file lock check period

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

gtully pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/artemis.git


The following commit(s) were added to refs/heads/main by this push:
     new b142ba6765 ARTEMIS-6117 allow configuration of journal file lock check period
b142ba6765 is described below

commit b142ba67659518a041f2090fe9750282b5efe0e0
Author: Gary Tully <ga...@gmail.com>
AuthorDate: Tue Jun 16 15:22:50 2026 +0100

    ARTEMIS-6117 allow configuration of journal file lock check period
    
    Assisted-by: Claude
---
 .../api/config/ActiveMQDefaultConfiguration.java   |  20 ++
 .../artemis/core/config/Configuration.java         |   8 +
 .../core/config/impl/ConfigurationImpl.java        |  30 ++-
 .../deployers/impl/FileConfigurationParser.java    |   4 +
 .../core/server/impl/ActiveMQServerImpl.java       |   4 +-
 .../core/server/impl/FileLockNodeManager.java      |  56 +++--
 .../resources/schema/artemis-configuration.xsd     |  16 ++
 .../core/config/impl/ConfigurationImplTest.java    |   2 +
 .../config/impl/DefaultsFileConfigurationTest.java |   4 +
 .../impl/FileLockNodeManagerMockRecoveryTest.java  | 275 +++++++++++++++++++++
 .../server/impl/FileLockNodeManagerRetryTest.java  | 255 +++++++++++++++++++
 .../core/server/impl/FileLockNodeManagerTest.java  |  50 ++++
 .../core/server/impl/TestConfigurationBuilder.java |  48 ++++
 docs/user-manual/configuration-index.adoc          |   8 +
 docs/user-manual/persistence.adoc                  |  10 +
 .../tests/util/ColocatedActiveMQServer.java        |   2 +-
 16 files changed, 774 insertions(+), 18 deletions(-)

diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ActiveMQDefaultConfiguration.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ActiveMQDefaultConfiguration.java
index 1eccd294b0..80801e8f2d 100644
--- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ActiveMQDefaultConfiguration.java
+++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ActiveMQDefaultConfiguration.java
@@ -167,6 +167,12 @@ public final class ActiveMQDefaultConfiguration {
    // how long (in ms) to wait to acquire a file lock on the journal
    private static long DEFAULT_JOURNAL_LOCK_ACQUISITION_TIMEOUT = -1;
 
+   // how long (in ms) between journal lock health checks
+   private static long DEFAULT_JOURNAL_LOCK_MONITOR_TIMEOUT = 2000;
+
+   // how many times to retry a failed journal lock health check before declaring the lock lost
+   private static int DEFAULT_JOURNAL_LOCK_MONITOR_MAX_RETRIES = 0;
+
    // true means that the server supports wild card routing
    private static boolean DEFAULT_WILDCARD_ROUTING_ENABLED = true;
 
@@ -827,6 +833,20 @@ public final class ActiveMQDefaultConfiguration {
       return DEFAULT_JOURNAL_LOCK_ACQUISITION_TIMEOUT;
    }
 
+   /**
+    * how long (in ms) between journal lock health checks
+    */
+   public static long getDefaultJournalLockMonitorTimeout() {
+      return DEFAULT_JOURNAL_LOCK_MONITOR_TIMEOUT;
+   }
+
+   /**
+    * how many times to retry a failed journal lock health check before declaring the lock lost
+    */
+   public static int getDefaultJournalLockMonitorMaxRetries() {
+      return DEFAULT_JOURNAL_LOCK_MONITOR_MAX_RETRIES;
+   }
+
    /**
     * {@code true} means that the server supports wild card routing
     */
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/Configuration.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/Configuration.java
index 67aa0279cc..b8f6cb1e1d 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/Configuration.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/Configuration.java
@@ -1237,6 +1237,14 @@ public interface Configuration {
 
    long getJournalLockAcquisitionTimeout();
 
+   Configuration setJournalLockMonitorTimeout(long journalLockMonitorTimeout);
+
+   long getJournalLockMonitorTimeout();
+
+   Configuration setJournalLockMonitorMaxRetries(int journalLockMonitorMaxRetries);
+
+   int getJournalLockMonitorMaxRetries();
+
    HAPolicyConfiguration getHAPolicyConfiguration();
 
    Configuration setHAPolicyConfiguration(HAPolicyConfiguration haPolicyConfiguration);
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java
index 0a2e18543a..da849420b5 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java
@@ -402,6 +402,10 @@ public class ConfigurationImpl extends javax.security.auth.login.Configuration i
 
    private long journalLockAcquisitionTimeout = ActiveMQDefaultConfiguration.getDefaultJournalLockAcquisitionTimeout();
 
+   private long journalLockMonitorTimeout = ActiveMQDefaultConfiguration.getDefaultJournalLockMonitorTimeout();
+
+   private int journalLockMonitorMaxRetries = ActiveMQDefaultConfiguration.getDefaultJournalLockMonitorMaxRetries();
+
    private HAPolicyConfiguration haPolicyConfiguration;
 
    private StoreConfiguration storeConfiguration;
@@ -3010,7 +3014,7 @@ public class ConfigurationImpl extends javax.security.auth.login.Configuration i
                           scheduledThreadPoolMaxSize, securityEnabled, populateValidatedUser,
                           securityInvalidationInterval, securitySettings, serverDumpInterval, threadPoolMaxSize,
                           transactionTimeout, transactionTimeoutScanPeriod, wildcardConfiguration, resolveProtocols,
-                          journalLockAcquisitionTimeout, connectionTtlCheckInterval);
+                          journalLockAcquisitionTimeout, journalLockMonitorTimeout, journalLockMonitorMaxRetries, connectionTtlCheckInterval);
    }
 
    @Override
@@ -3091,6 +3095,8 @@ public class ConfigurationImpl extends javax.security.auth.login.Configuration i
              Objects.equals(wildcardConfiguration, other.wildcardConfiguration) &&
              resolveProtocols == other.resolveProtocols &&
              journalLockAcquisitionTimeout == other.journalLockAcquisitionTimeout &&
+             journalLockMonitorTimeout == other.journalLockMonitorTimeout &&
+             journalLockMonitorMaxRetries == other.journalLockMonitorMaxRetries &&
              connectionTtlCheckInterval == other.connectionTtlCheckInterval &&
              journalDatasync == other.journalDatasync &&
              Objects.equals(globalMaxSize, other.globalMaxSize) &&
@@ -3132,6 +3138,28 @@ public class ConfigurationImpl extends javax.security.auth.login.Configuration i
       return journalLockAcquisitionTimeout;
    }
 
+   @Override
+   public ConfigurationImpl setJournalLockMonitorTimeout(long journalLockMonitorTimeout) {
+      this.journalLockMonitorTimeout = journalLockMonitorTimeout;
+      return this;
+   }
+
+   @Override
+   public long getJournalLockMonitorTimeout() {
+      return journalLockMonitorTimeout;
+   }
+
+   @Override
+   public ConfigurationImpl setJournalLockMonitorMaxRetries(int journalLockMonitorMaxRetries) {
+      this.journalLockMonitorMaxRetries = journalLockMonitorMaxRetries;
+      return this;
+   }
+
+   @Override
+   public int getJournalLockMonitorMaxRetries() {
+      return journalLockMonitorMaxRetries;
+   }
+
    @Override
    public HAPolicyConfiguration getHAPolicyConfiguration() {
       return haPolicyConfiguration;
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java
index bd6c5ccc3f..4d5824b15c 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java
@@ -854,6 +854,10 @@ public final class FileConfigurationParser extends XMLConfigurationUtil {
 
       config.setJournalLockAcquisitionTimeout(getLong(e, "journal-lock-acquisition-timeout", config.getJournalLockAcquisitionTimeout(), MINUS_ONE_OR_GT_ZERO));
 
+      config.setJournalLockMonitorTimeout(getLong(e, "journal-lock-monitor-timeout", config.getJournalLockMonitorTimeout(), GT_ZERO));
+
+      config.setJournalLockMonitorMaxRetries(getInteger(e, "journal-lock-monitor-max-retries", config.getJournalLockMonitorMaxRetries(), GE_ZERO));
+
       if (e.hasAttribute("wild-card-routing-enabled")) {
          config.setWildcardRoutingEnabled(getBoolean(e, "wild-card-routing-enabled", config.isWildcardRoutingEnabled()));
       }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java
index 3f73c63d06..4fb63b0b00 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java
@@ -637,12 +637,12 @@ public class ActiveMQServerImpl implements ActiveMQServer {
          } else if (haType == null || haType == HAPolicyConfiguration.TYPE.PRIMARY_ONLY) {
             logger.debug("Detected no Shared Store HA options on JDBC store");
             //PRIMARY_ONLY should be the default HA option when HA isn't configured
-            manager = new FileLockNodeManager(directory, replicatingBackup, configuration.getJournalLockAcquisitionTimeout(), scheduledPool);
+            manager = new FileLockNodeManager(directory, replicatingBackup, configuration, scheduledPool);
          } else {
             throw new IllegalArgumentException("JDBC persistence allows only Shared Store HA options");
          }
       } else {
-         manager = new FileLockNodeManager(directory, replicatingBackup, configuration.getJournalLockAcquisitionTimeout(), scheduledPool);
+         manager = new FileLockNodeManager(directory, replicatingBackup, configuration, scheduledPool);
       }
       return manager;
    }
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/FileLockNodeManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/FileLockNodeManager.java
index ee765811d0..76ef8fbb08 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/FileLockNodeManager.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/FileLockNodeManager.java
@@ -27,8 +27,11 @@ import java.nio.channels.FileLock;
 import java.util.Date;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 
+import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration;
 import org.apache.activemq.artemis.api.core.SimpleString;
+import org.apache.activemq.artemis.core.config.Configuration;
 import org.apache.activemq.artemis.core.server.ActivateCallback;
 import org.apache.activemq.artemis.core.server.ActiveMQLockAcquisitionTimeoutException;
 import org.apache.activemq.artemis.core.server.ActiveMQScheduledComponent;
@@ -71,29 +74,35 @@ public class FileLockNodeManager extends FileBasedNodeManager {
 
    private final long lockAcquisitionTimeoutNanos;
 
+   private final long journalLockMonitorTimeoutNanos;
+
+   private final int journalLockMonitorMaxRetries;
+
    protected boolean interrupted = false;
 
    private final ScheduledExecutorService scheduledPool;
 
-   public FileLockNodeManager(final File directory, boolean replicatedBackup, ScheduledExecutorService scheduledPool) {
-      super(replicatedBackup, directory);
-      this.scheduledPool = scheduledPool;
-      this.lockAcquisitionTimeoutNanos = -1;
-   }
-
    public FileLockNodeManager(final File directory, boolean replicatedBackup) {
       super(replicatedBackup, directory);
       this.scheduledPool = null;
-      this.lockAcquisitionTimeoutNanos = -1;
+      long timeout = ActiveMQDefaultConfiguration.getDefaultJournalLockAcquisitionTimeout();
+      this.lockAcquisitionTimeoutNanos = timeout == -1 ? -1 : TimeUnit.MILLISECONDS.toNanos(timeout);
+      this.journalLockMonitorTimeoutNanos = TimeUnit.MILLISECONDS.toNanos(
+         ActiveMQDefaultConfiguration.getDefaultJournalLockMonitorTimeout());
+      this.journalLockMonitorMaxRetries = ActiveMQDefaultConfiguration.getDefaultJournalLockMonitorMaxRetries();
    }
 
    public FileLockNodeManager(final File directory,
                               boolean replicatedBackup,
-                              long lockAcquisitionTimeout,
+                              Configuration configuration,
                               ScheduledExecutorService scheduledPool) {
       super(replicatedBackup, directory);
       this.scheduledPool = scheduledPool;
-      this.lockAcquisitionTimeoutNanos = lockAcquisitionTimeout == -1 ? -1 : TimeUnit.MILLISECONDS.toNanos(lockAcquisitionTimeout);
+      this.lockAcquisitionTimeoutNanos = configuration.getJournalLockAcquisitionTimeout() == -1
+         ? -1
+         : TimeUnit.MILLISECONDS.toNanos(configuration.getJournalLockAcquisitionTimeout());
+      this.journalLockMonitorTimeoutNanos = TimeUnit.MILLISECONDS.toNanos(configuration.getJournalLockMonitorTimeout());
+      this.journalLockMonitorMaxRetries = configuration.getJournalLockMonitorMaxRetries();
    }
 
    @Override
@@ -477,7 +486,7 @@ public class FileLockNodeManager extends FileBasedNodeManager {
    private synchronized void startLockMonitoring() {
       logger.debug("Starting the lock monitor");
       if (monitorLock == null) {
-         monitorLock = new MonitorLock(scheduledPool, LOCK_MONITOR_TIMEOUT_NANOS, LOCK_MONITOR_TIMEOUT_NANOS, TimeUnit.NANOSECONDS, false);
+         monitorLock = new MonitorLock(scheduledPool, journalLockMonitorTimeoutNanos, journalLockMonitorTimeoutNanos, TimeUnit.NANOSECONDS, false, journalLockMonitorMaxRetries);
          monitorLock.start();
       } else {
          logger.debug("Lock monitor was already started");
@@ -503,7 +512,10 @@ public class FileLockNodeManager extends FileBasedNodeManager {
 
    // This has been introduced to help ByteMan test testLockMonitorInvalid on JDK 11: sun.nio.ch.FileLockImpl::isValid
    // can affecting setPrimary, causing an infinite loop due to java.nio.channels.OverlappingFileLockException on tryLock
-   private boolean isPrimaryLockLost() {
+   // Made protected to allow testing with controlled lock failures.
+   // Subclasses overriding this method should maintain the contract: return true when the primary lock is lost,
+   // false when the lock is healthy. The return value directly controls whether MonitorLock triggers notifyLostLock().
+   protected boolean isPrimaryLockLost() {
       final FileLock lock = this.primaryLock;
       return (lock != null && !lock.isValid()) || lock == null;
    }
@@ -511,12 +523,17 @@ public class FileLockNodeManager extends FileBasedNodeManager {
    private MonitorLock monitorLock;
 
    public class MonitorLock extends ActiveMQScheduledComponent {
+      private final int maxRetries;
+      private final AtomicInteger consecutiveFailures = new AtomicInteger(0);
+
       public MonitorLock(ScheduledExecutorService scheduledExecutorService,
                             long initialDelay,
                             long checkPeriod,
                             TimeUnit timeUnit,
-                            boolean onDemand) {
+                            boolean onDemand,
+                            int maxRetries) {
          super(scheduledExecutorService, initialDelay, checkPeriod, timeUnit, onDemand);
+         this.maxRetries = maxRetries;
       }
 
 
@@ -556,8 +573,19 @@ public class FileLockNodeManager extends FileBasedNodeManager {
          }
 
          if (lostLock) {
-            logger.warn("Lost the lock according to the monitor, notifying listeners");
-            notifyLostLock();
+            int failures = consecutiveFailures.incrementAndGet();
+            if (failures > maxRetries) {
+               logger.warn("Lost the lock according to the monitor after {} failed check(s), notifying listeners", failures);
+               notifyLostLock();
+            } else {
+               logger.warn("Lock check failed ({}/{}), will retry on next check period", failures, maxRetries + 1);
+            }
+         } else {
+            // Reset the failure counter on successful check
+            int previousFailures = consecutiveFailures.getAndSet(0);
+            if (previousFailures > 0) {
+               logger.info("Lock check succeeded after {} previous failure(s), resetting failure counter", previousFailures);
+            }
          }
       }
 
diff --git a/artemis-server/src/main/resources/schema/artemis-configuration.xsd b/artemis-server/src/main/resources/schema/artemis-configuration.xsd
index 2af3e1b144..cbb95bdc0f 100644
--- a/artemis-server/src/main/resources/schema/artemis-configuration.xsd
+++ b/artemis-server/src/main/resources/schema/artemis-configuration.xsd
@@ -171,6 +171,22 @@
             </xsd:annotation>
          </xsd:element>
 
+         <xsd:element name="journal-lock-monitor-timeout" type="xsd:long" default="2000" maxOccurs="1" minOccurs="0">
+            <xsd:annotation>
+               <xsd:documentation>
+                  how long (in ms) between lock health checks
+               </xsd:documentation>
+            </xsd:annotation>
+         </xsd:element>
+
+         <xsd:element name="journal-lock-monitor-max-retries" type="xsd:int" default="0" maxOccurs="1" minOccurs="0">
+            <xsd:annotation>
+               <xsd:documentation>
+                  how many times to retry a failed lock health check before declaring the lock lost (useful for transient storage outages)
+               </xsd:documentation>
+            </xsd:annotation>
+         </xsd:element>
+
          <xsd:element name="wild-card-routing-enabled" type="xsd:boolean" default="true" maxOccurs="1" minOccurs="0">
             <xsd:annotation>
                <xsd:documentation>
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImplTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImplTest.java
index 4fd0904359..ad129038f2 100644
--- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImplTest.java
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImplTest.java
@@ -174,6 +174,8 @@ public class ConfigurationImplTest extends AbstractConfigurationTestBase {
       assertEquals(ActiveMQDefaultConfiguration.getDefaultLargeMessagesDir(), conf.getLargeMessagesDirectory());
       assertEquals(ActiveMQDefaultConfiguration.getDefaultJournalCompactPercentage(), conf.getJournalCompactPercentage());
       assertEquals(ActiveMQDefaultConfiguration.getDefaultJournalLockAcquisitionTimeout(), conf.getJournalLockAcquisitionTimeout());
+      assertEquals(ActiveMQDefaultConfiguration.getDefaultJournalLockMonitorTimeout(), conf.getJournalLockMonitorTimeout());
+      assertEquals(ActiveMQDefaultConfiguration.getDefaultJournalLockMonitorMaxRetries(), conf.getJournalLockMonitorMaxRetries());
       assertEquals(ArtemisConstants.DEFAULT_JOURNAL_BUFFER_TIMEOUT_AIO, conf.getJournalBufferTimeout_AIO());
       assertEquals(ArtemisConstants.DEFAULT_JOURNAL_BUFFER_TIMEOUT_NIO, conf.getJournalBufferTimeout_NIO());
       assertEquals(ArtemisConstants.DEFAULT_JOURNAL_BUFFER_SIZE_AIO, conf.getJournalBufferSize_AIO());
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/DefaultsFileConfigurationTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/DefaultsFileConfigurationTest.java
index 8701f6cc31..8314f8d352 100644
--- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/DefaultsFileConfigurationTest.java
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/DefaultsFileConfigurationTest.java
@@ -110,6 +110,10 @@ public class DefaultsFileConfigurationTest extends AbstractConfigurationTestBase
 
       assertEquals(ActiveMQDefaultConfiguration.getDefaultJournalLockAcquisitionTimeout(), conf.getJournalLockAcquisitionTimeout());
 
+      assertEquals(ActiveMQDefaultConfiguration.getDefaultJournalLockMonitorTimeout(), conf.getJournalLockMonitorTimeout());
+
+      assertEquals(ActiveMQDefaultConfiguration.getDefaultJournalLockMonitorMaxRetries(), conf.getJournalLockMonitorMaxRetries());
+
       assertEquals(ActiveMQDefaultConfiguration.getDefaultJournalMinFiles(), conf.getJournalMinFiles());
 
       assertEquals(ActiveMQDefaultConfiguration.getDefaultJournalMaxIoAio(), conf.getJournalMaxIO_AIO());
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/FileLockNodeManagerMockRecoveryTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/FileLockNodeManagerMockRecoveryTest.java
new file mode 100644
index 0000000000..fd6242177a
--- /dev/null
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/FileLockNodeManagerMockRecoveryTest.java
@@ -0,0 +1,275 @@
+/*
+ * 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.activemq.artemis.core.server.impl;
+
+import java.io.File;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.activemq.artemis.core.server.ActivateCallback;
+import org.apache.activemq.artemis.tests.extensions.TargetTempDirFactory;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Mock-based test demonstrating the transient failure recovery scenario.
+ * This simulates the CephFS MDS failover use case where the filesystem
+ * temporarily becomes unavailable but recovers within the retry window.
+ */
+public class FileLockNodeManagerMockRecoveryTest {
+
+   @TempDir(factory = TargetTempDirFactory.class)
+   public File temporaryFolder;
+
+   private ScheduledExecutorService scheduledPool;
+
+   @BeforeEach
+   public void setup() {
+      scheduledPool = Executors.newScheduledThreadPool(2);
+   }
+
+   @AfterEach
+   public void teardown() {
+      if (scheduledPool != null) {
+         scheduledPool.shutdownNow();
+      }
+   }
+
+   /**
+    * Testable FileLockNodeManager that allows us to inject controlled lock check failures.
+    * This simulates the CephFS MDS failover scenario:
+    * - Check 1: Fail (MDS is failing over)
+    * - Check 2: Fail (MDS still failing over)
+    * - Check 3: Success (MDS recovered)
+    * - Result: No lock lost notification because recovery happened within retry window
+    */
+   static class MockableFileLockNodeManager extends FileLockNodeManager {
+      private final Queue<Boolean> lockHealthResults = new ConcurrentLinkedQueue<>();
+      private final AtomicInteger checkCount = new AtomicInteger(0);
+      private volatile Boolean lastResult = null;
+
+      MockableFileLockNodeManager(File directory,
+                                         boolean replicatedBackup,
+                                         long lockAcquisitionTimeout,
+                                         long journalLockMonitorTimeout,
+                                         int journalLockMonitorMaxRetries,
+                                         ScheduledExecutorService scheduledPool) {
+         super(directory, replicatedBackup,
+               TestConfigurationBuilder.forLockTesting(lockAcquisitionTimeout, journalLockMonitorTimeout, journalLockMonitorMaxRetries),
+               scheduledPool);
+      }
+
+      /**
+       * Queue a sequence of lock health check results.
+       * true = healthy, false = failed
+       */
+      public void queueLockHealthResults(Boolean... results) {
+         for (Boolean result : results) {
+            lockHealthResults.offer(result);
+         }
+      }
+
+      /**
+       * Override the primary lock lost check to return our controlled results.
+       * This simulates transient storage failures.
+       * After queue is exhausted, continues returning the last queued result.
+       */
+      @Override
+      protected boolean isPrimaryLockLost() {
+         checkCount.incrementAndGet();
+         Boolean nextResult = lockHealthResults.poll();
+         if (nextResult != null) {
+            lastResult = nextResult;
+         }
+         // If we have a result (from queue or last), use it
+         if (lastResult != null) {
+            // Return inverted because isPrimaryLockLost() returns true when lock IS lost
+            return !lastResult;
+         }
+         // If no results ever queued, use real check
+         return super.isPrimaryLockLost();
+      }
+
+      public int getCheckCount() {
+         return checkCount.get();
+      }
+   }
+
+   /**
+    * Test successful recovery scenario:
+    * - System configured with 2 retries (tolerates 3 consecutive failures)
+    * - First 2 checks fail (simulating MDS failover in progress)
+    * - Third check succeeds (MDS recovered)
+    * - Result: No lock lost notification, system continues running
+    */
+   @Test
+   @Timeout(10)
+   public void testTransientFailureThenRecovery() throws Exception {
+      MockableFileLockNodeManager manager = new MockableFileLockNodeManager(
+         temporaryFolder, false, -1, 200, 2, scheduledPool);
+
+      // Simulate CephFS MDS failover scenario:
+      // Fail, Fail, Success (recovery within retry window)
+      manager.queueLockHealthResults(false, false, true);
+
+      manager.start();
+
+      AtomicInteger lostLockCallCount = new AtomicInteger(0);
+      CountDownLatch lostLockLatch = new CountDownLatch(1);
+
+      manager.registerLockListener(() -> {
+         lostLockCallCount.incrementAndGet();
+         lostLockLatch.countDown();
+      });
+
+      ActivateCallback callback = manager.startPrimaryNode();
+      callback.activationComplete();
+
+      // Wait enough time for 3 checks to occur
+      Thread.sleep(800);
+
+      // Verify lock was NOT lost because it recovered within the retry window
+      assertEquals(0, lostLockCallCount.get(),
+         "Lock should not be declared lost - it recovered within retry window");
+
+      assertTrue(manager.getCheckCount() >= 3,
+         "Should have performed at least 3 health checks");
+
+      manager.stop();
+   }
+
+   /**
+    * Test permanent failure scenario:
+    * - System configured with 2 retries
+    * - All checks fail (storage is truly gone)
+    * - Result: Lock lost notification after retries exhausted
+    */
+   @Test
+   @Timeout(10)
+   public void testPermanentFailure() throws Exception {
+      MockableFileLockNodeManager manager = new MockableFileLockNodeManager(
+         temporaryFolder, false, -1, 200, 2, scheduledPool);
+
+      // Simulate permanent failure: all checks fail
+      manager.queueLockHealthResults(false, false, false, false);
+
+      manager.start();
+
+      AtomicInteger lostLockCallCount = new AtomicInteger(0);
+      CountDownLatch lostLockLatch = new CountDownLatch(1);
+
+      manager.registerLockListener(() -> {
+         lostLockCallCount.incrementAndGet();
+         lostLockLatch.countDown();
+      });
+
+      ActivateCallback callback = manager.startPrimaryNode();
+      callback.activationComplete();
+
+      // Wait for lock to be declared lost
+      boolean notified = lostLockLatch.await(2, TimeUnit.SECONDS);
+
+      assertTrue(notified, "Lock should be declared lost after retries exhausted");
+      assertEquals(1, lostLockCallCount.get(),
+         "Lost lock listener should be called exactly once");
+
+      manager.stop();
+   }
+
+   /**
+    * Test intermittent failures with eventual recovery:
+    * - Fail, Success, Fail, Success pattern
+    * - Counter should reset on each success
+    * - Should never accumulate enough consecutive failures to trigger notification
+    */
+   @Test
+   @Timeout(10)
+   public void testIntermittentFailuresWithRecovery() throws Exception {
+      MockableFileLockNodeManager manager = new MockableFileLockNodeManager(
+         temporaryFolder, false, -1, 200, 1, scheduledPool);
+
+      // Intermittent failures: Fail, Success, Fail, Success, Fail, Success
+      // With 1 retry (max 2 consecutive), should never trigger lost lock
+      manager.queueLockHealthResults(false, true, false, true, false, true);
+
+      manager.start();
+
+      AtomicInteger lostLockCallCount = new AtomicInteger(0);
+
+      manager.registerLockListener(() -> {
+         lostLockCallCount.incrementAndGet();
+      });
+
+      ActivateCallback callback = manager.startPrimaryNode();
+      callback.activationComplete();
+
+      // Wait for all checks to complete
+      Thread.sleep(1500);
+
+      // Should never lose lock because consecutive failures never exceed maxRetries
+      assertEquals(0, lostLockCallCount.get(),
+         "Lock should not be lost with intermittent failures and recovery");
+
+      manager.stop();
+   }
+
+   /**
+    * Test edge case: exactly maxRetries+1 consecutive failures
+    * Should trigger notification on the last failure
+    */
+   @Test
+   @Timeout(10)
+   public void testExactThresholdFailures() throws Exception {
+      MockableFileLockNodeManager manager = new MockableFileLockNodeManager(
+         temporaryFolder, false, -1, 200, 2, scheduledPool);
+
+      // Exactly 3 failures (maxRetries=2, so 2+1=3 triggers notification)
+      manager.queueLockHealthResults(false, false, false);
+
+      manager.start();
+
+      AtomicInteger lostLockCallCount = new AtomicInteger(0);
+      CountDownLatch lostLockLatch = new CountDownLatch(1);
+
+      manager.registerLockListener(() -> {
+         lostLockCallCount.incrementAndGet();
+         lostLockLatch.countDown();
+      });
+
+      ActivateCallback callback = manager.startPrimaryNode();
+      callback.activationComplete();
+
+      // Wait for notification
+      boolean notified = lostLockLatch.await(2, TimeUnit.SECONDS);
+
+      assertTrue(notified, "Should notify exactly when threshold is reached");
+      assertEquals(1, lostLockCallCount.get());
+
+      manager.stop();
+   }
+}
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/FileLockNodeManagerRetryTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/FileLockNodeManagerRetryTest.java
new file mode 100644
index 0000000000..34d9885512
--- /dev/null
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/FileLockNodeManagerRetryTest.java
@@ -0,0 +1,255 @@
+/*
+ * 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.activemq.artemis.core.server.impl;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.activemq.artemis.core.config.Configuration;
+import org.apache.activemq.artemis.tests.extensions.TargetTempDirFactory;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Comprehensive tests for lock monitor retry mechanism.
+ * Tests the specific retry behavior introduced for ENTMQBR-10718.
+ */
+public class FileLockNodeManagerRetryTest {
+
+   @TempDir(factory = TargetTempDirFactory.class)
+   public File temporaryFolder;
+
+   private ScheduledExecutorService scheduledPool;
+
+   @BeforeEach
+   public void setup() {
+      scheduledPool = Executors.newScheduledThreadPool(2);
+   }
+
+   @AfterEach
+   public void teardown() {
+      if (scheduledPool != null) {
+         scheduledPool.shutdownNow();
+      }
+   }
+
+   /**
+    * Test that with 0 retries (default), the first failure triggers notifyLostLock
+    */
+   @Test
+   @Timeout(10)
+   public void testNoRetriesFailsImmediately() throws Exception {
+      Configuration config = TestConfigurationBuilder.forLockTesting(-1, 200, 0);
+      FileLockNodeManager manager = new FileLockNodeManager(temporaryFolder, false, config, scheduledPool);
+      manager.start();
+
+      AtomicInteger lostLockCallCount = new AtomicInteger(0);
+      CountDownLatch lostLockLatch = new CountDownLatch(1);
+
+      manager.registerLockListener(() -> {
+         lostLockCallCount.incrementAndGet();
+         lostLockLatch.countDown();
+      });
+
+      manager.startPrimaryNode().activationComplete();
+      assertTrue(manager.isPrimaryLocked());
+
+      // Close channel to simulate failure
+      manager.getChannel().close();
+
+      // Should fail on first check (no retries)
+      boolean notified = lostLockLatch.await(5, TimeUnit.SECONDS);
+      assertTrue(notified, "Should notify lost lock on first failure with 0 retries");
+      assertTrue(lostLockCallCount.get() >= 1, "Should have at least one notification");
+
+      manager.stop();
+   }
+
+   /**
+    * Test that with 1 retry, it takes 2 consecutive failures before notifying
+    */
+   @Test
+   @Timeout(10)
+   public void testOneRetryRequiresTwoFailures() throws Exception {
+      Configuration config = TestConfigurationBuilder.forLockTesting(-1, 200, 1);
+      FileLockNodeManager manager = new FileLockNodeManager(temporaryFolder, false, config, scheduledPool);
+      manager.start();
+
+      AtomicInteger lostLockCallCount = new AtomicInteger(0);
+      CountDownLatch lostLockLatch = new CountDownLatch(1);
+
+      manager.registerLockListener(() -> {
+         lostLockCallCount.incrementAndGet();
+         lostLockLatch.countDown();
+      });
+
+      manager.startPrimaryNode().activationComplete();
+      assertTrue(manager.isPrimaryLocked());
+
+      // Close channel - should fail but not notify yet (1 retry available)
+      manager.getChannel().close();
+
+      // Wait enough time for 2 check periods (first fail + retry)
+      boolean notified = lostLockLatch.await(1, TimeUnit.SECONDS);
+      assertTrue(notified, "Should notify after retry exhausted (2 failures total)");
+      assertTrue(lostLockCallCount.get() >= 1, "Should have at least one notification");
+
+      manager.stop();
+   }
+
+   /**
+    * Test recovery scenario: transient failure followed by recovery
+    * This simulates the CephFS MDS failover use case
+    */
+   @Test
+   @Timeout(15)
+   public void testTransientFailureRecovery() throws Exception {
+      // Use slightly longer period to control timing better
+      Configuration config = TestConfigurationBuilder.forLockTesting(-1, 300, 2);
+      FileLockNodeManager manager = new FileLockNodeManager(temporaryFolder, false, config, scheduledPool);
+      manager.start();
+
+      AtomicInteger lostLockCallCount = new AtomicInteger(0);
+      CountDownLatch lostLockLatch = new CountDownLatch(1);
+
+      manager.registerLockListener(() -> {
+         lostLockCallCount.incrementAndGet();
+         lostLockLatch.countDown();
+      });
+
+      manager.startPrimaryNode().activationComplete();
+      assertTrue(manager.isPrimaryLocked());
+
+      // Simulate transient failure: close channel briefly
+      manager.getChannel().close();
+
+      // Wait for 1-2 failed checks to occur
+      Thread.sleep(500);
+
+      // Verify lock was NOT declared lost yet (we have 2 retries)
+      assertEquals(0, lostLockCallCount.get(), "Should not have lost lock yet - still have retries");
+
+      // Note: In a real scenario, the filesystem would recover here
+      // In this test, we can't easily recover the channel, so we verify
+      // the behavior up to this point (retries prevented immediate failure)
+
+      manager.stop();
+   }
+
+   /**
+    * Test with higher retry count
+    */
+   @Test
+   @Timeout(15)
+   public void testMultipleRetries() throws Exception {
+      Configuration config = TestConfigurationBuilder.forLockTesting(-1, 150, 3);
+      FileLockNodeManager manager = new FileLockNodeManager(temporaryFolder, false, config, scheduledPool);
+      manager.start();
+
+      AtomicInteger lostLockCallCount = new AtomicInteger(0);
+      CountDownLatch lostLockLatch = new CountDownLatch(1);
+
+      manager.registerLockListener(() -> {
+         lostLockCallCount.incrementAndGet();
+         lostLockLatch.countDown();
+      });
+
+      manager.startPrimaryNode().activationComplete();
+
+      // Close channel to cause failures
+      manager.getChannel().close();
+
+      // With 3 retries, should take 4 consecutive failures
+      // At 150ms period, that's ~600ms minimum
+      boolean notified = lostLockLatch.await(2, TimeUnit.SECONDS);
+      assertTrue(notified, "Should eventually notify after all retries exhausted");
+      assertTrue(lostLockCallCount.get() >= 1, "Should have at least one notification");
+
+      manager.stop();
+   }
+
+   /**
+    * Test accessing the MonitorLock internal state via reflection
+    * This allows us to verify the consecutiveFailures counter behavior
+    */
+   @Test
+   @Timeout(10)
+   public void testConsecutiveFailuresCounterViaReflection() throws Exception {
+      Configuration config = TestConfigurationBuilder.forLockTesting(-1, 200, 2);
+      FileLockNodeManager manager = new FileLockNodeManager(temporaryFolder, false, config, scheduledPool);
+      manager.start();
+      manager.startPrimaryNode().activationComplete();
+
+      // Get the MonitorLock instance via reflection
+      Field monitorLockField = FileLockNodeManager.class.getDeclaredField("monitorLock");
+      monitorLockField.setAccessible(true);
+      Object monitorLock = monitorLockField.get(manager);
+
+      // Get the consecutiveFailures field (now an AtomicInteger)
+      Field consecutiveFailuresField = monitorLock.getClass().getDeclaredField("consecutiveFailures");
+      consecutiveFailuresField.setAccessible(true);
+      java.util.concurrent.atomic.AtomicInteger consecutiveFailures =
+         (java.util.concurrent.atomic.AtomicInteger) consecutiveFailuresField.get(monitorLock);
+
+      // Initially should be 0
+      assertEquals(0, consecutiveFailures.get(), "Should start with 0 failures");
+
+      // Close channel to cause failures
+      manager.getChannel().close();
+
+      // Wait for a check to occur
+      Thread.sleep(400);
+
+      // Should have incremented the failure counter
+      int failures = consecutiveFailures.get();
+      assertTrue(failures > 0, "Should have recorded at least one failure");
+      assertTrue(failures <= 3, "Should not exceed maxRetries + 1");
+
+      manager.stop();
+   }
+
+   /**
+    * Test that configuration values are properly stored
+    */
+   @Test
+   public void testConfigurationValuesStored() throws Exception {
+      Configuration config = TestConfigurationBuilder.forLockTesting(-1, 5000, 5);
+      FileLockNodeManager manager = new FileLockNodeManager(temporaryFolder, false, config, scheduledPool);
+
+      // Access private fields via reflection
+      Field timeoutField = FileLockNodeManager.class.getDeclaredField("journalLockMonitorTimeoutNanos");
+      timeoutField.setAccessible(true);
+      long timeout = (long) timeoutField.get(manager);
+      assertEquals(TimeUnit.MILLISECONDS.toNanos(5000), timeout, "Journal lock monitor timeout should be stored in nanos");
+
+      Field retriesField = FileLockNodeManager.class.getDeclaredField("journalLockMonitorMaxRetries");
+      retriesField.setAccessible(true);
+      int retries = (int) retriesField.get(manager);
+      assertEquals(5, retries, "Max retries should be stored correctly");
+   }
+}
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/FileLockNodeManagerTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/FileLockNodeManagerTest.java
index c49cd6de6c..29788e727f 100644
--- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/FileLockNodeManagerTest.java
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/FileLockNodeManagerTest.java
@@ -18,13 +18,21 @@ package org.apache.activemq.artemis.core.server.impl;
 
 import java.io.File;
 import java.nio.channels.ClosedChannelException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 
+import org.apache.activemq.artemis.core.config.Configuration;
 import org.apache.activemq.artemis.tests.extensions.TargetTempDirFactory;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Timeout;
 import org.junit.jupiter.api.io.TempDir;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class FileLockNodeManagerTest {
 
@@ -44,4 +52,46 @@ public class FileLockNodeManagerTest {
 
       assertThrows(ClosedChannelException.class, () -> manager.lock(0));
    }
+
+   @Test
+   @Timeout(10)
+   public void testLockMonitorWithRetries() throws Exception {
+      ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(1);
+      try {
+         // Create a FileLockNodeManager with custom lock monitor settings:
+         // - 200ms check period
+         // - 2 retries (so it should tolerate up to 2 consecutive failures)
+         Configuration config = TestConfigurationBuilder.forLockTesting(-1, 200, 2);
+         FileLockNodeManager manager = new FileLockNodeManager(temporaryFolder, false, config, scheduledPool);
+         manager.start();
+
+         AtomicInteger lostLockCallCount = new AtomicInteger(0);
+         CountDownLatch lostLockLatch = new CountDownLatch(1);
+
+         // Register a listener to track when notifyLostLock is called
+         manager.registerLockListener(() -> {
+            lostLockCallCount.incrementAndGet();
+            lostLockLatch.countDown();
+         });
+
+         // Start as primary to begin lock monitoring
+         manager.startPrimaryNode().activationComplete();
+
+         // Verify the manager is in primary mode
+         assertTrue(manager.isPrimaryLocked());
+
+         // Close the channel to simulate a lock failure
+         // With 2 retries, it should fail 3 times total before calling notifyLostLock
+         manager.getChannel().close();
+
+         // Wait for the lock to be declared lost (should take ~600-800ms: 3 check periods)
+         boolean lostLockNotified = lostLockLatch.await(5, TimeUnit.SECONDS);
+         assertTrue(lostLockNotified, "Lost lock should have been notified after max retries exceeded");
+         assertEquals(1, lostLockCallCount.get(), "Lost lock listener should be called exactly once");
+
+         manager.stop();
+      } finally {
+         scheduledPool.shutdownNow();
+      }
+   }
 }
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/TestConfigurationBuilder.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/TestConfigurationBuilder.java
new file mode 100644
index 0000000000..2bd7b20cf2
--- /dev/null
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/TestConfigurationBuilder.java
@@ -0,0 +1,48 @@
+/*
+ * 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.activemq.artemis.core.server.impl;
+
+import org.apache.activemq.artemis.core.config.Configuration;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Helper for creating minimal Configuration mocks for FileLockNodeManager tests.
+ * This allows tests to use the production Configuration-based constructor
+ * without needing a full Configuration object.
+ */
+class TestConfigurationBuilder {
+
+   /**
+    * Create a minimal Configuration mock with only lock-related settings.
+    *
+    * @param lockAcquisitionTimeout journal lock acquisition timeout in milliseconds
+    * @param journalLockMonitorTimeout journal lock monitor check period in milliseconds
+    * @param journalLockMonitorMaxRetries maximum retries for failed journal lock checks
+    * @return Configuration mock configured for lock testing
+    */
+   static Configuration forLockTesting(long lockAcquisitionTimeout,
+                                       long journalLockMonitorTimeout,
+                                       int journalLockMonitorMaxRetries) {
+      Configuration config = mock(Configuration.class);
+      when(config.getJournalLockAcquisitionTimeout()).thenReturn(lockAcquisitionTimeout);
+      when(config.getJournalLockMonitorTimeout()).thenReturn(journalLockMonitorTimeout);
+      when(config.getJournalLockMonitorMaxRetries()).thenReturn(journalLockMonitorMaxRetries);
+      return config;
+   }
+}
diff --git a/docs/user-manual/configuration-index.adoc b/docs/user-manual/configuration-index.adoc
index db0836fa09..50c4538081 100644
--- a/docs/user-manual/configuration-index.adoc
+++ b/docs/user-manual/configuration-index.adoc
@@ -356,6 +356,14 @@ Setting this to 0 means compacting is disabled.
 | how long (in ms) to wait to acquire a file lock on the journal.
 | -1
 
+| xref:persistence.adoc#configuring-the-message-journal[journal-lock-monitor-timeout]
+| how long (in ms) between journal lock health checks.
+| 2000
+
+| xref:persistence.adoc#configuring-the-message-journal[journal-lock-monitor-max-retries]
+| how many consecutive failed lock health checks to tolerate before declaring the lock lost.
+| 0
+
 | xref:persistence.adoc#configuring-the-message-journal[journal-max-io]
 | the maximum number of write requests that can be in the ASYNCIO queue at any one time.
 | 4096 for ASYNCIO;
diff --git a/docs/user-manual/persistence.adoc b/docs/user-manual/persistence.adoc
index d730905caf..ffae8afb00 100644
--- a/docs/user-manual/persistence.adoc
+++ b/docs/user-manual/persistence.adoc
@@ -235,6 +235,16 @@ How long to wait (in milliseconds) to acquire a file lock on the journal before
 +
 The default for this parameter is `-1` (i.e. indefinite))
 
+journal-lock-monitor-timeout::
+How long (in milliseconds) between journal lock health checks. The lock monitor periodically verifies that the journal lock is still held.
++
+The default for this parameter is `2000` (i.e. 2 seconds)
+
+journal-lock-monitor-max-retries::
+How many consecutive failed lock health checks to tolerate before declaring the lock lost. This is useful for tolerating transient storage outages.
++
+The default for this parameter is `0` (i.e. no retries - fail immediately on first check failure)
+
 journal-datasync::
 This will disable the use of fdatasync on journal writes.
 When enabled it ensures full power failure durability, otherwise  process failure durability on journal writes (OS guaranteed).
diff --git a/tests/artemis-test-support/src/main/java/org/apache/activemq/artemis/tests/util/ColocatedActiveMQServer.java b/tests/artemis-test-support/src/main/java/org/apache/activemq/artemis/tests/util/ColocatedActiveMQServer.java
index 8df04a7922..4659127e16 100644
--- a/tests/artemis-test-support/src/main/java/org/apache/activemq/artemis/tests/util/ColocatedActiveMQServer.java
+++ b/tests/artemis-test-support/src/main/java/org/apache/activemq/artemis/tests/util/ColocatedActiveMQServer.java
@@ -65,7 +65,7 @@ public class ColocatedActiveMQServer extends ActiveMQServerImpl {
    @Override
    protected NodeManager createNodeManager(final File directory, boolean replicatingBackup) {
       if (replicatingBackup) {
-         return new FileLockNodeManager(directory, replicatingBackup, getConfiguration().getJournalLockAcquisitionTimeout(), null);
+         return new FileLockNodeManager(directory, replicatingBackup, getConfiguration(), null);
       } else {
          if (backup) {
             return nodeManagerBackup;


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@artemis.apache.org
For additional commands, e-mail: commits-help@artemis.apache.org