package com.zillabyte.motherbrain.flow.operations; import java.util.concurrent.TimeoutException; import org.eclipse.jdt.annotation.NonNull; import com.zillabyte.motherbrain.coordination.CoordinationException; import com.zillabyte.motherbrain.flow.Fields; import com.zillabyte.motherbrain.flow.FlowStateException; import com.zillabyte.motherbrain.flow.MapTuple; import com.zillabyte.motherbrain.flow.StateMachineException; import com.zillabyte.motherbrain.flow.StateMachineHelper; import com.zillabyte.motherbrain.flow.collectors.OutputCollector; import com.zillabyte.motherbrain.flow.error.strategies.FakeLocalException; import com.zillabyte.motherbrain.top.MotherbrainException; import com.zillabyte.motherbrain.universe.Universe; import com.zillabyte.motherbrain.utils.Log4jWrapper; import com.zillabyte.motherbrain.utils.SerializableMonitor; public abstract class Sink extends Operation implements ProcessableOperation { /** * Serialization ID */ private static final long serialVersionUID = 4166297414097397908L; final SerializableMonitor prepareMonitor; // Used to make sure any initialization in prepare only happens once. protected SinkState _state = SinkState.INITIAL; private Fields _columns; private Log4jWrapper _log = Log4jWrapper.create(Sink.class, this); public Sink(final String id) { super(id); prepareMonitor = new SerializableMonitor(); } public Fields relationFields() { return _columns; } protected abstract void process(@NonNull MapTuple t) throws OperationException, InterruptedException; @Override public void handleProcess(MapTuple t, OutputCollector c) throws Exception { this.handleProcess(t); } @Override public void prePrepare() throws InterruptedException, OperationException { try { transitionToState(SinkState.STARTING.toString(), true); } catch (StateMachineException | TimeoutException | CoordinationException e) { throw new OperationException(this, e); } } @Override public final void postPrepare() throws InterruptedException, OperationException { try { transitionToState(SinkState.STARTED.toString(), true); } catch (StateMachineException | TimeoutException | CoordinationException e) { throw new OperationException(this, e); } } /*** * * @param t * @throws InterruptedException * @throws OperationException * @throws OperationDeadException */ public final void handleProcess(final MapTuple t) throws InterruptedException, OperationException, OperationDeadException { try { switch(_state) { case STARTED: case IDLE: // tuple received when we were idle or finished starting.. transition to active transitionToState(SinkState.ACTIVE.toString(), true); // trickle case SUSPECT: // When we're in loop_error, we might as well keep consuming...restarting the instance will likely just produce more loop errors anyway case PAUSING: case ACTIVE: heartbeatErrorCheck_ThreadUnsafe(); // Logging if (_ipcLogBackoff.tick() || Universe.instance().env().isLocal()) { _operationLogger.writeLog("[sampled #" + _ipcLogBackoff.counter() +"] sinking tuple: " + t, OperationLogger.LogPriority.IPC); } // Ensure we're alive.. if (isAlive() == false) { throw new OperationDeadException(Sink.this, "The operation is not alive."); } // process incLoop(); incConsumed(); markBeginActivity(); try { process(t); } finally { markEndActivity(); } return; case STARTING: case INITIAL: case PAUSED: // All these states, we're just waiting for somebody to change us... heartbeatErrorCheck_ThreadUnsafe(); return; case KILLING: case KILLED: case ERROR: // New: Do nothing, this will trigger the flow to kill us return; default: // This should never be reached. throw new FlowStateException(Sink.this, "don't know how to handle state: " + _state); } } catch(InterruptedException e) { // Continue processing... } catch (TimeoutException | OperationException e) { handleLoopError(e); } catch (MotherbrainException e) { handleFatalError(e); } catch(FakeLocalException e) { e.printAndWait(); } return; } /*** * * @throws OperationException */ @Override public void handlePause() throws OperationException { // Sinks pause when they IDLE during the PAUSING state _log.warn("pausing sink"); try { if(!getState().equalsIgnoreCase("ERROR")) transitionToState("PAUSING"); } catch (StateMachineException | CoordinationException | TimeoutException e) { _log.warn("An error occured while trying to resume "+e.getMessage()); } } /*** * * @throws OperationException */ @Override public void handleResume() throws OperationException { // Resume the operation try { if(!getState().equalsIgnoreCase("ERROR")) transitionToState("ACTIVE"); } catch (StateMachineException | CoordinationException | TimeoutException e) { _log.warn("An error occured while trying to resume "+e.getMessage()); } } /*** * */ @Override public void handleIdleDetected() throws InterruptedException, OperationException { try { if (_state == SinkState.PAUSING) { transitionToState(SinkState.PAUSED.toString(), true); } else if (_state == SinkState.ACTIVE || _state == SinkState.SUSPECT || _state == SinkState.STARTED) { transitionToState(SinkState.IDLE.toString(), true); } } catch (StateMachineException | TimeoutException | CoordinationException e) { throw new OperationException(this, e); } } @Override public final String type() { return "sink"; } /*** * * @param newState * @param transactional * @throws CoordinationException * @throws TimeoutException * @throws StateMachineException */ public synchronized void transitionToState(SinkState newState, boolean transactional) throws CoordinationException, TimeoutException, StateMachineException { SinkState oldState = _state; _state = StateMachineHelper.transition(_state, newState); if(_state != oldState) notifyOfNewState(newState.toString(), transactional); } @Override public synchronized void transitionToState(String newState, boolean transactional) throws CoordinationException, TimeoutException, StateMachineException { transitionToState(SinkState.valueOf(newState), transactional); } @Override public String getState() { return _state.toString(); } }