/** * Returns, and if necessary creates, the submodule interpreter actor for the given submodule. * * <p>A submodule interpreter actor is created at most once during the lifetime of the current * actor. If the submodule actor is created, it will be supervised by the current actor. * * <p>This method starts one asynchronous action for each out-port of the submodule that satisfies * {@link #isOutPortNeeded(RuntimeOutPort)}; these will be finished in {@link * #submoduleOutPortNoLongerNeeded}. This prevents this actor from terminating even though a child * interpreter may still send values for out-ports. * * <p>One additional asynchronous action is started for the child actor, which will be finished in * {@link #childActorTerminated(ActorRef)}. * * @param submodule submodule * @return reference to the submodule actor */ private ActorRef getChildExecutor(RuntimeModule submodule) { assert state == State.REPLAY || state == State.RUNNING || state == State.ALL_OUTPUTS; int submoduleId = submodule.getIndex(); if (childExecutors[submoduleId] == null) { List<SubmoduleInPortNode> submoduleInPortNodes = dependencyGraph.submodulesInPortNodes().get(submoduleId); ImmutableList<HasValue> submoduleInPortHasValueList = submoduleInPortNodes .stream() .map(SubmoduleInPortNode::getHasValue) .collect(ImmutableList.collector()); BitSet submoduleRecomputedInPorts = new BitSet(submodule.getInPorts().size()); submoduleInPortNodes .stream() .filter(node -> node.getPortState() == PortState.RECOMPUTE) .forEach(node -> submoduleRecomputedInPorts.set(node.getElement().getInIndex())); BitSet submoduleRequestedOutPorts = computeResumeState.getSubmodulesNeededOutPorts()[submoduleId]; submoduleRequestedOutPorts .stream() .mapToObj(id -> submodule.getOutPorts().get(id)) .forEach( outPort -> startAsynchronousAction( outPort, "waiting for value to pass through submodule out-port %s#%s", submodule.getSimpleName(), outPort.getSimpleName())); childExecutors[submoduleId] = getContext() .actorOf( interpreterPropsProvider.provideInterpreterProps( getInterpreterProperties(), stagingArea.resolveDescendant( ExecutionTrace.empty() .resolveContent() .resolveModule(submodule.getSimpleName())), submoduleId, submoduleInPortHasValueList, submoduleRecomputedInPorts, submoduleRequestedOutPorts), submodule.getSimpleName().toString()); getContext().watch(childExecutors[submoduleId]); childActorMap.put(childExecutors[submoduleId], submodule); startAsynchronousAction( childExecutors[submoduleId], "supervising interpreter for submodule %s", submodule.getSimpleName()); } return childExecutors[submoduleId]; }
/** * Handles event that the value of a submodule's out-port is no longer needed. * * <p>If the submodule of the given out-port no longer has any (other) out-port whose value is * required, this method starts (asynchronously) cleaning all intermediate results for this * module. * * <p>This method also finishes the asynchronous action (waiting for submodule out-port) started * previously in {@link #getChildExecutor}. * * @param outPort out-port whose value is no longer needed */ private void submoduleOutPortNoLongerNeeded(RuntimeOutPort outPort) { assert state.compareTo(State.RUNNING) >= 0; RuntimeModule submodule = outPort.getModule(); BitSet neededOutPorts = computeResumeState.getSubmodulesNeededOutPorts()[submodule.getIndex()]; neededOutPorts.set(outPort.getOutIndex(), false); if (neededOutPorts.isEmpty() && getInterpreterProperties().isCleaningRequested()) { ExecutionTrace executionTrace = ExecutionTrace.empty().resolveContent().resolveModule(submodule.getSimpleName()); CompletableFuture<Void> future = stagingArea.delete(executionTrace); awaitAsynchronousAction( future, "cleaning up intermediate output of submodule '%s'", submodule.getSimpleName()); } endAsynchronousAction(outPort); }
/** * Starts the interpretation of the current parent module if algorithm ComputeResumeState has * finished. * * <p>This method sets the state of this actor to {@link State#RUNNING} and thus also finished the * asynchronous action that was started in {@link #preStart()}. */ private void startRunningIfQueueEmpty() { assert state == State.STARTING; if (!computeResumeState.isFinished()) { return; } // Trigger in-ports initialInPortsTrigger(); state = State.RUNNING; // Start child interpreters for all submodules that have at least one in-port whose state is // READY submodules: for (List<SubmoduleInPortNode> submoduleInPortNodes : dependencyGraph.submodulesInPortNodes()) { for (SubmoduleInPortNode submoduleInPortNode : submoduleInPortNodes) { if (submoduleInPortNode.getPortState() == PortState.READY) { getChildExecutor(submoduleInPortNode.getElement().getModule()); continue submodules; } } } // Start child interpreters for all submodules that have no in-ports and whose state is READY for (SubmoduleNode submoduleNode : dependencyGraph.submoduleNodes()) { if (submoduleNode.getElement().getInPorts().isEmpty() && submoduleNode.getPortState() == PortState.READY) { getChildExecutor(submoduleNode.getElement()); } } // Trigger all out-ports whose state is READY dependencyGraph .outPortNodes() .stream() .filter(node -> node.getPortState() == PortState.READY) .forEach(node -> outportCarriesSignal(node.getElement().getOutIndex())); if (outPortsRequiringValue.isEmpty()) { state = State.ALL_OUTPUTS; } // Finish the asynchronous action started in #preStart(). checkIfNoAsynchronousActions(); }
/** * Performs the initialization steps of algorithm <em>ComputeResumeState</em>. * * <p>This method sets the state of this actor to {@link State#STARTING} and starts the * asynchronous initialization action, which is completed in {@link #startRunningIfQueueEmpty()}. * * <p>This method starts one asynchronous action for every in-port in {@link #recomputedInPorts}. * These asynchronous actions are finished in {@link #inPortHasSignal(int)}. */ @Override public void preStart() { assert state == State.STARTING; assert !requestedOutPorts.isEmpty(); // Another precondition: getPortState() is IRRELEVANT for all nodes in the dependency graph. publishStartModule(); // Start an asynchronous action for each in-port that is promised to receive a value recomputedInPorts .stream() .mapToObj(module.getInPorts()::get) .forEach( inPort -> startAsynchronousAction(inPort, "waiting for %s to receive value", inPort)); // Run algorithm ComputeResumeState computeResumeState.run(); startRunningIfQueueEmpty(); }
/** * Handles event that a future returned by {@link StagingArea#exists(RuntimeExecutionTrace)} * (called by {@link #asynchronousHasValueCheck(ValueNode)}) was completed successfully. * * @param node node in the dependency graph * @param hasValue whether the node in the dependency graph has a value */ private void finishedStagingAreaOperation(ValueNode node, boolean hasValue) { computeResumeState.updateHasValue(node, hasValue); startRunningIfQueueEmpty(); }
/** * Returns whether the given submodule's out-port is needed. * * @param outPort submodule's out-port */ private boolean isOutPortNeeded(RuntimeOutPort outPort) { assert outPort.getModule().getParent() == module; return computeResumeState.getSubmodulesNeededOutPorts()[outPort.getModule().getIndex()].get( outPort.getOutIndex()); }