/*
* 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.test;
import org.infinispan.Cache;
import org.infinispan.distribution.rehash.XAResourceAdapter;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
/**
* Utility class that can be used for writing tests that need to access a cache instance from multiple threads.
*
* @author Mircea.Markus@jboss.com
* @see Operations
* @see OperationsResult
*/
public final class PerCacheExecutorThread extends Thread {
private static final Log log = LogFactory.getLog(PerCacheExecutorThread.class);
private Cache<Object, Object> cache;
private BlockingQueue<Object> toExecute = new ArrayBlockingQueue<Object>(1);
private volatile Object response;
private CountDownLatch responseLatch = new CountDownLatch(1);
private volatile Transaction ongoingTransaction;
private volatile Object key, value;
public void setKeyValue(Object key, Object value) {
this.key = key;
this.value = value;
}
public PerCacheExecutorThread(Cache<Object, Object> cache, int index) {
super("PerCacheExecutorThread-" + index);
this.cache = cache;
start();
}
public Object execute(Operations op) {
try {
responseLatch = new CountDownLatch(1);
toExecute.put(op);
responseLatch.await();
return response;
} catch (InterruptedException e) {
throw new RuntimeException("Unexpected", e);
}
}
public void executeNoResponse(Operations op) {
try {
responseLatch = null;
response = null;
toExecute.put(op);
} catch (InterruptedException e) {
throw new RuntimeException("Unexpected", e);
}
}
@Override
public void run() {
Operations operation;
boolean run = true;
while (run) {
try {
operation = (Operations) toExecute.take();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.tracef("about to process operation %s", operation);
switch (operation) {
case BEGIN_TX: {
TransactionManager txManager = TestingUtil.getTransactionManager(cache);
try {
txManager.begin();
ongoingTransaction = txManager.getTransaction();
setResponse(OperationsResult.BEGIN_TX_OK);
} catch (Exception e) {
log.trace("Failure on beginning tx", e);
setResponse(e);
}
break;
}
case COMMIT_TX: {
TransactionManager txManager = TestingUtil.getTransactionManager(cache);
try {
txManager.commit();
ongoingTransaction = null;
setResponse(OperationsResult.COMMIT_TX_OK);
} catch (Exception e) {
log.trace("Exception while committing tx", e);
setResponse(e);
}
break;
}
case PUT_KEY_VALUE: {
try {
cache.put(key, value);
log.trace("Successfully executed putKeyValue(" + key + ", " + value + ")");
setResponse(OperationsResult.PUT_KEY_VALUE_OK);
} catch (Exception e) {
log.trace("Exception while executing putKeyValue(" + key + ", " + value + ")", e);
setResponse(e);
}
break;
}
case REMOVE_KEY: {
try {
cache.remove(key);
log.trace("Successfully executed remove(" + key + ")");
setResponse(OperationsResult.REMOVE_KEY_OK);
} catch (Exception e) {
log.trace("Exception while executing remove(" + key + ")", e);
setResponse(e);
}
break;
}
case REPLACE_KEY_VALUE: {
try {
cache.replace(key, value);
log.trace("Successfully executed replace(" + key + "," + value + ")");
setResponse(OperationsResult.REPLACE_KEY_VALUE_OK);
} catch (Exception e) {
log.trace("Exception while executing replace(" + key + "," + value + ")", e);
setResponse(e);
}
break;
}
case FORCE2PC: {
try {
TransactionManager txManager = TestingUtil.getTransactionManager(cache);
txManager.getTransaction().enlistResource(new XAResourceAdapter());
setResponse(OperationsResult.FORCE2PC_OK);
} catch (Exception e) {
log.trace("Exception while executing replace(" + key + "," + value + ")", e);
setResponse(e);
}
break;
}
case STOP_THREAD: {
log.trace("Exiting...");
toExecute = null;
run = false;
break;
}
default : {
setResponse(new IllegalStateException("Unknown operation!" + operation));
}
}
if (responseLatch != null) responseLatch.countDown();
}
}
private void setResponse(Object e) {
log.tracef("setResponse to %s", e);
response = e;
}
public void stopThread() {
execute(Operations.STOP_THREAD);
while (!this.getState().equals(State.TERMINATED)) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
}
public Object lastResponse() {
return response;
}
public void clearResponse() {
response = null;
}
public Object waitForResponse() {
while (response == null) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
return response;
}
/**
* Defines allowed operations for {@link PerCacheExecutorThread}.
*
* @author Mircea.Markus@jboss.com
*/
public static enum Operations {
BEGIN_TX, COMMIT_TX, PUT_KEY_VALUE, REMOVE_KEY, REPLACE_KEY_VALUE, STOP_THREAD, FORCE2PC;
public OperationsResult getCorrespondingOkResult() {
switch (this) {
case BEGIN_TX:
return OperationsResult.BEGIN_TX_OK;
case COMMIT_TX:
return OperationsResult.COMMIT_TX_OK;
case PUT_KEY_VALUE:
return OperationsResult.PUT_KEY_VALUE_OK;
case REMOVE_KEY:
return OperationsResult.REMOVE_KEY_OK;
case REPLACE_KEY_VALUE:
return OperationsResult.REPLACE_KEY_VALUE_OK;
case STOP_THREAD:
return OperationsResult.STOP_THREAD_OK;
case FORCE2PC:
return OperationsResult.FORCE2PC_OK;
default:
throw new IllegalStateException("Unrecognized operation: " + this);
}
}
}
/**
* Defines operation results returned by {@link PerCacheExecutorThread}.
*
* @author Mircea.Markus@jboss.com
*/
public static enum OperationsResult {
BEGIN_TX_OK, COMMIT_TX_OK, PUT_KEY_VALUE_OK, REMOVE_KEY_OK, REPLACE_KEY_VALUE_OK, STOP_THREAD_OK , FORCE2PC_OK
}
public Transaction getOngoingTransaction() {
return ongoingTransaction;
}
}