/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.component.execution.internal;
import java.util.Deque;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import de.rcenvironment.core.component.api.ComponentConstants;
import de.rcenvironment.core.component.api.ComponentException;
import de.rcenvironment.core.component.api.ComponentUtils;
import de.rcenvironment.core.component.execution.api.Component;
import de.rcenvironment.core.component.execution.api.ComponentExecutionException;
import de.rcenvironment.core.component.execution.api.ComponentState;
import de.rcenvironment.core.component.execution.api.ConsoleRow.Type;
import de.rcenvironment.core.component.execution.api.EndpointDatumSerializer;
import de.rcenvironment.core.component.execution.api.ThreadHandler;
import de.rcenvironment.core.component.execution.api.WorkflowGraphHop;
import de.rcenvironment.core.component.execution.api.WorkflowGraphNode;
import de.rcenvironment.core.component.model.endpoint.api.EndpointDatum;
import de.rcenvironment.core.configuration.ConfigurationService;
import de.rcenvironment.core.datamodel.api.DataModelConstants;
import de.rcenvironment.core.datamodel.api.FinalComponentRunState;
import de.rcenvironment.core.datamodel.api.TypedDatumService;
import de.rcenvironment.core.datamodel.types.api.NotAValueTD;
import de.rcenvironment.core.toolkitbridge.transitional.ConcurrencyUtils;
import de.rcenvironment.core.utils.common.LogUtils;
import de.rcenvironment.core.utils.common.StringUtils;
import de.rcenvironment.toolkit.modules.concurrency.api.AsyncCallbackExceptionPolicy;
import de.rcenvironment.toolkit.modules.concurrency.api.AsyncOrderedExecutionQueue;
import de.rcenvironment.toolkit.modules.concurrency.api.AsyncTaskService;
import de.rcenvironment.toolkit.modules.concurrency.api.TaskDescription;
/**
* Wrapper class that calls life cycle methods of components by considering optional limitations concerning the amount of parallel
* executions.
*
* @author Doreen Seider
*/
public class ComponentExecutor {
protected static final int DEAFULT_WAIT_INTERVAL_AFTER_CANCELLED_MSEC = 60000;
protected static final int DEAFULT_WAIT_INTERVAL_NOT_RUN_MSEC = 60000;
protected static int waitIntervalAfterCacelledCalledMSec = DEAFULT_WAIT_INTERVAL_AFTER_CANCELLED_MSEC;
protected static int waitIntervalNotRunMSec = DEAFULT_WAIT_INTERVAL_NOT_RUN_MSEC;
private static final Log LOG = LogFactory.getLog(ComponentExecutor.class);
/**
* Type of component execution to be performed.
*
* @author Doreen Seider
*/
protected enum ComponentExecutionType {
StartAsInit(ComponentState.STARTING, ComponentStateMachineEventType.START_FAILED),
StartAsRun(ComponentState.STARTING, ComponentStateMachineEventType.START_FAILED),
ProcessInputs(ComponentState.PROCESSING_INPUTS, ComponentStateMachineEventType.PROCESSING_INPUTS_FAILED),
Reset(ComponentState.RESETTING),
TearDown(),
HandleVerificationToken(),
CompleteVerification();
private final ComponentState compStateOnSuccess;
private final ComponentStateMachineEventType compStateMachineEventTypeOnFailure;
private Component.FinalComponentState finalCompStateAfterTearedDown;
private FinalComponentRunState finalCompRunState;
private String verificationToken;
ComponentExecutionType() {
this.compStateOnSuccess = null;
this.compStateMachineEventTypeOnFailure = null;
}
ComponentExecutionType(ComponentState compStateOnSuccess) {
this.compStateOnSuccess = compStateOnSuccess;
this.compStateMachineEventTypeOnFailure = null;
}
ComponentExecutionType(ComponentState compStateOnSuccess, ComponentStateMachineEventType compStateMachineEventTypeOnFailure) {
this.compStateOnSuccess = compStateOnSuccess;
this.compStateMachineEventTypeOnFailure = compStateMachineEventTypeOnFailure;
}
protected void setFinalComponentStateAfterTearedDown(Component.FinalComponentState finalState) {
this.finalCompStateAfterTearedDown = finalState;
}
protected void setFinalComponentStateAfterRun(FinalComponentRunState finalState) {
this.finalCompRunState = finalState;
}
protected void setVerificationToken(String verificationToken) {
this.verificationToken = verificationToken;
}
}
private static TypedDatumService typedDatumService;
private static ComponentExecutionPermitsService componentExecutionPermitService;
private static EndpointDatumSerializer endpointDatumSerializer;
private static ComponentExecutionStatsService compExeStatsService;
private static boolean sendInputsProcessedToWfCtrl;
private final AsyncTaskService threadPool = ConcurrencyUtils.getAsyncTaskService();
private final AsyncOrderedExecutionQueue executionQueue = ConcurrencyUtils.getFactory().createAsyncOrderedExecutionQueue(
AsyncCallbackExceptionPolicy.LOG_AND_PROCEED);
private ComponentExecutionRelatedInstances compExeRelatedInstances;
private ComponentExecutionType compExeType;
private boolean isVerificationRequired;
private boolean treatAsRun;
private boolean isTearDown;
private boolean writesCompRunRelatedDataToDM;
private AtomicReference<Future<Boolean>> aquirePermissionTask = new AtomicReference<Future<Boolean>>(null);
private AtomicReference<Future<ComponentException>> executeTask = new AtomicReference<Future<ComponentException>>(null);
private AtomicBoolean isDone = new AtomicBoolean(false);
private AtomicBoolean isCancelled = new AtomicBoolean(false);
private CountDownLatch executionLatch = new CountDownLatch(1);
private boolean executionPermissionAcquired = false;
@Deprecated
public ComponentExecutor() {}
protected ComponentExecutor(ComponentExecutionRelatedInstances compExeRelatedInstances, ComponentExecutionType compExeType) {
this.compExeRelatedInstances = compExeRelatedInstances;
this.compExeType = compExeType;
this.isVerificationRequired = ComponentExecutionUtils.isVerificationRequired(compExeRelatedInstances.compExeCtx
.getComponentDescription().getConfigurationDescription().getComponentConfigurationDefinition());
this.treatAsRun = compExeType == ComponentExecutionType.StartAsRun || compExeType == ComponentExecutionType.ProcessInputs;
this.writesCompRunRelatedDataToDM = treatAsRun;
this.isTearDown = compExeType == ComponentExecutionType.TearDown;
}
protected void executeByConsideringLimitations() throws ComponentException, ComponentExecutionException {
acquireExecutionPermission();
performExecutionAndReleasePermission();
}
protected void acquireExecutionPermission() {
if (treatAsRun && !isCancelled.get()) {
try {
aquirePermissionTask.set(componentExecutionPermitService
.acquire(compExeRelatedInstances.compExeCtx.getComponentDescription().getIdentifier(),
compExeRelatedInstances.compExeCtx.getExecutionIdentifier()));
executionPermissionAcquired = aquirePermissionTask.get().get();
} catch (CancellationException e) {
if (isCancelled.get()) {
compExeRelatedInstances.compExeRelatedStates.isComponentCancelled.set(true);
return;
}
compExeRelatedInstances.compStateMachine
.postEvent(new ComponentStateMachineEvent(compExeType.compStateMachineEventTypeOnFailure, e));
} catch (ExecutionException | InterruptedException e) {
compExeRelatedInstances.compStateMachine
.postEvent(new ComponentStateMachineEvent(compExeType.compStateMachineEventTypeOnFailure, e));
}
}
}
protected void performExecutionAndReleasePermission() throws ComponentExecutionException, ComponentException {
try {
FinalComponentRunState finalState = FinalComponentRunState.FAILED;
if (isCancelled.get()) {
compExeRelatedInstances.compExeRelatedStates.isComponentCancelled.set(true);
finalState = FinalComponentRunState.CANCELLED;
return;
}
if (!isTearDown) {
if (compExeType.compStateOnSuccess != null) {
compExeRelatedInstances.compStateMachine
.postEvent(new ComponentStateMachineEvent(ComponentStateMachineEventType.RUNNING, compExeType.compStateOnSuccess));
}
if (treatAsRun) {
compExeRelatedInstances.compExeStorageBridge
.addComponentExecution(compExeRelatedInstances.compExeCtx,
compExeRelatedInstances.compExeRelatedStates.executionCount.get());
storeInputs();
compExeStatsService.addStatsAtComponentRunStart(compExeRelatedInstances.compExeCtx);
}
}
try {
executeAsync();
awaitExecution();
if (compExeType.finalCompRunState == null) {
finalState = FinalComponentRunState.FINISHED;
} else {
finalState = compExeType.finalCompRunState;
}
if (compExeRelatedInstances.compExeRelatedStates.compHasSentConsoleRowLogMessages.get()) {
writesCompRunRelatedDataToDM = true;
prepareDmForComponentRunRelatedDataIfNeeded();
}
} catch (ComponentException e) {
writesCompRunRelatedDataToDM = true;
prepareDmForComponentRunRelatedDataIfNeeded();
handleComponentExecutionFailure(e);
finalState = FinalComponentRunState.FAILED;
} finally {
if (treatAsRun) {
compExeStatsService.addStatsAtComponentRunTermination(compExeRelatedInstances.compExeCtx);
}
// TODO improve condition when the component run is considered as done from a data management perspective
if (treatAsRun && !compExeRelatedInstances.compExeScheduler.isLoopResetRequested() && !isVerificationRequired) {
finishExecutionFromDataManagementPerpective(finalState);
} else if (compExeType.equals(ComponentExecutionType.Reset)
&& (compExeRelatedInstances.compExeStorageBridge.hasUnfinishedComponentExecution() || writesCompRunRelatedDataToDM)) {
finishExecutionFromDataManagementPerpective(finalState);
} else if (compExeType.equals(ComponentExecutionType.CompleteVerification)) {
finishExecutionFromDataManagementPerpective(finalState);
} else if (writesCompRunRelatedDataToDM && !compExeRelatedInstances.compExeScheduler.isLoopResetRequested()
&& !compExeType.equals(ComponentExecutionType.HandleVerificationToken) && !isVerificationRequired) {
finishExecutionFromDataManagementPerpective(finalState);
}
if (isCancelled.get()) {
compExeRelatedInstances.compExeRelatedStates.isComponentCancelled.set(true);
}
executeTask.set(null);
}
} finally {
if (executionPermissionAcquired) {
componentExecutionPermitService
.release(compExeRelatedInstances.compExeCtx.getComponentDescription().getIdentifier());
}
}
}
private void finishExecutionFromDataManagementPerpective(FinalComponentRunState finalState) throws ComponentExecutionException {
compExeRelatedInstances.consoleRowsSender.sendLogFileWriteTriggerAsConsoleRow();
compExeRelatedInstances.compExeStorageBridge.setComponentExecutionFinished(finalState);
}
private void awaitExecution() throws ComponentException {
ComponentException exception = null;
try {
if (compExeType == ComponentExecutionType.StartAsInit || compExeType == ComponentExecutionType.StartAsRun
|| compExeType == ComponentExecutionType.ProcessInputs || compExeType == ComponentExecutionType.HandleVerificationToken) {
try {
exception = executeTask.get().get();
} catch (CancellationException e) {
LOG.debug(StringUtils.format("Task was cancelled that was executing '%s' of %s", compExeType.name(),
ComponentExecutionUtils.getStringWithInfoAboutComponentAndWorkflowLowerCase(compExeRelatedInstances.compExeCtx)));
}
} else {
try {
exception = executeTask.get().get(waitIntervalNotRunMSec, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
executeTask.get().cancel(true);
exception = new ComponentException(StringUtils.format(
"Task didn't terminate in time and is cancelled; it is executing '%s' of %s", compExeType.name(),
ComponentExecutionUtils.getStringWithInfoAboutComponentAndWorkflowLowerCase(compExeRelatedInstances.compExeCtx)),
e);
}
}
} catch (InterruptedException e) {
executeTask.get().cancel(true);
exception = new ComponentException(StringUtils.format(
"Waiting for task to terminate was interrupted; it is cancelled now; it is executing '%s' of %s", compExeType.name(),
ComponentExecutionUtils.getStringWithInfoAboutComponentAndWorkflowLowerCase(compExeRelatedInstances.compExeCtx)),
e);
} catch (ExecutionException e) {
exception = new ComponentException("Unexpected error during component execution", e.getCause());
}
try {
if (!executionLatch.await(waitIntervalAfterCacelledCalledMSec, TimeUnit.MILLISECONDS)) {
exception = new ComponentException(StringUtils.format(
"Task didn't terminate in time after it was cancelled; it was executing '%s' of %s", compExeType.name(),
ComponentExecutionUtils.getStringWithInfoAboutComponentAndWorkflowLowerCase(compExeRelatedInstances.compExeCtx)));
}
} catch (InterruptedException e) {
exception = new ComponentException(
StringUtils.format("Interrupted when waiting for the task that was executing '%s' of %s", compExeType.name(),
ComponentExecutionUtils.getStringWithInfoAboutComponentAndWorkflowLowerCase(compExeRelatedInstances.compExeCtx)));
}
isDone.set(true);
if (exception != null) {
throw exception;
}
}
private void executeAsync() {
executeTask.set(threadPool.submit(new Callable<ComponentException>() {
@TaskDescription("Execute component life-cycle method")
@Override
public ComponentException call() throws Exception {
try {
execute();
} catch (ComponentException e) {
return e;
} catch (RuntimeException e) {
return new ComponentException("Unexpected error during execution", e);
} finally {
executionLatch.countDown();
}
return null;
}
}));
}
protected void execute() throws ComponentException, ComponentExecutionException {
if (compExeType == ComponentExecutionType.StartAsInit || compExeType == ComponentExecutionType.StartAsRun
|| compExeType == ComponentExecutionType.ProcessInputs) {
try {
if (compExeType == ComponentExecutionType.ProcessInputs) {
compExeRelatedInstances.component.get().processInputs();
} else {
compExeRelatedInstances.component.get().start();
}
} catch (ComponentException | RuntimeException e) {
checkForThreadInterrupted();
if (!isDone.get()) {
callCompleteStartOrProcessInputsOnFailure();
throw e;
} else {
LOG.error(StringUtils.format("Ignored error in %s of %s as the task was already considered as done "
+ "(most likely, it was interupted before): %s", compExeType.name(),
ComponentExecutionUtils.getStringWithInfoAboutComponentAndWorkflowLowerCase(compExeRelatedInstances.compExeCtx),
e.getMessage()));
}
}
} else if (compExeType == ComponentExecutionType.Reset) {
compExeRelatedInstances.component.get().reset();
} else if (compExeType == ComponentExecutionType.TearDown) {
compExeRelatedInstances.component.get().tearDown(compExeType.finalCompStateAfterTearedDown);
} else if (compExeType == ComponentExecutionType.HandleVerificationToken) {
compExeRelatedInstances.component.get().handleVerificationToken(compExeType.verificationToken);
} else if (compExeType == ComponentExecutionType.CompleteVerification) {
compExeRelatedInstances.component.get().completeStartOrProcessInputsAfterVerificationDone();
} else {
throw new ComponentExecutionException("Given component execution type not supported: " + compExeType);
}
}
private void checkForThreadInterrupted() {
if (Thread.interrupted()) {
LOG.warn(StringUtils.format("Task (thread) was interrupted after executing '%s' of %s",
compExeType.name(),
ComponentExecutionUtils.getStringWithInfoAboutComponentAndWorkflowLowerCase(compExeRelatedInstances.compExeCtx)));
}
}
private void prepareDmForComponentRunRelatedDataIfNeeded() throws ComponentExecutionException {
if (!compExeRelatedInstances.compExeStorageBridge.hasUnfinishedComponentExecution()) {
if (isTearDown) {
compExeRelatedInstances.compExeStorageBridge.addComponentExecution(
compExeRelatedInstances.compExeCtx,
DataModelConstants.TEAR_DOWN_RUN);
} else {
compExeRelatedInstances.compExeStorageBridge.addComponentExecution(compExeRelatedInstances.compExeCtx,
compExeRelatedInstances.compExeRelatedStates.executionCount.get());
}
}
}
private void handleComponentExecutionFailure(Exception e) throws ComponentException, ComponentExecutionException {
String errId = LogUtils.logExceptionWithStacktraceAndAssignUniqueMarker(LOG,
StringUtils.format("Executing %s failed",
ComponentExecutionUtils.getStringWithInfoAboutComponentAndWorkflowLowerCase(compExeRelatedInstances.compExeCtx)),
e);
String errorConsoleRow = ComponentUtils.createErrorLogMessage(e, errId);
compExeRelatedInstances.consoleRowsSender.sendLogMessageAsConsoleRow(Type.COMPONENT_ERROR, errorConsoleRow,
compExeRelatedInstances.compExeRelatedStates.executionCount.get());
WorkflowGraphNode loopDriver = compExeRelatedInstances.compExeCtx.getWorkflowGraph()
.getLoopDriver(compExeRelatedInstances.compExeCtx.getExecutionIdentifier());
if (loopDriver != null && loopDriver.isDrivingFaultTolerantLoop()) {
writeFailureOutputData();
} else {
throw new ComponentException(errId);
}
}
private void writeFailureOutputData() throws ComponentExecutionException {
Map<String, Set<Deque<WorkflowGraphHop>>> hopsToTraverseOnFailure =
compExeRelatedInstances.compExeCtx.getWorkflowGraph()
.getHopsToTraverseOnFailure(compExeRelatedInstances.compExeCtx.getExecutionIdentifier());
for (String outputName : hopsToTraverseOnFailure.keySet()) {
for (Queue<WorkflowGraphHop> hops : hopsToTraverseOnFailure.get(outputName)) {
WorkflowGraphHop firstHop = hops.poll();
NotAValueTD notAValue = typedDatumService.getFactory().createNotAValue(
UUID.randomUUID().toString(), NotAValueTD.Cause.Failure);
Long outputDmId = compExeRelatedInstances.compExeStorageBridge.addOutput(outputName,
typedDatumService.getSerializer().serialize(notAValue));
compExeRelatedInstances.compExeScheduler.addNotAValueDatumSent(notAValue.getIdentifier());
InternalTDImpl failureDatum = new InternalTDImpl(InternalTDImpl.InternalTDType.FailureInLoop,
notAValue.getIdentifier(), hops, String.valueOf(outputDmId));
compExeRelatedInstances.typedDatumToOutputWriter.writeTypedDatumToOutputConsideringOnlyCertainInputs(outputName,
failureDatum,
firstHop.getTargetExecutionIdentifier(), firstHop.getTargetInputName());
}
}
}
private void storeInputs() throws ComponentExecutionException {
for (final Entry<String, EndpointDatum> entry : compExeRelatedInstances.compCtxBridge.getEndpointDatumsForExecution().entrySet()) {
if (entry.getValue().getDataManagementId() != null) {
compExeRelatedInstances.compExeStorageBridge.addInput(entry.getKey(),
entry.getValue().getDataManagementId());
if (sendInputsProcessedToWfCtrl) {
executionQueue.enqueue(new Runnable() {
@Override
public void run() {
compExeRelatedInstances.wfExeCtrlBridgeDelegator
.onInputProcessed(endpointDatumSerializer.serializeEndpointDatum(entry.getValue()));
}
});
}
}
}
}
protected void onCancelled() {
isCancelled.set(true);
if (aquirePermissionTask.get() != null && !aquirePermissionTask.get().isDone()) {
aquirePermissionTask.get().cancel(true);
} else if (executeTask.get() != null && !executeTask.get().isDone()) {
try {
switch (compExeType) {
case StartAsInit:
case StartAsRun:
compExeRelatedInstances.component.get().onStartInterrupted(new ThreadHandler(executeTask.get()));
break;
case ProcessInputs:
compExeRelatedInstances.component.get().onProcessInputsInterrupted(new ThreadHandler(executeTask.get()));
break;
default:
break;
}
} catch (RuntimeException e) {
LOG.error(StringUtils.format("Failed to interrupt task that is executing '%s' of %s",
compExeType.name(),
ComponentExecutionUtils.getStringWithInfoAboutComponentAndWorkflowLowerCase(compExeRelatedInstances.compExeCtx)), e);
}
}
}
private void callCompleteStartOrProcessInputsOnFailure() {
try {
compExeRelatedInstances.component.get().completeStartOrProcessInputsAfterFailure();
} catch (ComponentException | RuntimeException e) {
LOG.error(StringUtils.format("Error in 'completeStartOrProcessInputsAfterFailure' after executing %s", compExeType.name(),
ComponentExecutionUtils.getStringWithInfoAboutComponentAndWorkflowUpperCase(compExeRelatedInstances.compExeCtx)), e);
}
}
protected void bindTypedDatumService(TypedDatumService newService) {
ComponentExecutor.typedDatumService = newService;
}
protected void bindComponentExecutionPermitsService(ComponentExecutionPermitsService newService) {
ComponentExecutor.componentExecutionPermitService = newService;
}
protected void bindEndpointDatumSerializer(EndpointDatumSerializer newService) {
ComponentExecutor.endpointDatumSerializer = newService;
}
protected void bindComponentExecutionStatsService(ComponentExecutionStatsService newService) {
ComponentExecutor.compExeStatsService = newService;
}
protected void bindConfigurationService(ConfigurationService newService) {
ComponentExecutor.sendInputsProcessedToWfCtrl = newService.getConfigurationSegment("general")
.getBoolean(ComponentConstants.CONFIG_KEY_ENABLE_INPUT_TAB, false);
}
}