package com.bigdata.journal; import java.io.IOException; import java.util.concurrent.ConcurrentHashMap; import org.apache.log4j.Logger; import com.bigdata.counters.CounterSet; import com.bigdata.counters.Instrument; import com.bigdata.resources.StoreManager; import com.bigdata.service.IBigdataFederation; import com.bigdata.service.IDataService; import com.bigdata.service.ITxState; /** * Manages the client side of a transaction either for a standalone * {@link Journal} or for an {@link IDataService} in an * {@link IBigdataFederation}. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> */ abstract public class AbstractLocalTransactionManager implements ILocalTransactionManager { /** * Logger. */ private static final Logger log = Logger .getLogger(AbstractLocalTransactionManager.class); public AbstractLocalTransactionManager() { } /* * ILocalTransactionManager */ /** * A hash map containing all active transactions. A transaction that is * preparing will remain in this collection until it has either successfully * prepared or aborted. * * TODO Configure the initial capacity and concurrency. For example, this * should be sized to the #of client connections for both the * initialCapacity and the concurrency level. */ final private ConcurrentHashMap<Long, Tx> activeTx = new ConcurrentHashMap<Long, Tx>(); @Override public ITxState[] getActiveTx() { return activeTx.values().toArray(new ITxState[] {}); } /** * Notify the journal that a new transaction is being activated (starting on * the journal). * * @param localState * The transaction. * * @throws IllegalStateException */ public void activateTx(final Tx localState) throws IllegalStateException { if (localState == null) throw new IllegalArgumentException(); localState.lock.lock(); try { if (activeTx .putIfAbsent(localState.getStartTimestamp(), localState) != null) { throw new IllegalStateException("Already in local table: tx=" + localState); } } finally { localState.lock.unlock(); } } /** * Removes the transaction from the local tables. * * @param localState * The transaction. */ protected void deactivateTx(final Tx localState) throws IllegalStateException { if (localState == null) throw new IllegalArgumentException(); localState.lock.lock(); try { if (!localState.isReadOnly() && !localState.isComplete()) throw new IllegalStateException("Not complete: "+localState); // release any local resources. localState.releaseResources(); if (activeTx.remove(localState.getStartTimestamp()) == null) { throw new IllegalStateException("Not in local tables: tx=" + localState); } } finally { localState.lock.unlock(); } } /** * Return the local state for a transaction. * * @param tx * The transaction identifier. * * @return The local state for the identified transaction -or- * <code>null</code> if the start time is not mapped to either an * active or prepared transaction. */ public Tx getTx(final long tx) { return activeTx.get(tx); } public boolean isOpen() { return open; } private volatile boolean open = true; synchronized public void shutdown() { if(!open) return; open = false; } synchronized public void shutdownNow() { // Note: currently a NOP. if(!open) return; open = false; } /** * Delay between attempts reach the remote service (ms). */ final long delay = 10L; /** * #of attempts to reach the remote service. * <p> * Note: delay*maxtries == 1000ms of trying before we give up, plus however * long we are willing to wait for service discovery if the problem is * locating the {@link ITransactionService}. * <p> * If this is not enough, then consider adding an optional parameter giving * the time the caller will wait and letting the {@link StoreManager} wait * longer during startup to discover the timestamp service. */ final int maxtries = 100; /** * Note: The reason for all this retry logic is to work around race * conditions during service startup (and possibly during service failover) * when the {@link ITimestampService} has not been discovered yet. */ public long nextTimestamp() { final long begin = System.currentTimeMillis(); IOException cause = null; int ntries; for (ntries = 1; ntries <= maxtries; ntries++) { try { final ITransactionService transactionService = getTransactionService(); if (transactionService == null) { log.warn("Service not discovered yet?"); try { Thread.sleep(delay/* ms */); continue; } catch (InterruptedException e2) { throw new RuntimeException( "Interrupted awaiting timestamp service discovery: " + e2); } } return transactionService.nextTimestamp(); } catch (IOException e) { log.warn("Problem with timestamp service? : ntries=" + ntries + ", " + e, e); cause = e; } } final long elapsed = System.currentTimeMillis() - begin; final String msg = "Could not get timestamp after " + ntries + " tries and " + elapsed + "ms"; log.error(msg, cause); throw new RuntimeException(msg, cause); } public void notifyCommit(final long commitTime) { final long begin = System.currentTimeMillis(); IOException cause = null; int ntries; for (ntries = 1; ntries <= maxtries; ntries++) { try { final ITransactionService transactionService = getTransactionService(); if (transactionService == null) { log.warn("Service not discovered?"); try { Thread.sleep(delay/* ms */); } catch (InterruptedException e2) { throw new RuntimeException( "Interrupted awaiting timestamp service discovery: " + e2); } continue; } transactionService.notifyCommit(commitTime); return; } catch (IOException e) { log.warn("Problem with timestamp service? : ntries=" + ntries + ", " + e, e); cause = e; } } final long elapsed = System.currentTimeMillis() - begin; final String msg = "Could not notify timestamp service after " + ntries + " tries and " + elapsed + "ms"; log.error(msg, cause); throw new RuntimeException(msg, cause); } /** * Return interesting statistics about the transaction manager. */ public CounterSet getCounters() { final CounterSet countersRoot = new CounterSet(); countersRoot.addCounter("#active", new Instrument<Integer>() { protected void sample() { setValue(activeTx.size()); } }); return countersRoot; } }