예제 #1
0
    def observe(self, observer_info: ObserverInfo):
        self.on_observe(observer_info)

        if self.subscriber.subscribe_scheduler.idle:
            raise Exception(
                to_operator_exception(
                    message=
                    'observe method call should be scheduled on subscribe scheduler',
                    stack=self.stack,
                ))

        observer = DebugObserver(
            source=observer_info.observer,
            name=self.name,
            on_next_func=self.on_next,
            on_completed_func=self.on_completed,
            on_error_func=self.on_error,
            on_sync_ack=self.on_sync_ack,
            on_async_ack=self.on_async_ack,
            on_raw_ack=self.on_raw_ack,
            stack=self.stack,
        )

        # def action(_, __):
        #     observer.has_scheduled_next = True
        # self.subscriber.subscribe_scheduler.schedule(action)

        return self.source.observe(observer_info.copy(observer=observer, ))
예제 #2
0
    def _iterate_over_batch(self, elem: ElementType, is_left: bool):
        """
        this function is called on `on_next` call from left or right observable
        """

        # if elem is a list, make an iterator out of it
        iterable = iter(elem)

        # in case the zip process is started and the output observer returns a synchronous acknowledgment,
        # then `upstream_ack` is not actually needed; nevertheless, it is created here, because it makes
        # the code simpler
        upstream_ack = AckSubject()

        # prepare next raw state
        next_state = RawZipStates.ElementReceived(
            is_left=is_left,
            ack=upstream_ack,
            iter=iterable,
        )

        # synchronous update the state
        with self.lock:
            next_state.prev_raw_state = self.state
            next_state.prev_raw_termination_state = self.termination_state
            self.state = next_state

        meas_state = next_state.get_measured_state(
            next_state.prev_raw_termination_state)

        # pattern match measured state
        if isinstance(meas_state, ZipStates.Stopped):
            return stop_ack

        # wait on other observable
        elif isinstance(meas_state, ZipStates.WaitOnRight) or isinstance(
                meas_state, ZipStates.WaitOnLeft):
            return upstream_ack

        # start zipping operation
        elif isinstance(meas_state, ZipStates.ZipElements):
            if is_left:
                other_upstream_ack = meas_state.right_ack
            else:
                other_upstream_ack = meas_state.left_ack

        else:
            raise Exception(f'unknown state "{meas_state}", is_left {is_left}')

        # in case left and right batch don't match in number of elements,
        # n1 will not be None after zipping
        n1 = [None]

        def gen_zipped_elements():
            """ generate a sequence of zipped elements """
            while True:
                n1[0] = None
                try:
                    n1[0] = next(meas_state.left_iter)
                    n2 = next(meas_state.right_iter)
                except StopIteration:
                    break

                # yield self.selector(n1[0], n2)
                yield (n1[0], n2)

        try:
            # zip left and right batch
            zipped_elements = list(gen_zipped_elements())

        except Exception as exc:
            # self.observer.on_error(exc)
            other_upstream_ack.on_next(stop_ack)
            # return stop_ack
            raise Exception(
                to_operator_exception(
                    message='',
                    stack=self.stack,
                ))

        if 0 < len(zipped_elements):
            downstream_ack = self.observer.on_next(zipped_elements)
        else:
            downstream_ack = continue_ack

        if isinstance(downstream_ack, StopAck):
            other_upstream_ack.on_next(stop_ack)
            return stop_ack

        # request new element from left source
        if n1[0] is None:
            new_left_iter = None
            request_new_elem_from_left = True

            # request new element also from right source?
            try:
                val = next(meas_state.right_iter)
                new_right_iter = itertools.chain([val], meas_state.right_iter)
                request_new_elem_from_right = False

            # request new element from left and right source
            except StopIteration:
                new_right_iter = None
                request_new_elem_from_right = True

        # request new element only from right source
        else:
            new_left_iter = itertools.chain(n1, meas_state.left_iter)
            new_right_iter = None

            request_new_elem_from_left = False
            request_new_elem_from_right = True

        # define next state after zipping
        # -------------------------------

        # request new element from both sources
        if request_new_elem_from_left and request_new_elem_from_right:
            next_state = RawZipStates.WaitOnLeftRight()

        # request new element only from right source
        elif request_new_elem_from_right:

            next_state = RawZipStates.WaitOnRight(
                left_iter=new_left_iter,
                left_ack=meas_state.left_ack,
            )

        # request new element only from left source
        elif request_new_elem_from_left:

            next_state = RawZipStates.WaitOnLeft(
                right_iter=new_right_iter,
                right_ack=meas_state.right_ack,
            )

        else:
            raise Exception('after the zip operation, a new element needs '
                            'to be requested from at least one source')

        with self.lock:
            # get termination state
            raw_prev_termination_state = self.termination_state

            # set next state
            self.state = next_state

        meas_state = next_state.get_measured_state(raw_prev_termination_state)

        # stop zip observable
        # previous state cannot be "Stopped", therefore don't check previous state
        if isinstance(meas_state, ZipStates.Stopped):

            prev_termination_state = raw_prev_termination_state.get_measured_state(
            )

            if isinstance(prev_termination_state,
                          TerminationStates.ErrorState):
                self.observer.on_error(prev_termination_state.ex)
                other_upstream_ack.on_next(stop_ack)
                return stop_ack

            else:
                self.observer.on_completed()
                other_upstream_ack.on_next(stop_ack)
                return stop_ack

        # request new elements
        else:

            if request_new_elem_from_left and request_new_elem_from_right:
                downstream_ack.subscribe(other_upstream_ack)
                return downstream_ack

            elif request_new_elem_from_right:
                if is_left:
                    downstream_ack.subscribe(other_upstream_ack)
                else:
                    return downstream_ack

            elif request_new_elem_from_left:
                if is_left:
                    return downstream_ack
                else:
                    downstream_ack.subscribe(other_upstream_ack)

            else:
                raise Exception('at least one side should be back-pressured')

            return upstream_ack