-
Notifications
You must be signed in to change notification settings - Fork 38.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Omit cancellation of transactional Monos in TransactionOperator #23562
Omit cancellation of transactional Monos in TransactionOperator #23562
Conversation
TransactionOperator.as(Mono) now no longer short-cuts via a Flux.next() but provides an implementation via Mono.usingWhen(…). The short-cut previously issued a cancellation signal to the transactional Mono causing the transaction cleanup to happen without a handle for synchronization. Using Mono.usingWhen(…) initiates transaction cleanup when the Mono completes eliminating the need for cancellation of the transactional Publisher. This change does not fully fix spring-projectsgh-23304 but it softens its impact because TransactionalOperator.transactional(Mono) avoids cancellation.
// This will normally result in a target object being invoked. | ||
// Need re-wrapping of ReactiveTransaction until we get hold of the exception | ||
// through usingWhen. | ||
return status.flatMap(it -> Mono.usingWhen(Mono.just(it), ignore -> mono, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wouldn't it be better to start from Mono.usingWhen
?
return Mono.usingWhen(status, ignore -> mono, this.transactionManager::commit, ...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We require the status value outside of usingWhen(…)
(see rollbackOnException
)
// Need re-wrapping of ReactiveTransaction until we get hold of the exception | ||
// through usingWhen. | ||
return status.flatMap(it -> Mono.usingWhen(Mono.just(it), ignore -> mono, | ||
this.transactionManager::commit, s -> Mono.empty()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note that in case of cancellation, since no asyncCancel
is defined here, it defaults to commit
. Also, it feels weird that the error case is basically ignored
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commit on cancel is what we want if just a subset of data is processed. In imperative code this compares to processing a subset of a result list.
The error case is ignored because errors are handled after the usingWhen
operator. In case a pre-commit action fails, we want to issue a rollback. Committing a transaction consists of several activities of which the actual commit is just one of them. So if a commit fails, we still need to rollback,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
makes sense, thanks for the explanations. nothing to add 👍
This PR is good to be merged from my side. It would be good to have this fix available in 5.2 GA. |
TransactionOperator.as(Mono)
now no longer short-cuts via aFlux.next()
but provides an implementation viaMono.usingWhen(…)
.The short-cut previously issued a cancellation signal to the transactional
Mono
causing the transaction cleanup to happen without a handle for synchronization.Using
Mono.usingWhen(…)
initiates transaction cleanup when theMono
completes eliminating the need for cancellation of the transactionalPublisher
.This change does not fully fix #23304 but it softens its impact because
TransactionalOperator.transactional(Mono)
avoids cancellation./cc @michael-simons @simonbasle @smaldini