All Downloads are FREE. Search and download functionalities are using the official Maven repository.

me.ahoo.simba.jdbc.JdbcMutexOwnerRepository.kt Maven / Gradle / Ivy

/*
 * Copyright [2021-present] [ahoo wang  (https://github.com/Ahoo-Wang)].
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *      http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package me.ahoo.simba.jdbc

import me.ahoo.simba.SimbaException
import org.slf4j.LoggerFactory
import java.sql.Connection
import java.sql.SQLException
import java.sql.SQLIntegrityConstraintViolationException
import javax.sql.DataSource

/**
 * Jdbc Mutex Owner Repository.
 *
 * @author ahoo wang
 */
class JdbcMutexOwnerRepository(private val dataSource: DataSource) : MutexOwnerRepository {
    companion object {
        private val log = LoggerFactory.getLogger(JdbcMutexOwnerRepository::class.java)
        private const val SQL_INIT_MUTEX =
            """
                insert into simba_mutex 
                (mutex, acquired_at, ttl_at, transition_at, owner_id, version) 
                values 
                (?, 0, 0, 0, '', 0);
            """
        private const val SQL_GET =
            """
                select acquired_at, ttl_at, transition_at, owner_id, version, cast(unix_timestamp(current_timestamp(3)) * 1000 as unsigned) as current_at 
                from simba_mutex 
                where mutex = ?;
            """
        private const val SQL_ACQUIRE =
            """
                update simba_mutex 
                set acquired_at=cast(unix_timestamp(current_timestamp(3)) * 1000 as unsigned),
                ttl_at=(cast(unix_timestamp(current_timestamp(3)) * 1000 as unsigned) + ?),
                transition_at=(cast(unix_timestamp(current_timestamp(3)) * 1000 as unsigned) + ?),
                owner_id= ?,
                version=version + 1 
                where mutex = ? 
                and (
                    (transition_at < (unix_timestamp(current_timestamp(3)) * 1000))
                    or
                    (owner_id = ? and transition_at > (unix_timestamp(current_timestamp(3)) * 1000))
                );
            """
        private const val SQL_RELEASE =
            """
                update simba_mutex 
                set acquired_at=0,
                ttl_at=0,
                transition_at=0,
                owner_id='',
                version=version + 1 
                where mutex = ? and owner_id = ?
            """
    }

    @Throws(SQLException::class, SQLIntegrityConstraintViolationException::class)
    override fun initMutex(mutex: String): Boolean {
        require(mutex.isNotBlank()) { "mutex is blank!" }
        if (log.isInfoEnabled) {
            log.info("initMutex - mutex:[{}].", mutex)
        }
        dataSource.connection.use { connection -> return initMutex(connection, mutex) }
    }

    @Throws(SQLException::class)
    private fun initMutex(connection: Connection, mutex: String?): Boolean {
        connection.prepareStatement(SQL_INIT_MUTEX).use { initStatement ->
            initStatement.setString(1, mutex)
            val affected = initStatement.executeUpdate()
            return affected > 0
        }
    }

    override fun tryInitMutex(mutex: String): Boolean {
        return try {
            initMutex(mutex)
            true
        } catch (throwable: Throwable) {
            if (log.isInfoEnabled) {
                log.info("tryInitMutex failed.[{}]", throwable.message)
            }
            false
        }
    }

    override fun getOwner(mutex: String): MutexOwnerEntity {
        dataSource.connection.use { connection -> return getOwner(connection, mutex) }
    }

    @Throws(SQLException::class)
    private fun getOwner(connection: Connection, mutex: String): MutexOwnerEntity {
        connection.prepareStatement(SQL_GET).use { getStatement ->
            getStatement.setString(1, mutex)
            getStatement.executeQuery().use { resultSet ->
                if (!resultSet.next()) {
                    throw NotFoundMutexOwnerException(
                        "No mutex:[$mutex] is found, please initialize[MutexOwnerRepository.tryInitMutex] it first."
                    )
                }
                val acquiredAt = resultSet.getLong(1)
                val ttlAt = resultSet.getLong(2)
                val transitionAt = resultSet.getLong(3)
                val ownerId = resultSet.getString(4)
                val version = resultSet.getInt(5)
                val currentAt = resultSet.getLong(6)
                val entity = MutexOwnerEntity(mutex, ownerId, acquiredAt, ttlAt, transitionAt)
                entity.version = version
                entity.currentDbAt = currentAt
                return entity
            }
        }
    }

