def handle_exception(self, exception, decision_context): """ Handles exceptions from the event loop. The default behavior is to log it, fail the workflow, and continue. This method only gets used when in distributed mode. :param exception: :return: True if we want to exit, False otherwise """ self.logger.exception('Exception caught while running the event loop.') # Reset the decisions that we want to make; we can't schedule new activities and fail a workflow in the same # call decision_context.decisions = Layer1Decisions() decision_context.decisions.fail_workflow_execution( reason='Decider exception', details=exception.message[:3000]) return False
def handle_nextPageToken(self): # Quick test for nextPageToken try: if self.decision["nextPageToken"]: # nextPageToken should be paging if the decider is configured properly # If there is a nextPageToken # something has gone wrong and terminate the workflow execution with # extreme prejudice d = Layer1Decisions() reason="nextPageToken found, maximum_page_size of " + str(self.maximum_page_size) + " exceeded" d.fail_workflow_execution(reason) out = self.conn.respond_decision_task_completed(self.token,d._data) self.logger.info(reason) self.logger.info('respond_decision_task_completed returned %s' % out) self.token = None return False except KeyError: # No nextPageToken, so we did not exceed the maximum_page_size, continue pass
def run_decider(self): """ run one iteration of a simple decision engine """ # Poll for a decision task. tries = 0 while True: dtask = self.conn.poll_for_decision_task(self._domain, self._task_list, reverse_order=True) if dtask.get('taskToken') is not None: # This means a real decision task has arrived. break time.sleep(2) tries += 1 if tries > 10: # Give up if it's taking too long. Probably # means something is broken somewhere else. assert False, 'no decision task occurred' # Get the most recent interesting event. ignorable = ( 'DecisionTaskScheduled', 'DecisionTaskStarted', 'DecisionTaskTimedOut', ) event = None for tevent in dtask['events']: if tevent['eventType'] not in ignorable: event = tevent break # Construct the decision response. decisions = Layer1Decisions() if event['eventType'] == 'WorkflowExecutionStarted': activity_id = str(uuid.uuid1()) decisions.schedule_activity_task( activity_id, self._activity_type_name, self._activity_type_version, task_list=self._task_list, input=event['workflowExecutionStartedEventAttributes'] ['input']) elif event['eventType'] == 'ActivityTaskCompleted': decisions.complete_workflow_execution( result=event['activityTaskCompletedEventAttributes']['result']) elif event['eventType'] == 'ActivityTaskFailed': decisions.fail_workflow_execution( reason=event['activityTaskFailedEventAttributes']['reason'], details=event['activityTaskFailedEventAttributes']['details']) else: decisions.fail_workflow_execution( reason='unhandled decision task type; %r' % (event['eventType'], )) # Send the decision response. r = self.conn.respond_decision_task_completed( dtask['taskToken'], decisions=decisions._data, execution_context=None) assert r is None
def complete(self, result): decisions = self._decisions = Layer1Decisions() decisions.complete_workflow_execution(result) return self.flush()
def fail(self, reason): decisions = self._decisions = Layer1Decisions() decisions.fail_workflow_execution(reason=str(reason)[:256]) return self.flush()
def restart(self, spec, input, tags): decisions = self._decisions = Layer1Decisions() spec.restart(decisions, input, tags) return self.flush()
def __init__(self, swf_client, token, rate_limit=64): self._swf_client = swf_client self._token = token self._rate_limit = rate_limit self._decisions = Layer1Decisions() self._closed = False
def get_decision_manager(): return Layer1Decisions()
def start(self): """ Starts the event loop This method blocks and runs infinitely. Call this method to start a decisioner in distributed mode after construction. :return: """ while True: self.logger.debug('Polling') try: decision_task = self._swf.poll_for_decision_task( domain=self._swf_domain, task_list=self._swf_task_list) # No-op if we got no events if 'events' not in decision_task: self.logger.debug('Calling the no-op handler') self.handle_no_op(decision_task) continue self.logger.debug('Received an decision task') # Get full event history history = decision_task['events'] next_page_token = decision_task.get('nextPageToken') while next_page_token: self.logger.debug('Polling for additional history...') additional_history = self._swf.poll_for_decision_task( domain=self._swf_domain, task_list=self._swf_task_list, next_page_token=next_page_token) if 'events' in additional_history: history.extend(additional_history['events']) decision_task['events'] = list() next_page_token = additional_history.get('nextPageToken') except Exception as e: self.logger.exception( 'Exception while polling for a task and/or history') raise e # Populate the context try: decision_context = self._populate_decision_context( decision_task, history) # Here's where we make the context available to the decorated function self._scanner.decision_context = decision_context # Make sure that timers get them, too PTimer.decision_context = decision_context PMarker.decision_context = decision_context # And markers except Exception as e: self.logger.exception( 'Exception while parsing a workflow history') message = str(e) if message: message = message[:3000] decision_context.decisions = Layer1Decisions() decision_context.decisions.fail_workflow_execution( reason='Failed to parse workflow history', details=message) self._swf.respond_decision_task_completed( decision_task['taskToken'], decision_context.decisions._data) return # get the args and run the handle function in a thread args = (list(), dict()) if decision_context.workflow.input: self.logger.debug('Unpacking input arguments') serialized_args = self._input_data_store.get( decision_context.workflow.input) args = self._input_serializer.deserialize_input( serialized_args) # Do we have a self that we need to pass in? if self._decision_object: args[0].insert(0, self._decision_object) try: finished = False result = self._decision_function(*args[0], **args[1]) finished = True except UnfulfilledPromiseException: finished = False except Exception as e: self.logger.debug('Calling the exception handler') should_exit = self.handle_exception(e, decision_context) if should_exit: self.logger.debug( 'Exiting due to return value from handle_exception()') return # Is our workflow finished? if finished is True: swf_result = None if result: self.logger.debug('Packing workflow results') serialized_result = self._result_serializer.serialize_result( result) key = '{}-result'.format(decision_context.workflow.run_id) swf_result = self._result_data_store.put( serialized_result, key) decision_context.decisions.complete_workflow_execution( result=swf_result) self.logger.debug('Returning decisions to SWF.') try: self._swf.respond_decision_task_completed( decision_task['taskToken'], decision_context.decisions._data) except Exception: self.logger.exception( 'Error when responding with decision tasks')