Example #1
0
class DecisionContext:
    decider: ReplayDecider
    scheduled_activities: Dict[int,
                               Future[bytes]] = field(default_factory=dict)
    workflow_clock: ClockDecisionContext = None
    current_run_id: str = None

    def __post_init__(self):
        if not self.workflow_clock:
            self.workflow_clock = ClockDecisionContext(self.decider, self)

    async def schedule_activity_task(self,
                                     parameters: ExecuteActivityParameters):
        attr = ScheduleActivityTaskDecisionAttributes()
        attr.activity_type = parameters.activity_type
        attr.input = parameters.input
        if parameters.heartbeat_timeout_seconds > 0:
            attr.heartbeat_timeout_seconds = parameters.heartbeat_timeout_seconds
        attr.schedule_to_close_timeout_seconds = parameters.schedule_to_close_timeout_seconds
        attr.schedule_to_start_timeout_seconds = parameters.schedule_to_start_timeout_seconds
        attr.start_to_close_timeout_seconds = parameters.start_to_close_timeout_seconds
        attr.activity_id = parameters.activity_id
        if not attr.activity_id:
            attr.activity_id = self.decider.get_and_increment_next_id()
        attr.task_list = TaskList()
        attr.task_list.name = parameters.task_list

        if parameters.retry_parameters:
            attr.retry_policy = parameters.retry_parameters.to_retry_policy()

        scheduled_event_id = self.decider.schedule_activity_task(schedule=attr)
        future = self.decider.event_loop.create_future()
        self.scheduled_activities[scheduled_event_id] = future
        try:
            await future
        except CancelledError as e:
            logger.debug("Coroutine cancelled (expected)")
            raise e
        except Exception as ex:
            pass
        ex = future.exception()
        if ex:
            activity_failure = ActivityFailureException(
                scheduled_event_id, parameters.activity_type.name,
                parameters.activity_id, serialize_exception(ex))
            raise activity_failure
        assert future.done()
        raw_bytes = future.result()
        return json.loads(str(raw_bytes, "utf-8"))

    async def schedule_timer(self, seconds: int):
        future = self.decider.event_loop.create_future()

        def callback(ex: Exception):
            nonlocal future
            if ex:
                future.set_exception(ex)
            else:
                future.set_result("time-fired")

        self.decider.decision_context.create_timer(delay_seconds=seconds,
                                                   callback=callback)
        await future
        assert future.done()
        exception = future.exception()
        if exception:
            raise exception
        return

    def handle_activity_task_completed(self, event: HistoryEvent):
        attr = event.activity_task_completed_event_attributes
        if self.decider.handle_activity_task_closed(attr.scheduled_event_id):
            future = self.scheduled_activities.get(attr.scheduled_event_id)
            if future:
                self.scheduled_activities.pop(attr.scheduled_event_id)
                future.set_result(attr.result)
            else:
                raise NonDeterministicWorkflowException(
                    f"Trying to complete activity event {attr.scheduled_event_id} that is not in scheduled_activities"
                )

    def handle_activity_task_failed(self, event: HistoryEvent):
        attr = event.activity_task_failed_event_attributes
        if self.decider.handle_activity_task_closed(attr.scheduled_event_id):
            future = self.scheduled_activities.get(attr.scheduled_event_id)
            if future:
                self.scheduled_activities.pop(attr.scheduled_event_id)
                # TODO: attr.reason - what should we do with it?
                ex = deserialize_exception(attr.details)
                future.set_exception(ex)
            else:
                raise NonDeterministicWorkflowException(
                    f"Trying to complete activity event {attr.scheduled_event_id} that is not in scheduled_activities"
                )

    def handle_activity_task_timed_out(self, event: HistoryEvent):
        attr = event.activity_task_timed_out_event_attributes
        if self.decider.handle_activity_task_closed(attr.scheduled_event_id):
            future = self.scheduled_activities.get(attr.scheduled_event_id)
            if future:
                self.scheduled_activities.pop(attr.scheduled_event_id)
                ex = ActivityTaskTimeoutException(event.event_id,
                                                  attr.timeout_type,
                                                  attr.details)
                future.set_exception(ex)
            else:
                raise NonDeterministicWorkflowException(
                    f"Trying to complete activity event {attr.scheduled_event_id} that is not in scheduled_activities"
                )

    def create_timer(self, delay_seconds: int, callback: Callable):
        return self.workflow_clock.create_timer(delay_seconds, callback)

    def set_replay_current_time_milliseconds(
            self, replay_current_time_milliseconds: int):
        if replay_current_time_milliseconds < self.workflow_clock.current_time_millis(
        ):
            raise Exception("workflow clock moved back")
        self.workflow_clock.set_replay_current_time_milliseconds(
            replay_current_time_milliseconds)

    def current_time_millis(self):
        return self.workflow_clock.current_time_millis()

    def set_replaying(self, replaying: bool):
        self.workflow_clock.set_replaying(replaying)

    def is_replaying(self):
        return self.workflow_clock.is_replaying()

    def handle_timer_fired(self, attributes: TimerFiredEventAttributes):
        self.workflow_clock.handle_timer_fired(attributes)

    def handle_timer_canceled(self, event: HistoryEvent):
        self.workflow_clock.handle_timer_canceled(event)

    def set_current_run_id(self, run_id: str):
        self.current_run_id = run_id

    def random_uuid(self) -> uuid.UUID:
        return uuid.uuid3(uuid.UUID(self.current_run_id),
                          str(self.decider.get_and_increment_next_id()))

    def new_random(self) -> random.Random:
        random_uuid = self.random_uuid()
        lsb = random_uuid.bytes[:8]
        generator = random.Random()
        generator.seed(lsb, version=2)
        return generator

    def record_marker(self, marker_name: str, header: Header, details: bytes):
        marker = RecordMarkerDecisionAttributes()
        marker.marker_name = marker_name
        marker.header = header
        marker.details = details
        decision = Decision()
        decision.decision_type = DecisionType.RecordMarker
        decision.record_marker_decision_attributes = marker
        next_decision_event_id = self.decider.next_decision_event_id
        decision_id = DecisionId(DecisionTarget.MARKER, next_decision_event_id)
        self.decider.add_decision(
            decision_id,
            MarkerDecisionStateMachine(id=decision_id, decision=decision))

    def get_version(self, change_id: str, min_supported: int,
                    max_supported: int) -> int:
        return self.workflow_clock.get_version(change_id, min_supported,
                                               max_supported)

    def get_logger(self, name) -> logging.Logger:
        replay_aware_logger = logging.getLogger(name)
        make_replay_aware(replay_aware_logger)
        return replay_aware_logger
