Beispiel #1
0
    def data(self) -> ExecutionData:
        result_dict = dict()

        for name, ui_control in self.ui_controls.items():
            if isinstance(self.ui_controls[name], npyscreen.TitleMultiSelect):
                result = {self.values[name][it] for it in ui_control.value}
                result_dict[name] = result
                continue

            if isinstance(self.ui_controls[name], npyscreen.TitleCombo):
                result = (
                    self.values[name][ui_control.value]
                    if ui_control.value >= 0
                    else None
                )
                result_dict[name] = result
                continue

            if isinstance(self.ui_controls[name], npyscreen.TitleSelectOne):
                result = (
                    self.values[name][ui_control.value[0]]
                    if len(ui_control.value) > 0
                    else None
                )
                result_dict[name] = result
                continue

            result_dict[name] = ui_control.value

        if self.buttons is not DEFAULT_BUTTONS:
            assert self.selected_button
            result_dict[self.selected_button.name] = self.selected_button.value

        return ExecutionData(result_dict)
Beispiel #2
0
    def __init__(self,
                 *,
                 task: ExecutableNode,
                 execution_id: str,
                 token_id: str,
                 data: Optional[Dict],
                 workspace: Optional[Workspace] = None,
                 lane: Optional[ExecutionLaneId] = None) -> None:
        self.task = task
        self.data: T = cast(T, ExecutionData(data))
        self.execution_id = execution_id
        self.token_id = token_id

        # we need to define it before calling token_utils.parse_name. That's
        # since parse_name() will read this object using as_mapping(), that in
        # turn reads the task_name
        self.task_name = ""

        # These are None until the task is assgined to a lane
        self.workspace: Optional[Workspace] = workspace
        # The lane execution id is kept in case of clonning to allow
        # tracking from what lane dhis event came from.
        self.lane: Optional[ExecutionLaneId] = lane

        self.loop: Optional[ExecutionLoop] = None
    def test_process_data_merge(self):
        data1 = ExecutionData({
            "list": [1, 2, 3],
            "set": {"a", "b"},
            "dict": {
                "a": "a",
                "b": "b",
            },
            "number": 3,
            "to_remove": 1,
        })

        data2 = ExecutionData({
            "list": [4, 5, 6],
            "set": {"b", "c"},
            "dict": {
                "b": "b2",
                "c": "c",
            },
            "number": 1,
            "to_remove": None,
        })

        result = ExecutionData.merge(data1, data2)

        self.assertEqual(
            {
                "list": [4, 5, 6],  # same type, gets overwritten
                "set": {"a", "b", "c"},
                "dict": {
                    "a": "a",
                    "b": "b2",  # keys are taken first from the 2nd object
                    "c": "c",
                },
                "number": 1,
            },
            result.as_dict(),
        )
Beispiel #4
0
        def run_on_curses(x):
            try:
                # build the UI components on ncurses:
                ui.form = ConsoleUserTaskForm(ui_builder=ui, name=event.task.name)

                for ncurses_call in ui.ncurses_calls:
                    ncurses_call()

                # call the actual UI
                ui.form.edit()

                # put the values in the data.
                context.data = ExecutionData.merge(context.data, ui.data)

                event.future.set_result(context)
            except Exception as e:
                event.future.set_exception(e)
