package org.jdiameter.client.impl.fsm; /* * Copyright (c) 2006 jDiameter. * https://jdiameter.dev.java.net/ * * License: GPL v3 * * e-mail: erick.svenson@yahoo.com * */ import static org.jdiameter.client.impl.fsm.PeerFSMImpl.CIntState.DOWN; import static org.jdiameter.client.impl.fsm.PeerFSMImpl.CIntState.REOPEN; import static org.jdiameter.client.impl.helpers.Parameters.CeaTimeOut; import static org.jdiameter.client.impl.helpers.Parameters.DpaTimeOut; import static org.jdiameter.client.impl.helpers.Parameters.DwaTimeOut; import static org.jdiameter.client.impl.helpers.Parameters.IacTimeOut; import static org.jdiameter.client.impl.helpers.Parameters.QueueSize; import static org.jdiameter.client.impl.helpers.Parameters.RecTimeOut; import java.util.Random; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import org.jdiameter.api.Configuration; import org.jdiameter.api.OverloadException; import org.jdiameter.api.PeerState; import org.jdiameter.api.ResultCode; import org.jdiameter.api.app.State; import org.jdiameter.api.app.StateChangeListener; import org.jdiameter.api.app.StateEvent; import org.jdiameter.client.api.IMessage; import org.jdiameter.client.api.fsm.EventTypes; import org.jdiameter.client.api.fsm.ExecutorFactory; import org.jdiameter.client.api.fsm.FsmEvent; import org.jdiameter.client.api.fsm.IContext; import org.jdiameter.client.api.fsm.IStateMachine; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class PeerFSMImpl implements IStateMachine { protected Logger logger = LoggerFactory.getLogger(PeerFSMImpl.class); protected ConcurrentLinkedQueue<StateChangeListener> listeners; protected LinkedBlockingQueue<StateEvent> eventQueue; protected CIntState state = CIntState.DOWN; protected boolean watchdogSent; protected long timer; protected long CEA_TIMEOUT = 0, IAC_TIMEOUT = 0, REC_TIMEOUT = 0, DWA_TIMEOUT = 0, DPA_TIMEOUT = 0; protected final StateEvent timeOutEvent = new FsmEvent(EventTypes.TIMEOUT_EVENT); protected Random random = new Random(); protected ExecutorFactory executorFactory; protected ExecutorService executor; protected IContext context; protected State[] states; protected int predefSize; public static class CIntState { protected static int index; public static CIntState OKAY = new CIntState("OKAY", PeerState.OKAY); public static CIntState SUSPECT = new CIntState("SUSPECT",PeerState.SUSPECT); public static CIntState DOWN = new CIntState("DOWN",PeerState.DOWN); public static CIntState REOPEN = new CIntState("REOPEN",PeerState.REOPEN); public static CIntState INITIAL = new CIntState("INITIAL",PeerState.INITIAL); public static CIntState STOPPING = new CIntState("STOPPING",PeerState.DOWN, true); private String name; private int ordinal; private Enum publicState; private boolean isInternal; public CIntState(String name, Enum publicState) { this.name = name; this.publicState = publicState; this.ordinal = index++; } public CIntState(String name,Enum publicState, boolean isInternal) { this.name = name; this.publicState = publicState; this.isInternal = isInternal; this.ordinal = index++; } public int ordinal() { return ordinal; } public String name() { return name; } public Enum getPublicState() { return publicState; } public boolean isInternal() { return isInternal; } public String toString() { return name; } } public void removeStateChangeNotification(StateChangeListener stateChangeListener) { listeners.remove(stateChangeListener); } public PeerFSMImpl(IContext aContext, ExecutorFactory executorFactory, Configuration config) { context = aContext; predefSize = config.getIntValue( QueueSize.ordinal(), (Integer) QueueSize.defValue() ); eventQueue = new LinkedBlockingQueue<StateEvent>(predefSize); listeners = new ConcurrentLinkedQueue<StateChangeListener>(); loadTimeOuts(config); this.executorFactory = executorFactory; runQueueProcessing(executorFactory); } private void runQueueProcessing(ExecutorFactory executorFactory) { executorFactory.getExecutor().execute( new Runnable() { public void run() { while (true) { StateEvent event; try { event = eventQueue.poll(100, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { logger.debug("Peer fsm stopped"); break; } try { if (event != null) { logger.debug("Process event {}", event); getStates()[state.ordinal()].processEvent(event); } if (timer != 0 && timer < System.currentTimeMillis()) { timer = 0; handleEvent( timeOutEvent ); } } catch (Exception e) { logger.debug("Error during processing fsm event", e); } } } } ); } 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()); } public void addStateChangeNotification(StateChangeListener stateChangeListener) { if (listeners.contains(stateChangeListener)) return; listeners.add(stateChangeListener); } public void remStateChangeNotification(StateChangeListener stateChangeListener) { listeners.remove(stateChangeListener); } protected void swithToNextState(CIntState newState) { if (!newState.isInternal()) { for (StateChangeListener l : listeners) { l.stateChanged(state.getPublicState(), newState.getPublicState()); } } getStates()[newState.ordinal()].exitAction(); logger.debug("{} fsm swith state {} -> {}", new Object[] {context.getPeerDescription(), state, newState}); state = newState; getStates()[state.ordinal()].entryAction(); } public boolean handleEvent(StateEvent event) throws InternalError, OverloadException { if (state.getPublicState() == PeerState.DOWN && event.encodeType(EventTypes.class) == EventTypes.START_EVENT) { runQueueProcessing(executorFactory); } boolean rc; try { rc = eventQueue.offer(event, IAC_TIMEOUT, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { throw new InternalError("Can not put event to fsm " + this.toString()); } if ( !rc ) { throw new OverloadException("FSM overloaded"); } return true; } protected void setInActiveTimer() { timer = IAC_TIMEOUT - 2 * 1000 + random.nextInt(5) * 1000 + System.currentTimeMillis(); } public String toString() { return "PeerFSM{" + "context=" + context + ", state=" + state + '}'; } public <E> E getState(Class<E> a) { if (a == PeerState.class) { return (E) state.getPublicState(); } else { return null; } } protected abstract class MyState implements State { public void entryAction() {} public void exitAction() {} protected void doEndConnection() { if ( context.isRestoreConnection() ) { timer = REC_TIMEOUT + System.currentTimeMillis(); swithToNextState(REOPEN); } else { swithToNextState(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 State[] getStates() { if (states == null) { states = new State[] { // todo merge and redesign with server fsm new MyState() // OKEY { public void entryAction() { setInActiveTimer(); watchdogSent = false; } public boolean processEvent(StateEvent event) { switch (event.encodeType(EventTypes.class)) { case DISCONNECT_EVENT: timer = REC_TIMEOUT + System.currentTimeMillis(); swithToNextState(CIntState.REOPEN); break; case TIMEOUT_EVENT: try { context.sendDwrMessage(); setTimer(DWA_TIMEOUT); if (watchdogSent) swithToNextState(CIntState.SUSPECT); else watchdogSent = true; } catch (Throwable e) { logger.debug("Can not send DWR", e); doDisconnect(); setTimer(REC_TIMEOUT); swithToNextState(CIntState.REOPEN); } break; case STOP_EVENT: try { context.sendDprMessage(ResultCode.SUCCESS); setTimer(DPA_TIMEOUT); swithToNextState(CIntState.STOPPING); } catch (Throwable e) { logger.debug("Can not send DPR", e); doDisconnect(); swithToNextState(CIntState.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(); swithToNextState(CIntState.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(); swithToNextState(CIntState.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); swithToNextState(CIntState.REOPEN); } break; default: logger.debug("Unknown event type: {} in state {}", event.encodeType(EventTypes.class), state); return false; } return true; } }, new MyState() // SUSPECT { public boolean processEvent(StateEvent event) { switch (event.encodeType(EventTypes.class)) { case DISCONNECT_EVENT: setTimer(REC_TIMEOUT); swithToNextState(CIntState.REOPEN); break; case TIMEOUT_EVENT: doDisconnect(); setTimer(REC_TIMEOUT); swithToNextState(CIntState.REOPEN); break; case STOP_EVENT: try { context.sendDprMessage(ResultCode.SUCCESS); setInActiveTimer(); swithToNextState(CIntState.STOPPING); } catch (Throwable e) { logger.debug("Can not send DPR", e); doDisconnect(); swithToNextState(CIntState.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(); swithToNextState(CIntState.DOWN); break; case DWA_EVENT: swithToNextState(CIntState.OKAY); break; case DWR_EVENT: try { int code = context.processDwrMessage(message(event)); context.sendDwaMessage(message(event), code, null); swithToNextState(CIntState.OKAY); } catch (Throwable e) { logger.debug("Can not send DWA", e); doDisconnect(); swithToNextState(CIntState.DOWN); } break; case RECEIVE_MSG_EVENT: context.receiveMessage(message(event)); swithToNextState(CIntState.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 { public void entryAction() { clearTimer(); } public boolean processEvent(StateEvent event) { switch (event.encodeType(EventTypes.class)) { case START_EVENT: try { context.connect(); context.sendCerMessage(); setTimer(CEA_TIMEOUT); swithToNextState(CIntState.INITIAL); } catch (Throwable e) { logger.debug("Connect error", e); setTimer(REC_TIMEOUT); swithToNextState(CIntState.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 { public boolean processEvent(StateEvent event) { switch (event.encodeType(EventTypes.class)) { case CONNECT_EVENT: try { context.sendCerMessage(); setTimer(CEA_TIMEOUT); swithToNextState(CIntState.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(); swithToNextState(CIntState.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 { public void entryAction() { setTimer(CEA_TIMEOUT); } public boolean processEvent(StateEvent event) { switch (event.encodeType(EventTypes.class)) { case DISCONNECT_EVENT: setTimer(REC_TIMEOUT); swithToNextState(CIntState.REOPEN); break; case TIMEOUT_EVENT: doDisconnect(); setTimer(REC_TIMEOUT); swithToNextState(CIntState.REOPEN); break; case STOP_EVENT: clearTimer(); doDisconnect(); swithToNextState(CIntState.DOWN); break; case CEA_EVENT: clearTimer(); if (context.processCeaMessage(((FsmEvent) event).getKey(), ((FsmEvent) event).getMessage())) { swithToNextState(CIntState.OKAY); } else { doDisconnect(); setTimer(REC_TIMEOUT); swithToNextState(CIntState.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 { public boolean processEvent(StateEvent event) { switch (event.encodeType(EventTypes.class)) { case TIMEOUT_EVENT: case DPA_EVENT: doDisconnect(); swithToNextState(CIntState.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; } }