/* * 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.geode.internal.cache; import static org.junit.Assert.*; import static org.mockito.Mockito.*; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; import org.apache.geode.cache.Cache; import org.apache.geode.distributed.internal.DistributionManager; import org.apache.geode.distributed.internal.membership.InternalDistributedMember; import org.apache.geode.internal.cache.partitioned.DestroyMessage; import org.apache.geode.test.fake.Fakes; import org.apache.geode.test.junit.categories.UnitTest; import com.jayway.awaitility.Awaitility; @Category(UnitTest.class) public class TXManagerImplTest { private TXManagerImpl txMgr; TXId txid; DestroyMessage msg; TXCommitMessage txCommitMsg; TXId completedTxid; TXId notCompletedTxid; InternalDistributedMember member; CountDownLatch latch; TXStateProxy tx1, tx2; DistributionManager dm; TXRemoteRollbackMessage rollbackMsg; TXRemoteCommitMessage commitMsg; @Before public void setUp() { Cache cache = Fakes.cache(); dm = mock(DistributionManager.class); txMgr = new TXManagerImpl(mock(CachePerfStats.class), cache); txid = new TXId(null, 0); msg = mock(DestroyMessage.class); txCommitMsg = mock(TXCommitMessage.class); member = mock(InternalDistributedMember.class); completedTxid = new TXId(member, 1); notCompletedTxid = new TXId(member, 2); latch = new CountDownLatch(1); rollbackMsg = new TXRemoteRollbackMessage(); commitMsg = new TXRemoteCommitMessage(); when(this.msg.canStartRemoteTransaction()).thenReturn(true); when(this.msg.canParticipateInTransaction()).thenReturn(true); } @Test public void getOrSetHostedTXStateAbleToSetTXStateAndGetLock() { TXStateProxy tx = txMgr.getOrSetHostedTXState(txid, msg); assertNotNull(tx); assertEquals(tx, txMgr.getHostedTXState(txid)); assertTrue(txMgr.getLock(tx, txid)); } @Test public void getLockAfterTXStateRemoved() throws InterruptedException { TXStateProxy tx = txMgr.getOrSetHostedTXState(txid, msg); assertEquals(tx, txMgr.getHostedTXState(txid)); assertTrue(txMgr.getLock(tx, txid)); assertNotNull(tx); assertTrue(txMgr.getLock(tx, txid)); tx.getLock().unlock(); TXStateProxy oldtx = txMgr.getOrSetHostedTXState(txid, msg); assertEquals(tx, oldtx); Thread t1 = new Thread(new Runnable() { public void run() { txMgr.removeHostedTXState(txid); } }); t1.start(); t1.join(); TXStateProxy curTx = txMgr.getHostedTXState(txid); assertNull(curTx); // after failover command removed the txid from hostedTXState, // getLock should put back the original TXStateProxy assertTrue(txMgr.getLock(tx, txid)); assertEquals(tx, txMgr.getHostedTXState(txid)); tx.getLock().unlock(); } @Test public void getLockAfterTXStateReplaced() throws InterruptedException { TXStateProxy oldtx = txMgr.getOrSetHostedTXState(txid, msg); assertEquals(oldtx, txMgr.getHostedTXState(txid)); assertTrue(txMgr.getLock(oldtx, txid)); assertNotNull(oldtx); oldtx.getLock().unlock(); TXStateProxy tx = txMgr.getOrSetHostedTXState(txid, msg); assertEquals(tx, oldtx); Thread t1 = new Thread(new Runnable() { public void run() { txMgr.removeHostedTXState(txid); // replace with new TXState txMgr.getOrSetHostedTXState(txid, msg); } }); t1.start(); t1.join(); TXStateProxy curTx = txMgr.getHostedTXState(txid); assertNotNull(curTx); // replaced assertNotEquals(tx, curTx); // after TXStateProxy replaced, getLock will not get assertFalse(txMgr.getLock(tx, txid)); } @Test public void getLockAfterTXStateCommitted() throws InterruptedException { TXStateProxy oldtx = txMgr.getOrSetHostedTXState(txid, msg); assertEquals(oldtx, txMgr.getHostedTXState(txid)); assertTrue(txMgr.getLock(oldtx, txid)); assertNotNull(oldtx); oldtx.getLock().unlock(); TXStateProxy tx = txMgr.getOrSetHostedTXState(txid, msg); assertEquals(tx, oldtx); Thread t1 = new Thread(new Runnable() { public void run() { when(msg.getTXOriginatorClient()).thenReturn(mock(InternalDistributedMember.class)); TXStateProxy tx; try { tx = txMgr.masqueradeAs(commitMsg); } catch (InterruptedException e) { e.printStackTrace(); throw new RuntimeException(e); } tx.setCommitOnBehalfOfRemoteStub(true); try { tx.commit(); } finally { txMgr.unmasquerade(tx); } txMgr.removeHostedTXState(txid); txMgr.saveTXCommitMessageForClientFailover(txid, txCommitMsg); } }); t1.start(); t1.join(); TXStateProxy curTx = txMgr.getHostedTXState(txid); assertNull(curTx); assertFalse(tx.isInProgress()); // after TXStateProxy committed, getLock will get the lock for the oldtx // but caller should not perform ops on this TXStateProxy assertTrue(txMgr.getLock(tx, txid)); } @Test public void masqueradeAsCanGetLock() throws InterruptedException { TXStateProxy tx; tx = txMgr.masqueradeAs(msg); assertNotNull(tx); } @Test public void masqueradeAsCanGetLockAfterTXStateIsReplaced() throws InterruptedException { TXStateProxy tx; Thread t1 = new Thread(new Runnable() { public void run() { tx1 = txMgr.getHostedTXState(txid); assertNull(tx1); tx1 = txMgr.getOrSetHostedTXState(txid, msg); assertNotNull(tx1); assertTrue(txMgr.getLock(tx1, txid)); latch.countDown(); Awaitility.await().pollInterval(10, TimeUnit.MILLISECONDS) .pollDelay(10, TimeUnit.MILLISECONDS).atMost(30, TimeUnit.SECONDS) .until(() -> tx1.getLock().hasQueuedThreads()); txMgr.removeHostedTXState(txid); tx2 = txMgr.getOrSetHostedTXState(txid, msg); assertNotNull(tx2); assertTrue(txMgr.getLock(tx2, txid)); tx2.getLock().unlock(); tx1.getLock().unlock(); } }); t1.start(); assertTrue(latch.await(60, TimeUnit.SECONDS)); tx = txMgr.masqueradeAs(msg); assertNotNull(tx); assertEquals(tx, tx2); tx.getLock().unlock(); t1.join(); } @Test public void testTxStateWithNotFinishedTx() { TXStateProxy tx = txMgr.getOrSetHostedTXState(notCompletedTxid, msg); assertTrue(tx.isInProgress()); } @Test public void testTxStateWithCommittedTx() throws InterruptedException { when(msg.getTXOriginatorClient()).thenReturn(mock(InternalDistributedMember.class)); setupTx(); TXStateProxy tx = txMgr.masqueradeAs(commitMsg); try { tx.commit(); } finally { txMgr.unmasquerade(tx); } assertFalse(tx.isInProgress()); } @Test public void testTxStateWithRolledBackTx() throws InterruptedException { when(msg.getTXOriginatorClient()).thenReturn(mock(InternalDistributedMember.class)); setupTx(); TXStateProxy tx = txMgr.masqueradeAs(rollbackMsg); try { tx.rollback(); } finally { txMgr.unmasquerade(tx); } assertFalse(tx.isInProgress()); } private void setupTx() throws InterruptedException { TXStateProxy tx = txMgr.masqueradeAs(msg); tx.setCommitOnBehalfOfRemoteStub(true); txMgr.unmasquerade(tx); } @Test public void txRolledbackShouldCompleteTx() throws InterruptedException { when(msg.getTXOriginatorClient()).thenReturn(mock(InternalDistributedMember.class)); Thread t1 = new Thread(new Runnable() { public void run() { try { tx1 = txMgr.masqueradeAs(msg); } catch (InterruptedException e) { e.printStackTrace(); throw new RuntimeException(e); } msg.process(dm); TXStateProxy existingTx = masqueradeToRollback(); latch.countDown(); Awaitility.await().pollInterval(10, TimeUnit.MILLISECONDS) .pollDelay(10, TimeUnit.MILLISECONDS).atMost(30, TimeUnit.SECONDS) .until(() -> tx1.getLock().hasQueuedThreads()); rollbackTransaction(existingTx); } }); t1.start(); assertTrue(latch.await(60, TimeUnit.SECONDS)); TXStateProxy tx = txMgr.masqueradeAs(rollbackMsg); assertEquals(tx, tx1); t1.join(); rollbackTransaction(tx); } private TXStateProxy masqueradeToRollback() { TXStateProxy existingTx; try { existingTx = txMgr.masqueradeAs(rollbackMsg); } catch (InterruptedException e) { e.printStackTrace(); throw new RuntimeException(e); } return existingTx; } private void rollbackTransaction(TXStateProxy existingTx) { try { if (!txMgr.isHostedTxRecentlyCompleted(txid)) { txMgr.rollback(); } } finally { txMgr.unmasquerade(existingTx); } } }