Beispiel #5
0
    def done_check_event(self, event: ActiveEvent, finish_mode: TaskFinishMode) -> None:
        """
        Runs the handling for an event that is considered an end
        event in its parent.
        :param _event:
        :return:
        """

        # even if there might be other edges getting out from the task,
        # this event might need to register the next event in a serial
        # loop execution.
        if event.loop_type == ActiveLoopType.COLLECTION_SERIAL:
            if event._next_event:
                event._next_event.context.data = ExecutionData.merge(
                    event._next_event.context.data, event.context.data
                )

                self.register_event(event._next_event)

        if (
            isinstance(finish_mode, OutgoingEdgesFinishMode)
            and finish_mode.outgoing_edges
            or isinstance(finish_mode, CancelTaskFinishModeException)
            and not finish_mode.root_node
        ):
            self.events.transition(
                event=event,
                state=ActiveEventState.DONE,
            )
            return None

        # if the event is a deduplication ID, we should get the next waiting event
        # (if it exists) and continue it.
        if event.deduplication_id:
            if not self.events.get_running_deduplication_event_count(event=event):
                ev = self.events.get_waiting_deduplication(event=event)

                # if we have another
                if ev:
                    # FIXME, there is a whole procedure to unmark things
                    self.events.clear_waiting_deduplication(event=ev)
                    self.events.transition(
                        event=ev,
                        state=ActiveEventState.RUNNING,
                        reason="last event for same deduplication_id was done",
                    )

                    self.events.transition(
                        event=event,
                        state=ActiveEventState.DONE,
                        reason="deduplication_id previous last event cleanup",
                    )

                    # if our deduplication node is an end state, we need to merge its
                    # content into the parent
                    if (
                        isinstance(finish_mode, OutgoingEdgesFinishMode)
                        and not finish_mode.outgoing_edges
                    ):
                        assert event.parent_id

                        # since this happens in done, we don't need to update the task_name anymore
                        self.events[event.parent_id].context.data = ExecutionData.merge(
                            self.events[event.parent_id].context.data,
                            event.context.data,
                        )

                    return None  # ==> we're done

        # we should check all the WAITING processes if they finished.
        event_count: Dict[ExecutableNode, int] = dict()
        waiting_events: List[ActiveEvent] = list()

        process = self.get_process(event)

        for id, self_event in self.events.events.items():
            if self_event.state in DONE_STATES:
                continue

            if self_event.task.process_id != process.id:
                continue

            event_count[self_event.task] = event_count.get(self_event.task, 0) + 1

            # deduplication waiting events need to be woken up only when there's no other
            # event downstream running.
            if (
                self_event.state == ActiveEventState.WAITING
                and not is_deduplication_event(self_event)
            ):
                waiting_events.append(self_event)

        for waiting_event in waiting_events:
            if event_count[waiting_event.task] > 1:
                continue

            if waiting_event.task.process_id != process.id:
                continue

            potential_predecessors = list(
                map(
                    lambda e: e.task,
                    filter(
                        lambda e: is_potential_predecessor(self, waiting_event, e),
                        self.events.events.values(),
                    ),
                )
            )

            # FIXME: it seems that WAITING events get invoked even if they shouldn't be
            # since they are deduplication events that should still be waiting
            # until the count of deduplication_id events is 0. Probably the check
            # for the deduplication events should be separate.
            if not process.are_predecessors(waiting_event.task, potential_predecessors):
                self.events.transition(
                    event=waiting_event,
                    state=ActiveEventState.RUNNING,
                    reason="no more predecessors for waiting event",
                )

        # check sub-process termination
        is_active_event_same_parent = False
        for not_done_event in self.events.excluding(ActiveEventState.DONE):
            if not_done_event.parent_id == event.parent_id and not_done_event != event:
                is_active_event_same_parent = True
                break

        # we merge into the parent event if it's an end state.
        if event.parent_id is not None and not isinstance(
            finish_mode, CancelTaskFinishModeException
        ):
            # since this happens in done, we don't need to update the task_name anymore
            self.events[event.parent_id].context.data = ExecutionData.merge(
                self.events[event.parent_id].context.data, event.context.data
            )

            if (
                not is_active_event_same_parent
                and event.parent_id == self.root_event.token_id
                and self.are_active_futures
            ):
                self.events.transition(event=event, state=ActiveEventState.DONE)
                return None  # ==> if we still have running futures, we don't kill the main process

            if not is_active_event_same_parent:
                parent_event = self.events[event.parent_id]
                self.events.transition(
                    event=parent_event,
                    state=ActiveEventState.ROUTING,
                    data=parent_event.context,
                )

        self.events.transition(event=event, state=ActiveEventState.DONE)
