/*
* INESC-ID, Instituto de Engenharia de Sistemas e Computadores Investigação e Desevolvimento em Lisboa
* Copyright 2013 INESC-ID 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 3.0 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.reconfigurableprotocol.manager;
import org.infinispan.commands.CommandsFactory;
import org.infinispan.commands.remote.ReconfigurableProtocolCommand;
import org.infinispan.commands.write.WriteCommand;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.factories.ComponentRegistry;
import org.infinispan.factories.annotations.Inject;
import org.infinispan.interceptors.InterceptorChain;
import org.infinispan.jmx.annotations.MBean;
import org.infinispan.jmx.annotations.ManagedAttribute;
import org.infinispan.jmx.annotations.ManagedOperation;
import org.infinispan.reconfigurableprotocol.ReconfigurableProtocol;
import org.infinispan.reconfigurableprotocol.ReconfigurableProtocolRegistry;
import org.infinispan.reconfigurableprotocol.exception.AlreadyRegisterProtocolException;
import org.infinispan.reconfigurableprotocol.exception.NoSuchReconfigurableProtocolException;
import org.infinispan.reconfigurableprotocol.exception.SwitchInProgressException;
import org.infinispan.reconfigurableprotocol.protocol.PassiveReplicationCommitProtocol;
import org.infinispan.reconfigurableprotocol.protocol.TotalOrderCommitProtocol;
import org.infinispan.reconfigurableprotocol.protocol.TwoPhaseCommitProtocol;
import org.infinispan.remoting.rpc.RpcManager;
import org.infinispan.remoting.transport.Address;
import org.infinispan.transaction.LocalTransaction;
import org.infinispan.transaction.TransactionProtocol;
import org.infinispan.transaction.xa.GlobalTransaction;
import org.infinispan.util.Util;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
import javax.transaction.Transaction;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import static org.infinispan.commands.remote.ReconfigurableProtocolCommand.Type;
/**
* Manages everything about the replication protocols, namely the switch between protocols and the registry of new
* replication protocols
*
* @author Pedro Ruivo
* @since 5.2
*/
@MBean(objectName = "ReconfigurableReplicationManager", description = "Manages the replication protocol used to commit" +
" the transactions and for the switching between them")
public class ReconfigurableReplicationManager {
private static final Log log = LogFactory.getLog(ReconfigurableReplicationManager.class);
private final ReconfigurableProtocolRegistry registry;
private final ProtocolManager protocolManager;
private final CoolDownTimeManager coolDownTimeManager;
private final StatisticManager statisticManager;
private RpcManager rpcManager;
private CommandsFactory commandsFactory;
private Configuration configuration;
private ComponentRegistry componentRegistry;
private volatile boolean allowSwitch = false;
public ReconfigurableReplicationManager() {
statisticManager = new StatisticManager();
registry = new ReconfigurableProtocolRegistry();
protocolManager = new ProtocolManager(statisticManager);
coolDownTimeManager = new CoolDownTimeManager();
}
@Inject
public final void inject(InterceptorChain interceptorChain, RpcManager rpcManager, CommandsFactory commandsFactory,
Configuration configuration, ComponentRegistry componentRegistry) {
allowSwitch = configuration.clustering().cacheMode().isClustered();
registry.inject(interceptorChain);
this.rpcManager = rpcManager;
this.commandsFactory = commandsFactory;
this.configuration = configuration;
this.componentRegistry = componentRegistry;
ReconfigurableProtocol protocol = new TwoPhaseCommitProtocol();
try {
protocol.initialize(configuration, componentRegistry, this);
registry.registerNewProtocol(protocol);
} catch (AlreadyRegisterProtocolException e) {
//this exception is catch when you inject the extended statistics
log.errorf("Tried to register Two Phase Commit protocol but it is already register.");
}
protocol = new PassiveReplicationCommitProtocol();
try {
protocol.initialize(configuration, componentRegistry, this);
registry.registerNewProtocol(protocol);
} catch (AlreadyRegisterProtocolException e) {
//this exception is catch when you inject the extended statistics
log.errorf("Tried to register Passive Replication protocol but it is already register.");
}
protocol = new TotalOrderCommitProtocol();
try {
protocol.initialize(configuration, componentRegistry, this);
registry.registerNewProtocol(protocol);
} catch (AlreadyRegisterProtocolException e) {
//this exception is catch when you inject the extended statistics
log.errorf("Tried to register Total Order protocol but it is already register.");
}
initialProtocol(configuration.transaction().transactionProtocol());
}
/**
* method invoked when a message is received from the network. it contains data for the specific replication
* protocol
*
* @param protocolId the target protocol Id
* @param data the data
* @param from the sender
*/
public final void handleProtocolData(String protocolId, Object data, Address from) {
ReconfigurableProtocol protocol = registry.getProtocolById(protocolId);
if (protocol != null) {
protocol.handleData(data, from);
} else {
log.warnf("Received data [%s] for protocol %s from %s but it does not exits", data, protocol, from);
}
}
public final String beginTransaction(Transaction transaction) throws InterruptedException {
if (log.isDebugEnabled()) {
log.debugf("[%s] local transaction %s is starting. check if switch is in progress...",
Thread.currentThread().getName(), transaction);
}
protocolManager.ensureNotInProgress();
ReconfigurableProtocol currentProtocol = protocolManager.getCurrent();
currentProtocol.startTransaction(transaction);
if (log.isDebugEnabled()) {
log.debugf("[%s] local transaction %s will use %s while executing", Thread.currentThread().getName(),
transaction, currentProtocol.getUniqueProtocolName());
}
return currentProtocol.getUniqueProtocolName();
}
/**
* notifies the protocol to a new local transaction that wants to commit. sets the epoch and the protocol to use for
* this transaction. it blocks if a switch between protocols is in progress.
*
* @param globalTransaction the global transaction
* @throws InterruptedException if interrupted while waiting for the switch to finish
*/
public final void notifyLocalTransaction(GlobalTransaction globalTransaction, WriteCommand[] writeSet,
String executionProtocolId, Transaction transaction)
throws InterruptedException {
//returns immediately if no switch is in progress
if (log.isDebugEnabled()) {
log.debugf("[%s] local transaction %s [%s] wants to commit",
Thread.currentThread().getName(), globalTransaction.prettyPrint(), transaction);
}
ProtocolManager.CurrentProtocolInfo currentProtocolInfo =
protocolManager.startCommitTransaction(globalTransaction, writeSet,
registry.getProtocolById(executionProtocolId), transaction);
long epoch = currentProtocolInfo.getEpoch();
ReconfigurableProtocol actual = currentProtocolInfo.getCurrent();
globalTransaction.setEpochId(epoch);
globalTransaction.setProtocolId(actual.getUniqueProtocolName());
globalTransaction.setReconfigurableProtocol(actual);
if (log.isDebugEnabled()) {
log.debugf("[%s] local transaction %s will use %s as commit protocol", Thread.currentThread().getName(),
globalTransaction.prettyPrint(), currentProtocolInfo);
}
}
public final void notifyLocalTransactionForRollback(LocalTransaction localTransaction, String executionProtocolId) {
GlobalTransaction globalTransaction = localTransaction.getGlobalTransaction();
if (log.isDebugEnabled()) {
log.debugf("[%s] local transaction %s [%s] wants to rollback",
Thread.currentThread().getName(), globalTransaction.prettyPrint(), localTransaction.getTransaction());
}
if (globalTransaction.getReconfigurableProtocol() == null) {
String protocolId = globalTransaction.getProtocolId();
if (protocolId == null) {
protocolId = executionProtocolId;
}
//this probably will originate a epoch mismatch, but it is not a problem
globalTransaction.setReconfigurableProtocol(registry.getProtocolById(protocolId));
globalTransaction.setProtocolId(protocolId);
globalTransaction.setEpochId(-1);
}
try {
globalTransaction.getReconfigurableProtocol().commitTransaction(localTransaction.getTransaction());
} catch (Exception e) {
//ignore: this probably will throw an exception saying that the transaction is marked for rollback...
}
}
/**
* notifies the actual protocol for a remote transaction. if the transaction epoch is lower than the actual epoch
* then the actual protocol is notified and decides if the transaction can commit or should be aborted
* <p/>
* if a transaction with higher epoch is received then it blocks it until the epoch changes
*
* @param globalTransaction the global transaction
* @throws InterruptedException if interrupted while waiting for the new epoch
*/
public final void notifyRemoteTransaction(GlobalTransaction globalTransaction, WriteCommand[] writeSet)
throws InterruptedException, NoSuchReconfigurableProtocolException {
long txEpoch = globalTransaction.getEpochId();
ReconfigurableProtocol txProtocol = globalTransaction.getReconfigurableProtocol();
ProtocolManager.CurrentProtocolInfo currentProtocolInfo = protocolManager.getCurrentProtocolInfo();
long epoch = currentProtocolInfo.getEpoch();
if (log.isDebugEnabled()) {
log.debugf("[%s] remote transaction %s received. Epoch is %s (current epoch is %s) and protocol ID is %s",
Thread.currentThread().getName(), globalTransaction.prettyPrint(), txEpoch, epoch,
globalTransaction.getProtocolId());
}
if (txProtocol == null) {
log.warnf("Protocol ID %s does not exists. Transaction %s will be aborted", globalTransaction.getProtocolId(),
globalTransaction.prettyPrint());
throw new NoSuchReconfigurableProtocolException(globalTransaction.getProtocolId());
}
ReconfigurableProtocol currentProtocol = currentProtocolInfo.getCurrent();
protocolManager.ensure(txEpoch);
if (txEpoch < epoch) {
txProtocol.processOldTransaction(globalTransaction, writeSet, currentProtocol);
} else if (txEpoch == epoch) {
if (!currentProtocol.equals(txProtocol)) {
throw new IllegalStateException("Transaction protocol differs from the Current transaction protocol for " +
"the same epoch");
}
if (currentProtocolInfo.isUnsafe()) {
currentProtocol.processSpeculativeTransaction(globalTransaction, writeSet, currentProtocolInfo.getOld());
} else {
currentProtocol.processTransaction(globalTransaction, writeSet);
}
}
txProtocol.addRemoteTransaction(globalTransaction, writeSet);
}
/**
* notifies the ending of the local transaction
*
* @param globalTransaction the global transaction
*/
public final void notifyLocalTransactionFinished(GlobalTransaction globalTransaction) {
ReconfigurableProtocol protocol = registry.getProtocolById(globalTransaction.getProtocolId());
if (protocol != null) {
if (log.isTraceEnabled()) {
log.tracef("Local transaction %s is finished", globalTransaction.prettyPrint());
}
protocol.removeLocalTransaction(globalTransaction);
} else {
log.fatalf("Local transaction %s is finished but the commit protocol %s does not exits",
globalTransaction.prettyPrint(), globalTransaction.getProtocolId());
}
}
/**
* notifies the ending of the remote transaction
*
* @param globalTransaction the global transaction
*/
public final void notifyRemoteTransactionFinished(GlobalTransaction globalTransaction) {
ReconfigurableProtocol protocol = registry.getProtocolById(globalTransaction.getProtocolId());
if (protocol != null) {
if (log.isTraceEnabled()) {
log.tracef("Remote transaction %s is finished", globalTransaction.prettyPrint());
}
protocol.removeRemoteTransaction(globalTransaction);
} else {
log.fatalf("Remote transaction %s is finished but the commit protocol %s does not exits",
globalTransaction.prettyPrint(), globalTransaction.getProtocolId());
}
}
/**
* initializes the global transaction (possibly remote)
*
* @param globalTransaction the global transaction
*/
public final void initGlobalTransactionIfNeeded(GlobalTransaction globalTransaction) {
if (globalTransaction == null || globalTransaction.getReconfigurableProtocol() != null) {
return;
}
ReconfigurableProtocol protocol = registry.getProtocolById(globalTransaction.getProtocolId());
globalTransaction.setReconfigurableProtocol(protocol);
}
/**
* register a new replication protocol in the ReconfigurableProtocolRegistry.
*
* @param clazzName the full class name
* @throws Exception if it was not registered, due to the class does not extend ReconfigurableProtocol or the
* protocol is already registered
*/
public final void internalRegister(String clazzName) throws Exception {
Class<?> clazz = Util.loadClass(clazzName, this.getClass().getClassLoader());
if (!ReconfigurableProtocol.class.isAssignableFrom(clazz)) {
log.warnf("Tried to register new replication protocol %s but it does not extends ReconfigurableProtocol",
clazzName);
throw new Exception("Class " + clazzName + " does not extends ReconfigurableProtocol class");
}
ReconfigurableProtocol newProtocol = (ReconfigurableProtocol) clazz.newInstance();
newProtocol.initialize(configuration, componentRegistry, this);
registry.registerNewProtocol(newProtocol);
}
/**
* change the protocol and set the state as safe (i.e it is safe to process new epoch transactions)
*
* @param newProtocol the new replication protocol
*/
public final void safeSwitch(ReconfigurableProtocol newProtocol) {
protocolManager.change(newProtocol, true);
}
/**
* change the protocol and set the state as unsafe (i.e it is not safe to process new epoch transactions and some
* precautions may be needed)
*
* @param newProtocol the new replication protocol
*/
public final void unsafeSwitch(ReconfigurableProtocol newProtocol) {
protocolManager.change(newProtocol, false);
}
public final void internalSetSwitchCoolDownTime(int seconds) {
coolDownTimeManager.setCoolDownTimePeriod(seconds);
}
public final boolean isTotalOrderBasedProtocol(String protocolId) {
ReconfigurableProtocol protocol = registry.getProtocolById(protocolId);
return protocol != null && protocol.useTotalOrder();
}
public final void startSwitchTask(String protocolId, boolean forceStopTheWorld, boolean abortOnStop, CountDownLatch notifier) {
new Thread(new SwitchTask(protocolId, forceStopTheWorld, abortOnStop, notifier), "Switch-Thread").start();
}
public final void addNumberOfAbortedTransactionDueToSwitch(int val) {
protocolManager.addNumberOfTransactionsAborted(val);
}
public final void initialProtocol(String protocolName, long epoch) {
ReconfigurableProtocol protocol = registry.getProtocolById(protocolName);
if (protocol == null) {
throw new RuntimeException("Protocol ID [" + protocolName + "] not found!");
}
if (log.isTraceEnabled()) {
log.tracef("Initial replication protocol is %s", protocol.getUniqueProtocolName());
}
protocolManager.init(protocol, epoch);
}
public final void initialProtocol(TransactionProtocol transactionProtocol) {
ReconfigurableProtocol protocol = null;
switch (transactionProtocol) {
case TOTAL_ORDER:
protocol = registry.getProtocolById(TotalOrderCommitProtocol.UID);
break;
case TWO_PHASE_COMMIT:
protocol = registry.getProtocolById(TwoPhaseCommitProtocol.UID);
break;
case PASSIVE_REPLICATION:
protocol = registry.getProtocolById(PassiveReplicationCommitProtocol.UID);
break;
}
if (protocol == null) {
throw new RuntimeException("Protocol for " + transactionProtocol + " not found!");
}
if (log.isTraceEnabled()) {
log.tracef("Initial replication protocol is %s", protocol.getUniqueProtocolName());
}
protocolManager.init(protocol, 0);
}
public final ProtocolManager getProtocolManager() {
return protocolManager;
}
@ManagedOperation(description = "Registers a new replication protocol. The new protocol must extend the " +
"ReconfigurableProtocol")
public final void register(String clazzName) throws Exception {
if (!allowSwitch) {
return;
}
try {
internalRegister(clazzName);
ReconfigurableProtocolCommand command = commandsFactory.buildReconfigurableProtocolCommand(Type.REGISTER, clazzName);
rpcManager.broadcastRpcCommand(command, false, false);
} catch (Exception e) {
throw new Exception("Exception while registering class: " + e.getMessage());
}
}
@ManagedOperation(description = "Switch the current replication protocol for the new one. It fails if the protocol " +
"does not exists or it is equals to the current")
public final void switchTo(String protocolId, boolean forceStopTheWorld, boolean abortOnStop) throws Exception {
if (!allowSwitch) {
return;
}
if (!rpcManager.getTransport().isCoordinator()) {
ReconfigurableProtocolCommand command = commandsFactory.buildReconfigurableProtocolCommand(Type.SWITCH_REQ, protocolId);
command.setForceStop(forceStopTheWorld);
command.setAbortOnStop(abortOnStop);
rpcManager.invokeRemotely(Collections.singleton(rpcManager.getTransport().getCoordinator()), command, true);
return;
}
if (registry.getProtocolById(protocolId) == null) {
log.warnf("Tried to switch the replication protocol to %s but it does not exist", protocolId);
throw new NoSuchReconfigurableProtocolException(protocolId);
} else if (protocolManager.isCurrentProtocol(registry.getProtocolById(protocolId))) {
log.warnf("Tried to switch the replication protocol to %s but it is already the current protocol", protocolId);
return; //nothing to do
} else if (!coolDownTimeManager.checkAndSetToSwitch()) {
log.warnf("Tried to switch to %s but you cannot do it right now...", protocolId);
throw new Exception("You need to wait before perform a new switch");
}
ReconfigurableProtocolCommand command = commandsFactory.buildReconfigurableProtocolCommand(Type.SWITCH, protocolId);
command.setForceStop(forceStopTheWorld);
command.setAbortOnStop(abortOnStop);
if (protocolManager.getCurrent().useTotalOrder()) {
rpcManager.broadcastRpcCommand(command, false, true);
return;
}
rpcManager.broadcastRpcCommand(command, false, false);
startSwitchTask(protocolId, forceStopTheWorld, abortOnStop, new CountDownLatch(1));
}
@ManagedAttribute(description = "Returns a collection of replication protocols IDs that can be used in the switchTo",
writable = false)
public final String getAvailableProtocolIds() {
Collection<ReconfigurableProtocol> protocols = registry.getAllAvailableProtocols();
String result = "";
for (ReconfigurableProtocol p : protocols) {
result += p.getUniqueProtocolName()+" " ;
}
return result;
}
@ManagedAttribute(description = "Returns a collection with the information about the replication protocols available, " +
"namely, the protocol ID and the class name", writable = false)
public final Map<String, String> getAvailableProtocolsInfo() {
Collection<ReconfigurableProtocol> protocols = registry.getAllAvailableProtocols();
Map<String, String> result = new HashMap<String, String>(protocols.size() * 2);
for (ReconfigurableProtocol p : protocols) {
result.putAll(getProtocolInfo(p));
}
return result;
}
@ManagedAttribute(description = "Returns the current replication protocol ID", writable = false)
public final String getCurrentProtocolId() {
return protocolManager.getCurrent().getUniqueProtocolName();
}
@ManagedAttribute(description = "Returns the current replication protocol information, namely the protocol ID and " +
"the class name", writable = false)
public final Map<String, String> getCurrentProtocolInfo() {
return getProtocolInfo(protocolManager.getCurrent());
}
@ManagedAttribute(description = "Returns the cool down time period in seconds", writable = false)
public final int getSwitchCoolDownTime() {
return coolDownTimeManager.getCoolDownTimePeriod();
}
@ManagedOperation(description = "Sets the new cool down time period (in seconds) to wait before two consecutive switches")
public final void setSwitchCoolDownTime(int seconds) {
if (!allowSwitch) {
return;
}
internalSetSwitchCoolDownTime(seconds);
ReconfigurableProtocolCommand command = commandsFactory.buildReconfigurableProtocolCommand(Type.SET_COOL_DOWN_TIME, null);
command.setData(seconds);
rpcManager.broadcastRpcCommand(command, false, false);
}
@ManagedOperation(description = "Prints the current state")
public final String printState() {
ProtocolManager.CurrentProtocolInfo currentProtocolInfo = protocolManager.getCurrentProtocolInfo();
StringBuilder sb = new StringBuilder();
sb.append("Epoch=").append(currentProtocolInfo.getEpoch()).append("\n");
Map.Entry<String, String> info = getProtocolInfo(currentProtocolInfo.getCurrent()).entrySet().iterator().next();
sb.append("Current Protocol ID=").append(info.getKey()).append("\n");
sb.append("Current Protocol Class=").append(info.getValue()).append("\n");
if (currentProtocolInfo.getOld() == null) {
sb.append("Old Protocol ID=null\n");
sb.append("Old Protocol Class=null\n");
} else {
info = getProtocolInfo(currentProtocolInfo.getOld()).entrySet().iterator().next();
sb.append("Old Protocol ID=").append(info.getKey()).append("\n");
sb.append("Old Protocol Class=").append(info.getValue()).append("\n");
}
sb.append("State=").append(currentProtocolInfo.printState());
return sb.toString();
}
@ManagedAttribute(description = "Current Epoch")
public final long getCurrentEpoch() {
return protocolManager.getEpoch();
}
@ManagedOperation(description = "Prints the pending local transactions for the protocol Id")
public final String printLocalTransactions(String protocolId) {
ReconfigurableProtocol protocol = registry.getProtocolById(protocolId);
return protocol == null ? "No such protocol" : protocol.printLocalTransactions();
}
@ManagedOperation(description = "Prints the pending remote transactions for the protocol Id")
public final String printRemoteTransactions(String protocolId) {
ReconfigurableProtocol protocol = registry.getProtocolById(protocolId);
return protocol == null ? "No such protocol" : protocol.printRemoteTransactions();
}
@ManagedOperation(description = "Returns the average time in duration between two safe states for a particular " +
"switch")
public final double getAvgSafeToSafeDuration(String from, String to) {
return statisticManager.getSafeToSafe(from, to);
}
@ManagedOperation(description = "Returns the average time in duration to change between a safe state to an unsafe " +
"state for a particular switch")
public final double getAvgSafeToUnsafeDuration(String from, String to) {
return statisticManager.getSafeToUnsafe(from, to);
}
@ManagedOperation(description = "Returns the average time in duration to change between an unsafe state to a safe " +
"state for a particular switch")
public final double getAvgUnsafeToSafeDuration(String from, String to) {
return statisticManager.getUnsafeToSafe(from, to);
}
@ManagedOperation(description = "Returns the number of times that this particular switch happen")
public final int getSwitchCounter(String from, String to) {
return statisticManager.getSwitchCounter(from, to);
}
@ManagedOperation(description = "Returns the number of transactions aborted by this particular switch")
public final int getNumberOfAbortedTransactions(String from, String to) {
return statisticManager.getNumberOfAbortedTransactions(from, to);
}
@ManagedOperation(description = "Returns all the averages times for all the switches")
public final String printSwitchAvgDurations() {
return statisticManager.printAllStats();
}
@ManagedOperation(description = "Resets the switch statistics")
public final void resetSwitchStats() {
statisticManager.reset();
}
@ManagedAttribute(description = "Returns true if it is possible to change the reconfigurable protocol", writable = false)
public final boolean getAllowProtocolSwitch() {
return allowSwitch;
}
/**
* Returns the information about the protocol, namely the protocol ID and the full class name
*
* @param protocol the protocol
* @return the information about the protocol, namely the protocol ID and the full class name
*/
private Map<String, String> getProtocolInfo(ReconfigurableProtocol protocol) {
Map<String, String> info = new LinkedHashMap<String, String>();
info.put(protocol.getUniqueProtocolName(), protocol.getClass().getCanonicalName());
return info;
}
/**
* switch the replication protocol with the new. the switch will not happen if you try to switch to the same
* replication protocol or the new protocol does not exist
* <p/>
* Note: 1) first it tries to use the non-blocking switch (switchTo method in protocol) 2) if the first fails, it
* uses the stop-the-world model
*
* @param protocolId the new protocol ID
* @param forceStopTheWorld true if it must use the stop the world switch (no optimization)
* @throws NoSuchReconfigurableProtocolException
* if the new protocol does not exist
* @throws InterruptedException if it is interrupted
*/
private void internalSwitchTo(String protocolId, boolean forceStopTheWorld, boolean abortOnStop, CountDownLatch notifier)
throws NoSuchReconfigurableProtocolException, InterruptedException, SwitchInProgressException {
try {
ReconfigurableProtocol newProtocol = registry.getProtocolById(protocolId);
if (newProtocol == null) {
log.warnf("Tried to switch the replication protocol to %s but it does not exist", protocolId);
throw new NoSuchReconfigurableProtocolException(protocolId);
} else if (protocolManager.isCurrentProtocol(newProtocol)) {
log.warnf("Tried to switch the replication protocol to %s but it is already the current protocol", protocolId);
return; //nothing to do
} else if (protocolManager.isInProgress() || protocolManager.isUnsafe()) {
log.warnf("Tried to switch the replication protocol to %s but a switch is already in progress", protocolId);
throw new SwitchInProgressException("Switch is in progress");
}
protocolManager.inProgress();
ReconfigurableProtocol currentProtocol = protocolManager.getCurrent();
if (!forceStopTheWorld && currentProtocol.canSwitchTo(newProtocol)) {
if (log.isDebugEnabled()) {
log.debugf("Perform switch from %s to %s with the optimized switch", currentProtocol.getUniqueProtocolName(),
newProtocol.getUniqueProtocolName());
}
currentProtocol.switchTo(newProtocol);
} else {
if (log.isDebugEnabled()) {
log.debugf("Perform switch from %s to %s by stop-the-world model", currentProtocol.getUniqueProtocolName(),
newProtocol.getUniqueProtocolName());
}
notifier.countDown();
currentProtocol.stopProtocol(abortOnStop);
newProtocol.bootProtocol();
safeSwitch(newProtocol);
}
} finally {
notifier.countDown();
}
}
/**
* manages the cool down time between two consecutive switches
*/
private class CoolDownTimeManager {
private long nextSwitchTime; //in milliseconds
private long coolDownTimePeriod; //in milliseconds;
public CoolDownTimeManager() {
nextSwitchTime = System.currentTimeMillis();
coolDownTimePeriod = 60000; //1 min
}
public synchronized boolean checkAndSetToSwitch() {
if (nextSwitchTime <= System.currentTimeMillis()) {
nextSwitchTime = System.currentTimeMillis() + coolDownTimePeriod;
return true;
} else {
return false;
}
}
public synchronized int getCoolDownTimePeriod() {
return (int) (coolDownTimePeriod / 1000);
}
public synchronized void setCoolDownTimePeriod(int seconds) {
coolDownTimePeriod = seconds * 1000;
}
}
private class SwitchTask implements Runnable {
private final String protocolId;
private final CountDownLatch notifier;
private final boolean forceStopTheWorld;
private final boolean abortOnStop;
private SwitchTask(String protocolId, boolean forceStopTheWorld, boolean abortOnStop, CountDownLatch notifier) {
this.protocolId = protocolId;
this.notifier = notifier;
this.forceStopTheWorld = forceStopTheWorld;
this.abortOnStop = abortOnStop;
}
@Override
public void run() {
try {
internalSwitchTo(protocolId, forceStopTheWorld, abortOnStop, notifier);
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debugf(e, "Error switching protocol to %s.", protocolId);
} else {
log.warnf("Error switching protocol to %s. %s", protocolId, e.getMessage());
}
}
}
}
}