/***********************************************************************************************************************
* Copyright (C) 2010-2013 by the Stratosphere project (http://stratosphere.eu)
*
* 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 eu.stratosphere.compiler.deadlockdetect;
import java.util.ArrayList;
import java.util.List;
import eu.stratosphere.compiler.plan.BulkIterationPlanNode;
import eu.stratosphere.compiler.plan.DualInputPlanNode;
import eu.stratosphere.compiler.plan.PlanNode;
import eu.stratosphere.compiler.plan.SingleInputPlanNode;
import eu.stratosphere.compiler.plan.WorksetIterationPlanNode;
import eu.stratosphere.pact.runtime.task.DamBehavior;
import eu.stratosphere.pact.runtime.task.DriverStrategy;
import eu.stratosphere.util.Visitor;
/**
* Certain pipelined flows may lead to deadlocks, in which case we need to make sure the pipelines are broken or made elastic enough to prevent that.
*
* This is only relevat to pipelined data flows where one operator has more than one consumers (successors in the flow).
*
* Most cases are caught by the general logic that deals with branching/joining flows. The following cases need additional checks:
*
* <build>
* (source1) ------ (join)
* \ / <probe>
* \ /
* \/
* /\
* / \
* / \ <probe>
* (source2) ------(join)
* <build>
*
* Since both sources pipeline their data into a build and a probe side, they get stalled by the back pressure from the probe side (which waits for the build side to complete) and never finish the build side.
*
* We can model dependencies of pipelined / materialized connections and do a reguar cyclic dependencies check to detect such situations. Pipelined connections have a dependency from sender to receiver, non-pipelined (fully dammed) connections have a dependency from receiver to sender.
*
*/
public class DeadlockPreventer implements Visitor<PlanNode> {
private DeadlockGraph g;
public DeadlockPreventer() {
this.g = new DeadlockGraph();
}
public void resolveDeadlocks(List<? extends PlanNode> sinks) {
for(PlanNode s : sinks) {
s.accept(this);
}
if(g.hasCycle()) {
// in the remaining plan is a cycle
for(DeadlockVertex v : g.vertices) {
// first strategy to fix -> swap build and probe side
if(v.getOriginal().getDriverStrategy().equals(DriverStrategy.HYBRIDHASH_BUILD_FIRST)) {
v.getOriginal().setDriverStrategy(DriverStrategy.HYBRIDHASH_BUILD_SECOND);
if(hasDeadlock(sinks)) {
// Didn't fix anything -> revert
v.getOriginal().setDriverStrategy(DriverStrategy.HYBRIDHASH_BUILD_FIRST);
}
else {
// deadlock resolved
break;
}
}
// other direction
if(v.getOriginal().getDriverStrategy().equals(DriverStrategy.HYBRIDHASH_BUILD_SECOND)) {
v.getOriginal().setDriverStrategy(DriverStrategy.HYBRIDHASH_BUILD_FIRST);
if(hasDeadlock(sinks)) {
// Didn't fix anything -> revert
v.getOriginal().setDriverStrategy(DriverStrategy.HYBRIDHASH_BUILD_SECOND);
}
else {
// deadlock resolved
break;
}
}
}
// switching build and probe side did not help -> pipeline breaker
for(DeadlockVertex v : g.vertices) {
if(v.getOriginal() instanceof DualInputPlanNode) {
DualInputPlanNode n = (DualInputPlanNode) v.getOriginal();
// what is the pipelined side? (other side should be a dam, otherwise operator could not be source of deadlock)
if(!(n.getDriverStrategy().firstDam().equals(DamBehavior.FULL_DAM) || n.getInput1().getLocalStrategy().dams() || n.getInput1().getTempMode().breaksPipeline())
&& (n.getDriverStrategy().secondDam().equals(DamBehavior.FULL_DAM) || n.getInput2().getLocalStrategy().dams() || n.getInput2().getTempMode().breaksPipeline())) {
n.getInput1().setTempMode(n.getInput1().getTempMode().makePipelineBreaker());
}
else if( !(n.getDriverStrategy().secondDam().equals(DamBehavior.FULL_DAM) || n.getInput2().getLocalStrategy().dams() || n.getInput2().getTempMode().breaksPipeline())
&& (n.getDriverStrategy().firstDam().equals(DamBehavior.FULL_DAM) || n.getInput1().getLocalStrategy().dams() || n.getInput1().getTempMode().breaksPipeline())) {
n.getInput2().setTempMode(n.getInput2().getTempMode().makePipelineBreaker());
}
// Deadlock resolved?
if(!hasDeadlock(sinks)) {
break;
}
}
}
}
}
/**
* Creates new DeadlockGraph from plan and checks for cycles
*
* @param plan
* @return
*/
public boolean hasDeadlock(List<? extends PlanNode> sinks) {
this.g = new DeadlockGraph();
for(PlanNode s : sinks) {
s.accept(this);
}
if(g.hasCycle()) {
return true;
} else {
return false;
}
}
/**
*
* @param visitable
* @return
*/
@Override
public boolean preVisit(PlanNode visitable) {
g.addVertex(visitable);
return true;
}
@Override
public void postVisit(PlanNode visitable) {
if(visitable instanceof SingleInputPlanNode) {
SingleInputPlanNode n = (SingleInputPlanNode) visitable;
if(n.getDriverStrategy().firstDam().equals(DamBehavior.FULL_DAM) || n.getInput().getLocalStrategy().dams() || n.getInput().getTempMode().breaksPipeline()) {
g.addEdge(n, n.getPredecessor());
}
else {
g.addEdge(n.getPredecessor(), n);
}
}
else if(visitable instanceof DualInputPlanNode) {
DualInputPlanNode n = (DualInputPlanNode) visitable;
if(n.getDriverStrategy().firstDam().equals(DamBehavior.FULL_DAM) || n.getInput1().getLocalStrategy().dams() || n.getInput1().getTempMode().breaksPipeline()) {
g.addEdge(n, n.getInput1().getSource());
}
else {
g.addEdge(n.getInput1().getSource(), n);
}
if(!n.getDriverStrategy().equals(DriverStrategy.NONE) && (n.getDriverStrategy().secondDam().equals(DamBehavior.FULL_DAM) || n.getInput2().getLocalStrategy().dams() || n.getInput2().getTempMode().breaksPipeline())) {
g.addEdge(n, n.getInput2().getSource());
}
else {
g.addEdge(n.getInput2().getSource(), n);
}
}
// recursively fix iterations
if (visitable instanceof BulkIterationPlanNode) {
DeadlockPreventer dp = new DeadlockPreventer();
List<PlanNode> planSinks = new ArrayList<PlanNode>(1);
BulkIterationPlanNode pspn = (BulkIterationPlanNode) visitable;
planSinks.add(pspn.getRootOfStepFunction());
dp.resolveDeadlocks(planSinks);
}
else if (visitable instanceof WorksetIterationPlanNode) {
DeadlockPreventer dp = new DeadlockPreventer();
List<PlanNode> planSinks = new ArrayList<PlanNode>(2);
WorksetIterationPlanNode pspn = (WorksetIterationPlanNode) visitable;
planSinks.add(pspn.getSolutionSetDeltaPlanNode());
planSinks.add(pspn.getNextWorkSetPlanNode());
dp.resolveDeadlocks(planSinks);
}
}
}