+ /**
+ * Transaction cancellation is a heavyweight operation. We only support cancelation of a locked transaction
+ * and return false for everything else. Cancelling such a transaction will result in all transactions in the
+ * batch to be cancelled.
+ *
+ * @param tx Backend shared transaction
+ * @param frontendTx transaction
+ * @param isOpen indicator whether the transaction was already closed
+ */
+ synchronized void cancelTransaction(final PingPongTransaction tx, final DOMDataReadWriteTransaction frontendTx) {
+ // Attempt to unlock the operation.
+ final boolean lockedMatch = LOCKED_UPDATER.compareAndSet(this, tx, null);
+ Verify.verify(lockedMatch, "Cancelling transaction %s collided with locked transaction %s", tx, lockedTx);
+
+ // Cancel the backend transaction, so we do not end up leaking it.
+ final boolean backendCancelled = tx.getTransaction().cancel();
+
+ if (failed) {
+ // The transaction has failed, this is probably the user just clearing up the transaction they had. We have
+ // already cancelled the transaction anyway,
+ return;
+ } else if (!backendCancelled) {
+ LOG.warn("Backend transaction cannot be cancelled during cancellation of {}, attempting to continue", tx);
+ }
+
+ // We have dealt with canceling the backend transaction and have unlocked the transaction. Since we are still
+ // inside the synchronized block, any allocations are blocking on the slow path. Now we have to decide the fate
+ // of this transaction chain.
+ //
+ // If there are no other frontend transactions in this batch we are aligned with backend state and we can
+ // continue processing.
+ if (frontendTx.equals(tx.getFrontendTransaction())) {
+ LOG.debug("Cancelled transaction {} was head of the batch, resuming processing", tx);
+ return;
+ }
+
+ // There are multiple frontend transactions in this batch. We have to report them as failed, which dooms this
+ // transaction chain, too. Since we just came off of a locked transaction, we do not have a ready transaction
+ // at the moment, but there may be some transaction in-flight. So we proceed to shutdown the backend chain
+ // and mark the fact that we should be turning its completion into a failure.
+ deadTx = new SimpleImmutableEntry<>(tx, new CancellationException("Transaction " + frontendTx + " canceled")
+ .fillInStackTrace());
+ delegate.close();
+ }
+