Skip to content

Instantly share code, notes, and snippets.

@134130
Last active August 6, 2023 07:42
Show Gist options
  • Save 134130/f6e51d9af697bcb3d92fd3e3f8105fca to your computer and use it in GitHub Desktop.
Save 134130/f6e51d9af697bcb3d92fd3e3f8105fca to your computer and use it in GitHub Desktop.
Simple Distributed Lock with Databases on Spring Application
  • Simple distributed lock for spring applications
  • With Databases
    • MySQLDistLock.kt
    • PostgresDistLock.kt
@Component
class Lock(
dataSource: DataSource,
) {
init {
initDataSource(dataSource)
}
companion object {
private lateinit var dataSource: DataSource
fun <T> executeWithLock(key: String, timeout: Int, timeUnit: TimeUnit, action: () -> T): T =
dataSource.connection.use {
if (!tryGetLock(it, key, timeUnit.toSeconds(timeout))) {
throw LockWaitTimeoutException()
}
try {
action()
} finally {
releaseLock(it, key.hashCode().toLong())
}
}
private fun tryGetLock(connection: Connection, key: String, timeout: Int): Boolean =
connection.prepareStatement("SELECT GET_LOCK(?, ?)").use {
it.setString(1, key)
it.setInt(2, timeout)
it.executeQuery().use { result ->
result.next()
result.getBoolean(1)
}
}
private fun releaseLock(connection: Connection, key: String) {
connection.prepareStatement("SELECT RELEASE_LOCK(?)").use {
it.setString(1, key)
it.executeQuery()
}
}
}
}
@Component
class Lock(
dataSource: DataSource,
) {
init {
initDataSource(dataSource)
}
companion object {
private lateinit var dataSource: DataSource
fun <T> executeWithLock(key: String, timeout: Long, timeUnit: TimeUnit, action: () -> T): T =
executeWithLock(key.hashCode().toLong(), timeout, timeUnit, action)
private fun <T> executeWithLock(key: Long, timeout: Long, timeUnit: TimeUnit, action: () -> T): T =
dataSource.connection.use {
val expiry = System.currentTimeMillis() + timeUnit.toMillis(timeout)
var lock: Boolean = tryGetLock(it, key)
while (!lock && System.currentTimeMillis() < expiry) {
Thread.sleep(100)
lock = tryGetLock(it, key)
}
if (!lock) {
throw LockWaitTimeoutException()
}
try {
action()
} finally {
releaseLock(it, key)
}
}
private fun tryGetLock(connection: Connection, key: Long): Boolean =
connection.prepareStatement("SELECT pg_try_advisory_lock(?)").use {
it.setLong(1, key)
it.executeQuery().use { result ->
result.next()
result.getBoolean(1)
}
}
private fun releaseLock(connection: Connection, key: Long) {
connection.prepareStatement("SELECT pg_advisory_unlock(?)").use {
it.setLong(1, key)
it.executeQuery()
}
}
private fun initDataSource(dataSource: DataSource) {
this.dataSource = dataSource
}
}
}
class LockWaitTimeoutException : TimeoutException("Lock wait timeout exceeded")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment