/* * 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.flink.optimizer.dag; import static org.apache.flink.optimizer.plan.PlanNode.SourceAndDamReport.FOUND_SOURCE; import static org.apache.flink.optimizer.plan.PlanNode.SourceAndDamReport.FOUND_SOURCE_AND_DAM; import static org.apache.flink.optimizer.plan.PlanNode.SourceAndDamReport.NOT_FOUND; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.flink.api.common.ExecutionMode; import org.apache.flink.api.common.operators.DualInputOperator; import org.apache.flink.api.common.operators.Operator; import org.apache.flink.api.common.operators.SemanticProperties; import org.apache.flink.api.common.operators.util.FieldList; import org.apache.flink.optimizer.CompilerException; import org.apache.flink.optimizer.Optimizer; import org.apache.flink.optimizer.costs.CostEstimator; import org.apache.flink.optimizer.dataproperties.GlobalProperties; import org.apache.flink.optimizer.dataproperties.InterestingProperties; import org.apache.flink.optimizer.dataproperties.LocalProperties; import org.apache.flink.optimizer.dataproperties.RequestedGlobalProperties; import org.apache.flink.optimizer.dataproperties.RequestedLocalProperties; import org.apache.flink.optimizer.operators.OperatorDescriptorDual; import org.apache.flink.optimizer.operators.OperatorDescriptorDual.GlobalPropertiesPair; import org.apache.flink.optimizer.operators.OperatorDescriptorDual.LocalPropertiesPair; import org.apache.flink.optimizer.plan.Channel; import org.apache.flink.optimizer.plan.DualInputPlanNode; import org.apache.flink.optimizer.plan.NamedChannel; import org.apache.flink.optimizer.plan.PlanNode; import org.apache.flink.optimizer.plan.PlanNode.SourceAndDamReport; import org.apache.flink.configuration.Configuration; import org.apache.flink.runtime.io.network.DataExchangeMode; import org.apache.flink.runtime.operators.DamBehavior; import org.apache.flink.runtime.operators.DriverStrategy; import org.apache.flink.runtime.operators.shipping.ShipStrategyType; import org.apache.flink.util.Visitor; import com.google.common.collect.Sets; /** * A node in the optimizer plan that represents an operator with a two different inputs, such as Join, * Cross, CoGroup, or Union. * The two inputs are not substitutable in their sides. */ public abstract class TwoInputNode extends OptimizerNode { protected final FieldList keys1; // The set of key fields for the first input. may be null. protected final FieldList keys2; // The set of key fields for the second input. may be null. protected DagConnection input1; // The first input edge protected DagConnection input2; // The second input edge private List<OperatorDescriptorDual> cachedDescriptors; // -------------------------------------------------------------------------------------------- /** * Creates a new two input node for the optimizer plan, representing the given operator. * * @param operator The operator that the optimizer DAG node should represent. */ public TwoInputNode(DualInputOperator<?, ?, ?, ?> operator) { super(operator); int[] k1 = operator.getKeyColumns(0); int[] k2 = operator.getKeyColumns(1); this.keys1 = k1 == null || k1.length == 0 ? null : new FieldList(k1); this.keys2 = k2 == null || k2.length == 0 ? null : new FieldList(k2); if (this.keys1 != null) { if (this.keys2 != null) { if (this.keys1.size() != this.keys2.size()) { throw new CompilerException("Unequal number of key fields on the two inputs."); } } else { throw new CompilerException("Keys are set on first input, but not on second."); } } else if (this.keys2 != null) { throw new CompilerException("Keys are set on second input, but not on first."); } } // ------------------------------------------------------------------------ @Override public DualInputOperator<?, ?, ?, ?> getOperator() { return (DualInputOperator<?, ?, ?, ?>) super.getOperator(); } /** * Gets the DagConnection through which this node receives its <i>first</i> input. * * @return The first input connection. */ public DagConnection getFirstIncomingConnection() { return this.input1; } /** * Gets the DagConnection through which this node receives its <i>second</i> input. * * @return The second input connection. */ public DagConnection getSecondIncomingConnection() { return this.input2; } public OptimizerNode getFirstPredecessorNode() { if(this.input1 != null) { return this.input1.getSource(); } else { return null; } } public OptimizerNode getSecondPredecessorNode() { if(this.input2 != null) { return this.input2.getSource(); } else { return null; } } @Override public List<DagConnection> getIncomingConnections() { ArrayList<DagConnection> inputs = new ArrayList<DagConnection>(2); inputs.add(input1); inputs.add(input2); return inputs; } @Override public void setInput(Map<Operator<?>, OptimizerNode> contractToNode, ExecutionMode defaultExecutionMode) { // see if there is a hint that dictates which shipping strategy to use for BOTH inputs final Configuration conf = getOperator().getParameters(); ShipStrategyType preSet1 = null; ShipStrategyType preSet2 = null; String shipStrategy = conf.getString(Optimizer.HINT_SHIP_STRATEGY, null); if (shipStrategy != null) { if (Optimizer.HINT_SHIP_STRATEGY_FORWARD.equals(shipStrategy)) { preSet1 = preSet2 = ShipStrategyType.FORWARD; } else if (Optimizer.HINT_SHIP_STRATEGY_BROADCAST.equals(shipStrategy)) { preSet1 = preSet2 = ShipStrategyType.BROADCAST; } else if (Optimizer.HINT_SHIP_STRATEGY_REPARTITION_HASH.equals(shipStrategy)) { preSet1 = preSet2 = ShipStrategyType.PARTITION_HASH; } else if (Optimizer.HINT_SHIP_STRATEGY_REPARTITION_RANGE.equals(shipStrategy)) { preSet1 = preSet2 = ShipStrategyType.PARTITION_RANGE; } else if (shipStrategy.equalsIgnoreCase(Optimizer.HINT_SHIP_STRATEGY_REPARTITION)) { preSet1 = preSet2 = ShipStrategyType.PARTITION_RANDOM; } else { throw new CompilerException("Unknown hint for shipping strategy: " + shipStrategy); } } // see if there is a hint that dictates which shipping strategy to use for the FIRST input shipStrategy = conf.getString(Optimizer.HINT_SHIP_STRATEGY_FIRST_INPUT, null); if (shipStrategy != null) { if (Optimizer.HINT_SHIP_STRATEGY_FORWARD.equals(shipStrategy)) { preSet1 = ShipStrategyType.FORWARD; } else if (Optimizer.HINT_SHIP_STRATEGY_BROADCAST.equals(shipStrategy)) { preSet1 = ShipStrategyType.BROADCAST; } else if (Optimizer.HINT_SHIP_STRATEGY_REPARTITION_HASH.equals(shipStrategy)) { preSet1 = ShipStrategyType.PARTITION_HASH; } else if (Optimizer.HINT_SHIP_STRATEGY_REPARTITION_RANGE.equals(shipStrategy)) { preSet1 = ShipStrategyType.PARTITION_RANGE; } else if (shipStrategy.equalsIgnoreCase(Optimizer.HINT_SHIP_STRATEGY_REPARTITION)) { preSet1 = ShipStrategyType.PARTITION_RANDOM; } else { throw new CompilerException("Unknown hint for shipping strategy of input one: " + shipStrategy); } } // see if there is a hint that dictates which shipping strategy to use for the SECOND input shipStrategy = conf.getString(Optimizer.HINT_SHIP_STRATEGY_SECOND_INPUT, null); if (shipStrategy != null) { if (Optimizer.HINT_SHIP_STRATEGY_FORWARD.equals(shipStrategy)) { preSet2 = ShipStrategyType.FORWARD; } else if (Optimizer.HINT_SHIP_STRATEGY_BROADCAST.equals(shipStrategy)) { preSet2 = ShipStrategyType.BROADCAST; } else if (Optimizer.HINT_SHIP_STRATEGY_REPARTITION_HASH.equals(shipStrategy)) { preSet2 = ShipStrategyType.PARTITION_HASH; } else if (Optimizer.HINT_SHIP_STRATEGY_REPARTITION_RANGE.equals(shipStrategy)) { preSet2 = ShipStrategyType.PARTITION_RANGE; } else if (shipStrategy.equalsIgnoreCase(Optimizer.HINT_SHIP_STRATEGY_REPARTITION)) { preSet2 = ShipStrategyType.PARTITION_RANDOM; } else { throw new CompilerException("Unknown hint for shipping strategy of input two: " + shipStrategy); } } // get the predecessors DualInputOperator<?, ?, ?, ?> contr = getOperator(); Operator<?> leftPred = contr.getFirstInput(); Operator<?> rightPred = contr.getSecondInput(); OptimizerNode pred1; DagConnection conn1; if (leftPred == null) { throw new CompilerException("Error: Node for '" + getOperator().getName() + "' has no input set for first input."); } else { pred1 = contractToNode.get(leftPred); conn1 = new DagConnection(pred1, this, defaultExecutionMode); if (preSet1 != null) { conn1.setShipStrategy(preSet1); } } // create the connection and add it this.input1 = conn1; pred1.addOutgoingConnection(conn1); OptimizerNode pred2; DagConnection conn2; if (rightPred == null) { throw new CompilerException("Error: Node for '" + getOperator().getName() + "' has no input set for second input."); } else { pred2 = contractToNode.get(rightPred); conn2 = new DagConnection(pred2, this, defaultExecutionMode); if (preSet2 != null) { conn2.setShipStrategy(preSet2); } } // create the connection and add it this.input2 = conn2; pred2.addOutgoingConnection(conn2); } protected abstract List<OperatorDescriptorDual> getPossibleProperties(); private List<OperatorDescriptorDual> getProperties() { if (this.cachedDescriptors == null) { this.cachedDescriptors = getPossibleProperties(); } return this.cachedDescriptors; } @Override public void computeInterestingPropertiesForInputs(CostEstimator estimator) { // get what we inherit and what is preserved by our user code final InterestingProperties props1 = getInterestingProperties().filterByCodeAnnotations(this, 0); final InterestingProperties props2 = getInterestingProperties().filterByCodeAnnotations(this, 1); // add all properties relevant to this node for (OperatorDescriptorDual dpd : getProperties()) { for (GlobalPropertiesPair gp : dpd.getPossibleGlobalProperties()) { // input 1 props1.addGlobalProperties(gp.getProperties1()); // input 2 props2.addGlobalProperties(gp.getProperties2()); } for (LocalPropertiesPair lp : dpd.getPossibleLocalProperties()) { // input 1 props1.addLocalProperties(lp.getProperties1()); // input 2 props2.addLocalProperties(lp.getProperties2()); } } this.input1.setInterestingProperties(props1); this.input2.setInterestingProperties(props2); for (DagConnection conn : getBroadcastConnections()) { conn.setInterestingProperties(new InterestingProperties()); } } @Override public List<PlanNode> getAlternativePlans(CostEstimator estimator) { // check if we have a cached version if (this.cachedPlans != null) { return this.cachedPlans; } boolean childrenSkippedDueToReplicatedInput = false; // step down to all producer nodes and calculate alternative plans final List<? extends PlanNode> subPlans1 = getFirstPredecessorNode().getAlternativePlans(estimator); final List<? extends PlanNode> subPlans2 = getSecondPredecessorNode().getAlternativePlans(estimator); // calculate alternative sub-plans for predecessor final Set<RequestedGlobalProperties> intGlobal1 = this.input1.getInterestingProperties().getGlobalProperties(); final Set<RequestedGlobalProperties> intGlobal2 = this.input2.getInterestingProperties().getGlobalProperties(); // calculate alternative sub-plans for broadcast inputs final List<Set<? extends NamedChannel>> broadcastPlanChannels = new ArrayList<Set<? extends NamedChannel>>(); List<DagConnection> broadcastConnections = getBroadcastConnections(); List<String> broadcastConnectionNames = getBroadcastConnectionNames(); for (int i = 0; i < broadcastConnections.size(); i++ ) { DagConnection broadcastConnection = broadcastConnections.get(i); String broadcastConnectionName = broadcastConnectionNames.get(i); List<PlanNode> broadcastPlanCandidates = broadcastConnection.getSource().getAlternativePlans(estimator); // wrap the plan candidates in named channels HashSet<NamedChannel> broadcastChannels = new HashSet<NamedChannel>(broadcastPlanCandidates.size()); for (PlanNode plan: broadcastPlanCandidates) { final NamedChannel c = new NamedChannel(broadcastConnectionName, plan); DataExchangeMode exMode = DataExchangeMode.select(broadcastConnection.getDataExchangeMode(), ShipStrategyType.BROADCAST, broadcastConnection.isBreakingPipeline()); c.setShipStrategy(ShipStrategyType.BROADCAST, exMode); broadcastChannels.add(c); } broadcastPlanChannels.add(broadcastChannels); } final GlobalPropertiesPair[] allGlobalPairs; final LocalPropertiesPair[] allLocalPairs; { Set<GlobalPropertiesPair> pairsGlob = new HashSet<GlobalPropertiesPair>(); Set<LocalPropertiesPair> pairsLoc = new HashSet<LocalPropertiesPair>(); for (OperatorDescriptorDual ods : getProperties()) { pairsGlob.addAll(ods.getPossibleGlobalProperties()); pairsLoc.addAll(ods.getPossibleLocalProperties()); } allGlobalPairs = pairsGlob.toArray(new GlobalPropertiesPair[pairsGlob.size()]); allLocalPairs = pairsLoc.toArray(new LocalPropertiesPair[pairsLoc.size()]); } final ArrayList<PlanNode> outputPlans = new ArrayList<PlanNode>(); final ExecutionMode input1Mode = this.input1.getDataExchangeMode(); final ExecutionMode input2Mode = this.input2.getDataExchangeMode(); final int parallelism = getParallelism(); final int inParallelism1 = getFirstPredecessorNode().getParallelism(); final int inParallelism2 = getSecondPredecessorNode().getParallelism(); final boolean dopChange1 = parallelism != inParallelism1; final boolean dopChange2 = parallelism != inParallelism2; final boolean input1breaksPipeline = this.input1.isBreakingPipeline(); final boolean input2breaksPipeline = this.input2.isBreakingPipeline(); // enumerate all pairwise combination of the children's plans together with // all possible operator strategy combination // create all candidates for (PlanNode child1 : subPlans1) { if (child1.getGlobalProperties().isFullyReplicated()) { // fully replicated input is always locally forwarded if parallelism is not changed if (dopChange1) { // can not continue with this child childrenSkippedDueToReplicatedInput = true; continue; } else { this.input1.setShipStrategy(ShipStrategyType.FORWARD); } } for (PlanNode child2 : subPlans2) { if (child2.getGlobalProperties().isFullyReplicated()) { // fully replicated input is always locally forwarded if parallelism is not changed if (dopChange2) { // can not continue with this child childrenSkippedDueToReplicatedInput = true; continue; } else { this.input2.setShipStrategy(ShipStrategyType.FORWARD); } } // check that the children go together. that is the case if they build upon the same // candidate at the joined branch plan. if (!areBranchCompatible(child1, child2)) { continue; } for (RequestedGlobalProperties igps1: intGlobal1) { // create a candidate channel for the first input. mark it cached, if the connection says so final Channel c1 = new Channel(child1, this.input1.getMaterializationMode()); if (this.input1.getShipStrategy() == null) { // free to choose the ship strategy igps1.parameterizeChannel(c1, dopChange1, input1Mode, input1breaksPipeline); // if the parallelism changed, make sure that we cancel out properties, unless the // ship strategy preserves/establishes them even under changing parallelisms if (dopChange1 && !c1.getShipStrategy().isNetworkStrategy()) { c1.getGlobalProperties().reset(); } } else { // ship strategy fixed by compiler hint ShipStrategyType shipType = this.input1.getShipStrategy(); DataExchangeMode exMode = DataExchangeMode.select(input1Mode, shipType, input1breaksPipeline); if (this.keys1 != null) { c1.setShipStrategy(shipType, this.keys1.toFieldList(), exMode); } else { c1.setShipStrategy(shipType, exMode); } if (dopChange1) { c1.adjustGlobalPropertiesForFullParallelismChange(); } } for (RequestedGlobalProperties igps2: intGlobal2) { // create a candidate channel for the first input. mark it cached, if the connection says so final Channel c2 = new Channel(child2, this.input2.getMaterializationMode()); if (this.input2.getShipStrategy() == null) { // free to choose the ship strategy igps2.parameterizeChannel(c2, dopChange2, input2Mode, input2breaksPipeline); // if the parallelism changed, make sure that we cancel out properties, unless the // ship strategy preserves/establishes them even under changing parallelisms if (dopChange2 && !c2.getShipStrategy().isNetworkStrategy()) { c2.getGlobalProperties().reset(); } } else { // ship strategy fixed by compiler hint ShipStrategyType shipType = this.input2.getShipStrategy(); DataExchangeMode exMode = DataExchangeMode.select(input2Mode, shipType, input2breaksPipeline); if (this.keys2 != null) { c2.setShipStrategy(shipType, this.keys2.toFieldList(), exMode); } else { c2.setShipStrategy(shipType, exMode); } if (dopChange2) { c2.adjustGlobalPropertiesForFullParallelismChange(); } } /* ******************************************************************** * NOTE: Depending on how we proceed with different partitioning, * we might at some point need a compatibility check between * the pairs of global properties. * *******************************************************************/ outer: for (GlobalPropertiesPair gpp : allGlobalPairs) { if (gpp.getProperties1().isMetBy(c1.getGlobalProperties()) && gpp.getProperties2().isMetBy(c2.getGlobalProperties()) ) { for (OperatorDescriptorDual desc : getProperties()) { if (desc.areCompatible(gpp.getProperties1(), gpp.getProperties2(), c1.getGlobalProperties(), c2.getGlobalProperties())) { Channel c1Clone = c1.clone(); c1Clone.setRequiredGlobalProps(gpp.getProperties1()); c2.setRequiredGlobalProps(gpp.getProperties2()); // we form a valid combination, so create the local candidates // for this addLocalCandidates(c1Clone, c2, broadcastPlanChannels, igps1, igps2, outputPlans, allLocalPairs, estimator); break outer; } } } } // break the loop over input2's possible global properties, if the property // is fixed via a hint. All the properties are overridden by the hint anyways, // so we can stop after the first if (this.input2.getShipStrategy() != null) { break; } } // break the loop over input1's possible global properties, if the property // is fixed via a hint. All the properties are overridden by the hint anyways, // so we can stop after the first if (this.input1.getShipStrategy() != null) { break; } } } } if(outputPlans.isEmpty()) { if(childrenSkippedDueToReplicatedInput) { throw new CompilerException("No plan meeting the requirements could be created @ " + this + ". Most likely reason: Invalid use of replicated input."); } else { throw new CompilerException("No plan meeting the requirements could be created @ " + this + ". Most likely reason: Too restrictive plan hints."); } } // cost and prune the plans for (PlanNode node : outputPlans) { estimator.costOperator(node); } prunePlanAlternatives(outputPlans); outputPlans.trimToSize(); this.cachedPlans = outputPlans; return outputPlans; } protected void addLocalCandidates(Channel template1, Channel template2, List<Set<? extends NamedChannel>> broadcastPlanChannels, RequestedGlobalProperties rgps1, RequestedGlobalProperties rgps2, List<PlanNode> target, LocalPropertiesPair[] validLocalCombinations, CostEstimator estimator) { for (RequestedLocalProperties ilp1 : this.input1.getInterestingProperties().getLocalProperties()) { final Channel in1 = template1.clone(); ilp1.parameterizeChannel(in1); for (RequestedLocalProperties ilp2 : this.input2.getInterestingProperties().getLocalProperties()) { final Channel in2 = template2.clone(); ilp2.parameterizeChannel(in2); for (OperatorDescriptorDual dps: getProperties()) { for (LocalPropertiesPair lpp : dps.getPossibleLocalProperties()) { if (lpp.getProperties1().isMetBy(in1.getLocalProperties()) && lpp.getProperties2().isMetBy(in2.getLocalProperties()) ) { // valid combination // for non trivial local properties, we need to check that they are co compatible // (such as when some sort order is requested, that both are the same sort order if (dps.areCoFulfilled(lpp.getProperties1(), lpp.getProperties2(), in1.getLocalProperties(), in2.getLocalProperties())) { // copy, because setting required properties and instantiation may // change the channels and should not affect prior candidates Channel in1Copy = in1.clone(); in1Copy.setRequiredLocalProps(lpp.getProperties1()); Channel in2Copy = in2.clone(); in2Copy.setRequiredLocalProps(lpp.getProperties2()); // all right, co compatible instantiate(dps, in1Copy, in2Copy, broadcastPlanChannels, target, estimator, rgps1, rgps2, ilp1, ilp2); break; } // else cannot use this pair, fall through the loop and try the next one } } } } } } protected void instantiate(OperatorDescriptorDual operator, Channel in1, Channel in2, List<Set<? extends NamedChannel>> broadcastPlanChannels, List<PlanNode> target, CostEstimator estimator, RequestedGlobalProperties globPropsReq1, RequestedGlobalProperties globPropsReq2, RequestedLocalProperties locPropsReq1, RequestedLocalProperties locPropsReq2) { final PlanNode inputSource1 = in1.getSource(); final PlanNode inputSource2 = in2.getSource(); for (List<NamedChannel> broadcastChannelsCombination: Sets.cartesianProduct(broadcastPlanChannels)) { boolean validCombination = true; // check whether the broadcast inputs use the same plan candidate at the branching point for (int i = 0; i < broadcastChannelsCombination.size(); i++) { NamedChannel nc = broadcastChannelsCombination.get(i); PlanNode bcSource = nc.getSource(); if (!(areBranchCompatible(bcSource, inputSource1) || areBranchCompatible(bcSource, inputSource2))) { validCombination = false; break; } // check branch compatibility against all other broadcast variables for (int k = 0; k < i; k++) { PlanNode otherBcSource = broadcastChannelsCombination.get(k).getSource(); if (!areBranchCompatible(bcSource, otherBcSource)) { validCombination = false; break; } } } if (!validCombination) { continue; } placePipelineBreakersIfNecessary(operator.getStrategy(), in1, in2); DualInputPlanNode node = operator.instantiate(in1, in2, this); node.setBroadcastInputs(broadcastChannelsCombination); SemanticProperties semPropsGlobalPropFiltering = getSemanticPropertiesForGlobalPropertyFiltering(); GlobalProperties gp1 = in1.getGlobalProperties().clone() .filterBySemanticProperties(semPropsGlobalPropFiltering, 0); GlobalProperties gp2 = in2.getGlobalProperties().clone() .filterBySemanticProperties(semPropsGlobalPropFiltering, 1); GlobalProperties combined = operator.computeGlobalProperties(gp1, gp2); SemanticProperties semPropsLocalPropFiltering = getSemanticPropertiesForLocalPropertyFiltering(); LocalProperties lp1 = in1.getLocalProperties().clone() .filterBySemanticProperties(semPropsLocalPropFiltering, 0); LocalProperties lp2 = in2.getLocalProperties().clone() .filterBySemanticProperties(semPropsLocalPropFiltering, 1); LocalProperties locals = operator.computeLocalProperties(lp1, lp2); node.initProperties(combined, locals); node.updatePropertiesWithUniqueSets(getUniqueFields()); target.add(node); } } protected void placePipelineBreakersIfNecessary(DriverStrategy strategy, Channel in1, Channel in2) { // before we instantiate, check for deadlocks by tracing back to the open branches and checking // whether either no input, or all of them have a dam if (in1.isOnDynamicPath() && in2.isOnDynamicPath() && this.hereJoinedBranches != null && this.hereJoinedBranches.size() > 0) { boolean someDamOnLeftPaths = false; boolean damOnAllLeftPaths = true; boolean someDamOnRightPaths = false; boolean damOnAllRightPaths = true; if (strategy.firstDam() == DamBehavior.FULL_DAM || in1.getLocalStrategy().dams() || in1.getTempMode().breaksPipeline()) { someDamOnLeftPaths = true; } else { for (OptimizerNode brancher : this.hereJoinedBranches) { PlanNode candAtBrancher = in1.getSource().getCandidateAtBranchPoint(brancher); // not all candidates are found, because this list includes joined branched from both regular inputs and broadcast vars if (candAtBrancher == null) { continue; } SourceAndDamReport res = in1.getSource().hasDamOnPathDownTo(candAtBrancher); if (res == NOT_FOUND) { throw new CompilerException("Bug: Tracing dams for deadlock detection is broken."); } else if (res == FOUND_SOURCE) { damOnAllLeftPaths = false; } else if (res == FOUND_SOURCE_AND_DAM) { someDamOnLeftPaths = true; } else { throw new CompilerException(); } } } if (strategy.secondDam() == DamBehavior.FULL_DAM || in2.getLocalStrategy().dams() || in2.getTempMode().breaksPipeline()) { someDamOnRightPaths = true; } else { for (OptimizerNode brancher : this.hereJoinedBranches) { PlanNode candAtBrancher = in2.getSource().getCandidateAtBranchPoint(brancher); // not all candidates are found, because this list includes joined branched from both regular inputs and broadcast vars if (candAtBrancher == null) { continue; } SourceAndDamReport res = in2.getSource().hasDamOnPathDownTo(candAtBrancher); if (res == NOT_FOUND) { throw new CompilerException("Bug: Tracing dams for deadlock detection is broken."); } else if (res == FOUND_SOURCE) { damOnAllRightPaths = false; } else if (res == FOUND_SOURCE_AND_DAM) { someDamOnRightPaths = true; } else { throw new CompilerException(); } } } // okay combinations are both all dam or both no dam if ( (damOnAllLeftPaths & damOnAllRightPaths) | (!someDamOnLeftPaths & !someDamOnRightPaths) ) { // good, either both materialize already on the way, or both fully pipeline } else { if (someDamOnLeftPaths & !damOnAllRightPaths) { // right needs a pipeline breaker in2.setTempMode(in2.getTempMode().makePipelineBreaker()); } if (someDamOnRightPaths & !damOnAllLeftPaths) { // right needs a pipeline breaker in1.setTempMode(in1.getTempMode().makePipelineBreaker()); } } } } @Override public void computeUnclosedBranchStack() { if (this.openBranches != null) { return; } // handle the data flow branching for the regular inputs addClosedBranches(getFirstPredecessorNode().closedBranchingNodes); addClosedBranches(getSecondPredecessorNode().closedBranchingNodes); List<UnclosedBranchDescriptor> result1 = getFirstPredecessorNode().getBranchesForParent(getFirstIncomingConnection()); List<UnclosedBranchDescriptor> result2 = getSecondPredecessorNode().getBranchesForParent(getSecondIncomingConnection()); ArrayList<UnclosedBranchDescriptor> inputsMerged = new ArrayList<UnclosedBranchDescriptor>(); mergeLists(result1, result2, inputsMerged, true); // handle the data flow branching for the broadcast inputs List<UnclosedBranchDescriptor> result = computeUnclosedBranchStackForBroadcastInputs(inputsMerged); this.openBranches = (result == null || result.isEmpty()) ? Collections.<UnclosedBranchDescriptor>emptyList() : result; } @Override public SemanticProperties getSemanticProperties() { return getOperator().getSemanticProperties(); } protected SemanticProperties getSemanticPropertiesForLocalPropertyFiltering() { return this.getSemanticProperties(); } protected SemanticProperties getSemanticPropertiesForGlobalPropertyFiltering() { return this.getSemanticProperties(); } // -------------------------------------------------------------------------------------------- // Miscellaneous // -------------------------------------------------------------------------------------------- @Override public void accept(Visitor<OptimizerNode> visitor) { if (visitor.preVisit(this)) { if (this.input1 == null || this.input2 == null) { throw new CompilerException(); } getFirstPredecessorNode().accept(visitor); getSecondPredecessorNode().accept(visitor); for (DagConnection connection : getBroadcastConnections()) { connection.getSource().accept(visitor); } visitor.postVisit(this); } } }