Skip to content

Commit

Permalink
set lock timeout at connection creation
Browse files Browse the repository at this point in the history
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
  • Loading branch information
pm47 committed Apr 20, 2021
1 parent 38dba58 commit 7a5bf88
Show file tree
Hide file tree
Showing 3 changed files with 11 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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`")
}

Expand Down
15 changes: 6 additions & 9 deletions eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -140,15 +140,15 @@ 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
if (attempt > 3) return Left(LockFailure.TooManyLockAttempts)

try {
inTransaction { implicit connection =>
acquireExclusiveTableLock(lockTimeout)
acquireExclusiveTableLock()
logger.debug("database lease was successfully acquired")
checkDatabaseLease(connection, instanceId) match {
case Right(_) =>
Expand All @@ -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))
}
Expand All @@ -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")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down

0 comments on commit 7a5bf88

Please sign in to comment.