All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.amazonaws.services.simpleworkflow.flow.worker.AsyncDecider Maven / Gradle / Ivy

Go to download

The Amazon Web Services SDK for Java provides Java APIs for building software on AWS' cost-effective, scalable, and reliable infrastructure products. The AWS Java SDK allows developers to code against APIs for all of Amazon's infrastructure web services (Amazon S3, Amazon EC2, Amazon SQS, Amazon Relational Database Service, Amazon AutoScaling, etc).

The newest version!
/*
 * Copyright 2012-2014 Amazon.com, Inc. or its affiliates. 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. A copy of the License is
 * located at
 * 
 * http://aws.amazon.com/apache2.0
 * 
 * or in the "license" file accompanying this file. This file 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.amazonaws.services.simpleworkflow.flow.worker;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CancellationException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.amazonaws.AmazonServiceException;
import com.amazonaws.AmazonServiceException.ErrorType;
import com.amazonaws.services.simpleworkflow.flow.DecisionContext;
import com.amazonaws.services.simpleworkflow.flow.WorkflowException;
import com.amazonaws.services.simpleworkflow.flow.core.AsyncScope;
import com.amazonaws.services.simpleworkflow.flow.core.AsyncTaskInfo;
import com.amazonaws.services.simpleworkflow.flow.core.Promise;
import com.amazonaws.services.simpleworkflow.flow.core.Task;
import com.amazonaws.services.simpleworkflow.flow.generic.ContinueAsNewWorkflowExecutionParameters;
import com.amazonaws.services.simpleworkflow.flow.generic.WorkflowDefinition;
import com.amazonaws.services.simpleworkflow.flow.generic.WorkflowDefinitionFactory;
import com.amazonaws.services.simpleworkflow.flow.worker.HistoryHelper.EventsIterator;
import com.amazonaws.services.simpleworkflow.model.DecisionTask;
import com.amazonaws.services.simpleworkflow.model.EventType;
import com.amazonaws.services.simpleworkflow.model.HistoryEvent;
import com.amazonaws.services.simpleworkflow.model.StartTimerFailedEventAttributes;
import com.amazonaws.services.simpleworkflow.model.TimerFiredEventAttributes;
import com.amazonaws.services.simpleworkflow.model.TimerStartedEventAttributes;
import com.amazonaws.services.simpleworkflow.model.WorkflowExecutionSignaledEventAttributes;
import com.amazonaws.services.simpleworkflow.model.WorkflowExecutionStartedEventAttributes;

class AsyncDecider {

    private static abstract class WorkflowAsyncScope extends AsyncScope {

        public WorkflowAsyncScope() {
            super(false, true);
        }

        public abstract Promise getOutput();

    }

    private final class WorkflowExecuteAsyncScope extends WorkflowAsyncScope {

        private final WorkflowExecutionStartedEventAttributes attributes;

        private Promise output;

        public WorkflowExecuteAsyncScope(HistoryEvent event) {
            assert event.getEventType().equals(EventType.WorkflowExecutionStarted.toString());
            this.attributes = event.getWorkflowExecutionStartedEventAttributes();
        }

        @Override
        protected void doAsync() throws Throwable {
            output = definition.execute(attributes.getInput());
        }

        @Override
        public Promise getOutput() {
            return output;
        }

    }

    private final class UnhandledSignalAsyncScope extends WorkflowAsyncScope {

        private final Promise output;

        private Throwable failure;

        private boolean cancellation;

        public UnhandledSignalAsyncScope(Promise output, Throwable failure, boolean cancellation) {
            this.output = output;
            this.failure = failure;
            this.cancellation = cancellation;
        }

        @Override
        protected void doAsync() throws Throwable {
        }

        @Override
        public Promise getOutput() {
            return output;
        }

        @Override
        public boolean isCancelRequested() {
            return super.isCancelRequested() || cancellation;
        }

        @Override
        public Throwable getFailure() {
            Throwable result = super.getFailure();
            if (failure != null) {
                result = failure;
            }
            return result;
        }

        @Override
        public boolean eventLoop() throws Throwable {
            boolean completed = super.eventLoop();
            if (completed && failure != null) {
                throw failure;
            }
            return completed;
        }

    }

    private static final Log log = LogFactory.getLog(AsyncDecider.class);

    private final WorkflowDefinitionFactory workflowDefinitionFactory;

    private WorkflowDefinition definition;

    private final HistoryHelper historyHelper;

    private final DecisionsHelper decisionsHelper;

    private final GenericActivityClientImpl activityClient;

    private final GenericWorkflowClientImpl workflowClient;

    private final WorkflowClockImpl workflowClock;

    private final DecisionContext context;

    private WorkflowAsyncScope workflowAsyncScope;

    private boolean cancelRequested;

    private WorkfowContextImpl workflowContext;

    private boolean unhandledDecision;

    private boolean completed;

    private Throwable failure;

    public AsyncDecider(WorkflowDefinitionFactory workflowDefinitionFactory, HistoryHelper historyHelper,
            DecisionsHelper decisionsHelper) throws Exception {
        this.workflowDefinitionFactory = workflowDefinitionFactory;
        this.historyHelper = historyHelper;
        this.decisionsHelper = decisionsHelper;
        this.activityClient = new GenericActivityClientImpl(decisionsHelper);
        DecisionTask decisionTask = historyHelper.getDecisionTask();
        workflowContext = new WorkfowContextImpl(decisionTask);
        this.workflowClient = new GenericWorkflowClientImpl(decisionsHelper, workflowContext);
        this.workflowClock = new WorkflowClockImpl(decisionsHelper);
        context = new DecisionContextImpl(activityClient, workflowClient, workflowClock, workflowContext);
    }

    public boolean isCancelRequested() {
        return cancelRequested;
    }

    private void handleWorkflowExecutionStarted(HistoryEvent event) {
        workflowAsyncScope = new WorkflowExecuteAsyncScope(event);
    }

    private void processEvent(HistoryEvent event, EventType eventType) throws Throwable {
        switch (eventType) {
        case ActivityTaskCanceled:
            activityClient.handleActivityTaskCanceled(event);
            break;
        case ActivityTaskCompleted:
            activityClient.handleActivityTaskCompleted(event);
            break;
        case ActivityTaskFailed:
            activityClient.handleActivityTaskFailed(event);
            break;
        case ActivityTaskStarted:
            activityClient.handleActivityTaskStarted(event.getActivityTaskStartedEventAttributes());
            break;
        case ActivityTaskTimedOut:
            activityClient.handleActivityTaskTimedOut(event);
            break;
        case ExternalWorkflowExecutionCancelRequested:
            workflowClient.handleChildWorkflowExecutionCancelRequested(event);
            break;
        case ChildWorkflowExecutionCanceled:
            workflowClient.handleChildWorkflowExecutionCanceled(event);
            break;
        case ChildWorkflowExecutionCompleted:
            workflowClient.handleChildWorkflowExecutionCompleted(event);
            break;
        case ChildWorkflowExecutionFailed:
            workflowClient.handleChildWorkflowExecutionFailed(event);
            break;
        case ChildWorkflowExecutionStarted:
            workflowClient.handleChildWorkflowExecutionStarted(event);
            break;
        case ChildWorkflowExecutionTerminated:
            workflowClient.handleChildWorkflowExecutionTerminated(event);
            break;
        case ChildWorkflowExecutionTimedOut:
            workflowClient.handleChildWorkflowExecutionTimedOut(event);
            break;
        case DecisionTaskCompleted:
            handleDecisionTaskCompleted(event);
            break;
        case DecisionTaskScheduled:
            // NOOP
            break;
        case DecisionTaskStarted:
            handleDecisionTaskStarted(event);
            break;
        case DecisionTaskTimedOut:
            // Handled in the processEvent(event)
            break;
        case ExternalWorkflowExecutionSignaled:
            workflowClient.handleExternalWorkflowExecutionSignaled(event);
            break;
        case ScheduleActivityTaskFailed:
            activityClient.handleScheduleActivityTaskFailed(event);
            break;
        case SignalExternalWorkflowExecutionFailed:
            workflowClient.handleSignalExternalWorkflowExecutionFailed(event);
            break;
        case StartChildWorkflowExecutionFailed:
            workflowClient.handleStartChildWorkflowExecutionFailed(event);
            break;
        case StartTimerFailed:
            handleStartTimerFailed(event);
            break;
        case TimerFired:
            handleTimerFired(event);
            break;
        case WorkflowExecutionCancelRequested:
            handleWorkflowExecutionCancelRequested(event);
            break;
        case WorkflowExecutionSignaled:
            handleWorkflowExecutionSignaled(event);
            break;
        case WorkflowExecutionStarted:
            handleWorkflowExecutionStarted(event);
            break;
        case WorkflowExecutionTerminated:
            // NOOP
            break;
        case WorkflowExecutionTimedOut:
            // NOOP
            break;
        case ActivityTaskScheduled:
            decisionsHelper.handleActivityTaskScheduled(event);
            break;
        case ActivityTaskCancelRequested:
            decisionsHelper.handleActivityTaskCancelRequested(event);
            break;
        case RequestCancelActivityTaskFailed:
            decisionsHelper.handleRequestCancelActivityTaskFailed(event);
            break;
        case MarkerRecorded:
            break;
        case RecordMarkerFailed:
        	break;
        case WorkflowExecutionCompleted:
            break;
        case CompleteWorkflowExecutionFailed:
            unhandledDecision = true;
            decisionsHelper.handleCompleteWorkflowExecutionFailed(event);
            break;
        case WorkflowExecutionFailed:
            break;
        case FailWorkflowExecutionFailed:
            unhandledDecision = true;
            decisionsHelper.handleFailWorkflowExecutionFailed(event);
            break;
        case WorkflowExecutionCanceled:
            break;
        case CancelWorkflowExecutionFailed:
            unhandledDecision = true;
            decisionsHelper.handleCancelWorkflowExecutionFailed(event);
            break;
        case WorkflowExecutionContinuedAsNew:
            break;
        case ContinueAsNewWorkflowExecutionFailed:
            unhandledDecision = true;
            decisionsHelper.handleContinueAsNewWorkflowExecutionFailed(event);
            break;
        case TimerStarted:
            handleTimerStarted(event);
            break;
        case TimerCanceled:
            workflowClock.handleTimerCanceled(event);
            break;
        case SignalExternalWorkflowExecutionInitiated:
            decisionsHelper.handleSignalExternalWorkflowExecutionInitiated(event);
            break;
        case RequestCancelExternalWorkflowExecutionInitiated:
            decisionsHelper.handleRequestCancelExternalWorkflowExecutionInitiated(event);
            break;
        case RequestCancelExternalWorkflowExecutionFailed:
            decisionsHelper.handleRequestCancelExternalWorkflowExecutionFailed(event);
            break;
        case StartChildWorkflowExecutionInitiated:
            decisionsHelper.handleStartChildWorkflowExecutionInitiated(event);
            break;
        case CancelTimerFailed:
            decisionsHelper.handleCancelTimerFailed(event);
        }
    }

    private void eventLoop(HistoryEvent event) throws Throwable {
        if (completed) {
            return;
        }
        try {
            completed = workflowAsyncScope.eventLoop();
        }
        catch (CancellationException e) {
            if (!cancelRequested) {
                failure = e;
            }
            completed = true;
        }
        catch (Throwable e) {
            failure = e;
            completed = true;
        }
    }

    private void completeWorkflow() {
        if (completed && !unhandledDecision) {
            if (failure != null) {
                decisionsHelper.failWorkflowExecution(failure);
            }
            else if (cancelRequested) {
                decisionsHelper.cancelWorkflowExecution();
            }
            else {
                ContinueAsNewWorkflowExecutionParameters continueAsNewOnCompletion = workflowContext.getContinueAsNewOnCompletion();
                if (continueAsNewOnCompletion != null) {
                    decisionsHelper.continueAsNewWorkflowExecution(continueAsNewOnCompletion);
                }
                else {
                    Promise output = workflowAsyncScope.getOutput();
                    if (output != null && output.isReady()) {
                        String workflowOutput = output.get();
                        decisionsHelper.completeWorkflowExecution(workflowOutput);
                    }
                    else {
                        decisionsHelper.completeWorkflowExecution(null);
                    }
                }
            }
        }
    }

    private void handleDecisionTaskStarted(HistoryEvent event) throws Throwable {
    }

    private void handleWorkflowExecutionCancelRequested(HistoryEvent event) throws Throwable {
        workflowContext.setCancelRequested(true);
        workflowAsyncScope.cancel(new CancellationException());
        cancelRequested = true;
    }

    private void handleStartTimerFailed(HistoryEvent event) {
        StartTimerFailedEventAttributes attributes = event.getStartTimerFailedEventAttributes();
        String timerId = attributes.getTimerId();
        if (timerId.equals(DecisionsHelper.FORCE_IMMEDIATE_DECISION_TIMER)) {
            return;
        }
        workflowClock.handleStartTimerFailed(event);
    }

    private void handleTimerFired(HistoryEvent event) throws Throwable {
        TimerFiredEventAttributes attributes = event.getTimerFiredEventAttributes();
        String timerId = attributes.getTimerId();
        if (timerId.equals(DecisionsHelper.FORCE_IMMEDIATE_DECISION_TIMER)) {
            return;
        }
        workflowClock.handleTimerFired(event.getEventId(), attributes);
    }

    private void handleTimerStarted(HistoryEvent event) {
        TimerStartedEventAttributes attributes = event.getTimerStartedEventAttributes();
        String timerId = attributes.getTimerId();
        if (timerId.equals(DecisionsHelper.FORCE_IMMEDIATE_DECISION_TIMER)) {
            return;
        }
        decisionsHelper.handleTimerStarted(event);
    }

    private void handleWorkflowExecutionSignaled(HistoryEvent event) throws Throwable {
        assert (event.getEventType().equals(EventType.WorkflowExecutionSignaled.toString()));
        final WorkflowExecutionSignaledEventAttributes signalAttributes = event.getWorkflowExecutionSignaledEventAttributes();
        if (completed) {
            workflowAsyncScope = new UnhandledSignalAsyncScope(workflowAsyncScope.getOutput(), workflowAsyncScope.getFailure(),
                    workflowAsyncScope.isCancelRequested());
            completed = false;
        }
        // This task is attached to the root context of the workflowAsyncScope
        new Task(workflowAsyncScope) {

            @Override
            protected void doExecute() throws Throwable {
                definition.signalRecieved(signalAttributes.getSignalName(), signalAttributes.getInput());
            }

        };
    }

    private void handleDecisionTaskCompleted(HistoryEvent event) {
        decisionsHelper.handleDecisionCompletion(event.getDecisionTaskCompletedEventAttributes());
    }

    public void decide() throws Exception {
        try {
            definition = workflowDefinitionFactory.getWorkflowDefinition(context);
            if (definition == null) {
                throw new IllegalStateException("Unknown workflow type: " + context.getWorkflowContext().getWorkflowType());
            }
            long lastNonReplayedEventId = historyHelper.getLastNonReplayEventId();
            // Buffer events until the next DecisionTaskStarted and then process them
            // setting current time to the time of DecisionTaskStarted event
            EventsIterator eventsIterator = historyHelper.getEvents();
            List reordered = null;
            do {
                List decisionStartToCompletionEvents = new ArrayList();
                List decisionCompletionToStartEvents = new ArrayList();
                boolean concurrentToDecision = true;
                int lastDecisionIndex = -1;
                while (eventsIterator.hasNext()) {
                    HistoryEvent event = eventsIterator.next();
                    EventType eventType = EventType.valueOf(event.getEventType());
                    if (eventType == EventType.DecisionTaskCompleted) {
                        decisionsHelper.setWorkflowContextData(event.getDecisionTaskCompletedEventAttributes().getExecutionContext());
                        concurrentToDecision = false;
                    }
                    else if (eventType == EventType.DecisionTaskStarted) {
                        decisionsHelper.handleDecisionTaskStartedEvent();

                        if (!eventsIterator.isNextDecisionTimedOut()) {
                            long replayCurrentTimeMilliseconds = event.getEventTimestamp().getTime();
                            workflowClock.setReplayCurrentTimeMilliseconds(replayCurrentTimeMilliseconds);
                            break;
                        }
                    }
                    else if (eventType == EventType.DecisionTaskScheduled || eventType == EventType.DecisionTaskTimedOut) {
                        // skip
                    }
                    else {
                        if (concurrentToDecision) {
                            decisionStartToCompletionEvents.add(event);
                        }
                        else {
                            if (isDecisionEvent(eventType)) {
                                lastDecisionIndex = decisionCompletionToStartEvents.size();
                            }
                            decisionCompletionToStartEvents.add(event);
                        }
                    }
                }
                int size = decisionStartToCompletionEvents.size() + decisionStartToCompletionEvents.size();
                // Reorder events to correspond to the order that decider sees them. 
                // The main difference is that events that were added during decision task execution 
                // should be processed after events that correspond to the decisions. 
                // Otherwise the replay is going to break.
                reordered = new ArrayList(size);
                // First are events that correspond to the previous task decisions
                if (lastDecisionIndex >= 0) {
                    reordered.addAll(decisionCompletionToStartEvents.subList(0, lastDecisionIndex + 1));
                }
                // Second are events that were added during previous task execution
                reordered.addAll(decisionStartToCompletionEvents);
                // The last are events that were added after previous task completion
                if (decisionCompletionToStartEvents.size() > lastDecisionIndex + 1) {
                    reordered.addAll(decisionCompletionToStartEvents.subList(lastDecisionIndex + 1,
                            decisionCompletionToStartEvents.size()));
                }
                for (HistoryEvent event : reordered) {
                    if (event.getEventId() >= lastNonReplayedEventId) {
                        workflowClock.setReplaying(false);
                    }
                    EventType eventType = EventType.valueOf(event.getEventType());
                    processEvent(event, eventType);
                    eventLoop(event);
                }
                completeWorkflow();

            }
            while (eventsIterator.hasNext());
            if (unhandledDecision) {
                unhandledDecision = false;
                completeWorkflow();
            }
        }
        catch (AmazonServiceException e) {
            // We don't want to fail workflow on service exceptions like 500 or throttling
            // Throwing from here drops decision task which is OK as it is rescheduled after its StartToClose timeout.
            if (e.getErrorType() == ErrorType.Client && !"ThrottlingException".equals(e.getErrorCode())) {
                if (log.isErrorEnabled()) {
                    log.error("Failing workflow " + workflowContext.getWorkflowExecution(), e);
                }
                decisionsHelper.failWorkflowDueToUnexpectedError(e);
            }
            else {
                throw e;
            }
        }
        catch (Throwable e) {
            if (log.isErrorEnabled()) {
                log.error("Failing workflow " + workflowContext.getWorkflowExecution(), e);
            }
            decisionsHelper.failWorkflowDueToUnexpectedError(e);
        }
        finally {
            try {
                decisionsHelper.setWorkflowContextData(definition.getWorkflowState());
            }
            catch (WorkflowException e) {
                decisionsHelper.setWorkflowContextData(e.getDetails());
            }
            catch (Throwable e) {
                decisionsHelper.setWorkflowContextData(e.getMessage());
            }
            workflowDefinitionFactory.deleteWorkflowDefinition(this.definition);
        }
    }

    private boolean isDecisionEvent(EventType eventType) {
        switch (eventType) {
        case ActivityTaskScheduled:
        case ScheduleActivityTaskFailed:
        case ActivityTaskCancelRequested:
        case RequestCancelActivityTaskFailed:
        case MarkerRecorded:
        case RecordMarkerFailed:
        case WorkflowExecutionCompleted:
        case CompleteWorkflowExecutionFailed:
        case WorkflowExecutionFailed:
        case FailWorkflowExecutionFailed:
        case WorkflowExecutionCanceled:
        case CancelWorkflowExecutionFailed:
        case WorkflowExecutionContinuedAsNew:
        case ContinueAsNewWorkflowExecutionFailed:
        case TimerStarted:
        case StartTimerFailed:
        case TimerCanceled:
        case CancelTimerFailed:
        case SignalExternalWorkflowExecutionInitiated:
        case SignalExternalWorkflowExecutionFailed:
        case RequestCancelExternalWorkflowExecutionInitiated:
        case RequestCancelExternalWorkflowExecutionFailed:
        case StartChildWorkflowExecutionInitiated:
        case StartChildWorkflowExecutionFailed:
            return true;
        default:
            return false;
        }
    }

    public List getAsynchronousThreadDump() {
        checkAsynchronousThreadDumpState();
        return workflowAsyncScope.getAsynchronousThreadDump();
    }

    public String getAsynchronousThreadDumpAsString() {
        checkAsynchronousThreadDumpState();
        return workflowAsyncScope.getAsynchronousThreadDumpAsString();
    }

    private void checkAsynchronousThreadDumpState() {
        if (workflowAsyncScope == null) {
            throw new IllegalStateException("workflow hasn't started yet");
        }
        if (decisionsHelper.isWorkflowFailed()) {
            throw new IllegalStateException("Cannot get AsynchronousThreadDump of a failed workflow",
                    decisionsHelper.getWorkflowFailureCause());
        }
    }

    public DecisionsHelper getDecisionsHelper() {
        return decisionsHelper;
    }

    public WorkflowDefinition getWorkflowDefinition() {
        return definition;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy