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.
/*
* 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 org.apache.kafka.streams.processor.internals;
import org.apache.kafka.clients.consumer.Consumer;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.OffsetAndMetadata;
import org.apache.kafka.common.KafkaException;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.errors.TimeoutException;
import org.apache.kafka.common.header.internals.RecordHeaders;
import org.apache.kafka.common.metrics.Sensor;
import org.apache.kafka.common.utils.LogContext;
import org.apache.kafka.common.utils.Time;
import org.apache.kafka.streams.errors.DeserializationExceptionHandler;
import org.apache.kafka.streams.errors.LockException;
import org.apache.kafka.streams.errors.StreamsException;
import org.apache.kafka.streams.errors.TaskCorruptedException;
import org.apache.kafka.streams.errors.TaskMigratedException;
import org.apache.kafka.streams.errors.TopologyException;
import org.apache.kafka.streams.processor.Cancellable;
import org.apache.kafka.streams.processor.PunctuationType;
import org.apache.kafka.streams.processor.Punctuator;
import org.apache.kafka.streams.processor.TaskId;
import org.apache.kafka.streams.processor.TimestampExtractor;
import org.apache.kafka.streams.processor.api.Record;
import org.apache.kafka.streams.processor.internals.AbstractPartitionGroup.RecordInfo;
import org.apache.kafka.streams.processor.internals.metrics.ProcessorNodeMetrics;
import org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl;
import org.apache.kafka.streams.processor.internals.metrics.TaskMetrics;
import org.apache.kafka.streams.processor.internals.metrics.ThreadMetrics;
import org.apache.kafka.streams.TopologyConfig.TaskConfig;
import org.apache.kafka.streams.state.internals.ThreadCache;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import static java.util.Collections.singleton;
import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.maybeRecordSensor;
import static org.apache.kafka.streams.processor.internals.metrics.StreamsMetricsImpl.maybeMeasureLatency;
/**
* A StreamTask is associated with a {@link AbstractPartitionGroup}, and is assigned to a StreamThread for processing.
*/
public class StreamTask extends AbstractTask implements ProcessorNodePunctuator, Task {
private final Time time;
private final Consumer mainConsumer;
// we want to abstract eos logic out of StreamTask, however
// there's still an optimization that requires this info to be
// leaked into this class, which is to checkpoint after committing if EOS is not enabled.
private final boolean eosEnabled;
private final int maxBufferedSize;
private final AbstractPartitionGroup partitionGroup;
private final RecordCollector recordCollector;
private final AbstractPartitionGroup.RecordInfo recordInfo;
private final Map consumedOffsets;
private final Map committedOffsets;
private final Map highWatermark;
private final Set resetOffsetsForPartitions;
private final Set partitionsToResume;
private final PunctuationQueue streamTimePunctuationQueue;
private final PunctuationQueue systemTimePunctuationQueue;
private final StreamsMetricsImpl streamsMetrics;
private long processTimeMs = 0L;
private final Sensor closeTaskSensor;
private final Sensor processRatioSensor;
private final Sensor processLatencySensor;
private final Sensor restoreSensor;
private final Sensor restoreRemainingSensor;
private final Sensor punctuateLatencySensor;
private final Sensor bufferedRecordsSensor;
private final Map e2eLatencySensors = new HashMap<>();
private final RecordQueueCreator recordQueueCreator;
@SuppressWarnings("rawtypes")
protected final InternalProcessorContext processorContext;
private StampedRecord record;
private boolean commitNeeded = false;
private boolean commitRequested = false;
private boolean hasPendingTxCommit = false;
private Optional timeCurrentIdlingStarted;
@SuppressWarnings({"rawtypes", "this-escape", "checkstyle:ParameterNumber"})
public StreamTask(final TaskId id,
final Set inputPartitions,
final ProcessorTopology topology,
final Consumer mainConsumer,
final TaskConfig config,
final StreamsMetricsImpl streamsMetrics,
final StateDirectory stateDirectory,
final ThreadCache cache,
final Time time,
final ProcessorStateManager stateMgr,
final RecordCollector recordCollector,
final InternalProcessorContext processorContext,
final LogContext logContext,
final boolean processingThreadsEnabled
) {
super(
id,
topology,
stateDirectory,
stateMgr,
inputPartitions,
config,
"task",
StreamTask.class
);
this.mainConsumer = mainConsumer;
this.processorContext = processorContext;
processorContext.transitionToActive(this, recordCollector, cache);
this.time = time;
this.recordCollector = recordCollector;
this.eosEnabled = config.eosEnabled;
final String threadId = Thread.currentThread().getName();
this.streamsMetrics = streamsMetrics;
closeTaskSensor = ThreadMetrics.closeTaskSensor(threadId, streamsMetrics);
final String taskId = id.toString();
restoreSensor = TaskMetrics.restoreSensor(threadId, taskId, streamsMetrics);
restoreRemainingSensor = TaskMetrics.restoreRemainingRecordsSensor(threadId, taskId, streamsMetrics);
processRatioSensor = TaskMetrics.activeProcessRatioSensor(threadId, taskId, streamsMetrics);
processLatencySensor = TaskMetrics.processLatencySensor(threadId, taskId, streamsMetrics);
punctuateLatencySensor = TaskMetrics.punctuateSensor(threadId, taskId, streamsMetrics);
bufferedRecordsSensor = TaskMetrics.activeBufferedRecordsSensor(threadId, taskId, streamsMetrics);
for (final String terminalNodeName : topology.terminalNodes()) {
e2eLatencySensors.put(
terminalNodeName,
ProcessorNodeMetrics.e2ELatencySensor(threadId, taskId, terminalNodeName, streamsMetrics)
);
}
for (final ProcessorNode sourceNode : topology.sources()) {
final String sourceNodeName = sourceNode.name();
e2eLatencySensors.put(
sourceNodeName,
ProcessorNodeMetrics.e2ELatencySensor(threadId, taskId, sourceNodeName, streamsMetrics)
);
}
streamTimePunctuationQueue = new PunctuationQueue();
systemTimePunctuationQueue = new PunctuationQueue();
maxBufferedSize = config.maxBufferedSize;
// initialize the consumed and committed offset cache
consumedOffsets = new HashMap<>();
resetOffsetsForPartitions = new HashSet<>();
partitionsToResume = new HashSet<>();
recordQueueCreator = new RecordQueueCreator(this.logContext, config.timestampExtractor, config.deserializationExceptionHandler);
recordInfo = new RecordInfo();
final Sensor enforcedProcessingSensor;
enforcedProcessingSensor = TaskMetrics.enforcedProcessingSensor(threadId, taskId, streamsMetrics);
final long maxTaskIdleMs = config.maxTaskIdleMs;
if (processingThreadsEnabled) {
partitionGroup = new SynchronizedPartitionGroup(new PartitionGroup(
logContext,
createPartitionQueues(),
mainConsumer::currentLag,
TaskMetrics.recordLatenessSensor(threadId, taskId, streamsMetrics),
enforcedProcessingSensor,
maxTaskIdleMs
));
} else {
partitionGroup = new PartitionGroup(
logContext,
createPartitionQueues(),
mainConsumer::currentLag,
TaskMetrics.recordLatenessSensor(threadId, taskId, streamsMetrics),
enforcedProcessingSensor,
maxTaskIdleMs
);
}
stateMgr.registerGlobalStateStores(topology.globalStateStores());
committedOffsets = new HashMap<>();
highWatermark = new HashMap<>();
for (final TopicPartition topicPartition: inputPartitions) {
committedOffsets.put(topicPartition, -1L);
highWatermark.put(topicPartition, -1L);
}
timeCurrentIdlingStarted = Optional.empty();
}
// create queues for each assigned partition and associate them
// to corresponding source nodes in the processor topology
private Map createPartitionQueues() {
final Map partitionQueues = new HashMap<>();
for (final TopicPartition partition : inputPartitions()) {
partitionQueues.put(partition, recordQueueCreator.createQueue(partition));
}
return partitionQueues;
}
@Override
public boolean isActive() {
return true;
}
@Override
public void recordRestoration(final Time time, final long numRecords, final boolean initRemaining) {
if (initRemaining) {
maybeRecordSensor(numRecords, time, restoreRemainingSensor);
} else {
maybeRecordSensor(numRecords, time, restoreSensor);
maybeRecordSensor(-1 * numRecords, time, restoreRemainingSensor);
}
}
/**
* @throws TaskCorruptedException if the state cannot be reused (with EOS) and needs to be reset
* @throws LockException could happen when multi-threads within the single instance, could retry
* @throws TimeoutException if initializing record collector timed out
* @throws StreamsException fatal error, should close the thread
*/
@Override
public void initializeIfNeeded() {
if (state() == State.CREATED) {
recordCollector.initialize();
StateManagerUtil.registerStateStores(log, logPrefix, topology, stateMgr, stateDirectory, processorContext);
// without EOS the checkpoint file would not be deleted after loading, and
// with EOS we would not checkpoint ever during running state anyways.
// therefore we can initialize the snapshot as empty so that we would checkpoint right after loading
offsetSnapshotSinceLastFlush = Collections.emptyMap();
transitionTo(State.RESTORING);
log.info("Initialized");
}
}
public void addPartitionsForOffsetReset(final Set partitionsForOffsetReset) {
mainConsumer.pause(partitionsForOffsetReset);
resetOffsetsForPartitions.addAll(partitionsForOffsetReset);
}
/**
* @throws TimeoutException if fetching committed offsets timed out
*/
@Override
public void completeRestoration(final java.util.function.Consumer> offsetResetter) {
switch (state()) {
case RUNNING:
return;
case RESTORING:
resetOffsetsIfNeededAndInitializeMetadata(offsetResetter);
initializeTopology();
processorContext.initialize();
if (!eosEnabled) {
maybeCheckpoint(true);
}
transitionTo(State.RUNNING);
log.info("Restored and ready to run");
break;
case CREATED:
case SUSPENDED:
case CLOSED:
throw new IllegalStateException("Illegal state " + state() + " while completing restoration for active task " + id);
default:
throw new IllegalStateException("Unknown state " + state() + " while completing restoration for active task " + id);
}
}
@Override
public void suspend() {
switch (state()) {
case CREATED:
transitToSuspend();
break;
case RESTORING:
transitToSuspend();
break;
case RUNNING:
try {
// use try-catch to ensure state transition to SUSPENDED even if user code throws in `Processor#close()`
closeTopology();
// we must clear the buffered records when suspending because upon resuming the consumer would
// re-fetch those records starting from the committed position
partitionGroup.clear();
} finally {
transitToSuspend();
}
break;
case SUSPENDED:
log.info("Skip suspending since state is {}", state());
break;
case CLOSED:
throw new IllegalStateException("Illegal state " + state() + " while suspending active task " + id);
default:
throw new IllegalStateException("Unknown state " + state() + " while suspending active task " + id);
}
}
@SuppressWarnings("unchecked")
private void closeTopology() {
log.trace("Closing processor topology");
// close the processors
// make sure close() is called for each node even when there is a RuntimeException
RuntimeException exception = null;
for (final ProcessorNode node : topology.processors()) {
processorContext.setCurrentNode(node);
try {
node.close();
} catch (final RuntimeException e) {
exception = e;
} finally {
processorContext.setCurrentNode(null);
}
}
if (exception != null) {
throw exception;
}
}
/**
*
* - resume the task
*
*/
@Override
public void resume() {
switch (state()) {
case CREATED:
case RUNNING:
case RESTORING:
// no need to do anything, just let them continue running / restoring / closing
log.trace("Skip resuming since state is {}", state());
break;
case SUSPENDED:
// just transit the state without any logical changes: suspended and restoring states
// are not actually any different for inner modules
// Deleting checkpoint file before transition to RESTORING state (KAFKA-10362)
try {
stateMgr.deleteCheckPointFileIfEOSEnabled();
log.debug("Deleted check point file upon resuming with EOS enabled");
} catch (final IOException ioe) {
log.error("Encountered error while deleting the checkpoint file due to this exception", ioe);
}
transitionTo(State.RESTORING);
log.info("Resumed to restoring state");
break;
case CLOSED:
throw new IllegalStateException("Illegal state " + state() + " while resuming active task " + id);
default:
throw new IllegalStateException("Unknown state " + state() + " while resuming active task " + id);
}
timeCurrentIdlingStarted = Optional.empty();
}
public void flush() {
stateMgr.flushCache();
recordCollector.flush();
}
/**
* @throws StreamsException fatal error that should cause the thread to die
* @throws TaskMigratedException recoverable error that would cause the task to be removed
* @return offsets that should be committed for this task
*/
@Override
public Map prepareCommit() {
switch (state()) {
case CREATED:
case RESTORING:
case RUNNING:
case SUSPENDED:
// the commitNeeded flag just indicates whether we have reached RUNNING and processed any new data,
// so it only indicates whether the record collector should be flushed or not, whereas the state
// manager should always be flushed; either there is newly restored data or the flush will be a no-op
if (commitNeeded) {
// we need to flush the store caches before flushing the record collector since it may cause some
// cached records to be processed and hence generate more records to be sent out
//
// TODO: this should be removed after we decouple caching with emitting
flush();
hasPendingTxCommit = eosEnabled;
log.debug("Prepared {} task for committing", state());
return committableOffsetsAndMetadata();
} else {
log.debug("Skipped preparing {} task for commit since there is nothing to commit", state());
return Collections.emptyMap();
}
case CLOSED:
throw new IllegalStateException("Illegal state " + state() + " while preparing active task " + id + " for committing");
default:
throw new IllegalStateException("Unknown state " + state() + " while preparing active task " + id + " for committing");
}
}
private Long findOffset(final TopicPartition partition) {
Long offset = partitionGroup.headRecordOffset(partition);
if (offset == null) {
try {
offset = mainConsumer.position(partition);
} catch (final TimeoutException error) {
// the `consumer.position()` call should never block, because we know that we did process data
// for the requested partition and thus the consumer should have a valid local position
// that it can return immediately
// hence, a `TimeoutException` indicates a bug and thus we rethrow it as fatal `IllegalStateException`
throw new IllegalStateException(error);
} catch (final KafkaException fatal) {
throw new StreamsException(fatal);
}
}
return offset;
}
private Map committableOffsetsAndMetadata() {
final Map committableOffsets;
switch (state()) {
case CREATED:
case RESTORING:
committableOffsets = Collections.emptyMap();
break;
case RUNNING:
case SUSPENDED:
final Map partitionTimes = extractPartitionTimes();
// If there's processor metadata to be committed. We need to commit them to all
// input partitions
final Set partitionsNeedCommit = processorContext.getProcessorMetadata().needsCommit() ?
inputPartitions() : consumedOffsets.keySet();
committableOffsets = new HashMap<>(partitionsNeedCommit.size());
for (final TopicPartition partition : partitionsNeedCommit) {
final Long offset = findOffset(partition);
final long partitionTime = partitionTimes.get(partition);
committableOffsets.put(partition, new OffsetAndMetadata(offset,
new TopicPartitionMetadata(partitionTime, processorContext.getProcessorMetadata()).encode()));
}
break;
case CLOSED:
throw new IllegalStateException("Illegal state " + state() + " while getting committable offsets for active task " + id);
default:
throw new IllegalStateException("Unknown state " + state() + " while post committing active task " + id);
}
return committableOffsets;
}
@Override
public void postCommit(final boolean enforceCheckpoint) {
switch (state()) {
case CREATED:
// We should never write a checkpoint for a CREATED task as we may overwrite an existing checkpoint
// with empty uninitialized offsets
log.debug("Skipped writing checkpoint for {} task", state());
break;
case RESTORING:
case SUSPENDED:
maybeCheckpoint(enforceCheckpoint);
log.debug("Finalized commit for {} task with enforce checkpoint {}", state(), enforceCheckpoint);
break;
case RUNNING:
if (enforceCheckpoint || !eosEnabled) {
maybeCheckpoint(enforceCheckpoint);
}
log.debug("Finalized commit for {} task with eos {} enforce checkpoint {}", state(), eosEnabled, enforceCheckpoint);
break;
case CLOSED:
throw new IllegalStateException("Illegal state " + state() + " while post committing active task " + id);
default:
throw new IllegalStateException("Unknown state " + state() + " while post committing active task " + id);
}
clearCommitStatuses();
}
private void clearCommitStatuses() {
commitNeeded = false;
commitRequested = false;
hasPendingTxCommit = false;
processorContext.getProcessorMetadata().setNeedsCommit(false);
}
private Map extractPartitionTimes() {
final Map partitionTimes = new HashMap<>();
for (final TopicPartition partition : partitionGroup.partitions()) {
partitionTimes.put(partition, partitionGroup.partitionTimestamp(partition));
}
return partitionTimes;
}
@Override
public void closeClean() {
validateClean();
removeAllSensors();
clearCommitStatuses();
close(true);
log.info("Closed clean");
}
@Override
public void closeDirty() {
removeAllSensors();
clearCommitStatuses();
close(false);
log.info("Closed dirty");
}
@Override
public void updateInputPartitions(final Set topicPartitions, final Map> allTopologyNodesToSourceTopics) {
super.updateInputPartitions(topicPartitions, allTopologyNodesToSourceTopics);
partitionGroup.updatePartitions(topicPartitions, recordQueueCreator::createQueue);
processorContext.getProcessorMetadata().setNeedsCommit(true);
}
@Override
public void prepareRecycle() {
validateClean();
removeAllSensors();
clearCommitStatuses();
switch (state()) {
case SUSPENDED:
stateMgr.recycle();
partitionGroup.close();
recordCollector.closeClean();
break;
case CREATED:
case RESTORING:
case RUNNING:
case CLOSED:
throw new IllegalStateException("Illegal state " + state() + " while recycling active task " + id);
default:
throw new IllegalStateException("Unknown state " + state() + " while recycling active task " + id);
}
closeTaskSensor.record();
transitionTo(State.CLOSED);
log.info("Closed and recycled state");
}
@Override
public void resumePollingForPartitionsWithAvailableSpace() {
if (!partitionsToResume.isEmpty()) {
mainConsumer.resume(partitionsToResume);
partitionsToResume.clear();
}
}
@Override
public void updateLags() {
if (state() == State.RUNNING) {
partitionGroup.updateLags();
}
}
/**
* The following exceptions maybe thrown from the state manager flushing call
*
* @throws TaskMigratedException recoverable error sending changelog records that would cause the task to be removed
* @throws StreamsException fatal error when flushing the state store, for example sending changelog records failed
* or flushing state store get IO errors; such error should cause the thread to die
*/
@Override
public void maybeCheckpoint(final boolean enforceCheckpoint) {
// commitNeeded indicates we may have processed some records since last commit
// and hence we need to refresh checkpointable offsets regardless whether we should checkpoint or not
if (commitNeeded || enforceCheckpoint) {
stateMgr.updateChangelogOffsets(checkpointableOffsets());
}
super.maybeCheckpoint(enforceCheckpoint);
}
private void validateClean() {
// It may be that we failed to commit a task during handleRevocation, but "forgot" this and tried to
// closeClean in handleAssignment. We should throw if we detect this to force the TaskManager to closeDirty
if (commitNeeded) {
log.debug("Tried to close clean but there was pending uncommitted data, this means we failed to"
+ " commit and should close as dirty instead");
throw new TaskMigratedException("Tried to close dirty task as clean");
}
}
private void removeAllSensors() {
streamsMetrics.removeAllTaskLevelSensors(Thread.currentThread().getName(), id.toString());
for (final String nodeName : e2eLatencySensors.keySet()) {
streamsMetrics.removeAllNodeLevelSensors(Thread.currentThread().getName(), id.toString(), nodeName);
}
}
/**
* You must commit a task and checkpoint the state manager before closing as this will release the state dir lock
*/
private void close(final boolean clean) {
switch (state()) {
case SUSPENDED:
TaskManager.executeAndMaybeSwallow(
clean,
partitionGroup::close,
"partition group close",
log
);
// first close state manager (which is idempotent) then close the record collector
// if the latter throws and we re-close dirty which would close the state manager again.
TaskManager.executeAndMaybeSwallow(
clean,
() -> StateManagerUtil.closeStateManager(
log,
logPrefix,
clean,
eosEnabled,
stateMgr,
stateDirectory,
TaskType.ACTIVE
),
"state manager close",
log);
TaskManager.executeAndMaybeSwallow(
clean,
clean ? recordCollector::closeClean : recordCollector::closeDirty,
"record collector close",
log
);
break;
case CLOSED:
log.trace("Skip closing since state is {}", state());
return;
case CREATED:
case RESTORING:
case RUNNING:
throw new IllegalStateException("Illegal state " + state() + " while closing active task " + id);
default:
throw new IllegalStateException("Unknown state " + state() + " while closing active task " + id);
}
record = null;
closeTaskSensor.record();
partitionsToResume.clear();
transitionTo(State.CLOSED);
}
/**
* An active task is processable if its buffer contains data for all of its input
* source topic partitions, or if it is enforced to be processable.
*/
public boolean isProcessable(final long wallClockTime) {
if (state() == State.CLOSED) {
// a task is only closing / closed when 1) task manager is closing, 2) a rebalance is undergoing;
// in either case we can just log it and move on without notifying the thread since the consumer
// would soon be updated to not return any records for this task anymore.
log.info("Stream task {} is already in {} state, skip processing it.", id(), state());
return false;
}
if (hasPendingTxCommit) {
// if the task has a pending TX commit, we should just retry the commit but not process any records
// thus, the task is not processable, even if there is available data in the record queue
return false;
}
final boolean readyToProcess = partitionGroup.readyToProcess(wallClockTime);
if (!readyToProcess) {
if (!timeCurrentIdlingStarted.isPresent()) {
timeCurrentIdlingStarted = Optional.of(wallClockTime);
}
} else {
timeCurrentIdlingStarted = Optional.empty();
}
return readyToProcess;
}
/**
* Process one record.
*
* @return true if this method processes a record, false if it does not process a record.
* @throws TaskMigratedException if the task producer got fenced (EOS only)
*/
@SuppressWarnings("unchecked")
public boolean process(final long wallClockTime) {
if (record == null) {
if (!isProcessable(wallClockTime)) {
return false;
}
// get the next record to process
record = partitionGroup.nextRecord(recordInfo, wallClockTime);
// if there is no record to process, return immediately
if (record == null) {
return false;
}
}
try {
final TopicPartition partition = recordInfo.partition();
if (!(record instanceof CorruptedRecord)) {
doProcess(wallClockTime);
}
// update the consumed offset map after processing is done
consumedOffsets.put(partition, record.offset());
commitNeeded = true;
// after processing this record, if its partition queue's buffered size has been
// decreased to the threshold, we can then resume the consumption on this partition
if (recordInfo.queue().size() == maxBufferedSize) {
partitionsToResume.add(partition);
}
record = null;
} catch (final TimeoutException timeoutException) {
if (!eosEnabled) {
throw timeoutException;
} else {
record = null;
throw new TaskCorruptedException(Collections.singleton(id));
}
} catch (final StreamsException exception) {
record = null;
throw exception;
} catch (final RuntimeException e) {
final StreamsException error = new StreamsException(
String.format(
"Exception caught in process. taskId=%s, processor=%s, topic=%s, partition=%d, offset=%d, stacktrace=%s",
id(),
processorContext.currentNode().name(),
record.topic(),
record.partition(),
record.offset(),
getStacktraceString(e)
),
e
);
record = null;
throw error;
} finally {
processorContext.setCurrentNode(null);
}
return true;
}
@SuppressWarnings("unchecked")
private void doProcess(final long wallClockTime) {
// process the record by passing to the source node of the topology
final ProcessorNode