/* * JBoss, Home of Professional Open Source * Copyright 2011 Red Hat Inc. and/or its affiliates and other * contributors as indicated by the @author tags. All rights reserved. * See the copyright.txt in the distribution for a full listing of * individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.infinispan.distribution.rehash; import org.infinispan.Cache; import org.infinispan.commands.control.CacheViewControlCommand; import org.infinispan.commands.remote.CacheRpcCommand; import org.infinispan.commands.tx.CommitCommand; import org.infinispan.commands.tx.PrepareCommand; import org.infinispan.config.Configuration; import org.infinispan.context.impl.TxInvocationContext; import org.infinispan.interceptors.InterceptorChain; import org.infinispan.interceptors.TxInterceptor; import org.infinispan.interceptors.base.CommandInterceptor; import org.infinispan.manager.CacheContainer; import org.infinispan.remoting.InboundInvocationHandler; import org.infinispan.remoting.InboundInvocationHandlerImpl; import org.infinispan.remoting.responses.Response; import org.infinispan.remoting.transport.Address; import org.infinispan.remoting.transport.Transport; import org.infinispan.remoting.transport.jgroups.CommandAwareRpcDispatcher; import org.infinispan.remoting.transport.jgroups.JGroupsTransport; import org.infinispan.test.MultipleCacheManagersTest; import org.infinispan.test.TestingUtil; import org.infinispan.test.fwk.CleanupAfterMethod; import org.testng.annotations.Test; import javax.transaction.Transaction; import java.lang.reflect.Field; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import static org.infinispan.test.TestingUtil.extractComponent; import static org.infinispan.test.TestingUtil.replaceComponent; /** * This tests the following scenario: * <p/> * 1 node exists. Transactions running. Some complete, some in prepare, some in commit. New node joins, rehash occurs. * Test that the new node is the owner and receives this state. */ @Test(groups = "functional", testName = "distribution.rehash.OngoingTransactionsAndJoinTest", enabled = false) @CleanupAfterMethod public class OngoingTransactionsAndJoinTest extends MultipleCacheManagersTest { Configuration configuration; ScheduledExecutorService delayedExecutor = Executors.newScheduledThreadPool(1); @Override protected void createCacheManagers() throws Throwable { configuration = getDefaultClusteredConfig(Configuration.CacheMode.DIST_SYNC); configuration.setLockAcquisitionTimeout(60000); configuration.setUseLockStriping(false); addClusterEnabledCacheManager(configuration); } private void injectListeningHandler(CacheContainer ecm, ListeningHandler lh) { replaceComponent(ecm, InboundInvocationHandler.class, lh, true); JGroupsTransport t = (JGroupsTransport) extractComponent(cache(0), Transport.class); CommandAwareRpcDispatcher card = t.getCommandAwareRpcDispatcher(); Field f = null; try { f = card.getClass().getDeclaredField("inboundInvocationHandler"); f.setAccessible(true); f.set(card, lh); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } public void testRehashOnJoin() throws InterruptedException { Cache<Object, Object> firstNode = cache(0); final CountDownLatch txsStarted = new CountDownLatch(3), txsReady = new CountDownLatch(3), joinEnded = new CountDownLatch(1), rehashStarted = new CountDownLatch(1); ListeningHandler listeningHandler = new ListeningHandler(extractComponent(firstNode, InboundInvocationHandler.class), txsReady, joinEnded, rehashStarted); injectListeningHandler(firstNode.getCacheManager(), listeningHandler); assert firstNode.getAdvancedCache().getComponentRegistry().getComponent(InboundInvocationHandler.class) instanceof ListeningHandler; for (int i = 0; i < 10; i++) firstNode.put("OLD" + i, "value"); UnpreparedDuringRehashTask ut = new UnpreparedDuringRehashTask(firstNode, txsStarted, txsReady, joinEnded, rehashStarted); PrepareDuringRehashTask pt = new PrepareDuringRehashTask(firstNode, txsStarted, txsReady, joinEnded, rehashStarted); CommitDuringRehashTask ct = new CommitDuringRehashTask(firstNode, txsStarted, txsReady, joinEnded, rehashStarted); InterceptorChain ic = TestingUtil.extractComponent(firstNode, InterceptorChain.class); ic.addInterceptorAfter(pt, TxInterceptor.class); ic.addInterceptorAfter(ct, TxInterceptor.class); Set<Thread> threads = new HashSet<Thread>(); threads.add(new Thread(ut, "Worker-UnpreparedDuringRehashTask")); threads.add(new Thread(pt, "Worker-PrepareDuringRehashTask")); threads.add(new Thread(ct, "Worker-CommitDuringRehashTask")); for (Thread t : threads) t.start(); txsStarted.await(); // we don't have a hook for the start of the rehash any more delayedExecutor.schedule(new Callable<Object>() { @Override public Object call() throws Exception { rehashStarted.countDown(); return null; } }, 10, TimeUnit.MILLISECONDS); // start a new node! addClusterEnabledCacheManager(configuration); ListeningHandler listeningHandler2 = new ListeningHandler(extractComponent(firstNode, InboundInvocationHandler.class), txsReady, joinEnded, rehashStarted); injectListeningHandler(cacheManagers.get(1), listeningHandler); Cache<?, ?> joiner = cache(1); for (Thread t : threads) t.join(); TestingUtil.waitForRehashToComplete(cache(0), cache(1)); for (int i = 0; i < 10; i++) { Object key = "OLD" + i; Object value = joiner.get(key); log.infof(" TEST: Key %s is %s", key, value); assert "value".equals(value) : "Couldn't see key " + key + " on joiner!"; } for (Object key: Arrays.asList(ut.key(), pt.key(), ct.key())) { Object value = joiner.get(key); log.infof(" TEST: Key %s is %s", key, value); assert "value".equals(value) : "Couldn't see key " + key + " on joiner!"; } } abstract class TransactionalTask extends CommandInterceptor implements Runnable { Cache<Object, Object> cache; CountDownLatch txsStarted, txsReady, joinEnded, rehashStarted; volatile Transaction tx; protected void startTx() throws Exception { tm(cache).begin(); cache.put(key(), "value"); tx = tm(cache).getTransaction(); tx.enlistResource(new XAResourceAdapter()); // this is to force 2PC and to prevent transaction managers attempting to optimise the call to a 1PC. txsStarted.countDown(); } abstract Object key(); } class UnpreparedDuringRehashTask extends TransactionalTask { UnpreparedDuringRehashTask(Cache<Object, Object> cache, CountDownLatch txsStarted, CountDownLatch txsReady, CountDownLatch joinEnded, CountDownLatch rehashStarted) { this.cache = cache; this.txsStarted = txsStarted; this.txsReady = txsReady; this.joinEnded = joinEnded; this.rehashStarted = rehashStarted; } Object key() { return "unprepared_during_rehash"; } @Override public void run() { try { // start a tx startTx(); txsReady.countDown(); joinEnded.await(); tm(cache).commit(); } catch (Exception e) { throw new RuntimeException(e); } } } class PrepareDuringRehashTask extends TransactionalTask { PrepareDuringRehashTask(Cache<Object, Object> cache, CountDownLatch txsStarted, CountDownLatch txsReady, CountDownLatch joinEnded, CountDownLatch rehashStarted) { this.cache = cache; this.txsStarted = txsStarted; this.txsReady = txsReady; this.joinEnded = joinEnded; this.rehashStarted = rehashStarted; } Object key() { return "prepare_during_rehash"; } @Override public void run() { try { startTx(); tm(cache).commit(); } catch (Exception e) { throw new RuntimeException(e); } } @Override public Object visitPrepareCommand(TxInvocationContext tcx, PrepareCommand cc) throws Throwable { if (tx.equals(tcx.getTransaction())) { txsReady.countDown(); rehashStarted.await(); } return super.visitPrepareCommand(tcx, cc); } @Override public Object visitCommitCommand(TxInvocationContext tcx, CommitCommand cc) throws Throwable { if (tx.equals(tcx.getTransaction())) { try { joinEnded.await(); } catch (InterruptedException e) { e.printStackTrace(); } } return super.visitCommitCommand(tcx, cc); } } class CommitDuringRehashTask extends TransactionalTask { CommitDuringRehashTask(Cache<Object, Object> cache, CountDownLatch txsStarted, CountDownLatch txsReady, CountDownLatch joinEnded, CountDownLatch rehashStarted) { this.cache = cache; this.txsStarted = txsStarted; this.txsReady = txsReady; this.joinEnded = joinEnded; this.rehashStarted = rehashStarted; } Object key() { return "commit_during_rehash"; } @Override public void run() { try { startTx(); tm(cache).commit(); } catch (Exception e) { throw new RuntimeException(e); } } @Override public Object visitPrepareCommand(TxInvocationContext tcx, PrepareCommand cc) throws Throwable { Object o = super.visitPrepareCommand(tcx, cc); if (tx.equals(tcx.getTransaction())) { txsReady.countDown(); } return o; } @Override public Object visitCommitCommand(TxInvocationContext tcx, CommitCommand cc) throws Throwable { if (tx.equals(tcx.getTransaction())) { try { rehashStarted.await(); } catch (InterruptedException e) { e.printStackTrace(); } } return super.visitCommitCommand(tcx, cc); } } class ListeningHandler extends InboundInvocationHandlerImpl { final InboundInvocationHandler delegate; final CountDownLatch txsReady, joinEnded, rehashStarted; public ListeningHandler(InboundInvocationHandler delegate, CountDownLatch txsReady, CountDownLatch joinEnded, CountDownLatch rehashStarted) { this.delegate = delegate; this.txsReady = txsReady; this.joinEnded = joinEnded; this.rehashStarted = rehashStarted; } @Override public Object handle(CacheRpcCommand cmd, Address origin) throws Throwable { boolean notifyRehashStarted = false; if (cmd instanceof CacheViewControlCommand) { CacheViewControlCommand rcc = (CacheViewControlCommand) cmd; log.debugf("Intercepted command: %s", cmd); switch (rcc.getType()) { case PREPARE_VIEW: txsReady.await(); notifyRehashStarted = true; break; case COMMIT_VIEW: joinEnded.countDown(); break; } } Object r = delegate.handle(cmd, origin); if (notifyRehashStarted) rehashStarted.countDown(); return r; } } }