/*
* JBoss, Home of Professional Open Source
* Copyright 2009 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.replication;
import org.infinispan.Cache;
import org.infinispan.config.Configuration;
import org.infinispan.config.GlobalConfiguration;
import org.infinispan.executors.ScheduledExecutorFactory;
import org.infinispan.factories.KnownComponentNames;
import org.infinispan.manager.CacheContainer;
import org.infinispan.remoting.ReplicationQueue;
import org.infinispan.test.MultipleCacheManagersTest;
import org.infinispan.test.TestingUtil;
import org.infinispan.test.fwk.TestCacheManagerFactory;
import org.testng.annotations.Test;
import javax.transaction.TransactionManager;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Tests ReplicationQueue's functionality.
*
* @author Mircea.Markus@jboss.com
*/
@Test(groups = "functional", testName = "replication.ReplicationQueueTest")
public class ReplicationQueueTest extends MultipleCacheManagersTest {
private static final int REPL_QUEUE_INTERVAL = 5000;
private static final int REPL_QUEUE_MAX_ELEMENTS = 10;
long creationTime;
protected void createCacheManagers() throws Throwable {
GlobalConfiguration globalConfiguration = GlobalConfiguration.getClusteredDefault();
globalConfiguration.setReplicationQueueScheduledExecutorFactoryClass(ReplQueueTestScheduledExecutorFactory.class.getName());
globalConfiguration.setReplicationQueueScheduledExecutorProperties(ReplQueueTestScheduledExecutorFactory.myProps);
CacheContainer first = TestCacheManagerFactory.createCacheManager(globalConfiguration);
CacheContainer second = TestCacheManagerFactory.createCacheManager(globalConfiguration);
registerCacheManager(first, second);
Configuration config = getDefaultClusteredConfig(Configuration.CacheMode.REPL_ASYNC, true);
config.setUseReplQueue(true);
config.setReplQueueInterval(REPL_QUEUE_INTERVAL);
config.setReplQueueMaxElements(REPL_QUEUE_MAX_ELEMENTS);
creationTime = System.currentTimeMillis();
manager(0).defineConfiguration("replQueue", config);
Configuration conf2 = config.clone();
conf2.setUseReplQueue(false);
manager(1).defineConfiguration("replQueue", conf2);
}
/**
* tests that the replication queue will use an appropriate executor defined through
* <tt>replicationQueueScheduledExecutor</tt> config param.
*/
@Test(dependsOnMethods = "testReplicationBasedOnTime")
public void testAppropriateExecutorIsUsed() {
assert ReplQueueTestScheduledExecutorFactory.methodCalled;
assert ReplQueueTestScheduledExecutorFactory.command != null;
assert ReplQueueTestScheduledExecutorFactory.delay == REPL_QUEUE_INTERVAL;
assert ReplQueueTestScheduledExecutorFactory.initialDelay == REPL_QUEUE_INTERVAL;
assert ReplQueueTestScheduledExecutorFactory.unit == TimeUnit.MILLISECONDS;
}
/**
* Make sure that replication will occur even if <tt>replQueueMaxElements</tt> are not reached, but the
* <tt>replQueueInterval</tt> is reached.
*/
public void testReplicationBasedOnTime() throws InterruptedException {
Cache cache1 = cache(0, "replQueue");
Cache cache2 = cache(1, "replQueue");
//only place one element, queue size is 10.
cache1.put("key", "value");
ReplicationQueue replicationQueue = TestingUtil.extractComponent(cache1, ReplicationQueue.class);
assert replicationQueue != null;
assert replicationQueue.getElementsCount() == 1;
assert cache2.get("key") == null;
assert cache1.get("key").equals("value");
ReplQueueTestScheduledExecutorFactory.command.run();
//in next 5 secs, expect the replication to occur
long start = System.currentTimeMillis();
while (System.currentTimeMillis() - start < 5000) {
if (cache2.get("key") != null) break;
Thread.sleep(50);
}
assert cache2.get("key").equals("value");
assert replicationQueue.getElementsCount() == 0;
}
/**
* Make sure that replication will occur even if <tt>replQueueMaxElements</tt> are not reached, but the
* <tt>replQueueInterval</tt> is reached.
*/
public void testReplicationBasedOnTimeWithTx() throws Exception {
Cache cache1 = cache(0, "replQueue");
Cache cache2 = cache(1, "replQueue");
//only place one element, queue size is 10.
TransactionManager transactionManager = TestingUtil.getTransactionManager(cache1);
transactionManager.begin();
cache1.put("key", "value");
transactionManager.commit();
ReplicationQueue replicationQueue = TestingUtil.extractComponent(cache1, ReplicationQueue.class);
assert replicationQueue != null;
assert replicationQueue.getElementsCount() == 1;
assert cache2.get("key") == null;
assert cache1.get("key").equals("value");
ReplQueueTestScheduledExecutorFactory.command.run();
//in next 5 secs, expect the replication to occur
long start = System.currentTimeMillis();
while (System.currentTimeMillis() - start < 5000) {
if (cache2.get("key") != null) break;
Thread.sleep(50);
}
assert cache2.get("key").equals("value");
assert replicationQueue.getElementsCount() == 0;
}
/**
* Make sure that replication will occur even if <tt>replQueueMaxElements</tt> is reached, but the
* <tt>replQueueInterval</tt> is not reached.
*/
public void testReplicationBasedOnSize() throws Exception {
Cache cache1 = cache(0, "replQueue");
Cache cache2 = cache(1, "replQueue");
//place 10 elements, queue size is 10.
for (int i = 0; i < REPL_QUEUE_MAX_ELEMENTS; i++) {
cache1.put("key" + i, "value" + i);
}
//expect that in next 3 secs all commands are replicated
long start = System.currentTimeMillis();
while (System.currentTimeMillis() - start < 3000) {
if (cache2.size() == REPL_QUEUE_MAX_ELEMENTS) break;
Thread.sleep(50);
}
for (int i = 0; i < REPL_QUEUE_MAX_ELEMENTS; i++) {
assert cache2.get("key" + i).equals("value" + i);
}
}
/**
* Make sure that replication will occur even if <tt>replQueueMaxElements</tt> is reached, but the
* <tt>replQueueInterval</tt> is not reached.
*/
public void testReplicationBasedOnSizeWithTx() throws Exception {
Cache cache1 = cache(0, "replQueue");
Cache cache2 = cache(1, "replQueue");
//only place one element, queue size is 10.
TransactionManager transactionManager = TestingUtil.getTransactionManager(cache1);
for (int i = 0; i < REPL_QUEUE_MAX_ELEMENTS; i++) {
transactionManager.begin();
cache1.put("key" + i, "value" + i);
transactionManager.commit();
}
//expect that in next 3 secs all commands are replicated
long start = System.currentTimeMillis();
while (System.currentTimeMillis() - start < 3000) {
if (cache2.size() == REPL_QUEUE_MAX_ELEMENTS) break;
Thread.sleep(50);
}
for (int i = 0; i < REPL_QUEUE_MAX_ELEMENTS; i++) {
assert cache2.get("key" + i).equals("value" + i);
}
}
/**
* Test that replication queue works fine when multiple threads are putting into the queue.
*/
public void testReplicationQueueMultipleThreads() throws Exception {
final Cache cache1 = cache(0, "replQueue");
Cache cache2 = cache(1, "replQueue");
// put 10 elements in the queue from 5 different threads
int numThreads = 5;
final int numLoopsPerThread = 2;
Thread[] threads = new Thread[numThreads];
final CountDownLatch latch = new CountDownLatch(1);
for (int i = 0; i < numThreads; i++) {
final int i1 = i;
threads[i] = new Thread() {
int index = i1;
public void run() {
try {
latch.await();
}
catch (InterruptedException e) {
// do nothing
}
for (int j = 0; j < numLoopsPerThread; j++) {
cache1.put("key" + index + "_" + j, "value");
}
}
};
threads[i].start();
}
latch.countDown();
// wait for threads to join
for (Thread t : threads) t.join();
long start = System.currentTimeMillis();
while (System.currentTimeMillis() - start < 3000) {
if (cache2.size() == REPL_QUEUE_MAX_ELEMENTS) break;
Thread.sleep(50);
}
assert cache2.size() == REPL_QUEUE_MAX_ELEMENTS;
ReplicationQueue replicationQueue = TestingUtil.extractComponent(cache1, ReplicationQueue.class);
assert replicationQueue.getElementsCount() == numThreads * numLoopsPerThread - REPL_QUEUE_MAX_ELEMENTS;
}
public static class ReplQueueTestScheduledExecutorFactory implements ScheduledExecutorFactory {
static Properties myProps = new Properties();
static boolean methodCalled = false;
static Runnable command;
static long initialDelay;
static long delay;
static TimeUnit unit;
static {
myProps.put("aaa", "bbb");
myProps.put("ddd", "ccc");
}
public ScheduledExecutorService getScheduledExecutor(Properties p) {
Properties toCompareWith = new Properties();
for (Map.Entry<Object, Object> entry: myProps.entrySet())
toCompareWith.setProperty((String) entry.getKey(), (String) entry.getValue());
toCompareWith.setProperty("componentName", "replicationQueue-thread");
toCompareWith.setProperty("threadPriority", "" + KnownComponentNames.getDefaultThreadPrio(KnownComponentNames.ASYNC_REPLICATION_QUEUE_EXECUTOR));
assert p.equals(toCompareWith) : "Expected " + p + " but was " + toCompareWith;
methodCalled = true;
return new ScheduledThreadPoolExecutor(1) {
@Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable commandP, long initialDelayP, long delayP, TimeUnit unitP) {
command = commandP;
initialDelay = initialDelayP;
delay = delayP;
unit = unitP;
return null;
}
};
}
}
}