Beispiel #6
0
    def waiting_event(self, event: ActiveEvent, data: Any) -> None:
        # if we have deduplication, we might be waiting, even without predecessors,
        # since we need to check for events after in the graph with the same
        # deduplication id
        if self.cleanup_clustered_deduplication_events(
            event, search_state=ActiveEventState.WAITING
        ):
            return

        if (
            isinstance(event.task, ProcessTask)
            and cast(ProcessTask, event.task).deduplicate is not None
        ):
            self.events.set_waiting_deduplication(event=event)

            # if we don't have events downstream running, we're done, we need to
            # start executing.
            if not self.events.get_running_deduplication_event_count(event=event):
                self.events.clear_waiting_deduplication(event=event)
                self.events.transition(
                    event=event,
                    state=ActiveEventState.RUNNING,
                    reason="no deduplication downstream event running",
                )

            return

        # is another waiting task already present?
        other_waiting, tasks_waiting_count = self.events.get_other_task_waiting(event)

        # FIXME: deduplication events even if they land
        if other_waiting:
            new_data = ExecutionData.merge(
                other_waiting.context.data, event.context.data
            )
            other_waiting.context.data = new_data

            self.events.transition(
                event=event,
                state=ActiveEventState.DONE,
                reason="merged to other event waiting to same task",
            )
            # return  # this event is done

        # FIXME: implement a search map for the graph with the events
        potential_predecessors = list(
            map(
                lambda e: e.task,
                filter(
                    lambda e: is_potential_predecessor(self, event, e),
                    self.events.iterate(ACTIVE_STATES),
                ),
            )
        )

        # this should return for initial loops
        process = self.get_process(event)

        # if we have predecessors, we stay in waiting
        predecessor_id = process.are_predecessors(event.task, potential_predecessors)

        if predecessor_id:
            LOG.debug(f"Predecessor found for {event}. Waiting for {predecessor_id}.")
            return None

        if not other_waiting:
            self.events.transition(
                event=event,
                state=ActiveEventState.RUNNING,
                reason="no other event is running",
            )
            return

        if other_waiting.state == ActiveEventState.WAITING and tasks_waiting_count == 1:
            # FIXME: why no data mereg is happening here?
            self.events.transition(
                event=other_waiting,
                state=ActiveEventState.RUNNING,
                reason="transitioned by another waiting task",
            )
            return

        LOG.debug(f"Waiting for none, yet staying in WAITING? {event}")
        return None
Beispiel #7
0
    def execute(self, initial_data=None) -> ExecutionData:
        """
        Execute the current events. This will ensure new events are
        generating for forked events.
        """
        process = self.adhesive_process.process
        self.tasks_impl = dict()

        _validate_tasks(self, process)

        if adhesive.config.current.verify_mode:
            self._print_task_mappings()
            return ExecutionData(initial_data)

        signal.signal(signal.SIGUSR1, self.print_state)
        signal.signal(signal.SIGINT, self.kill_itself)

        # since the workspaces are allocated by lanes, we need to ensure
        # our default lane is existing.
        lane_controller.ensure_default_lane(self.adhesive_process)

        LOG.info(f"Adhesive version: {adhesive.version.current}")
        if config.current.pool_size:
            LOG.info(f"Config: Pool size: {config.current.pool_size}")
        else:
            LOG.info(
                f"Config: Pool size: {config.current.pool_size} "
                f"(defaulting to {ProcessExecutor.pool_size})"
            )
        LOG.info(
            f"Config: Parallel processing mode: {config.current.parallel_processing}"
        )
        LOG.info(f"Config: stdout: {config.current.stdout}")
        LOG.info(f"Config: temp_folder: {config.current.temp_polder}")

        # FIXME: it's getting pretty crowded
        token_id = str(uuid.uuid4())
        process_context: ExecutionToken = ExecutionToken(
            task=process,
            execution_id=self.execution_id,
            token_id=token_id,
            data=initial_data,
        )

        fake_event = ActiveEvent(
            execution_id=self.execution_id, parent_id=None, context=process_context
        )
        fake_event.token_id = ""  # FIXME: why

        root_event = self.clone_event(fake_event, process)
        self.root_event = root_event

        try:
            self.startup_processing_pool()

            self.start_message_event_listeners(root_event=root_event)
            self.execute_process_event_loop()
        finally:
            self.shutdown_processing_pool()

        return root_event.context.data