/*
* 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.write.WriteCommand;
import org.infinispan.reconfigurableprotocol.ReconfigurableProtocol;
import org.infinispan.transaction.xa.GlobalTransaction;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
import javax.transaction.Transaction;
/**
* Manages the current replication protocol in use and synchronize it with the epoch number
*
* @author Pedro Ruivo
* @since 5.2
*/
public class ProtocolManager {
private static final Log log = LogFactory.getLog(ProtocolManager.class);
private final StatisticManager statisticManager;
private long epoch = 0;
private ReconfigurableProtocol current;
private ReconfigurableProtocol old;
private State state;
private StatisticManager.Stats currentStat;
public ProtocolManager(StatisticManager statisticManager) {
this.statisticManager = statisticManager;
}
/**
* init the protocol manager with the initial replication protocol
*
* @param actual the initial replication protocol
* @param epoch the initial epoch
*/
public final synchronized void init(ReconfigurableProtocol actual, long epoch) {
this.old = null;
this.current = actual;
this.state = State.SAFE;
this.epoch = epoch;
}
/**
* returns the current replication protocol
*
* @return the current replication protocol
*/
public final synchronized ReconfigurableProtocol getCurrent() {
return current;
}
/**
* signal the switch start changing the state to "in progress"
*/
public final synchronized void inProgress() {
this.currentStat = statisticManager.createNewStats(current.getUniqueProtocolName());
this.state = State.IN_PROGRESS;
if (log.isDebugEnabled()) {
log.debugf("Changed state to %s", state);
}
}
/**
* atomically changes the current protocol and increments the epoch
*
* @param newProtocol the new replication protocol to use
*/
public final synchronized void change(ReconfigurableProtocol newProtocol, boolean safe) {
if (safe) {
currentStat.safe(newProtocol == null ? null : newProtocol.getUniqueProtocolName());
state = State.SAFE;
} else {
currentStat.unsafe(newProtocol == null ? null : newProtocol.getUniqueProtocolName());
state = State.UNSAFE;
}
notifyAll();
if (newProtocol == null || isCurrentProtocol(newProtocol)) {
if (log.isDebugEnabled()) {
log.debugf("Changed state to %s", state);
}
return;
}
old = current;
current = newProtocol;
epoch++;
this.notifyAll();
if (log.isDebugEnabled()) {
log.debugf("Changed to new protocol. Current protocol is %s, current epoch is %s and current state is %s",
current.getUniqueProtocolName(), epoch, state);
}
}
/**
* check if the {@param reconfigurableProtocol} is the current replication protocol in use
*
* @param reconfigurableProtocol the replication protocol to check
* @return true if it is the current replication protocol, false otherwise
*/
public final synchronized boolean isCurrentProtocol(ReconfigurableProtocol reconfigurableProtocol) {
return reconfigurableProtocol == current || reconfigurableProtocol.equals(current);
}
/**
* atomically returns the current replication protocol and epoch
*
* @return the current replication protocol and epoch
*/
public final synchronized CurrentProtocolInfo getCurrentProtocolInfo() {
return new CurrentProtocolInfo(epoch, current, old, state);
}
/**
* returns when the current epoch is higher or equals than {@param epoch}, blocking until that condition is true
*
* @param epoch the epoch to be ensured
* @throws InterruptedException if it is interrupted while waiting
*/
public final synchronized void ensure(long epoch) throws InterruptedException {
if (log.isDebugEnabled()) {
log.debugf("[%s] will block until %s >= %s", Thread.currentThread().getName(), this.epoch, epoch);
}
while (this.epoch < epoch) {
this.wait();
}
if (log.isDebugEnabled()) {
log.debugf("[%s] epoch is the desired. Moving on...", Thread.currentThread().getName());
}
}
/**
* returns true if the current state is unsafe
*
* @return true if the current state is unsafe
*/
public final synchronized boolean isUnsafe() {
return state == State.UNSAFE;
}
/**
* returns true if the current state is switch in progress
*
* @return true if the current state is switch in progress
*/
public final synchronized boolean isInProgress() {
return state == State.IN_PROGRESS;
}
/**
* ensure that the switch is not in progress when the method returns
*
* @throws InterruptedException if interrupted while waiting for the switch to end
*/
public final synchronized void ensureNotInProgress() throws InterruptedException {
if (log.isDebugEnabled()) {
log.debugf("[%s] will wait until no switch is in progress", Thread.currentThread().getName());
}
while (isInProgress()) {
wait();
}
if (log.isDebugEnabled()) {
log.debugf("[%s] no switch in progress. Moving on...", Thread.currentThread().getName());
}
}
public final synchronized CurrentProtocolInfo startCommitTransaction(GlobalTransaction globalTransaction, WriteCommand[] writeSet,
ReconfigurableProtocol execProtocol, Transaction transaction) {
if (isCurrentProtocol(execProtocol)) {
execProtocol.commitTransaction(globalTransaction, writeSet, transaction);
} else {
execProtocol.commitTransaction(transaction);
current.addLocalTransaction(globalTransaction, writeSet);
}
return getCurrentProtocolInfo();
}
public final synchronized void addNumberOfTransactionsAborted(int val) {
if (currentStat != null) {
currentStat.addNumberOfTransactionAborted(val);
}
}
public final synchronized long getEpoch() {
return epoch;
}
/**
* the possible states
*/
private static enum State {
SAFE,
UNSAFE,
IN_PROGRESS
}
/**
* class used to atomically retrieve the current replication protocol and epoch
*/
public static class CurrentProtocolInfo {
private final long epoch;
private final ReconfigurableProtocol current, old;
private final State state;
public CurrentProtocolInfo(long epoch, ReconfigurableProtocol current, ReconfigurableProtocol old, State state) {
this.epoch = epoch;
this.current = current;
this.old = old;
this.state = state;
}
public final long getEpoch() {
return epoch;
}
public final ReconfigurableProtocol getCurrent() {
return current;
}
public final ReconfigurableProtocol getOld() {
return old;
}
public final boolean isUnsafe() {
return state == State.UNSAFE;
}
public final String printState() {
return state.name();
}
@Override
public final String toString() {
return "CurrentProtocolInfo{" +
"epoch=" + epoch +
", current=" + current +
", old=" + old +
", state=" + state +
'}';
}
}
}