This is an automated email from the ASF dual-hosted git repository.

alexpl pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new 59d8c66dccb IGNITE-26059 Fix transaction closing on explicit rollback 
or commit - Fixes #12238.
59d8c66dccb is described below

commit 59d8c66dccb3ca1bb45a17c0294a7578c4f4e327
Author: Ilya Shishkov <[email protected]>
AuthorDate: Thu Aug 14 17:21:33 2025 +0300

    IGNITE-26059 Fix transaction closing on explicit rollback or commit - Fixes 
#12238.
    
    Signed-off-by: Aleksey Plekhanov <[email protected]>
---
 .../cache/transactions/IgniteTransactionsImpl.java |   4 +-
 .../cache/transactions/TransactionProxyImpl.java   |  34 ++++-
 .../TransactionContextCleanupTest.java             | 144 +++++++++++++++++++++
 .../cache/transactions/TxRollbackAsyncTest.java    |   3 +
 .../ignite/testsuites/IgniteCacheTestSuite6.java   |   3 +
 5 files changed, 184 insertions(+), 4 deletions(-)

diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTransactionsImpl.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTransactionsImpl.java
index 95c3c2acba6..45092f80999 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTransactionsImpl.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTransactionsImpl.java
@@ -217,9 +217,9 @@ public class IgniteTransactionsImpl<K, V> implements 
IgniteTransactionsEx {
 
     /** {@inheritDoc} */
     @Nullable @Override public Transaction tx() {
-        GridNearTxLocal tx = cctx.tm().userTx();
+        GridNearTxLocal tx = cctx.tm().threadLocalTx(null);
 
-        return tx != null ? tx.proxy() : null;
+        return tx != null && !tx.internal() ? tx.proxy() : null;
     }
 
     /** {@inheritDoc} */
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/TransactionProxyImpl.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/TransactionProxyImpl.java
index 7a2bd504d8b..c9dccdbea66 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/TransactionProxyImpl.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/TransactionProxyImpl.java
@@ -319,6 +319,8 @@ public class TransactionProxyImpl<K, V> implements 
TransactionProxy, Externaliza
     @Override public void commit() {
         Span span = MTC.span();
 
+        IgniteException ex = null;
+
         try (TraceSurroundings ignored =
                  MTC.support(cctx.kernalContext().tracing().create(TX_COMMIT, 
span))) {
             enter();
@@ -332,15 +334,28 @@ public class TransactionProxyImpl<K, V> implements 
TransactionProxy, Externaliza
                     commitFut.get();
             }
             catch (IgniteCheckedException e) {
-                throw U.convertException(e);
+                ex = U.convertException(e);
             }
             finally {
+                try {
+                    cctx.endTx(tx);
+                }
+                catch (IgniteCheckedException e) {
+                    if (ex == null)
+                        ex = U.convertException(e);
+                    else
+                        ex.addSuppressed(e);
+                }
+
                 leave();
             }
         }
         finally {
             span.end();
         }
+
+        if (ex != null)
+            throw ex;
     }
 
     /** {@inheritDoc} */
@@ -390,6 +405,8 @@ public class TransactionProxyImpl<K, V> implements 
TransactionProxy, Externaliza
     @Override public void rollback() {
         Span span = MTC.span();
 
+        IgniteException ex = null;
+
         try (TraceSurroundings ignored =
                  
MTC.support(cctx.kernalContext().tracing().create(TX_ROLLBACK, span))) {
             enter();
@@ -403,15 +420,28 @@ public class TransactionProxyImpl<K, V> implements 
TransactionProxy, Externaliza
                     rollbackFut.get();
             }
             catch (IgniteCheckedException e) {
-                throw U.convertException(e);
+                ex = U.convertException(e);
             }
             finally {
+                try {
+                    cctx.endTx(tx);
+                }
+                catch (IgniteCheckedException e) {
+                    if (ex == null)
+                        ex = U.convertException(e);
+                    else
+                        ex.addSuppressed(e);
+                }
+
                 leave();
             }
         }
         finally {
             span.end();
         }
