// Copyright 2016 Twitter. All rights reserved.
//
// Licensed 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.twitter.heron.instance.bolt;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import com.twitter.heron.api.Config;
import com.twitter.heron.api.bolt.IBolt;
import com.twitter.heron.api.bolt.OutputCollector;
import com.twitter.heron.api.generated.TopologyAPI;
import com.twitter.heron.api.metric.GlobalMetrics;
import com.twitter.heron.api.serializer.IPluggableSerializer;
import com.twitter.heron.api.topology.IUpdatable;
import com.twitter.heron.api.utils.Utils;
import com.twitter.heron.common.basics.Communicator;
import com.twitter.heron.common.basics.SingletonRegistry;
import com.twitter.heron.common.basics.SlaveLooper;
import com.twitter.heron.common.basics.TypeUtils;
import com.twitter.heron.common.config.SystemConfig;
import com.twitter.heron.common.utils.metrics.BoltMetrics;
import com.twitter.heron.common.utils.metrics.FullBoltMetrics;
import com.twitter.heron.common.utils.misc.PhysicalPlanHelper;
import com.twitter.heron.common.utils.misc.SerializeDeSerializeHelper;
import com.twitter.heron.common.utils.topology.TopologyContextImpl;
import com.twitter.heron.common.utils.tuple.TickTuple;
import com.twitter.heron.common.utils.tuple.TupleImpl;
import com.twitter.heron.instance.IInstance;
import com.twitter.heron.proto.system.HeronTuples;
public class BoltInstance implements IInstance {
protected PhysicalPlanHelper helper;
protected final IBolt bolt;
protected final BoltOutputCollectorImpl collector;
protected final IPluggableSerializer serializer;
protected final BoltMetrics boltMetrics;
// The bolt will read Data tuples from streamInQueue
private final Communicator<HeronTuples.HeronTupleSet> streamInQueue;
private final SlaveLooper looper;
private final SystemConfig systemConfig;
public BoltInstance(PhysicalPlanHelper helper,
Communicator<HeronTuples.HeronTupleSet> streamInQueue,
Communicator<HeronTuples.HeronTupleSet> streamOutQueue,
SlaveLooper looper) {
this.helper = helper;
this.looper = looper;
this.streamInQueue = streamInQueue;
this.boltMetrics = new FullBoltMetrics();
this.boltMetrics.initMultiCountMetrics(helper);
this.serializer =
SerializeDeSerializeHelper.getSerializer(helper.getTopologyContext().getTopologyConfig());
this.systemConfig = (SystemConfig) SingletonRegistry.INSTANCE.getSingleton(
SystemConfig.HERON_SYSTEM_CONFIG);
if (helper.getMyBolt() == null) {
throw new RuntimeException("HeronBoltInstance has no bolt in physical plan.");
}
// Get the bolt. Notice, in fact, we will always use the deserialization way to get bolt.
if (helper.getMyBolt().getComp().hasSerializedObject()) {
bolt = (IBolt) Utils.deserialize(
helper.getMyBolt().getComp().getSerializedObject().toByteArray());
} else if (helper.getMyBolt().getComp().hasClassName()) {
try {
String boltClassName = helper.getMyBolt().getComp().getClassName();
bolt = (IBolt) Class.forName(boltClassName).newInstance();
} catch (ClassNotFoundException ex) {
throw new RuntimeException(ex + " Bolt class must be in class path.");
} catch (InstantiationException ex) {
throw new RuntimeException(ex + " Bolt class must be concrete.");
} catch (IllegalAccessException ex) {
throw new RuntimeException(ex + " Bolt class must have a no-arg constructor.");
}
} else {
throw new RuntimeException("Neither java_object nor java_class_name set for bolt");
}
collector = new BoltOutputCollectorImpl(serializer, helper, streamOutQueue, boltMetrics);
}
@Override
public void update(PhysicalPlanHelper physicalPlanHelper) {
if (bolt instanceof IUpdatable) {
((IUpdatable) bolt).update(physicalPlanHelper.getTopologyContext());
}
collector.updatePhysicalPlanHelper(physicalPlanHelper);
// Re-prepare the CustomStreamGrouping since the downstream tasks can change
physicalPlanHelper.prepareForCustomStreamGrouping();
// Reset the helper
helper = physicalPlanHelper;
}
@Override
public void start() {
TopologyContextImpl topologyContext = helper.getTopologyContext();
// Initialize the GlobalMetrics
GlobalMetrics.init(topologyContext, systemConfig.getHeronMetricsExportInterval());
boltMetrics.registerMetrics(topologyContext);
// Delegate
bolt.prepare(
topologyContext.getTopologyConfig(), topologyContext, new OutputCollector(collector));
// Invoke user-defined prepare task hook
topologyContext.invokeHookPrepare();
// Init the CustomStreamGrouping
helper.prepareForCustomStreamGrouping();
addBoltTasks();
}
@Override
public void stop() {
// Invoke clean up hook before clean() is called
helper.getTopologyContext().invokeHookCleanup();
// Delegate to user-defined clean-up method
bolt.cleanup();
// Clean the resources we own
looper.exitLoop();
streamInQueue.clear();
collector.clear();
}
private void addBoltTasks() {
// add the readTupleAndExecute() to tasks
Runnable boltTasks = new Runnable() {
@Override
public void run() {
// Back-pressure -- only when we could send out tuples will we read & execute tuples
if (collector.isOutQueuesAvailable()) {
readTuplesAndExecute(streamInQueue);
// Though we may execute MAX_READ tuples, finally we will packet it as
// one outgoingPacket and push to out queues
collector.sendOutTuples();
// Here we need to inform the Gateway
} else {
boltMetrics.updateOutQueueFullCount();
}
// If there are more to read, we will wake up itself next time when it doWait()
if (collector.isOutQueuesAvailable() && !streamInQueue.isEmpty()) {
looper.wakeUp();
}
}
};
looper.addTasksOnWakeup(boltTasks);
PrepareTickTupleTimer();
}
@Override
public void readTuplesAndExecute(Communicator<HeronTuples.HeronTupleSet> inQueue) {
TopologyContextImpl topologyContext = helper.getTopologyContext();
Duration instanceExecuteBatchTime = systemConfig.getInstanceExecuteBatchTime();
long startOfCycle = System.nanoTime();
// Read data from in Queues
while (!inQueue.isEmpty()) {
HeronTuples.HeronTupleSet tuples = inQueue.poll();
// Handle the tuples
if (tuples.hasControl()) {
throw new RuntimeException("Bolt cannot get acks/fails from other components");
}
// Get meta data of tuples
TopologyAPI.StreamId stream = tuples.getData().getStream();
int nValues = topologyContext.getComponentOutputFields(
stream.getComponentName(), stream.getId()).size();
// We would reuse the System.nanoTime()
long currentTime = startOfCycle;
for (HeronTuples.HeronDataTuple dataTuple : tuples.getData().getTuplesList()) {
// Create the value list and fill the value
List<Object> values = new ArrayList<>(nValues);
for (int i = 0; i < nValues; i++) {
values.add(serializer.deserialize(dataTuple.getValues(i).toByteArray()));
}
// Decode the tuple
TupleImpl t = new TupleImpl(topologyContext, stream, dataTuple.getKey(),
dataTuple.getRootsList(), values, currentTime, false);
// Delegate to the use defined bolt
bolt.execute(t);
// Swap
long startTime = currentTime;
currentTime = System.nanoTime();
long executeLatency = currentTime - startTime;
// Invoke user-defined execute task hook
topologyContext.invokeHookBoltExecute(t, Duration.ofNanos(executeLatency));
// Update metrics
boltMetrics.executeTuple(stream.getId(), stream.getComponentName(), executeLatency);
}
// To avoid spending too much time
if (currentTime - startOfCycle - instanceExecuteBatchTime.toNanos() > 0) {
break;
}
}
}
@Override
public void activate() {
}
@Override
public void deactivate() {
}
private void PrepareTickTupleTimer() {
Object tickTupleFreqSecs =
helper.getTopologyContext().getTopologyConfig().get(Config.TOPOLOGY_TICK_TUPLE_FREQ_SECS);
if (tickTupleFreqSecs != null) {
Duration freq = TypeUtils.getDuration(tickTupleFreqSecs, ChronoUnit.SECONDS);
Runnable r = new Runnable() {
public void run() {
SendTickTuple();
}
};
looper.registerTimerEvent(freq, r);
}
}
private void SendTickTuple() {
TickTuple t = new TickTuple();
long startTime = System.nanoTime();
bolt.execute(t);
long latency = System.nanoTime() - startTime;
boltMetrics.executeTuple(t.getSourceStreamId(), t.getSourceComponent(), latency);
collector.sendOutTuples();
// reschedule ourselves again
PrepareTickTupleTimer();
}
}