diff --git a/client/conf/db.properties.in b/client/conf/db.properties.in index 0f7d2706a427..0ce929f6a7a8 100644 --- a/client/conf/db.properties.in +++ b/client/conf/db.properties.in @@ -29,7 +29,7 @@ db.cloud.driver=@DBDRIVER@ db.cloud.port=3306 db.cloud.name=cloud -# Connection URI to the database "cloud". When this property is set, only the following properties will be used along with it: db.cloud.maxActive, db.cloud.maxIdle, db.cloud.maxWait, db.cloud.username, db.cloud.password, db.cloud.driver, db.cloud.validationQuery, db.cloud.isolation.level. Other properties will be ignored. +# Connection URI to the database "cloud". When this property is set, only the following properties will be used along with it: db.cloud.maxActive, db.cloud.maxIdle, db.cloud.maxWait, db.cloud.minIdleConnections, db.cloud.connectionTimeout, db.cloud.keepAliveTime, db.cloud.leakDetectionThreshold, db.cloud.registerMbeans, db.cloud.username, db.cloud.password, db.cloud.driver, db.cloud.validationQuery, db.cloud.isolation.level. Other properties will be ignored. db.cloud.uri= @@ -41,6 +41,12 @@ db.cloud.maxWait=600000 db.cloud.minIdleConnections=5 db.cloud.connectionTimeout=30000 db.cloud.keepAliveTime=600000 +# HikariCP leak detection threshold in milliseconds. +# A value of 0 disables leak detection. +# Useful for debugging borrowed DB connections that are not returned to the pool. +# db.cloud.leakDetectionThreshold=0 +# Enable HikariCP JMX MBeans for observing pool counters. +# db.cloud.registerMbeans=false db.cloud.validationQuery=/* ping */ SELECT 1 db.cloud.testOnBorrow=true db.cloud.testWhileIdle=true diff --git a/framework/db/src/main/java/com/cloud/utils/db/TransactionLegacy.java b/framework/db/src/main/java/com/cloud/utils/db/TransactionLegacy.java index 18a90749e49c..3c77aee22488 100644 --- a/framework/db/src/main/java/com/cloud/utils/db/TransactionLegacy.java +++ b/framework/db/src/main/java/com/cloud/utils/db/TransactionLegacy.java @@ -1049,6 +1049,10 @@ private static T parseNumber(String value, Class type) { } } + private static Boolean parseBoolean(String value) { + return value == null ? null : Boolean.parseBoolean(value); + } + @SuppressWarnings({"rawtypes", "unchecked"}) public static void initDataSource(Properties dbProps) { try { @@ -1065,6 +1069,8 @@ public static void initDataSource(Properties dbProps) { final Integer cloudMinIdleConnections = parseNumber(dbProps.getProperty("db.cloud.minIdleConnections"), Integer.class); final Long cloudConnectionTimeout = parseNumber(dbProps.getProperty("db.cloud.connectionTimeout"), Long.class); final Long cloudKeepAliveTimeout = parseNumber(dbProps.getProperty("db.cloud.keepAliveTime"), Long.class); + final Long cloudLeakDetectionThreshold = parseNumber(dbProps.getProperty("db.cloud.leakDetectionThreshold"), Long.class); + final Boolean cloudRegisterMbeans = parseBoolean(dbProps.getProperty("db.cloud.registerMbeans")); final String cloudUsername = dbProps.getProperty("db.cloud.username"); final String cloudPassword = dbProps.getProperty("db.cloud.password"); final String cloudValidationQuery = dbProps.getProperty("db.cloud.validationQuery"); @@ -1107,7 +1113,7 @@ public static void initDataSource(Properties dbProps) { cloudUsername, cloudPassword, cloudMaxActive, cloudMaxIdle, cloudMaxWait, cloudTimeBtwEvictionRunsMillis, cloudMinEvcitableIdleTimeMillis, cloudTestWhileIdle, cloudTestOnBorrow, cloudValidationQuery, cloudMinIdleConnections, cloudConnectionTimeout, - cloudKeepAliveTimeout, isolationLevel, "cloud"); + cloudKeepAliveTimeout, cloudLeakDetectionThreshold, cloudRegisterMbeans, isolationLevel, "cloud"); // Configure the usage db final Integer usageMaxActive = parseNumber(dbProps.getProperty("db.usage.maxActive"), Integer.class); @@ -1127,7 +1133,7 @@ public static void initDataSource(Properties dbProps) { s_usageDS = createDataSource(dbProps.getProperty("db.usage.connectionPoolLib"), usageUriAndDriver.first(), usageUsername, usagePassword, usageMaxActive, usageMaxIdle, usageMaxWait, null, null, null, null, null, - usageMinIdleConnections, usageConnectionTimeout, usageKeepAliveTimeout, isolationLevel, "usage"); + usageMinIdleConnections, usageConnectionTimeout, usageKeepAliveTimeout, null, null, isolationLevel, "usage"); try { // Configure the simulator db @@ -1167,7 +1173,7 @@ public static void initDataSource(Properties dbProps) { simulatorConnectionUri, simulatorUsername, simulatorPassword, simulatorMaxActive, simulatorMaxIdle, simulatorMaxWait, null, null, null, null, cloudValidationQuery, simulatorMinIdleConnections, simulatorConnectionTimeout, - simulatorKeepAliveTimeout, isolationLevel, "simulator"); + simulatorKeepAliveTimeout, null, null, isolationLevel, "simulator"); } catch (Exception e) { LOGGER.debug("Simulator DB properties are not available. Not initializing simulator DS"); } @@ -1269,7 +1275,7 @@ protected static String buildConnectionUri(String loadBalanceStrategy, String dr private static DataSource createDataSource(String connectionPoolLib, String uri, String username, String password, Integer maxActive, Integer maxIdle, Long maxWait, Long timeBtwnEvictionRuns, Long minEvictableIdleTime, Boolean testWhileIdle, Boolean testOnBorrow, String validationQuery, Integer minIdleConnections, - Long connectionTimeout, Long keepAliveTime, Integer isolationLevel, String dsName) { + Long connectionTimeout, Long keepAliveTime, Long leakDetectionThreshold, Boolean registerMbeans, Integer isolationLevel, String dsName) { LOGGER.debug("Creating datasource for database: {} with connection pool lib: {}", dsName, connectionPoolLib); if (CONNECTION_POOL_LIB_DBCP.equals(connectionPoolLib)) { @@ -1277,13 +1283,13 @@ private static DataSource createDataSource(String connectionPoolLib, String uri, minEvictableIdleTime, testWhileIdle, testOnBorrow, validationQuery, isolationLevel); } return createHikaricpDataSource(uri, username, password, maxActive, maxIdle, maxWait, minIdleConnections, - connectionTimeout, keepAliveTime, isolationLevel, dsName); + connectionTimeout, keepAliveTime, leakDetectionThreshold, registerMbeans, isolationLevel, dsName); } private static DataSource createHikaricpDataSource(String uri, String username, String password, - Integer maxActive, Integer maxIdle, Long maxWait, - Integer minIdleConnections, Long connectionTimeout, Long keepAliveTime, - Integer isolationLevel, String dsName) { + Integer maxActive, Integer maxIdle, Long maxWait, + Integer minIdleConnections, Long connectionTimeout, Long keepAliveTime, + Long leakDetectionThreshold, Boolean registerMbeans, Integer isolationLevel, String dsName) { HikariConfig config = new HikariConfig(); config.setJdbcUrl(uri); config.setUsername(username); @@ -1298,6 +1304,7 @@ private static DataSource createHikaricpDataSource(String uri, String username, config.setMinimumIdle(ObjectUtils.defaultIfNull(minIdleConnections, 5)); config.setConnectionTimeout(ObjectUtils.defaultIfNull(connectionTimeout, 30000L)); config.setKeepaliveTime(ObjectUtils.defaultIfNull(keepAliveTime, 600000L)); + applyHikariDebugSettings(config, leakDetectionThreshold, registerMbeans, dsName); String isolationLevelString = "TRANSACTION_READ_COMMITTED"; if (isolationLevel == Connection.TRANSACTION_SERIALIZABLE) { @@ -1326,6 +1333,17 @@ private static DataSource createHikaricpDataSource(String uri, String username, return dataSource; } + static void applyHikariDebugSettings(HikariConfig config, Long leakDetectionThreshold, Boolean registerMbeans, String dsName) { + long effectiveLeakDetectionThreshold = ObjectUtils.defaultIfNull(leakDetectionThreshold, 0L); + boolean effectiveRegisterMbeans = ObjectUtils.defaultIfNull(registerMbeans, false); + + config.setLeakDetectionThreshold(effectiveLeakDetectionThreshold); + config.setRegisterMbeans(effectiveRegisterMbeans); + + LOGGER.debug("HikariCP datasource {} leakDetectionThreshold={} ms, registerMbeans={}", + dsName, effectiveLeakDetectionThreshold, effectiveRegisterMbeans); + } + private static DataSource createDbcpDataSource(String uri, String username, String password, Integer maxActive, Integer maxIdle, Long maxWait, Long timeBtwnEvictionRuns, Long minEvictableIdleTime, diff --git a/framework/db/src/test/java/com/cloud/utils/db/TransactionLegacyTest.java b/framework/db/src/test/java/com/cloud/utils/db/TransactionLegacyTest.java index 2e0af6fa1866..4d0294e2a8ab 100644 --- a/framework/db/src/test/java/com/cloud/utils/db/TransactionLegacyTest.java +++ b/framework/db/src/test/java/com/cloud/utils/db/TransactionLegacyTest.java @@ -17,6 +17,7 @@ package com.cloud.utils.db; import com.cloud.utils.Pair; +import com.zaxxer.hikari.HikariConfig; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -28,6 +29,8 @@ @RunWith(MockitoJUnitRunner.class) public class TransactionLegacyTest { + private static final String CONNECTION_PARAMS = "&" + TransactionLegacy.CONNECTION_PARAMS; + Properties properties; @Before @@ -47,7 +50,7 @@ public void getConnectionUriAndDriverTestWithoutUri() { Pair result = TransactionLegacy.getConnectionUriAndDriver(properties, null, false, "cloud"); - Assert.assertEquals("driver://host:5555/name?autoReconnect=false&someParams", result.first()); + Assert.assertEquals("driver://host:5555/name?autoReconnect=false&someParams" + CONNECTION_PARAMS, result.first()); Assert.assertEquals("driver", result.second()); } @@ -65,7 +68,7 @@ public void getConnectionUriAndDriverTestWithUri() { public void getPropertiesAndBuildConnectionUriTestDbHaDisabled() { String result = TransactionLegacy.getPropertiesAndBuildConnectionUri(properties, "strat", "driver", true, "cloud"); - Assert.assertEquals("driver://host:5555/name?autoReconnect=false&someParams&useSSL=true", result); + Assert.assertEquals("driver://host:5555/name?autoReconnect=false&someParams&useSSL=true" + CONNECTION_PARAMS, result); } @Test @@ -82,14 +85,14 @@ public void getPropertiesAndBuildConnectionUriTestDbHaEnabled() { String result = TransactionLegacy.getPropertiesAndBuildConnectionUri(properties, "strat", "driver", true, "cloud"); Assert.assertEquals("driver://host,second_host:5555/name?autoReconnect=false&someParams&useSSL=true&failOverReadOnly=true&reconnectAtTxEnd=false&autoReconnectFor" - + "Pools=true&secondsBeforeRetrySource=25&queriesBeforeRetrySource=105&initialTimeout=1000&loadBalanceStrategy=strat", result); + + "Pools=true&secondsBeforeRetrySource=25&queriesBeforeRetrySource=105&initialTimeout=1000&loadBalanceStrategy=strat" + CONNECTION_PARAMS, result); } @Test public void buildConnectionUriTestDbHaDisabled() { String result = TransactionLegacy.buildConnectionUri(null, "driver", false, "host", null, 5555, "cloud", false, null, null); - Assert.assertEquals("driver://host:5555/cloud?autoReconnect=false", result); + Assert.assertEquals("driver://host:5555/cloud?autoReconnect=false" + CONNECTION_PARAMS, result); } @Test @@ -98,20 +101,60 @@ public void buildConnectionUriTestDbHaEnabled() { String result = TransactionLegacy.buildConnectionUri("strat", "driver", false, "host", "second_host", 5555, "cloud", false, null, "dbHaParams"); - Assert.assertEquals("driver://host,second_host:5555/cloud?autoReconnect=false&dbHaParams&loadBalanceStrategy=strat", result); + Assert.assertEquals("driver://host,second_host:5555/cloud?autoReconnect=false&dbHaParams&loadBalanceStrategy=strat" + CONNECTION_PARAMS, result); } @Test public void buildConnectionUriTestUrlParamsNotNull() { String result = TransactionLegacy.buildConnectionUri(null, "driver", false, "host", null, 5555, "cloud", false, "urlParams", null); - Assert.assertEquals("driver://host:5555/cloud?autoReconnect=false&urlParams", result); + Assert.assertEquals("driver://host:5555/cloud?autoReconnect=false&urlParams" + CONNECTION_PARAMS, result); } @Test public void buildConnectionUriTestUseSslTrue() { String result = TransactionLegacy.buildConnectionUri(null, "driver", true, "host", null, 5555, "cloud", false, null, null); - Assert.assertEquals("driver://host:5555/cloud?autoReconnect=false&useSSL=true", result); + Assert.assertEquals("driver://host:5555/cloud?autoReconnect=false&useSSL=true" + CONNECTION_PARAMS, result); + } + + @Test + public void applyHikariDebugSettingsDefaultsWhenUnset() { + HikariConfig config = new HikariConfig(); + + TransactionLegacy.applyHikariDebugSettings(config, null, null, "cloud"); + + Assert.assertEquals(0L, config.getLeakDetectionThreshold()); + Assert.assertFalse(config.isRegisterMbeans()); + } + + @Test + public void applyHikariDebugSettingsKeepsZeroLeakDetectionDisabled() { + HikariConfig config = new HikariConfig(); + + TransactionLegacy.applyHikariDebugSettings(config, 0L, false, "cloud"); + + Assert.assertEquals(0L, config.getLeakDetectionThreshold()); + Assert.assertFalse(config.isRegisterMbeans()); + } + + @Test + public void applyHikariDebugSettingsAppliesPositiveLeakDetectionThreshold() { + HikariConfig config = new HikariConfig(); + + TransactionLegacy.applyHikariDebugSettings(config, 60000L, false, "cloud"); + + Assert.assertEquals(60000L, config.getLeakDetectionThreshold()); + Assert.assertFalse(config.isRegisterMbeans()); + } + + @Test + public void applyHikariDebugSettingsEnablesRegisterMbeans() { + HikariConfig config = new HikariConfig(); + + TransactionLegacy.applyHikariDebugSettings(config, null, true, "cloud"); + + Assert.assertEquals(0L, config.getLeakDetectionThreshold()); + Assert.assertTrue(config.isRegisterMbeans()); } }