+
+        if (ex != null)
+            throw ex;
     }
 
     /** {@inheritDoc} */
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/transactions/TransactionContextCleanupTest.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/transactions/TransactionContextCleanupTest.java
new file mode 100644
index 00000000000..de306cc4c68
--- /dev/null
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/transactions/TransactionContextCleanupTest.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.ignite.internal.processors.cache.transactions;
+
+import java.util.function.BiConsumer;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.IgniteTransactions;
+import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.CacheMode;
+import org.apache.ignite.cache.CacheWriteSynchronizationMode;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.util.lang.ConsumerX;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.apache.ignite.transactions.Transaction;
+import org.apache.ignite.transactions.TransactionRollbackException;
+import org.apache.ignite.transactions.TransactionState;
+import org.apache.ignite.transactions.TransactionTimeoutException;
+import org.junit.Test;
+
+import static org.apache.ignite.testframework.GridTestUtils.assertThrows;
+import static org.apache.ignite.testframework.GridTestUtils.runAsync;
+import static org.apache.ignite.testframework.GridTestUtils.waitForCondition;
+import static 
org.apache.ignite.transactions.TransactionConcurrency.PESSIMISTIC;
+import static 
org.apache.ignite.transactions.TransactionIsolation.READ_COMMITTED;
+
+/**
+ * Test checks that transaction context is cleaned up on explicit commit or 
rollback.
+ */
+public class TransactionContextCleanupTest extends GridCommonAbstractTest {
+    /** Transaction timeout. */
+    public static final int TX_TIMEOUT = 1000;
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String 
igniteInstanceName) throws Exception {
+        return super.getConfiguration(igniteInstanceName)
+            .setCacheConfiguration(new CacheConfiguration<>(DEFAULT_CACHE_NAME)
+                .setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL)
+                .setCacheMode(CacheMode.PARTITIONED)
+                .setBackups(1)
+                
.setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC));
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        super.afterTest();
+
+        stopAllGrids();
+    }
+
+    /** */
+    @Test
+    public void testContextCleanupOnCommit() throws Exception {
+        checkContextCleanup(
+            tx -> doSleep(TX_TIMEOUT * 2), // Rollback on timeout.
+            (cache, tx) -> tx.commit(),
+            TransactionTimeoutException.class
+        );
+    }
+
+    /** */
+    @Test
+    public void testContextCleanupOnRollback() throws Exception {
+        checkContextCleanup(
+            tx -> doSleep(TX_TIMEOUT * 2), // Rollback on timeout.
+            (cache, tx) -> {
+                try {
+                    cache.put(1, 2);
+                }
+                finally {
+                    tx.rollback();
+                }
+            },
+            TransactionTimeoutException.class);
+    }
+
+    /** */
+    @Test
+    public void testContextCleanupAfterAsyncRollback() throws Exception {
+        checkContextCleanup(
+            tx -> runAsync(tx::rollback).get(), // Rollback from other thread.
+            (cache, tx) -> {
+                try {
+                    cache.put(1, 2);
+                }
+                finally {
+                    tx.rollback();
+                }
+            },
+            TransactionRollbackException.class);
+    }
+
+    /** */
+    @SuppressWarnings("ThrowableNotThrown")
+    private void checkContextCleanup(ConsumerX<Transaction> rollbackAction,
+        BiConsumer<IgniteCache<Integer, Integer>, Transaction> txAction, 
Class<? extends Throwable> eCls) throws Exception {
+        IgniteEx ignite = startGrids(2);
+
+        IgniteCache<Integer, Integer> cache = ignite.cache(DEFAULT_CACHE_NAME);
+        cache.put(1, 1);
+
+        IgniteTransactions transactions = ignite.transactions();
+        Transaction tx = transactions.txStart(PESSIMISTIC, READ_COMMITTED, 
TX_TIMEOUT, 0);
+
+        cache.put(1, 2);
+
+        // Ensure rollback.
+        rollbackAction.accept(tx);
+
+        assertTrue("Transaction was not rolled back",
+            waitForCondition(() -> TransactionState.ROLLED_BACK == tx.state(), 
getTestTimeout() / 2));
+
+        assertNotNull("Transaction in context expected", transactions.tx());
+        assertEquals("Transactions are not equal", tx, transactions.tx());
+
+        assertThrows(null,
+            () -> {
+                txAction.accept(cache, tx);
+
+                return null;
+            },
+            eCls,
+            null);
+
+        assertNull("No transaction expected in context", transactions.tx());
+        assertEquals("Value should not be commited", 1, (int)cache.get(1));
+    }
+}
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/transactions/TxRollbackAsyncTest.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/transactions/TxRollbackAsyncTest.java
index b6608e84305..79f9acfc9ff 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/transactions/TxRollbackAsyncTest.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/transactions/TxRollbackAsyncTest.java
@@ -298,6 +298,9 @@ public class TxRollbackAsyncTest extends 
GridCommonAbstractTest {
             // Expected.
         }
 
+        // Cleanup context for further cache operations.
+        tx.close();
+
         checkFutures();
     }
 
diff --git 
a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite6.java
 
b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite6.java
index eecf72df48c..d0b620794a3 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite6.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite6.java
@@ -52,6 +52,7 @@ import 
org.apache.ignite.internal.processors.cache.distributed.PartitionsExchang
 import 
org.apache.ignite.internal.processors.cache.distributed.dht.preloader.latch.ExchangeLatchManagerTest;
 import 
org.apache.ignite.internal.processors.cache.distributed.rebalancing.GridCacheRebalancingOrderingTest;
 import 
org.apache.ignite.internal.processors.cache.transactions.StartImplicitlyTxOnStopCacheTest;
+import 
org.apache.ignite.internal.processors.cache.transactions.TransactionContextCleanupTest;
 import org.apache.ignite.internal.processors.cache.transactions.TxLabelTest;
 import 
org.apache.ignite.internal.processors.cache.transactions.TxMultiCacheAsyncOpsTest;
 import 
org.apache.ignite.internal.processors.cache.transactions.TxOnCachesStartTest;
@@ -169,6 +170,8 @@ public class IgniteCacheTestSuite6 {
 
         GridTestUtils.addTestIfNeeded(suite, 
StartImplicitlyTxOnStopCacheTest.class, ignoredTests);
 
+        GridTestUtils.addTestIfNeeded(suite, 
TransactionContextCleanupTest.class, ignoredTests);
+
         return suite;
     }
 }

Reply via email to