mirror of
https://github.com/corda/corda.git
synced 2024-12-20 05:28:21 +00:00
ENT-1447 Database transaction optional ability to retry on nested SQLException. (#3046)
Database transaction can be set to retry failure due to any exception with a cause or a nested cause of SQLException type. Rationale: By the default transaction is retried only for SQLException. It may happen that SQL Exception is wrapped by Hibernate exception, allow to retry such cases if requested e.g. database.transaction(recoverableFailureTolerance = 3, recoverAnyNestedSQLException = true) { .... }
This commit is contained in:
parent
1535a5f601
commit
bb95156262
@ -116,10 +116,8 @@ class CordaPersistence(
|
||||
* @param isolationLevel isolation level for the transaction.
|
||||
* @param statement to be executed in the scope of this transaction.
|
||||
*/
|
||||
fun <T> transaction(isolationLevel: TransactionIsolationLevel, statement: DatabaseTransaction.() -> T): T {
|
||||
_contextDatabase.set(this)
|
||||
return transaction(isolationLevel, 2, statement)
|
||||
}
|
||||
fun <T> transaction(isolationLevel: TransactionIsolationLevel, statement: DatabaseTransaction.() -> T): T =
|
||||
transaction(isolationLevel, 2, false, statement)
|
||||
|
||||
/**
|
||||
* Executes given statement in the scope of transaction with the transaction level specified at the creation time.
|
||||
@ -127,16 +125,26 @@ class CordaPersistence(
|
||||
*/
|
||||
fun <T> transaction(statement: DatabaseTransaction.() -> T): T = transaction(defaultIsolationLevel, statement)
|
||||
|
||||
private fun <T> transaction(isolationLevel: TransactionIsolationLevel, recoverableFailureTolerance: Int, statement: DatabaseTransaction.() -> T): T {
|
||||
/**
|
||||
* Executes given statement in the scope of transaction, with the given isolation level.
|
||||
* @param isolationLevel isolation level for the transaction.
|
||||
* @param recoverableFailureTolerance number of transaction commit retries for SQL while SQL exception is encountered.
|
||||
* @param recoverAnyNestedSQLException retry transaction on any SQL Exception wrapped as a cause of [Throwable].
|
||||
* @param statement to be executed in the scope of this transaction.
|
||||
*/
|
||||
fun <T> transaction(isolationLevel: TransactionIsolationLevel, recoverableFailureTolerance: Int,
|
||||
recoverAnyNestedSQLException: Boolean, statement: DatabaseTransaction.() -> T): T {
|
||||
_contextDatabase.set(this)
|
||||
val outer = contextTransactionOrNull
|
||||
return if (outer != null) {
|
||||
outer.statement()
|
||||
} else {
|
||||
inTopLevelTransaction(isolationLevel, recoverableFailureTolerance, statement)
|
||||
inTopLevelTransaction(isolationLevel, recoverableFailureTolerance, recoverAnyNestedSQLException, statement)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> inTopLevelTransaction(isolationLevel: TransactionIsolationLevel, recoverableFailureTolerance: Int, statement: DatabaseTransaction.() -> T): T {
|
||||
private fun <T> inTopLevelTransaction(isolationLevel: TransactionIsolationLevel, recoverableFailureTolerance: Int,
|
||||
recoverAnyNestedSQLException: Boolean, statement: DatabaseTransaction.() -> T): T {
|
||||
var recoverableFailureCount = 0
|
||||
fun <T> quietly(task: () -> T) = try {
|
||||
task()
|
||||
@ -149,13 +157,14 @@ class CordaPersistence(
|
||||
val answer = transaction.statement()
|
||||
transaction.commit()
|
||||
return answer
|
||||
} catch (e: SQLException) {
|
||||
quietly(transaction::rollback)
|
||||
if (++recoverableFailureCount > recoverableFailureTolerance) throw e
|
||||
log.warn("Caught failure, will retry:", e)
|
||||
} catch (e: Throwable) {
|
||||
quietly(transaction::rollback)
|
||||
if (e is SQLException || (recoverAnyNestedSQLException && e.hasSQLExceptionCause())) {
|
||||
if (++recoverableFailureCount > recoverableFailureTolerance) throw e
|
||||
log.warn("Caught failure, will retry $recoverableFailureCount/$recoverableFailureTolerance:", e)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
} finally {
|
||||
quietly(transaction::close)
|
||||
}
|
||||
@ -246,3 +255,12 @@ fun <T : Any> rx.Observable<T>.wrapWithDatabaseTransaction(db: CordaPersistence?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Check if any nested cause is of [SQLException] type. */
|
||||
private fun Throwable.hasSQLExceptionCause(): Boolean =
|
||||
if (cause == null)
|
||||
false
|
||||
else if (cause is SQLException)
|
||||
true
|
||||
else
|
||||
cause?.hasSQLExceptionCause() ?: false
|
Loading…
Reference in New Issue
Block a user