Skip to content

Commit

Permalink
fix: EXPOSED-80 Set repetition policy for suspended transactions
Browse files Browse the repository at this point in the history
Fix detekt maxLineLength issues.
  • Loading branch information
bog-walk committed Jul 14, 2023
1 parent 72588f3 commit 73c64c9
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,30 +34,33 @@ class DatabaseConfig private constructor(
*/
var defaultFetchSize: Int? = null,
/**
* Default transaction isolation level. If not specified database-specific level will be used.
* Can be overridden on per-transaction level by specifying `transactionIsolation` parameter of `transaction` function.
* Default transaction isolation level. If not specified, the database-specific level will be used.
* This can be overridden on a per-transaction level by specifying the `transactionIsolation` parameter of
* the `transaction` function.
* Check [Database.getDefaultIsolationLevel] for the database defaults.
*/
var defaultIsolationLevel: Int = -1,
/**
* How many retries will be made inside any `transaction` block if SQLException happens.
* Can be overridden on per-transaction level by specifying `repetitionAttempts` property in `transaction` block.
* This can be overridden on a per-transaction level by specifying the `repetitionAttempts` property in a
* `transaction` block.
* Default attempts are 3.
*/
var defaultRepetitionAttempts: Int = 3,
/**
* The minimum number of milliseconds to wait before retrying a transaction if SQLException happens.
* Can be overridden on per-transaction level by specifying `minRepetitionDelay` property in `transaction` block.
* This can be overridden on a per-transaction level by specifying the `minRepetitionDelay` property in a
* `transaction` block.
* Default minimum delay is 0.
*/
var defaultMinRepetitionDelay: Long = 0,
/**
* The maximum number of milliseconds to wait before retrying a transaction if SQLException happens.
* Can be overridden on per-transaction level by specifying `maxRepetitionDelay` property in `transaction` block.
* This can be overridden on a per-transaction level by specifying the `maxRepetitionDelay` property in a
* `transaction` block.
* Default maximum delay is 0.
*/
var defaultMaxRepetitionDelay: Long = 0,

/**
* Should all connections/transactions be executed in read-only mode by default or not.
* Default state is false.
Expand All @@ -66,25 +69,27 @@ class DatabaseConfig private constructor(
/**
* Threshold in milliseconds to log queries which exceed the threshold with WARN level.
* No tracing enabled by default.
* Can be set on per-transaction level by setting [Transaction.warnLongQueriesDuration] field.
* This can be set on a per-transaction level by setting [Transaction.warnLongQueriesDuration] field.
*/
var warnLongQueriesDuration: Long? = null,
/**
* Amount of entities to keep in an EntityCache per an Entity class.
* Applicable only when `exposed-dao` module is used.
* Can be overridden on per-transaction basis via [EntityCache.maxEntitiesToStore].
* This can be overridden on a per-transaction basis via [EntityCache.maxEntitiesToStore].
* All entities will be kept by default.
*/
var maxEntitiesToStoreInCachePerEntity: Int = Int.MAX_VALUE,
/**
* Turns on "mode" for Exposed DAO to store relations (after they were loaded)
* within the entity that will allow to access them outside the transaction.
* Turns on "mode" for Exposed DAO to store relations (after they were loaded) within the entity that will
* allow access to them outside the transaction.
* Useful when [eager loading](https://github.com/JetBrains/Exposed/wiki/DAO#eager-loading) is used.
*/
var keepLoadedReferencesOutOfTransaction: Boolean = false,

/**
* Set the explicit dialect for a database. Can be useful when working with unsupported dialects which have the same behavior as the one that Exposed supports.
* Set the explicit dialect for a database.
* This can be useful when working with unsupported dialects which have the same behavior as the one that
* Exposed supports.
*/
var explicitDialect: DatabaseDialect? = null,

Expand All @@ -95,7 +100,8 @@ class DatabaseConfig private constructor(

/**
* Log too much result sets opened in parallel.
* The error log will contain the stacktrace of the place in the code where new result set occurs, and it exceeds the threshold.
* The error log will contain the stacktrace of the place in the code where a new result set occurs, and it
* exceeds the threshold.
* 0 value means no log needed.
*/
var logTooMuchResultSetsThreshold: Int = 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ open class UserDataHolder {
fun <T : Any> getOrCreate(key: Key<T>, init: () -> T): T = userdata.getOrPut(key, init) as T
}

open class Transaction(private val transactionImpl: TransactionInterface) : UserDataHolder(), TransactionInterface by transactionImpl {
open class Transaction(
private val transactionImpl: TransactionInterface
) : UserDataHolder(), TransactionInterface by transactionImpl {
final override val db: Database = transactionImpl.db

var statementCount: Int = 0
Expand All @@ -43,8 +45,10 @@ open class Transaction(private val transactionImpl: TransactionInterface) : User

/** The number of retries that will be made inside this `transaction` block if SQLException happens */
var repetitionAttempts: Int = db.transactionManager.defaultRepetitionAttempts

/** The minimum number of milliseconds to wait before retrying this `transaction` if SQLException happens */
var minRepetitionDelay: Long = db.transactionManager.defaultMinRepetitionDelay

/** The maximum number of milliseconds to wait before retrying this `transaction` if SQLException happens */
var maxRepetitionDelay: Long = db.transactionManager.defaultMaxRepetitionDelay

Expand Down Expand Up @@ -100,7 +104,11 @@ open class Transaction(private val transactionImpl: TransactionInterface) : User
@Suppress("MagicNumber")
private fun describeStatement(delta: Long, stmt: String): String = "[${delta}ms] ${stmt.take(1024)}\n\n"

fun exec(@Language("sql") stmt: String, args: Iterable<Pair<IColumnType, Any?>> = emptyList(), explicitStatementType: StatementType? = null) =
fun exec(
@Language("sql") stmt: String,
args: Iterable<Pair<IColumnType, Any?>> = emptyList(),
explicitStatementType: StatementType? = null
) =
exec(stmt, args, explicitStatementType) { }

fun <T : Any> exec(
Expand Down Expand Up @@ -200,13 +208,18 @@ open class Transaction(private val transactionImpl: TransactionInterface) : User

internal fun getRetryInterval(): Long = if (repetitionAttempts > 0) {
maxOf((maxRepetitionDelay - minRepetitionDelay) / (repetitionAttempts + 1), 1)
} else 0
} else {
0
}

companion object {
internal val globalInterceptors = arrayListOf<GlobalStatementInterceptor>()

init {
ServiceLoader.load(GlobalStatementInterceptor::class.java, GlobalStatementInterceptor::class.java.classLoader).forEach {
ServiceLoader.load(
GlobalStatementInterceptor::class.java,
GlobalStatementInterceptor::class.java.classLoader
).forEach {
globalInterceptors.add(it)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,7 @@ class ThreadLocalTransactionManager(
get() = connectionLazy.value

private val useSavePoints = outerTransaction != null && db.useNestedTransactions
private var savepoint: ExposedSavepoint? = if (useSavePoints) {
connection.setSavepoint(savepointName)
} else null
private var savepoint: ExposedSavepoint? = if (useSavePoints) connection.setSavepoint(savepointName) else null

override fun commit() {
if (connectionLazy.isInitialized()) {
Expand Down Expand Up @@ -216,7 +214,6 @@ fun <T> inTopLevelTransaction(
outerTransaction: Transaction? = null,
statement: Transaction.() -> T
): T {

fun run(): T {
var repetitions = 0

Expand Down Expand Up @@ -259,7 +256,7 @@ fun <T> inTopLevelTransaction(
try {
Thread.sleep(delay)
} catch (cause: InterruptedException) {
// Do nothing
// Do nothing
}
} catch (cause: Throwable) {
val currentStatement = transaction.currentStatement
Expand Down Expand Up @@ -302,7 +299,9 @@ internal fun handleSQLException(cause: SQLException, transaction: Transaction, r
}
}
exposedLogger.warn(message, cause)
transaction.rollbackLoggingException { exposedLogger.warn("Transaction rollback failed: ${it.message}. See previous log line for statement", it) }
transaction.rollbackLoggingException {
exposedLogger.warn("Transaction rollback failed: ${it.message}. See previous log line for statement", it)
}
}

internal fun closeStatementsAndConnection(transaction: Transaction) {
Expand All @@ -317,5 +316,7 @@ internal fun closeStatementsAndConnection(transaction: Transaction) {
} catch (cause: Exception) {
exposedLogger.warn("Statements close failed", cause)
}
transaction.closeLoggingException { exposedLogger.warn("Transaction close failed: ${it.message}. Statement: $currentStatement", it) }
transaction.closeLoggingException {
exposedLogger.warn("Transaction close failed: ${it.message}. Statement: $currentStatement", it)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,5 @@ internal inline fun TransactionInterface.closeLoggingException(log: (Exception)
}

val Database?.transactionManager: TransactionManager
get() = TransactionManager.managerFor(this) ?: throw RuntimeException("database $this don't have any transaction manager")
get() = TransactionManager.managerFor(this)
?: throw RuntimeException("database $this don't have any transaction manager")
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ class TransactionStore<T : Any>(val init: (Transaction.() -> T)? = null) : ReadW

private val key = Key<T>()

@Suppress("UNCHECKED_CAST")
override fun getValue(thisRef: Any?, property: KProperty<*>): T? {
val currentOrNullTransaction = TransactionManager.currentOrNull()
return currentOrNullTransaction?.getUserData(key)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,16 @@ import kotlin.coroutines.coroutineContext

internal class TransactionContext(val manager: TransactionManager?, val transaction: Transaction?)

internal class TransactionScope(internal val tx: Lazy<Transaction>, parent: CoroutineContext) : CoroutineScope, CoroutineContext.Element {
internal class TransactionScope(
internal val tx: Lazy<Transaction>,
parent: CoroutineContext
) : CoroutineScope, CoroutineContext.Element {
private val baseScope = CoroutineScope(parent)
override val coroutineContext get() = baseScope.coroutineContext + this
override val key = Companion

internal fun holdsSameTransaction(transaction: Transaction?) = transaction != null && tx.isInitialized() && tx.value == transaction
internal fun holdsSameTransaction(transaction: Transaction?) =
transaction != null && tx.isInitialized() && tx.value == transaction
companion object : CoroutineContext.Key<TransactionScope>
}

Expand Down Expand Up @@ -73,7 +77,10 @@ suspend fun <T> newSuspendedTransaction(
* The resulting `TransactionScope` is derived from the current `coroutineContext` if the latter already holds [this] `Transaction`;
* otherwise, a new scope is created using [this] `Transaction` and a given coroutine [context].
*/
suspend fun <T> Transaction.withSuspendTransaction(context: CoroutineContext? = null, statement: suspend Transaction.() -> T): T =
suspend fun <T> Transaction.withSuspendTransaction(
context: CoroutineContext? = null,
statement: suspend Transaction.() -> T
): T =
withTransactionScope(context, this, db = null, transactionIsolation = null) {
suspendedTransactionAsyncInternal(false, statement).await()
}
Expand Down

0 comments on commit 73c64c9

Please sign in to comment.