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.
com.datatorrent.stram.engine.GenericNode Maven / Gradle / Ivy
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.datatorrent.stram.engine;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.commons.lang.UnhandledException;
import com.google.common.base.Throwables;
import com.datatorrent.api.Operator;
import com.datatorrent.api.Operator.IdleTimeHandler;
import com.datatorrent.api.Operator.InputPort;
import com.datatorrent.api.Operator.ProcessingMode;
import com.datatorrent.api.Operator.ShutdownException;
import com.datatorrent.api.Sink;
import com.datatorrent.api.annotation.Stateless;
import com.datatorrent.bufferserver.packet.MessageType;
import com.datatorrent.bufferserver.util.Codec;
import com.datatorrent.stram.api.StreamingContainerUmbilicalProtocol.ContainerStats;
import com.datatorrent.stram.debug.TappedReservoir;
import com.datatorrent.stram.plan.logical.LogicalPlan;
import com.datatorrent.stram.plan.logical.Operators;
import com.datatorrent.stram.tuple.ResetWindowTuple;
import com.datatorrent.stram.tuple.Tuple;
/**
* The base class for node implementation
*
* Implements the base interface {@link com.datatorrent.stram.engine.Node}
*
* This is the basic functional block of the DAG. It is responsible for the following
* It emits and consumes tuples
* Upon window boundary it does house cleaning, state sync up etc
* Interacts with Stram with a heartbeat protocol
*
*
* @since 0.3.2
*/
public class GenericNode extends Node
{
protected final HashMap inputs = new HashMap<>();
protected ArrayList deferredInputConnections = new ArrayList<>();
@Override
@SuppressWarnings("unchecked")
public void addSinks(Map> sinks)
{
for (Entry> e : sinks.entrySet()) {
SweepableReservoir original = inputs.get(e.getKey());
if (original instanceof TappedReservoir) {
TappedReservoir tr = (TappedReservoir)original;
tr.add(e.getValue());
} else if (original != null) {
TappedReservoir tr = new TappedReservoir(original, e.getValue());
inputs.put(e.getKey(), tr);
}
}
super.addSinks(sinks);
}
@Override
public void removeSinks(Map> sinks)
{
for (Entry> e : sinks.entrySet()) {
SweepableReservoir reservoir = inputs.get(e.getKey());
if (reservoir instanceof TappedReservoir) {
TappedReservoir tr = (TappedReservoir)reservoir;
tr.remove(e.getValue());
if (tr.getSinks().length == 0) {
tr.reservoir.setSink(tr.setSink(null));
inputs.put(e.getKey(), tr.reservoir);
}
}
}
super.removeSinks(sinks);
}
public GenericNode(Operator operator, OperatorContext context)
{
super(operator, context);
}
@SuppressWarnings("unchecked")
public InputPort getInputPort(String port)
{
return (InputPort)descriptor.inputPorts.get(port).component;
}
@Override
public void connectInputPort(String port, final SweepableReservoir reservoir)
{
if (reservoir == null) {
throw new IllegalArgumentException("Reservoir cannot be null for port '" + port + "' on operator '" + operator + "'");
}
InputPort inputPort = getInputPort(port);
if (inputPort == null) {
throw new IllegalArgumentException("Port '" + port + "' does not exist on operator '" + operator + "'");
}
if (inputs.containsKey(port)) {
deferredInputConnections.add(new DeferredInputConnection(port, reservoir));
} else {
inputPort.setConnected(true);
inputs.put(port, reservoir);
reservoir.setSink(inputPort.getSink());
}
}
/**
* @param endWindowTuple the value of endWindowTuple
*/
protected void processEndWindow(Tuple endWindowTuple)
{
if (++applicationWindowCount == APPLICATION_WINDOW_COUNT) {
insideWindow = false;
operator.endWindow();
applicationWindowCount = 0;
}
endWindowEmitTime = System.currentTimeMillis();
if (endWindowTuple == null) {
emitEndWindow();
} else {
for (int s = sinks.length; s-- > 0; ) {
sinks[s].put(endWindowTuple);
}
controlTupleCount++;
}
if (doCheckpoint) {
dagCheckpointOffsetCount = (dagCheckpointOffsetCount + 1) % DAG_CHECKPOINT_WINDOW_COUNT;
}
if (++checkpointWindowCount == CHECKPOINT_WINDOW_COUNT) {
checkpointWindowCount = 0;
if (doCheckpoint) {
checkpoint(currentWindowId);
lastCheckpointWindowId = currentWindowId;
doCheckpoint = false;
} else if (PROCESSING_MODE == ProcessingMode.EXACTLY_ONCE) {
checkpoint(currentWindowId);
lastCheckpointWindowId = currentWindowId;
}
}
ContainerStats.OperatorStats stats = new ContainerStats.OperatorStats();
reportStats(stats, currentWindowId);
if (!insideWindow) {
stats.metrics = collectMetrics();
}
handleRequests(currentWindowId);
}
class TupleTracker
{
final Tuple tuple;
SweepableReservoir[] ports;
TupleTracker(Tuple base, int count)
{
tuple = base;
ports = new SweepableReservoir[count];
}
}
boolean insideWindow;
boolean doCheckpoint;
long lastCheckpointWindowId = Stateless.WINDOW_ID;
@Override
public void activate()
{
super.activate();
insideWindow = applicationWindowCount != 0;
}
private boolean isInputPortConnectedToDelayOperator(String portName)
{
Operators.PortContextPair> pcPair = descriptor.inputPorts.get(portName);
if (pcPair == null || pcPair.context == null) {
return false;
}
return pcPair.context.getValue(LogicalPlan.IS_CONNECTED_TO_DELAY_OPERATOR);
}
/**
* Originally this method was defined in an attempt to implement the interface Runnable.
*
* Note that activate does not return as long as there is useful workload for the node.
*/
@Override
@SuppressWarnings({"SleepWhileInLoop", "UseSpecificCatch", "BroadCatchBlock", "TooBroadCatch"})
public final void run()
{
doCheckpoint = false;
final long maxSpinMillis = context.getValue(OperatorContext.SPIN_MILLIS);
long spinMillis = 0;
final boolean handleIdleTime = operator instanceof IdleTimeHandler;
int totalQueues = inputs.size();
int regularQueues = totalQueues;
// regularQueues is the number of queues that are not connected to a DelayOperator
for (String portName : inputs.keySet()) {
if (isInputPortConnectedToDelayOperator(portName)) {
regularQueues--;
}
}
ArrayList> activeQueues = new ArrayList<>();
activeQueues.addAll(inputs.entrySet());
int expectingBeginWindow = activeQueues.size();
int receivedEndWindow = 0;
long firstWindowId = -1;
calculateNextCheckpointWindow();
TupleTracker tracker;
LinkedList resetTupleTracker = new LinkedList<>();
try {
do {
Iterator> buffers = activeQueues.iterator();
activequeue:
while (buffers.hasNext()) {
Map.Entry activePortEntry = buffers.next();
SweepableReservoir activePort = activePortEntry.getValue();
Tuple t = activePort.sweep();
if (t != null) {
spinMillis = 0;
boolean delay = (operator instanceof Operator.DelayOperator);
long windowAhead = 0;
if (delay) {
windowAhead = WindowGenerator.getAheadWindowId(t.getWindowId(), firstWindowMillis, windowWidthMillis, 1);
}
switch (t.getType()) {
case BEGIN_WINDOW:
if (expectingBeginWindow == totalQueues) {
// This is the first begin window tuple among all ports
if (isInputPortConnectedToDelayOperator(activePortEntry.getKey())) {
// We need to wait for the first BEGIN_WINDOW from a port not connected to DelayOperator before
// we can do anything with it, because otherwise if a CHECKPOINT tuple arrives from
// upstream after the BEGIN_WINDOW tuple for the next window from the delay operator, it would end
// up checkpointing in the middle of the window. This code is assuming we have at least one
// input port that is not connected to a DelayOperator, and we might have to change this later.
// In the future, this condition will not be needed if we get rid of the CHECKPOINT tuple.
continue;
}
activePort.remove();
expectingBeginWindow--;
receivedEndWindow = 0;
currentWindowId = t.getWindowId();
if (delay) {
if (WindowGenerator.getBaseSecondsFromWindowId(windowAhead) > t.getBaseSeconds()) {
// Buffer server code strips out the base seconds from BEGIN_WINDOW and END_WINDOW tuples for
// serialization optimization. That's why we need a reset window here to tell the buffer
// server we are having a new baseSeconds now.
Tuple resetWindowTuple = new ResetWindowTuple(windowAhead);
for (int s = sinks.length; s-- > 0; ) {
sinks[s].put(resetWindowTuple);
}
controlTupleCount++;
}
t.setWindowId(windowAhead);
}
for (int s = sinks.length; s-- > 0; ) {
sinks[s].put(t);
}
controlTupleCount++;
context.setWindowsFromCheckpoint(nextCheckpointWindowCount--);
if (applicationWindowCount == 0) {
insideWindow = true;
operator.beginWindow(currentWindowId);
}
} else if (t.getWindowId() == currentWindowId) {
activePort.remove();
expectingBeginWindow--;
} else {
buffers.remove();
String port = activePortEntry.getKey();
if (PROCESSING_MODE == ProcessingMode.AT_MOST_ONCE) {
if (t.getWindowId() < currentWindowId) {
/*
* we need to fast forward this stream till we find the current
* window or the window which is bigger than the current window.
*/
/* lets move the current reservoir in the background */
Sink sink = activePort.setSink(Sink.BLACKHOLE);
deferredInputConnections.add(0, new DeferredInputConnection(port, activePort));
/* replace it with the reservoir which blocks the tuples in the past */
WindowIdActivatedReservoir wiar = new WindowIdActivatedReservoir(port, activePort, currentWindowId);
wiar.setSink(sink);
inputs.put(port, wiar);
activeQueues.add(new AbstractMap.SimpleEntry(port, wiar));
break activequeue;
} else {
expectingBeginWindow--;
if (++receivedEndWindow == totalQueues) {
processEndWindow(null);
activeQueues.addAll(inputs.entrySet());
expectingBeginWindow = activeQueues.size();
break activequeue;
}
}
} else {
logger.error("Catastrophic Error: Out of sequence {} tuple {} on port {} while expecting {}", t.getType(), Codec.getStringWindowId(t.getWindowId()), port, Codec.getStringWindowId(currentWindowId));
System.exit(2);
}
}
break;
case END_WINDOW:
buffers.remove();
if (t.getWindowId() == currentWindowId) {
activePort.remove();
endWindowDequeueTimes.put(activePort, System.currentTimeMillis());
if (++receivedEndWindow == totalQueues) {
assert (activeQueues.isEmpty());
if (delay) {
t.setWindowId(windowAhead);
}
processEndWindow(t);
activeQueues.addAll(inputs.entrySet());
expectingBeginWindow = activeQueues.size();
break activequeue;
}
}
break;
case CHECKPOINT:
activePort.remove();
long checkpointWindow = t.getWindowId();
if (lastCheckpointWindowId < checkpointWindow) {
dagCheckpointOffsetCount = 0;
if (PROCESSING_MODE == ProcessingMode.EXACTLY_ONCE) {
lastCheckpointWindowId = checkpointWindow;
} else if (!doCheckpoint) {
if (checkpointWindowCount == 0) {
checkpoint(checkpointWindow);
lastCheckpointWindowId = checkpointWindow;
} else {
doCheckpoint = true;
}
}
if (!delay) {
for (int s = sinks.length; s-- > 0; ) {
sinks[s].put(t);
}
controlTupleCount++;
}
}
break;
case RESET_WINDOW:
/**
* we will receive tuples which are equal to the number of input streams.
*/
activePort.remove();
if (isInputPortConnectedToDelayOperator(activePortEntry.getKey())) {
break; // breaking out of the switch/case
}
buffers.remove();
int baseSeconds = t.getBaseSeconds();
tracker = null;
for (Iterator trackerIterator = resetTupleTracker.iterator(); trackerIterator.hasNext(); ) {
tracker = trackerIterator.next();
if (tracker.tuple.getBaseSeconds() == baseSeconds) {
break;
}
}
if (tracker == null) {
tracker = new TupleTracker(t, regularQueues);
resetTupleTracker.add(tracker);
}
int trackerIndex = 0;
while (trackerIndex < tracker.ports.length) {
if (tracker.ports[trackerIndex] == null) {
tracker.ports[trackerIndex++] = activePort;
break;
} else if (tracker.ports[trackerIndex] == activePort) {
break;
}
trackerIndex++;
}
if (trackerIndex == regularQueues) {
Iterator trackerIterator = resetTupleTracker.iterator();
while (trackerIterator.hasNext()) {
if (trackerIterator.next().tuple.getBaseSeconds() <= baseSeconds) {
trackerIterator.remove();
}
}
if (!delay) {
for (int s = sinks.length; s-- > 0; ) {
sinks[s].put(t);
}
controlTupleCount++;
}
if (!activeQueues.isEmpty()) {
// make sure they are all queues from DelayOperator
for (Map.Entry entry : activeQueues) {
if (!isInputPortConnectedToDelayOperator(entry.getKey())) {
assert (false);
}
}
activeQueues.clear();
}
activeQueues.addAll(inputs.entrySet());
expectingBeginWindow = activeQueues.size();
if (firstWindowId == -1) {
if (delay) {
for (int s = sinks.length; s-- > 0; ) {
sinks[s].put(t);
}
controlTupleCount++;
// if it's a DelayOperator and this is the first RESET_WINDOW (start) or END_STREAM
// (recovery), fabricate the first window
fabricateFirstWindow((Operator.DelayOperator)operator, windowAhead);
}
firstWindowId = t.getWindowId();
}
break activequeue;
}
break;
case END_STREAM:
activePort.remove();
buffers.remove();
if (firstWindowId == -1) {
// this is for recovery from a checkpoint for DelayOperator
if (delay) {
// if it's a DelayOperator and this is the first RESET_WINDOW (start) or END_STREAM (recovery),
// fabricate the first window
fabricateFirstWindow((Operator.DelayOperator)operator, windowAhead);
}
firstWindowId = t.getWindowId();
}
for (Iterator> it = inputs.entrySet().iterator(); it.hasNext(); ) {
Entry e = it.next();
if (e.getValue() == activePort) {
if (!descriptor.inputPorts.isEmpty()) {
descriptor.inputPorts.get(e.getKey()).component.setConnected(false);
}
it.remove();
/* check the deferred connection list for any new port that should be connected here */
Iterator dici = deferredInputConnections.iterator();
while (dici.hasNext()) {
DeferredInputConnection dic = dici.next();
if (e.getKey().equals(dic.portname)) {
connectInputPort(dic.portname, dic.reservoir);
dici.remove();
activeQueues.add(new AbstractMap.SimpleEntry<>(dic.portname, dic.reservoir));
break activequeue;
}
}
break;
}
}
/**
* We are not going to receive begin window on this ever!
*/
expectingBeginWindow--;
/**
* Since one of the operators we care about it gone, we should relook at our ports.
* We need to make sure that the END_STREAM comes outside of the window.
*/
regularQueues--;
totalQueues--;
boolean break_activequeue = false;
if (regularQueues == 0) {
alive = false;
break_activequeue = true;
} else if (activeQueues.isEmpty()) {
assert (!inputs.isEmpty());
processEndWindow(null);
activeQueues.addAll(inputs.entrySet());
expectingBeginWindow = activeQueues.size();
break_activequeue = true;
}
/**
* also make sure that we update the reset tuple tracker if this stream had delivered any reset tuples.
* Check all the reset buffers to see if current input port has already delivered reset tuple. If it has
* then we are waiting for something else to deliver the reset tuple, so just clear current reservoir
* from the list of tracked reservoirs. If the current input port has not delivered the reset tuple, and
* it's the only one which has not, then we consider it delivered and release the reset tuple downstream.
*/
Tuple tuple = null;
for (Iterator trackerIterator = resetTupleTracker.iterator(); trackerIterator.hasNext(); ) {
tracker = trackerIterator.next();
trackerIndex = 0;
while (trackerIndex < tracker.ports.length) {
if (tracker.ports[trackerIndex] == activePort) {
SweepableReservoir[] ports = new SweepableReservoir[regularQueues];
System.arraycopy(tracker.ports, 0, ports, 0, trackerIndex);
if (trackerIndex < regularQueues) {
System.arraycopy(tracker.ports, trackerIndex + 1, ports, trackerIndex, tracker.ports.length - trackerIndex - 1);
}
tracker.ports = ports;
break;
} else if (tracker.ports[trackerIndex] == null) {
if (trackerIndex == regularQueues) { /* regularQueues is already adjusted above */
if (tuple == null || tuple.getBaseSeconds() < tracker.tuple.getBaseSeconds()) {
tuple = tracker.tuple;
}
trackerIterator.remove();
}
break;
} else {
tracker.ports = Arrays.copyOf(tracker.ports, regularQueues);
}
trackerIndex++;
}
}
/*
* Since we were waiting for a reset tuple on this stream, we should not any longer.
*/
if (tuple != null && !delay) {
for (int s = sinks.length; s-- > 0; ) {
sinks[s].put(tuple);
}
controlTupleCount++;
}
if (break_activequeue) {
break activequeue;
}
break;
default:
throw new UnhandledException("Unrecognized Control Tuple", new IllegalArgumentException(t.toString()));
}
}
}
if (activeQueues.isEmpty() && alive) {
logger.error("Catastrophic Error: Invalid State - the operator blocked forever!");
System.exit(2);
} else {
boolean need2sleep = true;
for (Map.Entry cb : activeQueues) {
need2sleep = cb.getValue().isEmpty();
if (!need2sleep) {
spinMillis = 0;
break;
}
}
if (need2sleep) {
if (handleIdleTime && insideWindow) {
((IdleTimeHandler)operator).handleIdleTime();
} else {
Thread.sleep(spinMillis);
spinMillis = Math.min(maxSpinMillis, spinMillis + 1);
}
}
}
} while (alive);
} catch (ShutdownException se) {
logger.debug("Shutdown requested by the operator when alive = {}.", alive);
alive = false;
} catch (Throwable cause) {
synchronized (this) {
if (alive) {
throw Throwables.propagate(cause);
}
}
Throwable rootCause = cause;
while (rootCause != null) {
if (rootCause instanceof InterruptedException) {
break;
}
rootCause = rootCause.getCause();
}
if (rootCause == null) {
throw Throwables.propagate(cause);
} else {
logger.debug("Ignoring InterruptedException after shutdown", cause);
}
}
/**
* TODO: If shutdown and inside window provide alternate way of notifying the operator in such ways
* TODO: as using a listener callback
*/
if (insideWindow && !shutdown) {
operator.endWindow();
endWindowEmitTime = System.currentTimeMillis();
if (++applicationWindowCount == APPLICATION_WINDOW_COUNT) {
applicationWindowCount = 0;
}
if (++checkpointWindowCount == CHECKPOINT_WINDOW_COUNT) {
checkpointWindowCount = 0;
if (doCheckpoint || PROCESSING_MODE == ProcessingMode.EXACTLY_ONCE) {
checkpoint(currentWindowId);
}
}
ContainerStats.OperatorStats stats = new ContainerStats.OperatorStats();
fixEndWindowDequeueTimesBeforeDeactivate();
reportStats(stats, currentWindowId);
stats.metrics = collectMetrics();
handleRequests(currentWindowId);
}
}
private void fabricateFirstWindow(Operator.DelayOperator delayOperator, long windowAhead)
{
Tuple beginWindowTuple = new Tuple(MessageType.BEGIN_WINDOW, windowAhead);
Tuple endWindowTuple = new Tuple(MessageType.END_WINDOW, windowAhead);
for (Sink sink : outputs.values()) {
sink.put(beginWindowTuple);
}
controlTupleCount++;
delayOperator.firstWindow();
for (Sink sink : outputs.values()) {
sink.put(endWindowTuple);
}
controlTupleCount++;
}
/**
* End window dequeue times may not have been saved for all the input ports during deactivate,
* so save them for reporting. SPOI-1324.
*/
private void fixEndWindowDequeueTimesBeforeDeactivate()
{
long endWindowDequeueTime = System.currentTimeMillis();
for (SweepableReservoir sr : inputs.values()) {
if (endWindowDequeueTimes.get(sr) == null) {
endWindowDequeueTimes.put(sr, endWindowDequeueTime);
}
}
}
@Override
protected void reportStats(ContainerStats.OperatorStats stats, long windowId)
{
ArrayList ipstats = new ArrayList<>();
for (Entry e : inputs.entrySet()) {
SweepableReservoir ar = e.getValue();
ContainerStats.OperatorStats.PortStats portStats = new ContainerStats.OperatorStats.PortStats(e.getKey());
portStats.queueSize = ar.size(DATA_TUPLE_AWARE);
portStats.tupleCount = ar.getCount(true);
portStats.endWindowTimestamp = endWindowDequeueTimes.get(e.getValue());
ipstats.add(portStats);
}
stats.inputPorts = ipstats;
super.reportStats(stats, windowId);
}
protected class DeferredInputConnection
{
String portname;
SweepableReservoir reservoir;
DeferredInputConnection(String portname, SweepableReservoir reservoir)
{
this.portname = portname;
this.reservoir = reservoir;
}
}
private static final Logger logger = LoggerFactory.getLogger(GenericNode.class);
}