IGNITE-5548 Deadlock Detection uses IgniteCheckedException instead of TransactionTimeoutException
Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/07bf0f6f Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/07bf0f6f Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/07bf0f6f Branch: refs/heads/ignite-2.1.2-exchange Commit: 07bf0f6f3ab5333ba42808c6b39000cf13b77f5c Parents: acf0441 Author: Igor Seliverstov <[email protected]> Authored: Wed Jun 21 15:05:05 2017 +0300 Committer: Igor Seliverstov <[email protected]> Committed: Tue Jun 27 15:53:55 2017 +0300 ---------------------------------------------------------------------- .../colocated/GridDhtColocatedLockFuture.java | 4 +- .../distributed/near/GridNearLockFuture.java | 4 +- .../cache/local/GridLocalLockFuture.java | 5 +- .../cache/transactions/TxDeadlockCauseTest.java | 279 +++++++++++++++++++ 4 files changed, 289 insertions(+), 3 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/07bf0f6f/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/colocated/GridDhtColocatedLockFuture.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/colocated/GridDhtColocatedLockFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/colocated/GridDhtColocatedLockFuture.java index fb4f28b..40eb371 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/colocated/GridDhtColocatedLockFuture.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/colocated/GridDhtColocatedLockFuture.java @@ -1409,7 +1409,9 @@ public final class GridDhtColocatedLockFuture extends GridCacheCompoundIdentityF TxDeadlock deadlock = fut.get(); if (deadlock != null) - err = new TransactionDeadlockException(deadlock.toString(cctx.shared())); + err = new IgniteTxTimeoutCheckedException("Failed to acquire lock within provided timeout for " + + "transaction [timeout=" + tx.timeout() + ", tx=" + tx + ']', + new TransactionDeadlockException(deadlock.toString(cctx.shared()))); } catch (IgniteCheckedException e) { err = e; http://git-wip-us.apache.org/repos/asf/ignite/blob/07bf0f6f/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearLockFuture.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearLockFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearLockFuture.java index 4f8d433..db030b0 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearLockFuture.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/near/GridNearLockFuture.java @@ -1466,7 +1466,9 @@ public final class GridNearLockFuture extends GridCacheCompoundIdentityFuture<Bo TxDeadlock deadlock = fut.get(); if (deadlock != null) - err = new TransactionDeadlockException(deadlock.toString(cctx.shared())); + err = new IgniteTxTimeoutCheckedException("Failed to acquire lock within provided timeout for " + + "transaction [timeout=" + tx.timeout() + ", tx=" + tx + ']', + new TransactionDeadlockException(deadlock.toString(cctx.shared()))); } catch (IgniteCheckedException e) { err = e; http://git-wip-us.apache.org/repos/asf/ignite/blob/07bf0f6f/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/local/GridLocalLockFuture.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/local/GridLocalLockFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/local/GridLocalLockFuture.java index 9641533..e2311b8 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/local/GridLocalLockFuture.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/local/GridLocalLockFuture.java @@ -41,6 +41,7 @@ import org.apache.ignite.internal.processors.cache.transactions.IgniteTxLocalEx; import org.apache.ignite.internal.processors.cache.transactions.TxDeadlock; import org.apache.ignite.internal.processors.cache.version.GridCacheVersion; import org.apache.ignite.internal.processors.timeout.GridTimeoutObjectAdapter; +import org.apache.ignite.internal.transactions.IgniteTxTimeoutCheckedException; import org.apache.ignite.transactions.TransactionDeadlockException; import org.apache.ignite.internal.util.tostring.GridToStringExclude; import org.apache.ignite.internal.util.tostring.GridToStringInclude; @@ -501,7 +502,9 @@ public final class GridLocalLockFuture<K, V> extends GridCacheFutureAdapter<Bool if (deadlock != null) ERR_UPD.compareAndSet(GridLocalLockFuture.this, null, - new TransactionDeadlockException(deadlock.toString(cctx.shared()))); + new IgniteTxTimeoutCheckedException("Failed to acquire lock within provided timeout for " + + "transaction [timeout=" + tx.timeout() + ", tx=" + tx + ']', + new TransactionDeadlockException(deadlock.toString(cctx.shared())))); } catch (IgniteCheckedException e) { U.warn(log, "Failed to detect deadlock.", e); http://git-wip-us.apache.org/repos/asf/ignite/blob/07bf0f6f/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/transactions/TxDeadlockCauseTest.java ---------------------------------------------------------------------- diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/transactions/TxDeadlockCauseTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/transactions/TxDeadlockCauseTest.java new file mode 100644 index 0000000..530009b --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/transactions/TxDeadlockCauseTest.java @@ -0,0 +1,279 @@ +/* + * 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 org.apache.ignite.Ignite; +import org.apache.ignite.IgniteCache; +import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.cache.CacheAtomicityMode; +import org.apache.ignite.cache.CacheMode; +import org.apache.ignite.configuration.*; +import org.apache.ignite.internal.IgniteInternalFuture; +import org.apache.ignite.internal.util.typedef.CAX; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.X; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi; +import org.apache.ignite.testframework.GridTestUtils; +import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; +import org.apache.ignite.transactions.*; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +/** + * + */ +public class TxDeadlockCauseTest extends GridCommonAbstractTest { + /** */ + private CacheConfiguration ccfg; + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { + IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); + + if (isDebug()) { + TcpDiscoverySpi discoSpi = new TcpDiscoverySpi(); + + discoSpi.failureDetectionTimeoutEnabled(false); + + cfg.setDiscoverySpi(discoSpi); + } + + MemoryConfiguration memCfg = new MemoryConfiguration(); + + MemoryPolicyConfiguration plc = new MemoryPolicyConfiguration(); + + plc.setName("dfltPlc"); + plc.setMaxSize(100L * 1024 * 1024); + + memCfg.setDefaultMemoryPolicyName("dfltPlc"); + memCfg.setMemoryPolicies(plc); + + cfg.setMemoryConfiguration(memCfg); + + CacheConfiguration ccfg0 = ccfg == null ? new CacheConfiguration(DEFAULT_CACHE_NAME) + .setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL) : ccfg; + + cfg.setCacheConfiguration(ccfg0); + + return cfg; + } + + /** {@inheritDoc} */ + @Override protected void afterTest() throws Exception { + super.afterTest(); + + stopAllGrids(); + } + + /** {@inheritDoc} */ + @Override protected void afterTestsStopped() throws Exception { + super.afterTestsStopped(); + + stopAllGrids(); + } + + /** + * @throws Exception If failed. + */ + public void testCause() throws Exception { + startGrids(1); + + for (TransactionIsolation isolation : TransactionIsolation.values()) { + testCauseObject(1, 2, 1000, isolation, true); + testCauseObject(1, 2, 1000, isolation, false); + } + } + + /** + * @throws Exception If failed. + */ + public void testCauseSeveralNodes() throws Exception { + startGrids(2); + + for (TransactionIsolation isolation : TransactionIsolation.values()) { + testCauseObject(2, 2, 1500, isolation, true); + testCauseObject(2, 2, 1500, isolation, false); + } + } + + /** + * @throws Exception If failed. + */ + public void testCauseNear() throws Exception { + ccfg = new CacheConfiguration(DEFAULT_CACHE_NAME) + .setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL) + .setNearConfiguration(new NearCacheConfiguration()); + + startGrids(1); + + for (TransactionIsolation isolation : TransactionIsolation.values()) { + testCauseObject(1, 2, 1000, isolation, true); + testCauseObject(1, 2, 1000, isolation, false); + } + } + + /** + * @throws Exception If failed. + */ + public void testCauseSeveralNodesNear() throws Exception { + ccfg = new CacheConfiguration(DEFAULT_CACHE_NAME) + .setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL) + .setNearConfiguration(new NearCacheConfiguration()); + + startGrids(4); + + for (TransactionIsolation isolation : TransactionIsolation.values()) { + testCauseObject(2, 2, 2000, isolation, true); + testCauseObject(2, 2, 2000, isolation, false); + } + } + + /** + * @param nodes Nodes count. + * @param keysCnt Keys count. + * @param timeout Timeout. + * @param isolation TransactionIsolation. + * @param oneOp Determines whether {@link IgniteCache#getAndPut(java.lang.Object, java.lang.Object)} + * instead of {@link IgniteCache#get(java.lang.Object)} and {@link IgniteCache#put(java.lang.Object, java.lang.Object)} operations sequence. + * @throws Exception If failed. + */ + public void testCauseObject(int nodes, final int keysCnt, final long timeout, final TransactionIsolation isolation, final boolean oneOp) throws Exception { + final Ignite ignite = grid(new Random().nextInt(nodes)); + + final IgniteCache<Integer, Account> cache = ignite.cache(DEFAULT_CACHE_NAME); + final List<Integer> keys = new ArrayList<>(keysCnt); + + for (int i = 0; i < keysCnt; i ++) { + keys.add(i); + cache.put(i, new Account(i, i * 100)); + } + + final List<Integer> keysReversed = new ArrayList<>(keys); + Collections.reverse(keysReversed); + + final AtomicBoolean reverse = new AtomicBoolean(); + final AtomicReference<Exception> ex = new AtomicReference<>(); + final CyclicBarrier barrier = new CyclicBarrier(2); + + IgniteInternalFuture<Long> fut = GridTestUtils.runMultiThreadedAsync(new CAX() { + @Override + public void applyx() throws IgniteCheckedException { + try (Transaction tx = ignite.transactions().txStart(TransactionConcurrency.PESSIMISTIC, isolation, timeout, keys.size())) { + + List<Integer> keys0 = getAndFlip(reverse) ? keys : keysReversed; + + for (int i = 0; i < keys0.size(); i++) { + Integer key = keys0.get(i); + + if(oneOp) + cache.getAndPut(key, new Account(key, (key + 1) * 100)); + else + cache.put(key, new Account(cache.get(key).id, (key + 1) * 100)); + + if (i == 0) + barrier.await(timeout >> 1, TimeUnit.MILLISECONDS); + } + + tx.commit(); + } catch (Exception e) { + ex.compareAndSet(null, e); + } + } + }, 2, "tx"); + + fut.get(timeout << 1); + + Exception e = ex.get(); + + assertNotNull(e); + + boolean detected = X.hasCause(e, TransactionDeadlockException.class); + + if(!detected) + U.error(log, "Failed to detect a deadlock.", e); + else + log.info(X.cause(e, TransactionDeadlockException.class).getMessage()); + + assertTrue(detected); + + try { + assertEquals(TransactionTimeoutException.class, e.getCause().getClass()); + assertEquals(TransactionDeadlockException.class, e.getCause().getCause().getClass()); + } + catch (AssertionError err) { + U.error(log, "Unexpected exception structure.", e); + + throw err; + } + } + + /** + * @param b AtomicBoolean. + * @return Current value. + */ + private boolean getAndFlip(AtomicBoolean b) { + while (true) { + boolean res = b.get(); + + if(b.compareAndSet(res, !res)) + return res; + } + } + + /** */ + static class Account implements Serializable { + /** Account ID. */ + private int id; + + /** Account balance. */ + private double balance; + + /** + * @param id Account ID. + * @param balance Balance. + */ + Account(int id, double balance) { + this.id = id; + this.balance = balance; + } + + /** + * Change balance by specified amount. + * + * @param amount Amount to add to balance (may be negative). + */ + void update(double amount) { + balance += amount; + } + + /** {@inheritDoc} */ + @Override public String toString() { + return "Account [id=" + id + ", balance=$" + balance + ']'; + } + } +}