    override fun ensureOwner(mutex: String): MutexOwnerEntity {
        dataSource.connection.use { connection -> return ensureOwner(connection, mutex) }
    }

    @Throws(SQLException::class)
    private fun ensureOwner(connection: Connection, mutex: String): MutexOwnerEntity {
        return try {
            getOwner(connection, mutex)
        } catch (notFoundMutexOwnerException: NotFoundMutexOwnerException) {
            try {
                if (log.isInfoEnabled) {
                    log.info("ensureOwner - initMutex:[{}].", mutex)
                }
                initMutex(connection, mutex)
            } catch (sqlIntegrityConstraintViolationException: SQLException) {
                if (log.isWarnEnabled) {
                    log.warn(
                        sqlIntegrityConstraintViolationException.message,
                        sqlIntegrityConstraintViolationException
                    )
                }
            }
            getOwner(connection, mutex)
        }
    }

    /**
     * acquire mutex.
     *
     * @param mutex mutex
     * @param contenderId contenderId
     * @param ttl [java.util.concurrent.TimeUnit.MILLISECONDS]
     * @param transition transition
     * @return if return true,acquired.
     */
    override fun acquire(mutex: String, contenderId: String, ttl: Long, transition: Long): Boolean {
        dataSource.connection.use { return acquire(it, mutex, contenderId, ttl, transition) }
    }

    @Throws(SQLException::class)
    private fun acquire(
        connection: Connection,
        mutex: String,
        contenderId: String,
        ttl: Long,
        transition: Long
    ): Boolean {
        connection.prepareStatement(SQL_ACQUIRE).use { acquireStatement ->
            acquireStatement.setLong(1, ttl)
            acquireStatement.setLong(2, ttl + transition)
            acquireStatement.setString(3, contenderId)
            acquireStatement.setString(4, mutex)
            acquireStatement.setString(5, contenderId)
            val affected = acquireStatement.executeUpdate()
            return affected > 0
        }
    }

    override fun acquireAndGetOwner(mutex: String, contenderId: String, ttl: Long, transition: Long): MutexOwnerEntity {
        try {
            dataSource.connection.use { connection ->
                connection.autoCommit = false
                return try {
                    var acquired = acquire(connection, mutex, contenderId, ttl, transition)
                    var mutexOwner = ensureOwner(connection, mutex)
                    if (!acquired && !mutexOwner.hasOwner()) {
                        /**
                         * 没有竞争到领导权 && 当前不存在领导者 ==> 初始化时
                         */
                        if (log.isInfoEnabled) {
                            log.info(
                                "acquireAndGetOwner - There is no competition for leadership && There is currently no leader [When initializing]. Retry!"
                            )
                        }
                        acquired = acquire(connection, mutex, contenderId, ttl, transition)
                        mutexOwner = ensureOwner(connection, mutex)
                    }
                    check(!(acquired && !mutexOwner.isOwner(contenderId))) {
                        /**
                         * 当前竞争者已竞争到领导权 && 最新 mutexOwner 不是当前竞争者
                         */
                        "Contender:[$contenderId] has acquired leadership, but MutexOwner status is inconsistent!"
                    }
                    connection.commit()
                    mutexOwner
                } catch (throwable: Throwable) {
                    connection.rollback()
                    throw SimbaException(throwable.message!!, throwable)
                }
            }
        } catch (sqlException: SQLException) {
            throw SimbaException(sqlException.message!!, sqlException)
        }
    }

    override fun release(mutex: String, contenderId: String): Boolean {
        dataSource.connection.use { connection ->
            connection.prepareStatement(SQL_RELEASE).use { initStatement ->
                initStatement.setString(1, mutex)
                initStatement.setString(2, contenderId)
                val affected = initStatement.executeUpdate()
                return affected > 0
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy