Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.jdiameter.client.impl.fsm.PeerFSMImpl Maven / Gradle / Ivy
/*
* TeleStax, Open Source Cloud Communications
* Copyright 2011-2016, TeleStax Inc. and individual contributors
* by the @authors tag.
*
* This program is free software: you can redistribute it and/or modify
* under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation; either version 3 of
* the License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* JBoss, Home of Professional Open Source
* Copyright 2007-2011, Red Hat, Inc. and individual contributors
* by the @authors tag. 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.jdiameter.client.impl.fsm;
import static org.jdiameter.client.impl.fsm.FsmState.DOWN;
import static org.jdiameter.client.impl.fsm.FsmState.REOPEN;
import static org.jdiameter.client.impl.helpers.Parameters.*;
import java.util.Random;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.jdiameter.api.*;
import org.jdiameter.api.app.State;
import org.jdiameter.api.app.StateChangeListener;
import org.jdiameter.api.app.StateEvent;
import org.jdiameter.api.validation.AvpNotAllowedException;
import org.jdiameter.api.validation.Dictionary;
import org.jdiameter.client.api.IMessage;
import org.jdiameter.client.api.fsm.EventTypes;
import org.jdiameter.client.api.fsm.FsmEvent;
import org.jdiameter.client.api.fsm.IContext;
import org.jdiameter.client.api.fsm.IStateMachine;
import org.jdiameter.client.impl.DictionarySingleton;
import org.jdiameter.common.api.concurrent.IConcurrentFactory;
import org.jdiameter.common.api.statistic.IStatistic;
import org.jdiameter.common.api.statistic.IStatisticManager;
import org.jdiameter.common.api.statistic.IStatisticRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author [email protected]
* @author Alexandre Mendonca
* @author Bartosz Baranowski
*/
@SuppressWarnings("all") //3rd party lib
public class PeerFSMImpl implements IStateMachine {
private static final Logger logger = LoggerFactory.getLogger(PeerFSMImpl.class);
protected final Dictionary dictionary = DictionarySingleton.getDictionary();
protected ConcurrentLinkedQueue listeners;
protected LinkedBlockingQueue eventQueue;
protected FsmState state = FsmState.DOWN;
protected boolean watchdogSent;
protected long timer;
protected long CEA_TIMEOUT = 0, IAC_TIMEOUT = 0, REC_TIMEOUT = 0, DWA_TIMEOUT = 0, DPA_TIMEOUT = 0;
//PCB made FSM queue multi-threaded
private static int FSM_THREAD_COUNT = 3;
protected final StateEvent timeOutEvent = new FsmEvent(EventTypes.TIMEOUT_EVENT);
protected Random random = new Random();
protected IConcurrentFactory concurrentFactory;
protected IContext context;
protected State[] states;
protected int predefSize;
private Lock lock = new ReentrantLock();
protected IStatisticManager statisticFactory;
protected IStatistic queueStat;
protected IStatisticRecord timeSumm;
protected IStatisticRecord timeCount;
//PCB changed for multi-thread
protected boolean mustRun = false;
protected AtomicInteger numberOfThreadsRunning = new AtomicInteger(0);
public PeerFSMImpl(IContext aContext, IConcurrentFactory concurrentFactory, Configuration config,
IStatisticManager statisticFactory) {
this.context = aContext;
this.statisticFactory = statisticFactory;
this.predefSize = config.getIntValue(QueueSize.ordinal(), (Integer) QueueSize.defValue());
//PCB added logging
logger.debug("Maximum FSM Queue size is [{}]", predefSize);
this.eventQueue = new LinkedBlockingQueue(predefSize);
this.listeners = new ConcurrentLinkedQueue();
loadTimeOuts(config);
this.concurrentFactory = concurrentFactory;
FSM_THREAD_COUNT = config.getIntValue(PeerFSMThreadCount.ordinal(), (Integer) PeerFSMThreadCount.defValue());
runQueueProcessing();
}
@Override
public IStatistic getStatistic() {
//
return queueStat;
}
@Override
public void removeStateChangeNotification(StateChangeListener stateChangeListener) {
listeners.remove(stateChangeListener);
}
private void runQueueProcessing() {
try {
// PCB - changed way it decides if queue processing must happen in order to allow for multithreaded FSM
lock.lock();
if (numberOfThreadsRunning.get() > 0) {
// runQueueProcessing has been called
return;
}
eventQueue.clear();
mustRun = true;
IStatisticRecord queueSize = statisticFactory.newCounterRecord(IStatisticRecord.Counters.QueueSize,
new IStatisticRecord.IntegerValueHolder() {
@Override
public int getValueAsInt() {
return eventQueue.size();
}
@Override
public String getValueAsString() {
return String.valueOf(getValueAsInt());
}
});
this.timeSumm = statisticFactory.newCounterRecord("TimeSumm", "TimeSumm");
this.timeCount = statisticFactory.newCounterRecord("TimeCount", "TimeCount");
final IStatisticRecord messagePrcAverageTime = statisticFactory.newCounterRecord(
IStatisticRecord.Counters.MessageProcessingTime,
new IStatisticRecord.DoubleValueHolder() {
@Override
public double getValueAsDouble() {
if (queueStat == null) {
return 0;
}
IStatisticRecord mpta = queueStat
.getRecordByName(IStatisticRecord.Counters.MessageProcessingTime.name());
org.jdiameter.api.StatisticRecord[] children = mpta.getChilds();
if (children.length == 2 && children[1].getValueAsLong() != 0) {
long count = children[1].getValueAsLong();
return ((float) children[0].getValueAsLong()) / ((float) (count != 0 ? count : 1));
} else {
return 0;
}
}
@Override
public String getValueAsString() {
return String.valueOf(getValueAsDouble());
}
}, timeSumm, timeCount);
logger.debug("Initializing QueueStat @ Thread[{}]", Thread.currentThread().getName());
queueStat = statisticFactory.newStatistic(context.getPeerDescription(), IStatistic.Groups.PeerFSM, queueSize,
messagePrcAverageTime);
logger.debug("Finished Initializing QueueStat @ Thread[{}]", Thread.currentThread().getName());
Runnable fsmQueueProcessor = new Runnable() {
@Override
public void run() {
int runningNow = numberOfThreadsRunning.incrementAndGet();
logger.debug("Starting ... [{}] FSM threads are running", runningNow);
//PCB changed for multi-thread
while (mustRun) {
StateEvent event;
try {
event = eventQueue.poll(100, TimeUnit.MILLISECONDS);
if (logger.isDebugEnabled() && event != null) {
logger.debug("Got Event [{}] from Queue", event);
}
} catch (InterruptedException e) {
logger.debug("Peer FSM stopped", e);
break;
}
//FIXME: baranowb: why this lock is here?
// PCB removed lock
// lock.lock();
try {
if (event != null) {
if (event instanceof FsmEvent && queueStat != null && queueStat.isEnabled()) {
timeSumm.inc(System.currentTimeMillis() - ((FsmEvent) event).getCreatedTime());
timeCount.inc();
}
logger.debug("Process event [{}]. Peer State is [{}]", event, state);
getStates()[state.ordinal()].processEvent(event);
}
if (timer != 0 && timer < System.currentTimeMillis()) {
// ZhixiaoLuo: add lock here to avoid 2 timeout events at the same time if 2 threads get into timer=0
// ZhixiaoLuo: use double check strategy to avoid locking most normal cases
lock.lock();
try {
if (timer != 0 && timer < System.currentTimeMillis()) {
timer = 0;
if (state != DOWN) { //without this check this event is fired in DOWN state.... it should not be.
logger.debug("Sending timeout event");
handleEvent(timeOutEvent); //FIXME: check why timer is not killed?
}
}
} finally {
lock.unlock();
}
}
} catch (Exception e) {
logger.debug("Error during processing FSM event", e);
} finally {
// PCB removed lock
// lock.unlock();
}
}
//PCB added logging
logger.debug("FSM Thread {} is exiting", Thread.currentThread().getName());
//this happens when peer FSM is down, lets remove stat
statisticFactory.removeStatistic(queueStat);
logger.debug("Setting QueueStat to null @ Thread [{}]", Thread.currentThread().getName());
queueStat = null;
logger.debug("Done Setting QueueStat to null @ Thread [{}]", Thread.currentThread().getName());
int runningNowAfterStop = numberOfThreadsRunning.decrementAndGet();
logger.debug("Stopping ... [{}] FSM threads are running", runningNowAfterStop);
}
};
//PCB added FSM multithread
for (int i = 1; i <= FSM_THREAD_COUNT; i++) {
logger.debug("Starting FSM Thread {} of {}", i, FSM_THREAD_COUNT);
concurrentFactory.getThreadPool().execute(fsmQueueProcessor);
}
} finally {
lock.unlock();
}
}
@Override
public double getQueueInfo() {
return eventQueue.size() * 1.0 / predefSize;
}
protected void loadTimeOuts(Configuration config) {
CEA_TIMEOUT = config.getLongValue(CeaTimeOut.ordinal(), (Long) CeaTimeOut.defValue());
IAC_TIMEOUT = config.getLongValue(IacTimeOut.ordinal(), (Long) IacTimeOut.defValue());
DWA_TIMEOUT = config.getLongValue(DwaTimeOut.ordinal(), (Long) DwaTimeOut.defValue());
DPA_TIMEOUT = config.getLongValue(DpaTimeOut.ordinal(), (Long) DpaTimeOut.defValue());
REC_TIMEOUT = config.getLongValue(RecTimeOut.ordinal(), (Long) RecTimeOut.defValue());
}
@Override
public void addStateChangeNotification(StateChangeListener stateChangeListener) {
if (!listeners.contains(stateChangeListener)) {
listeners.add(stateChangeListener);
}
}
@Override
public void remStateChangeNotification(StateChangeListener stateChangeListener) {
listeners.remove(stateChangeListener);
}
protected void switchToNextState(FsmState newState) {
// Fix for Issue #3026 (http://code.google.com/p/mobicents/issues/detail?id=3026)
// notify only when it's a new public state
if (newState.getPublicState() != state.getPublicState()) {
for (StateChangeListener l : listeners) {
l.stateChanged(state.getPublicState(), newState.getPublicState());
}
}
getStates()[state.ordinal()].exitAction();
if (logger.isDebugEnabled()) {
logger.debug("{} FSM switch state: {} -> {}", new Object[] { context.getPeerDescription(), state, newState });
}
state = newState;
getStates()[state.ordinal()].entryAction();
}
@Override
public boolean handleEvent(StateEvent event) throws InternalError, OverloadException {
//if (state.getPublicState() == PeerState.DOWN && event.encodeType(EventTypes.class) == EventTypes.START_EVENT) {
if (logger.isDebugEnabled()) {
logger.debug("Handling event with type [{}]", event.getType());
}
//PCB added FSM multithread
if (numberOfThreadsRunning.get() == 0) {
logger.debug("No FSM threads are running so calling runQueueProcessing()");
runQueueProcessing();
}
if (event.getData() != null && dictionary != null && dictionary.isEnabled()) {
boolean incoming = event.getType() == EventTypes.RECEIVE_MSG_EVENT;
if (incoming) {
logger.debug("Performing validation to INCOMING message since validator is ENABLED.");
// outgoing are done elsewhere: see BaseSessionImpl
try {
dictionary.validate((Message) event.getData(), incoming);
} catch (AvpNotAllowedException e) {
logger.error("Failed to validate incoming message.", e);
return false;
}
}
} else {
logger.debug("Not performing validation to message since validator is DISABLED.");
}
boolean rc = false;
try {
if (logger.isDebugEnabled()) {
logger.debug("Placing event [{}] into linked blocking queue with remaining capacity: [{}].", event,
eventQueue.remainingCapacity());
//PCB added logging
//int queueSize = eventQueue.size();
//if (System.currentTimeMillis() - lastLogged > 1000) {
// lastLogged = System.currentTimeMillis();
// if (queueSize >= 5) {
// logger.debug("Diameter Event Queue size is [{}]", queueSize);
// }
//}
}
rc = eventQueue.offer(event, IAC_TIMEOUT, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
logger.debug("Can not put event '" + event.toString() + "' to FSM " + this.toString(), e);
throw new InternalError("Can not put event '" + event.toString() + "' to FSM " + this.toString());
}
if (!rc) {
throw new OverloadException("FSM overloaded");
}
return true;
}
//PCB added logging
//private static long lastLogged;
protected void setInActiveTimer() {
timer = IAC_TIMEOUT - 2 * 1000 + random.nextInt(5) * 1000 + System.currentTimeMillis();
}
@Override
public String toString() {
return "PeerFSM{" + "context=" + context + ", state=" + state + '}';
}
@Override
public E getState(Class a) {
if (a == PeerState.class) {
return (E) state.getPublicState();
} else {
return null;
}
}
protected abstract class MyState implements org.jdiameter.api.app.State {
@Override
public void entryAction() {
}
@Override
public void exitAction() {
}
protected void doEndConnection() {
if (context.isRestoreConnection()) {
timer = REC_TIMEOUT + System.currentTimeMillis();
switchToNextState(REOPEN);
} else {
switchToNextState(DOWN);
}
}
protected void doDisconnect() {
try {
context.disconnect();
} catch (Throwable e) {
}
}
protected void setTimer(long value) {
timer = value + System.currentTimeMillis();
}
protected String key(StateEvent event) {
return ((FsmEvent) event).getKey();
}
protected IMessage message(StateEvent event) {
return ((FsmEvent) event).getMessage();
}
protected EventTypes type(StateEvent event) {
return (EventTypes) event.getType();
}
protected void clearTimer() {
timer = 0;
}
}
protected org.jdiameter.api.app.State[] getStates() {
if (states == null) {
states = new org.jdiameter.api.app.State[] { // todo merge and redesign with server fsm
new MyState() { // OKEY
@Override
public void entryAction() {
setInActiveTimer();
watchdogSent = false;
}
@Override
public boolean processEvent(StateEvent event) {
switch (event.encodeType(EventTypes.class)) {
case DISCONNECT_EVENT:
timer = REC_TIMEOUT + System.currentTimeMillis();
switchToNextState(FsmState.REOPEN);
break;
case TIMEOUT_EVENT:
try {
context.sendDwrMessage();
setTimer(DWA_TIMEOUT);
if (watchdogSent) {
switchToNextState(FsmState.SUSPECT);
} else {
watchdogSent = true;
}
} catch (Throwable e) {
logger.debug("Can not send DWR", e);
doDisconnect();
setTimer(REC_TIMEOUT);
switchToNextState(FsmState.REOPEN);
}
break;
case STOP_EVENT:
try {
if (event.getData() == null) {
context.sendDprMessage(DisconnectCause.REBOOTING);
} else {
Integer disconnectCause = (Integer) event.getData();
context.sendDprMessage(disconnectCause);
}
setTimer(DPA_TIMEOUT);
switchToNextState(FsmState.STOPPING);
} catch (Throwable e) {
logger.debug("Can not send DPR", e);
doDisconnect();
switchToNextState(FsmState.DOWN);
}
break;
case RECEIVE_MSG_EVENT:
setInActiveTimer();
context.receiveMessage(message(event));
break;
case DPR_EVENT:
try {
int code = context.processDprMessage(message(event));
context.sendDpaMessage(message(event), code, null);
} catch (Throwable e) {
logger.debug("Can not send DPA", e);
}
doDisconnect();
switchToNextState(FsmState.DOWN);
break;
case DWR_EVENT:
setInActiveTimer();
try {
int code = context.processDwrMessage(message(event));
context.sendDwaMessage(message(event), code, null);
} catch (Throwable e) {
logger.debug("Can not send DWA", e);
doDisconnect();
switchToNextState(FsmState.DOWN);
}
break;
case DWA_EVENT:
setInActiveTimer();
watchdogSent = false;
break;
case SEND_MSG_EVENT:
try {
context.sendMessage(message(event));
} catch (Throwable e) {
logger.debug("Can not send message", e);
doDisconnect();
setTimer(REC_TIMEOUT);
switchToNextState(FsmState.REOPEN);
}
break;
default:
logger.debug("Unknown event type: {} in state {}", event.encodeType(EventTypes.class),
state);
return false;
}
return true;
}
},
new MyState() { // SUSPECT
@Override
public boolean processEvent(StateEvent event) {
switch (event.encodeType(EventTypes.class)) {
case DISCONNECT_EVENT:
setTimer(REC_TIMEOUT);
switchToNextState(FsmState.REOPEN);
break;
case TIMEOUT_EVENT:
doDisconnect();
setTimer(REC_TIMEOUT);
switchToNextState(FsmState.REOPEN);
break;
case STOP_EVENT:
try {
if (event.getData() == null) {
context.sendDprMessage(DisconnectCause.REBOOTING);
} else {
Integer disconnectCause = (Integer) event.getData();
context.sendDprMessage(disconnectCause);
}
setInActiveTimer();
switchToNextState(FsmState.STOPPING);
} catch (Throwable e) {
logger.debug("Can not send DPR", e);
doDisconnect();
switchToNextState(FsmState.DOWN);
}
break;
case DPR_EVENT:
try {
int code = context.processDprMessage(message(event));
context.sendDpaMessage(message(event), code, null);
} catch (Throwable e) {
logger.debug("Can not send DPA", e);
}
doDisconnect();
switchToNextState(FsmState.DOWN);
break;
case DWA_EVENT:
switchToNextState(FsmState.OKAY);
break;
case DWR_EVENT:
try {
int code = context.processDwrMessage(message(event));
context.sendDwaMessage(message(event), code, null);
switchToNextState(FsmState.OKAY);
} catch (Throwable e) {
logger.debug("Can not send DWA", e);
doDisconnect();
switchToNextState(FsmState.DOWN);
}
break;
case RECEIVE_MSG_EVENT:
context.receiveMessage(message(event));
switchToNextState(FsmState.OKAY);
break;
case SEND_MSG_EVENT:
throw new RuntimeException("Connection is down");
default:
logger.debug("Unknown event type: {} in state {}", event.encodeType(EventTypes.class),
state);
return false;
}
return true;
}
},
new MyState() { // DOWN
@Override
public void entryAction() {
clearTimer();
//PCB changed multithread FSM
logger.debug("Setting mustRun to false @ Thread [{}]", Thread.currentThread().getName());
mustRun = false;
logger.debug("Finished Setting mustRun to false @ Thread [{}]", Thread.currentThread().getName());
context.removeStatistics();
}
@Override
public boolean processEvent(StateEvent event) {
switch (event.encodeType(EventTypes.class)) {
case START_EVENT:
try {
context.createStatistics();
context.connect();
context.sendCerMessage();
setTimer(CEA_TIMEOUT);
switchToNextState(FsmState.INITIAL);
} catch (Throwable e) {
logger.debug("Connect error", e);
setTimer(REC_TIMEOUT);
switchToNextState(FsmState.REOPEN);
}
break;
case SEND_MSG_EVENT:
throw new RuntimeException("Connection is down");
case STOP_EVENT:
case DISCONNECT_EVENT:
break;
default:
logger.debug("Unknown event type: {} in state {}", event.encodeType(EventTypes.class),
state);
return false;
}
return true;
}
},
new MyState() { // REOPEN
@Override
public boolean processEvent(StateEvent event) {
switch (event.encodeType(EventTypes.class)) {
case CONNECT_EVENT:
try {
context.sendCerMessage();
setTimer(CEA_TIMEOUT);
switchToNextState(FsmState.INITIAL);
} catch (Throwable e) {
logger.debug("Can not send CER", e);
setTimer(REC_TIMEOUT);
}
break;
case TIMEOUT_EVENT:
try {
context.connect();
} catch (Exception e) {
logger.debug("Timeout processed. Can not connect to {}", context.getPeerDescription());
setTimer(REC_TIMEOUT);
}
break;
case STOP_EVENT:
clearTimer();
doDisconnect();
switchToNextState(FsmState.DOWN);
break;
case DISCONNECT_EVENT:
break;
case SEND_MSG_EVENT:
throw new IllegalStateException("Connection is down");
default:
logger.debug("Unknown event type: {} in state {}", event.encodeType(EventTypes.class),
state);
return false;
}
return true;
}
},
new MyState() { // INITIAL
@Override
public void entryAction() {
setTimer(CEA_TIMEOUT);
}
@Override
public boolean processEvent(StateEvent event) {
switch (event.encodeType(EventTypes.class)) {
case DISCONNECT_EVENT:
setTimer(REC_TIMEOUT);
switchToNextState(FsmState.REOPEN);
break;
case TIMEOUT_EVENT:
doDisconnect();
setTimer(REC_TIMEOUT);
switchToNextState(FsmState.REOPEN);
break;
case STOP_EVENT:
clearTimer();
doDisconnect();
switchToNextState(FsmState.DOWN);
break;
case CEA_EVENT:
clearTimer();
if (context.processCeaMessage(((FsmEvent) event).getKey(),
((FsmEvent) event).getMessage())) {
switchToNextState(FsmState.OKAY);
} else {
doDisconnect();
setTimer(REC_TIMEOUT);
switchToNextState(FsmState.REOPEN);
}
break;
case SEND_MSG_EVENT:
throw new RuntimeException("Connection is down");
default:
logger.debug("Unknown event type: {} in state {}", event.encodeType(EventTypes.class),
state);
return false;
}
return true;
}
},
new MyState() { // STOPPING
@Override
public boolean processEvent(StateEvent event) {
switch (event.encodeType(EventTypes.class)) {
case TIMEOUT_EVENT:
case DPA_EVENT:
doDisconnect();
switchToNextState(FsmState.DOWN);
break;
case RECEIVE_MSG_EVENT:
context.receiveMessage(message(event));
break;
case SEND_MSG_EVENT:
throw new RuntimeException("Stack now is stopping");
case STOP_EVENT:
case DISCONNECT_EVENT:
doDisconnect();
break;
default:
logger.debug("Unknown event type: {} in state {}", event.encodeType(EventTypes.class),
state);
return false;
}
return true;
}
},
};
}
return states;
}
}