From 7a5bf88365fefb9c22e9642fc8cd73220c3d8b6a Mon Sep 17 00:00:00 2001 From: pm47 Date: Tue, 20 Apr 2021 11:40:37 +0200 Subject: [PATCH] set lock timeout at connection creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From Hikari doc: > 🔤connectionInitSql This property sets a SQL statement that will be executed after every new connection creation before adding it to the pool. If this SQL is not valid or throws an exception, it will be treated as a connection failure and the standard retry logic will be followed. Default: none --- .../main/scala/fr/acinq/eclair/db/Databases.scala | 5 ++++- .../scala/fr/acinq/eclair/db/pg/PgUtils.scala | 15 ++++++--------- .../scala/fr/acinq/eclair/TestDatabases.scala | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/Databases.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/Databases.scala index e691375e2a..cb8179f877 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/Databases.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/Databases.scala @@ -211,8 +211,11 @@ object Databases extends Logging { val leaseInterval = dbConfig.getDuration("postgres.lease.interval").toSeconds.seconds val leaseRenewInterval = dbConfig.getDuration("postgres.lease.renew-interval").toSeconds.seconds require(leaseInterval > leaseRenewInterval, "invalid configuration: `db.postgres.lease.interval` must be greater than `db.postgres.lease.renew-interval`") + // We use a timeout for locks, because we might not be able to get the lock right away due to concurrent access + // by other threads. That timeout gives time for other transactions to complete, then ours can take the lock val lockTimeout = dbConfig.getDuration("postgres.lease.lock-timeout").toSeconds.seconds - PgLock.LeaseLock(instanceId, leaseInterval, leaseRenewInterval, lockTimeout, lockExceptionHandler) + hikariConfig.setConnectionInitSql(s"SET lock_timeout TO '${lockTimeout.toSeconds}s'") + PgLock.LeaseLock(instanceId, leaseInterval, leaseRenewInterval, lockExceptionHandler) case unknownLock => throw new RuntimeException(s"unknown postgres lock type: `$unknownLock`") } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgUtils.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgUtils.scala index d8e512e3ed..9e72afa869 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgUtils.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgUtils.scala @@ -106,12 +106,12 @@ object PgUtils extends JdbcUtils { * * `lockExceptionHandler` provides a lock exception handler to customize the behavior when locking errors occur. */ - case class LeaseLock(instanceId: UUID, leaseDuration: FiniteDuration, leaseRenewInterval: FiniteDuration, lockTimeout: FiniteDuration, lockFailureHandler: LockFailureHandler) extends PgLock { + case class LeaseLock(instanceId: UUID, leaseDuration: FiniteDuration, leaseRenewInterval: FiniteDuration, lockFailureHandler: LockFailureHandler) extends PgLock { import LeaseLock._ override def obtainExclusiveLock(implicit ds: DataSource): Unit = { - obtainDatabaseLease(instanceId, leaseDuration, lockTimeout) match { + obtainDatabaseLease(instanceId, leaseDuration) match { case Right(_) => () case Left(ex) => lockFailureHandler(ex) } @@ -140,7 +140,7 @@ object PgUtils extends JdbcUtils { /** We use a [[LeaseLock]] mechanism to get a [[LockLease]]. */ case class LockLease(expiresAt: Timestamp, instanceId: UUID, expired: Boolean) - private def obtainDatabaseLease(instanceId: UUID, leaseDuration: FiniteDuration, lockTimeout: FiniteDuration, attempt: Int = 1)(implicit ds: DataSource): Either[LockFailure, LockLease] = synchronized { + private def obtainDatabaseLease(instanceId: UUID, leaseDuration: FiniteDuration, attempt: Int = 1)(implicit ds: DataSource): Either[LockFailure, LockLease] = synchronized { logger.debug(s"trying to acquire database lease (attempt #$attempt) instance ID=$instanceId") // this is a recursive method, we need to make sure we don't enter an infinite loop @@ -148,7 +148,7 @@ object PgUtils extends JdbcUtils { try { inTransaction { implicit connection => - acquireExclusiveTableLock(lockTimeout) + acquireExclusiveTableLock() logger.debug("database lease was successfully acquired") checkDatabaseLease(connection, instanceId) match { case Right(_) => @@ -169,7 +169,7 @@ object PgUtils extends JdbcUtils { connection => logger.warn(s"table $LeaseTable does not exist, trying to create it") initializeLeaseTable(connection) - obtainDatabaseLease(instanceId, leaseDuration, lockTimeout, attempt + 1) + obtainDatabaseLease(instanceId, leaseDuration, attempt + 1) } case t: Throwable => Left(LockFailure.GeneralLockException(t)) } @@ -183,12 +183,9 @@ object PgUtils extends JdbcUtils { } } - private def acquireExclusiveTableLock(lockTimeout: FiniteDuration)(implicit connection: Connection): Unit = { + private def acquireExclusiveTableLock()(implicit connection: Connection): Unit = { using(connection.createStatement()) { statement => - // We use a timeout here, because we might not be able to get the lock right away due to concurrent access - // by other threads. That timeout gives time for other transactions to complete, then ours can take the lock - statement.executeUpdate(s"SET lock_timeout TO '${lockTimeout.toSeconds}s'") withMetrics("utils/lock", Tags.DbBackends.Postgres) { statement.executeUpdate(s"LOCK TABLE $LeaseTable IN ACCESS EXCLUSIVE MODE") } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/TestDatabases.scala b/eclair-core/src/test/scala/fr/acinq/eclair/TestDatabases.scala index 541f47cfcf..6ffd01c0e7 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/TestDatabases.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/TestDatabases.scala @@ -59,7 +59,7 @@ object TestDatabases { val datasource: DataSource = pg.getPostgresDatabase val hikariConfig = new HikariConfig hikariConfig.setDataSource(datasource) - val lock: PgLock.LeaseLock = PgLock.LeaseLock(UUID.randomUUID(), 10 minutes, 8 minute, 5 seconds, LockFailureHandler.logAndThrow) + val lock: PgLock.LeaseLock = PgLock.LeaseLock(UUID.randomUUID(), 10 minutes, 8 minute, LockFailureHandler.logAndThrow) val jdbcUrlFile: File = new File(sys.props("tmp.dir"), s"jdbcUrlFile_${UUID.randomUUID()}.tmp") jdbcUrlFile.deleteOnExit()