Example #2
0
class DecisionContext:
    decider: ReplayDecider
    scheduled_activities: Dict[int, Future[bytes]] = field(default_factory=dict)
    workflow_clock: ClockDecisionContext = None
    current_run_id: str = None

    def __post_init__(self):
        if not self.workflow_clock:
            self.workflow_clock = ClockDecisionContext(self.decider)

    async def schedule_activity_task(self, parameters: ExecuteActivityParameters):
        attr = ScheduleActivityTaskDecisionAttributes()
        attr.activity_type = parameters.activity_type
        attr.input = parameters.input
        if parameters.heartbeat_timeout_seconds > 0:
            attr.heartbeat_timeout_seconds = parameters.heartbeat_timeout_seconds
        attr.schedule_to_close_timeout_seconds = parameters.schedule_to_close_timeout_seconds
        attr.schedule_to_start_timeout_seconds = parameters.schedule_to_start_timeout_seconds
        attr.start_to_close_timeout_seconds = parameters.start_to_close_timeout_seconds
        attr.activity_id = parameters.activity_id
        if not attr.activity_id:
            attr.activity_id = self.decider.get_and_increment_next_id()
        attr.task_list = TaskList()
        attr.task_list.name = parameters.task_list

        # PORT: RetryParameters retryParameters = parameters.getRetryParameters();
        # PORT: if (retryParameters != null) {
        # PORT:    attributes.setRetryPolicy(retryParameters.toRetryPolicy());
        # PORT: }

        scheduled_event_id = self.decider.schedule_activity_task(schedule=attr)
        future = self.decider.event_loop.create_future()
        self.scheduled_activities[scheduled_event_id] = future
        await future
        assert future.done()
        exception = future.exception()
        if exception:
            raise exception
        raw_bytes = future.result()
        return json.loads(str(raw_bytes, "utf-8"))

    async def schedule_timer(self, seconds: int):
        future = self.decider.event_loop.create_future()

        def callback(ex: Exception):
            nonlocal future
            if ex:
                future.set_exception(ex)
            else:
                future.set_result("time-fired")

        self.decider.decision_context.create_timer(delay_seconds=seconds, callback=callback)
        await future
        assert future.done()
        exception = future.exception()
        if exception:
            raise exception
        return

    def handle_activity_task_completed(self, event: HistoryEvent):
        attr = event.activity_task_completed_event_attributes
        if self.decider.handle_activity_task_closed(attr.scheduled_event_id):
            future = self.scheduled_activities.get(attr.scheduled_event_id)
            if future:
                self.scheduled_activities.pop(attr.scheduled_event_id)
                future.set_result(attr.result)
            else:
                raise NonDeterministicWorkflowException(
                    f"Trying to complete activity event {attr.scheduled_event_id} that is not in scheduled_activities")

    def handle_activity_task_failed(self, event: HistoryEvent):
        attr = event.activity_task_failed_event_attributes
        if self.decider.handle_activity_task_closed(attr.scheduled_event_id):
            future = self.scheduled_activities.get(attr.scheduled_event_id)
            if future:
                self.scheduled_activities.pop(attr.scheduled_event_id)
                ex = ActivityTaskFailedException(attr.reason, attr.details)
                future.set_exception(ex)
            else:
                raise NonDeterministicWorkflowException(
                    f"Trying to complete activity event {attr.scheduled_event_id} that is not in scheduled_activities")

    def handle_activity_task_timed_out(self, event: HistoryEvent):
        attr = event.activity_task_timed_out_event_attributes
        if self.decider.handle_activity_task_closed(attr.scheduled_event_id):
            future = self.scheduled_activities.get(attr.scheduled_event_id)
            if future:
                self.scheduled_activities.pop(attr.scheduled_event_id)
                ex = ActivityTaskTimeoutException(event.event_id, attr.timeout_type, attr.details)
                future.set_exception(ex)
            else:
                raise NonDeterministicWorkflowException(
                    f"Trying to complete activity event {attr.scheduled_event_id} that is not in scheduled_activities")

    def create_timer(self, delay_seconds: int, callback: Callable):
        return self.workflow_clock.create_timer(delay_seconds, callback)

    def set_replay_current_time_milliseconds(self, replay_current_time_milliseconds: int):
        if replay_current_time_milliseconds < self.workflow_clock.current_time_millis():
            raise Exception("workflow clock moved back")
        self.workflow_clock.set_replay_current_time_milliseconds(replay_current_time_milliseconds)

    def current_time_millis(self):
        return self.workflow_clock.current_time_millis()

    def set_replaying(self, replaying: bool):
        self.workflow_clock.set_replaying(replaying)

    def is_replaying(self):
        return self.workflow_clock.is_replaying()

    def handle_timer_fired(self, attributes: TimerFiredEventAttributes):
        self.workflow_clock.handle_timer_fired(attributes)

    def handle_timer_canceled(self, event: HistoryEvent):
        self.workflow_clock.handle_timer_canceled(event)

    def set_current_run_id(self, run_id: str):
        self.current_run_id = run_id

    def random_uuid(self) -> uuid.UUID:
        return uuid.uuid3(uuid.UUID(self.current_run_id), str(self.decider.get_and_increment_next_id()))
    
    def new_random(self) -> random.Random:
        random_uuid = self.random_uuid()
        lsb = random_uuid.bytes[:8]
        generator = random.Random()
        generator.seed(lsb, version=2)
        return generator