You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@activemq.apache.org by cl...@apache.org on 2022/09/22 18:35:29 UTC
[activemq-artemis] branch main updated: ARTEMIS-3763 Use margin when checking time difference between broker and database
This is an automated email from the ASF dual-hosted git repository.
clebertsuconic pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/activemq-artemis.git
The following commit(s) were added to refs/heads/main by this push:
new 9fa36e7c53 ARTEMIS-3763 Use margin when checking time difference between broker and database
new 58208765da This closes #4200
9fa36e7c53 is described below
commit 9fa36e7c53e454ddfae0d1ac29d6a30c5846a9e6
Author: Marlon Müller <ma...@united-internet.de>
AuthorDate: Fri Mar 25 09:42:12 2022 +0100
ARTEMIS-3763 Use margin when checking time difference between broker and database
* Add configurable time margin for checking offset between database server and broker when updating lease lock
---
.../api/config/ActiveMQDefaultConfiguration.java | 6 ++
.../storage/DatabaseStorageConfiguration.java | 10 +++
.../deployers/impl/FileConfigurationParser.java | 1 +
.../artemis/core/server/ActiveMQServerLogger.java | 4 +
.../core/server/impl/jdbc/JdbcLeaseLock.java | 42 +++++-----
.../core/server/impl/jdbc/JdbcNodeManager.java | 3 +
.../server/impl/jdbc/JdbcSharedStateManager.java | 31 +++++---
.../resources/schema/artemis-configuration.xsd | 8 ++
.../core/server/impl/jdbc/JdbcLeaseLockTest.java | 91 +++++++++++++++++++++-
.../impl/jdbc/JdbcSharedStateManagerTest.java | 1 +
.../artemis/tests/util/ActiveMQTestBase.java | 1 +
docs/user-manual/en/persistence.md | 10 ++-
12 files changed, 172 insertions(+), 36 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 734d58261b..00f37b7c21 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
@@ -489,6 +489,8 @@ public final class ActiveMQDefaultConfiguration {
private static final long DEFAULT_JDBC_LOCK_ACQUISITION_TIMEOUT_MILLIS = -1;
+ private static final long DEFAULT_JDBC_ALLOWED_TIME_DIFF_MILLIS = 250;
+
// Default period to wait between connection TTL checks
public static final long DEFAULT_CONNECTION_TTL_CHECK_INTERVAL = 2000;
@@ -1480,6 +1482,10 @@ public final class ActiveMQDefaultConfiguration {
return DEFAULT_JDBC_LOCK_ACQUISITION_TIMEOUT_MILLIS;
}
+ public static long getDefaultJdbcAllowedTimeDiffMillis() {
+ return DEFAULT_JDBC_ALLOWED_TIME_DIFF_MILLIS;
+ }
+
public static long getDefaultConnectionTtlCheckInterval() {
return DEFAULT_CONNECTION_TTL_CHECK_INTERVAL;
}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/storage/DatabaseStorageConfiguration.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/storage/DatabaseStorageConfiguration.java
index 03c9133f5f..51afacc98e 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/storage/DatabaseStorageConfiguration.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/storage/DatabaseStorageConfiguration.java
@@ -67,6 +67,8 @@ public class DatabaseStorageConfiguration implements StoreConfiguration {
private long jdbcJournalSyncPeriodMillis = ActiveMQDefaultConfiguration.getDefaultJdbcJournalSyncPeriodMillis();
+ private long jdbcAllowedTimeDiff = ActiveMQDefaultConfiguration.getDefaultJdbcAllowedTimeDiffMillis();
+
@Override
public StoreType getStoreType() {
return StoreType.DATABASE;
@@ -144,6 +146,10 @@ public class DatabaseStorageConfiguration implements StoreConfiguration {
return jdbcDriverClassName;
}
+ public long getJdbcAllowedTimeDiff() {
+ return jdbcAllowedTimeDiff;
+ }
+
/**
* The DataSource to use to store Artemis data in the data store (can be {@code null} if {@code jdbcConnectionUrl} and {@code jdbcDriverClassName} are used instead).
*
@@ -296,4 +302,8 @@ public class DatabaseStorageConfiguration implements StoreConfiguration {
public void setJdbcJournalSyncPeriodMillis(long jdbcJournalSyncPeriodMillis) {
this.jdbcJournalSyncPeriodMillis = jdbcJournalSyncPeriodMillis;
}
+
+ public void setJdbcAllowedTimeDiff(long jdbcAllowedTimeDiff) {
+ this.jdbcAllowedTimeDiff = jdbcAllowedTimeDiff;
+ }
}
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 0d7e98a85f..a058074112 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
@@ -1951,6 +1951,7 @@ public final class FileConfigurationParser extends XMLConfigurationUtil {
conf.setJdbcLockRenewPeriodMillis(getLong(storeNode, "jdbc-lock-renew-period", conf.getJdbcLockRenewPeriodMillis(), Validators.NO_CHECK));
conf.setJdbcLockExpirationMillis(getLong(storeNode, "jdbc-lock-expiration", conf.getJdbcLockExpirationMillis(), Validators.NO_CHECK));
conf.setJdbcJournalSyncPeriodMillis(getLong(storeNode, "jdbc-journal-sync-period", conf.getJdbcJournalSyncPeriodMillis(), Validators.NO_CHECK));
+ conf.setJdbcAllowedTimeDiff(getLong(storeNode, "jdbc-allowed-time-diff", conf.getJdbcAllowedTimeDiff(), Validators.NO_CHECK));
String jdbcUser = getString(storeNode, "jdbc-user", conf.getJdbcUser(), Validators.NO_CHECK);
if (jdbcUser != null) {
jdbcUser = PasswordMaskingUtil.resolveMask(mainConfig.isMaskPassword(), jdbcUser, mainConfig.getPasswordCodec());
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java
index 1a7c0748ee..0c237ed62d 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/ActiveMQServerLogger.java
@@ -2224,4 +2224,8 @@ public interface ActiveMQServerLogger extends BasicLogger {
@Message(id = 224117, value = "\"page-max-cache-size\" being used on broker.xml. This configuration attribute is no longer used and it will be ignored.", format = Message.Format.MESSAGE_FORMAT)
void pageMaxSizeUsed();
+ @LogMessage(level = Logger.Level.WARN)
+ @Message(id = 224118, value = "The SQL Database is returning a current time too far from this system current time. Adjust clock on the SQL Database server. DatabaseTime={0}, CurrentTime={1}, allowed variance={2}", format = Message.Format.MESSAGE_FORMAT)
+ void dbReturnedTimeOffClock(long dbTime, long systemTime, long variance);
+
}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/jdbc/JdbcLeaseLock.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/jdbc/JdbcLeaseLock.java
index 64800d02e7..88bdb23c23 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/jdbc/JdbcLeaseLock.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/jdbc/JdbcLeaseLock.java
@@ -27,13 +27,14 @@ import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
+import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
import org.apache.activemq.artemis.jdbc.store.drivers.JDBCConnectionProvider;
import org.jboss.logging.Logger;
/**
* JDBC implementation of a {@link LeaseLock} with a {@code String} defined {@link #holderId()}.
*/
-final class JdbcLeaseLock implements LeaseLock {
+class JdbcLeaseLock implements LeaseLock {
private static final Logger LOGGER = Logger.getLogger(JdbcLeaseLock.class);
private static final int MAX_HOLDER_ID_LENGTH = 128;
@@ -50,6 +51,7 @@ final class JdbcLeaseLock implements LeaseLock {
private boolean maybeAcquired;
private final String lockName;
private long localExpirationTime;
+ private long allowedTimeDiff;
/**
* The lock will be responsible (ie {@link #close()}) of all the {@link PreparedStatement}s used by it, but not of the {@link Connection},
@@ -65,7 +67,8 @@ final class JdbcLeaseLock implements LeaseLock {
String currentDateTimeTimeZoneId,
long expirationMIllis,
long queryTimeoutMillis,
- String lockName) {
+ String lockName,
+ long allowedTimeDiff) {
if (holderId.length() > MAX_HOLDER_ID_LENGTH) {
throw new IllegalArgumentException("holderId length must be <=" + MAX_HOLDER_ID_LENGTH);
}
@@ -77,6 +80,7 @@ final class JdbcLeaseLock implements LeaseLock {
this.currentDateTime = currentDateTime;
this.currentDateTimeTimeZone = currentDateTimeTimeZoneId == null ? null : TimeZone.getTimeZone(currentDateTimeTimeZoneId);
this.expirationMillis = expirationMIllis;
+ this.allowedTimeDiff = allowedTimeDiff;
this.maybeAcquired = false;
this.connectionProvider = connectionProvider;
this.lockName = lockName;
@@ -167,33 +171,29 @@ final class JdbcLeaseLock implements LeaseLock {
}
private long dbCurrentTimeMillis(Connection connection) throws SQLException {
- return dbCurrentTimeMillis(connection, queryTimeout, currentDateTime, currentDateTimeTimeZone);
+ final long currentTime = System.currentTimeMillis();
+ final long allowedStartTime = currentTime - allowedTimeDiff;
+ final long allowedEndTime = currentTime + allowedTimeDiff;
+ final long dbTime = fetchDatabaseTime(connection);
+ if (dbTime < allowedStartTime || dbTime > allowedEndTime) {
+ ActiveMQServerLogger.LOGGER.dbReturnedTimeOffClock(dbTime, currentTime, allowedTimeDiff);
+ }
+ return dbTime;
}
- public static long dbCurrentTimeMillis(final Connection connection,
- final int queryTimeout,
- final String currentDateTimeSql,
- final TimeZone currentDateTimeTimeZone) throws SQLException {
- try (PreparedStatement currentDateTime = connection.prepareStatement(currentDateTimeSql)) {
+ protected long fetchDatabaseTime(Connection connection) throws SQLException {
+ try (PreparedStatement currentDateTimeStatement = connection.prepareStatement(currentDateTime)) {
if (queryTimeout >= 0) {
- currentDateTime.setQueryTimeout(queryTimeout);
+ currentDateTimeStatement.setQueryTimeout(queryTimeout);
}
- final long startTime = stripMilliseconds(System.currentTimeMillis());
- try (ResultSet resultSet = currentDateTime.executeQuery()) {
+ try (ResultSet resultSet = currentDateTimeStatement.executeQuery()) {
resultSet.next();
- final long endTime = stripMilliseconds(System.currentTimeMillis());
- final long currentTime = (currentDateTimeTimeZone == null ?
+ final long dbTime = (currentDateTimeTimeZone == null ?
resultSet.getTimestamp(1) :
resultSet.getTimestamp(1, Calendar.getInstance(currentDateTimeTimeZone))).getTime();
- final long currentTimeNoMillis = stripMilliseconds(currentTime);
- if (currentTimeNoMillis < startTime) {
- LOGGER.warnf("currentTimestamp = %d on database should happen AFTER %d on broker", currentTimeNoMillis, startTime);
- }
- if (currentTimeNoMillis > endTime) {
- LOGGER.warnf("currentTimestamp = %d on database should happen BEFORE %d on broker", currentTimeNoMillis, endTime);
- }
- return currentTime;
+
+ return dbTime;
}
}
}
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/jdbc/JdbcNodeManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/jdbc/JdbcNodeManager.java
index f357925d73..90aa909bcb 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/jdbc/JdbcNodeManager.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/jdbc/JdbcNodeManager.java
@@ -71,6 +71,7 @@ public final class JdbcNodeManager extends NodeManager {
configuration.getJdbcLockExpirationMillis(),
configuration.getJdbcLockRenewPeriodMillis(),
configuration.getJdbcLockAcquisitionTimeoutMillis(),
+ configuration.getJdbcAllowedTimeDiff(),
configuration.getConnectionProvider(),
sqlProviderFactory.create(configuration.getNodeManagerStoreTableName(), SQLProvider.DatabaseStoreType.NODE_MANAGER),
scheduledExecutorService,
@@ -81,12 +82,14 @@ public final class JdbcNodeManager extends NodeManager {
long lockExpirationMillis,
long lockRenewPeriodMillis,
long lockAcquisitionTimeoutMillis,
+ long allowedTimeDiff,
JDBCConnectionProvider connectionProvider,
SQLProvider provider,
ScheduledExecutorService scheduledExecutorService,
ExecutorFactory executorFactory) {
return new JdbcNodeManager(() -> JdbcSharedStateManager.usingConnectionProvider(brokerId, lockExpirationMillis,
lockRenewPeriodMillis,
+ allowedTimeDiff,
connectionProvider,
provider),
lockExpirationMillis,
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/jdbc/JdbcSharedStateManager.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/jdbc/JdbcSharedStateManager.java
index 00c4d1acd4..76f65a3748 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/jdbc/JdbcSharedStateManager.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/jdbc/JdbcSharedStateManager.java
@@ -40,6 +40,7 @@ final class JdbcSharedStateManager extends AbstractJDBCDriver implements SharedS
private final String holderId;
private final long lockExpirationMillis;
private final long queryTimeoutMillis;
+ private final long allowedTimeDiff;
private JdbcLeaseLock liveLock;
private JdbcLeaseLock backupLock;
private String readNodeId;
@@ -50,18 +51,20 @@ final class JdbcSharedStateManager extends AbstractJDBCDriver implements SharedS
public static JdbcSharedStateManager usingConnectionProvider(String holderId,
long locksExpirationMillis,
+ long allowedTimeDiff,
JDBCConnectionProvider connectionProvider,
SQLProvider provider) {
- return usingConnectionProvider(holderId, locksExpirationMillis, -1, connectionProvider, provider);
+ return usingConnectionProvider(holderId, locksExpirationMillis, allowedTimeDiff, -1, connectionProvider, provider);
}
public static JdbcSharedStateManager usingConnectionProvider(String holderId,
long locksExpirationMillis,
long queryTimeoutMillis,
+ long allowedTimeDiff,
JDBCConnectionProvider connectionProvider,
SQLProvider provider) {
final JdbcSharedStateManager sharedStateManager = new JdbcSharedStateManager(holderId, locksExpirationMillis,
- queryTimeoutMillis);
+ queryTimeoutMillis, allowedTimeDiff);
sharedStateManager.setJdbcConnectionProvider(connectionProvider);
sharedStateManager.setSqlProvider(provider);
try {
@@ -85,36 +88,41 @@ final class JdbcSharedStateManager extends AbstractJDBCDriver implements SharedS
static JdbcLeaseLock createLiveLock(String holderId,
JDBCConnectionProvider connectionProvider,
SQLProvider sqlProvider,
- long expirationMillis) {
- return createLiveLock(holderId, connectionProvider, sqlProvider, expirationMillis, -1);
+ long expirationMillis,
+ long allowedTimeDiff) {
+ return createLiveLock(holderId, connectionProvider, sqlProvider, expirationMillis, -1, allowedTimeDiff);
}
static JdbcLeaseLock createLiveLock(String holderId,
JDBCConnectionProvider connectionProvider,
SQLProvider sqlProvider,
long expirationMillis,
- long queryTimeoutMillis) {
+ long queryTimeoutMillis,
+ long allowedTimeDiff) {
return new JdbcLeaseLock(holderId, connectionProvider, sqlProvider.tryAcquireLiveLockSQL(),
sqlProvider.tryReleaseLiveLockSQL(), sqlProvider.renewLiveLockSQL(),
sqlProvider.isLiveLockedSQL(), sqlProvider.currentTimestampSQL(),
- sqlProvider.currentTimestampTimeZoneId(), expirationMillis, queryTimeoutMillis, "LIVE");
+ sqlProvider.currentTimestampTimeZoneId(), expirationMillis, queryTimeoutMillis,
+ "LIVE", allowedTimeDiff);
}
static JdbcLeaseLock createBackupLock(String holderId,
JDBCConnectionProvider connectionProvider,
SQLProvider sqlProvider,
long expirationMillis,
- long queryTimeoutMillis) {
+ long queryTimeoutMillis,
+ long allowedTimeDiff) {
return new JdbcLeaseLock(holderId, connectionProvider, sqlProvider.tryAcquireBackupLockSQL(),
sqlProvider.tryReleaseBackupLockSQL(), sqlProvider.renewBackupLockSQL(),
sqlProvider.isBackupLockedSQL(), sqlProvider.currentTimestampSQL(),
- sqlProvider.currentTimestampTimeZoneId(), expirationMillis, queryTimeoutMillis, "BACKUP");
+ sqlProvider.currentTimestampTimeZoneId(), expirationMillis, queryTimeoutMillis,
+ "BACKUP", allowedTimeDiff);
}
@Override
protected void prepareStatements() {
- this.liveLock = createLiveLock(this.holderId, this.connectionProvider, sqlProvider, lockExpirationMillis, queryTimeoutMillis);
- this.backupLock = createBackupLock(this.holderId, this.connectionProvider, sqlProvider, lockExpirationMillis, queryTimeoutMillis);
+ this.liveLock = createLiveLock(this.holderId, this.connectionProvider, sqlProvider, lockExpirationMillis, queryTimeoutMillis, allowedTimeDiff);
+ this.backupLock = createBackupLock(this.holderId, this.connectionProvider, sqlProvider, lockExpirationMillis, queryTimeoutMillis, allowedTimeDiff);
this.readNodeId = sqlProvider.readNodeIdSQL();
this.writeNodeId = sqlProvider.writeNodeIdSQL();
this.initializeNodeId = sqlProvider.initializeNodeIdSQL();
@@ -122,10 +130,11 @@ final class JdbcSharedStateManager extends AbstractJDBCDriver implements SharedS
this.readState = sqlProvider.readStateSQL();
}
- private JdbcSharedStateManager(String holderId, long lockExpirationMillis, long queryTimeoutMillis) {
+ private JdbcSharedStateManager(String holderId, long lockExpirationMillis, long queryTimeoutMillis, long allowedTimeDiff) {
this.holderId = holderId;
this.lockExpirationMillis = lockExpirationMillis;
this.queryTimeoutMillis = queryTimeoutMillis;
+ this.allowedTimeDiff = allowedTimeDiff;
}
@Override
diff --git a/artemis-server/src/main/resources/schema/artemis-configuration.xsd b/artemis-server/src/main/resources/schema/artemis-configuration.xsd
index 036c7a09f2..8615b1c1d6 100644
--- a/artemis-server/src/main/resources/schema/artemis-configuration.xsd
+++ b/artemis-server/src/main/resources/schema/artemis-configuration.xsd
@@ -2937,6 +2937,14 @@
</xsd:documentation>
</xsd:annotation>
</xsd:element>
+ <xsd:element name="jdbc-allowed-time-diff" type="xsd:long" minOccurs="0" maxOccurs="1" default="250">
+ <xsd:annotation>
+ <xsd:documentation>
+ The maximal time offset between the broker and the database in milliseconds when requesting the
+ current time of the database while updating and validating live and backup locks.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
</xsd:all>
<xsd:attributeGroup ref="xml:specialAttrs"/>
</xsd:complexType>
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/jdbc/JdbcLeaseLockTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/jdbc/JdbcLeaseLockTest.java
index a0d482c330..593a674e4b 100644
--- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/jdbc/JdbcLeaseLockTest.java
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/jdbc/JdbcLeaseLockTest.java
@@ -17,6 +17,8 @@
package org.apache.activemq.artemis.core.server.impl.jdbc;
+import java.sql.Connection;
+import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
@@ -28,17 +30,23 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
+import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.activemq.artemis.core.config.storage.DatabaseStorageConfiguration;
import org.apache.activemq.artemis.core.server.NodeManager.LockListener;
import org.apache.activemq.artemis.jdbc.store.drivers.JDBCUtils;
import org.apache.activemq.artemis.jdbc.store.sql.SQLProvider;
+import org.apache.activemq.artemis.logs.AssertionLoggerHandler;
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.apache.activemq.artemis.utils.Wait;
import org.apache.activemq.artemis.utils.actors.ArtemisExecutor;
import org.apache.activemq.artemis.utils.actors.OrderedExecutorFactory;
import org.hamcrest.MatcherAssert;
+import org.jboss.logmanager.Level;
+import org.jboss.logmanager.LogManager;
import org.junit.After;
import org.junit.Assert;
import org.junit.Assume;
@@ -47,11 +55,15 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.lessThan;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsInstanceOf.instanceOf;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
@RunWith(Parameterized.class)
public class JdbcLeaseLockTest extends ActiveMQTestBase {
@@ -83,7 +95,8 @@ public class JdbcLeaseLockTest extends ActiveMQTestBase {
UUID.randomUUID().toString(),
jdbcSharedStateManager.getJdbcConnectionProvider(),
sqlProvider,
- acquireMillis);
+ acquireMillis,
+ dbConf.getJdbcAllowedTimeDiff());
} catch (Exception e) {
throw new IllegalStateException(e);
}
@@ -97,7 +110,23 @@ public class JdbcLeaseLockTest extends ActiveMQTestBase {
jdbcSharedStateManager.getJdbcConnectionProvider(),
sqlProvider,
acquireMillis,
- queryTimeoutMillis);
+ queryTimeoutMillis,
+ dbConf.getJdbcAllowedTimeDiff());
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private LeaseLock lock(long acquireMillis, long queryTimeoutMillis, long allowedTimeDiff) {
+ try {
+ return JdbcSharedStateManager
+ .createLiveLock(
+ UUID.randomUUID().toString(),
+ jdbcSharedStateManager.getJdbcConnectionProvider(),
+ sqlProvider,
+ acquireMillis,
+ queryTimeoutMillis,
+ allowedTimeDiff);
} catch (Exception e) {
throw new IllegalStateException(e);
}
@@ -124,6 +153,7 @@ public class JdbcLeaseLockTest extends ActiveMQTestBase {
.usingConnectionProvider(
UUID.randomUUID().toString(),
dbConf.getJdbcLockExpirationMillis(),
+ dbConf.getJdbcAllowedTimeDiff(),
dbConf.getConnectionProvider(),
sqlProvider);
}
@@ -402,5 +432,62 @@ public class JdbcLeaseLockTest extends ActiveMQTestBase {
executorService.shutdown();
scheduledExecutorService.shutdown();
}
+
+ @Test
+ public void shouldLogWaringIfTimeDiffIsOutsideAllowedRange() {
+ Handler mockHandler = Mockito.mock(Handler.class);
+ LogManager.getLogManager().getLogger(JdbcLeaseLock.class.getName()).addHandler(mockHandler);
+
+ final LeaseLock lock = lock(TimeUnit.SECONDS.toMillis(10), -1, -10);
+ Assume.assumeThat(lock, instanceOf(JdbcLeaseLock.class));
+ final JdbcLeaseLock jdbcLock = JdbcLeaseLock.class.cast(lock);
+ jdbcLock.dbCurrentTimeMillis();
+
+ ArgumentCaptor<LogRecord> captor = ArgumentCaptor.forClass(LogRecord.class);
+ verify(mockHandler, times(2)).publish(captor.capture());
+ List<String> warningLogs = captor
+ .getAllValues()
+ .stream()
+ .filter(logRecord -> logRecord.getLevel() == Level.WARN)
+ .map(LogRecord::getMessage)
+ .collect(Collectors.toList());
+ Assert.assertTrue(warningLogs.contains("currentTimestamp = %d on database should happen BEFORE %d on broker"));
+ Assert.assertTrue(warningLogs.contains("currentTimestamp = %d on database should happen AFTER %d on broker"));
+
+ LogManager.getLogManager().getLogger(JdbcLeaseLock.class.getName()).removeHandler(mockHandler);
+ }
+
+ @Test
+ public void shouldNotLogWaringIfTimeDiffIsInsideAllowedRange() {
+ AssertionLoggerHandler.startCapture();
+ runAfter(AssertionLoggerHandler::stopCapture);
+
+ AtomicInteger diff = new AtomicInteger(0);
+
+ JdbcLeaseLock hackLock = new JdbcLeaseLock("SomeID", jdbcSharedStateManager.getJdbcConnectionProvider(), sqlProvider.tryAcquireLiveLockSQL(),
+ sqlProvider.tryReleaseLiveLockSQL(), sqlProvider.renewLiveLockSQL(),
+ sqlProvider.isLiveLockedSQL(), sqlProvider.currentTimestampSQL(),
+ sqlProvider.currentTimestampTimeZoneId(), -1, 1000,
+ "LIVE", 1000) {
+ @Override
+ protected long fetchDatabaseTime(Connection connection) throws SQLException {
+ return System.currentTimeMillis() + diff.get();
+ }
+ };
+
+ diff.set(10_000);
+ hackLock.dbCurrentTimeMillis();
+ Assert.assertTrue(AssertionLoggerHandler.findText("AMQ224118"));
+
+ diff.set(-10_000);
+ AssertionLoggerHandler.clear();
+ hackLock.dbCurrentTimeMillis();
+ Assert.assertTrue(AssertionLoggerHandler.findText("AMQ224118"));
+
+ diff.set(0);
+ AssertionLoggerHandler.clear();
+ hackLock.dbCurrentTimeMillis();
+ Assert.assertFalse(AssertionLoggerHandler.findText("AMQ224118"));
+ }
}
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/jdbc/JdbcSharedStateManagerTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/jdbc/JdbcSharedStateManagerTest.java
index db146e19c1..3025662f08 100644
--- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/jdbc/JdbcSharedStateManagerTest.java
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/server/impl/jdbc/JdbcSharedStateManagerTest.java
@@ -51,6 +51,7 @@ public class JdbcSharedStateManagerTest extends ActiveMQTestBase {
return JdbcSharedStateManager.usingConnectionProvider(
UUID.randomUUID().toString(),
dbConf.getJdbcLockExpirationMillis(),
+ dbConf.getJdbcAllowedTimeDiff(),
dbConf.getConnectionProvider(),
sqlProvider);
}
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/tests/util/ActiveMQTestBase.java b/artemis-server/src/test/java/org/apache/activemq/artemis/tests/util/ActiveMQTestBase.java
index c42ac84089..fc784f973d 100644
--- a/artemis-server/src/test/java/org/apache/activemq/artemis/tests/util/ActiveMQTestBase.java
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/tests/util/ActiveMQTestBase.java
@@ -564,6 +564,7 @@ public abstract class ActiveMQTestBase extends Assert {
dbStorageConfiguration.setJdbcLockExpirationMillis(getJdbcLockExpirationMillis());
dbStorageConfiguration.setJdbcLockRenewPeriodMillis(getJdbcLockRenewPeriodMillis());
dbStorageConfiguration.setJdbcNetworkTimeout(-1);
+ dbStorageConfiguration.setJdbcAllowedTimeDiff(250L);
return dbStorageConfiguration;
}
diff --git a/docs/user-manual/en/persistence.md b/docs/user-manual/en/persistence.md
index 861694dd28..d770ee1831 100644
--- a/docs/user-manual/en/persistence.md
+++ b/docs/user-manual/en/persistence.md
@@ -519,9 +519,15 @@ To configure Apache ActiveMQ Artemis to use a database for persisting messages a
The time in milliseconds a JDBC lock is considered valid without keeping it alive. The default value
is 20000 milliseconds (ie 20 seconds).
-- `jdbc-journal-sync-period`
+- `jdbc-journal-sync-period`
- The time in milliseconds the journal will be synced with JDBC. The default value is 5 milliseconds.
+ The time in milliseconds the journal will be synced with JDBC. The default value is 5 milliseconds.
+
+- `jdbc-allowed-time-diff`
+
+ The maximal time offset between the broker and the database in milliseconds when requesting the current time
+ of the database while updating and validating live and backup locks. Currently this value only affects the logging and
+ will show a warning if the detected difference exceeds the limit. The default value is 250 milliseconds.
Note that some DBMS (e.g. Oracle, 30 chars) have restrictions on the size of table names, this should be taken into consideration when configuring table names for the Artemis database store, pay particular attention to the page store table name, which can be appended with a unique ID of up to 20 characters. (for Oracle this would mean configuring a page-store-table-name of max size of 10 chars).