def test_asynchronous_ack_from_selectors(self):
        """
                         ack.on_next
        WaitOnLeftRight ------------> WaitOnLeftRight
        """

        sink = TObserver(immediate_continue=0)
        left_sel_sink = TObserver(immediate_continue=0)
        right_sel_sink = TObserver(immediate_continue=0)
        obs = ControlledZipObservable(
            left=self.left, right=self.right, scheduler=self.scheduler,
            request_left=lambda left, right: left <= right,
            request_right=lambda left, right: right <= left,
            match_func=lambda left, right: left == right,
        )
        obs.observe(init_observer_info(sink))
        obs.left_selector.observe(init_observer_info(left_sel_sink))
        obs.right_selector.observe(init_observer_info(right_sel_sink))
        ack1 = AckSubject()
        ack2 = AckSubject()
        self.left.on_next_single(1).subscribe(ack1)
        self.right.on_next_single(1).subscribe(ack2)
        sink.ack.on_next(continue_ack)

        self.assertFalse(ack1.has_value)

        left_sel_sink.ack.on_next(continue_ack)

        self.assertTrue(ack1.has_value)
        self.assertFalse(ack2.has_value)

        right_sel_sink.ack.on_next(continue_ack)

        self.assertTrue(ack1.has_value)
        self.assertTrue(ack2.has_value)
    def test_select_message_if_no_two_elements_match(self):
        """
                         ack.on_next
        WaitOnLeftRight ------------> WaitOnLeft
        """

        sink = TObserver(immediate_continue=0)
        left_sel_sink = TObserver(immediate_continue=0)
        right_sel_sink = TObserver(immediate_continue=0)
        ack1 = AckSubject()
        ack2 = AckSubject()
        obs = ControlledZipObservable(
            left=self.left, right=self.right, scheduler=self.scheduler,
            request_left=lambda left, right: left <= right,
            request_right=lambda left, right: right <= left,
            match_func=lambda left, right: left == right,
        )
        obs.observe(init_observer_info(sink))
        obs.left_selector.observe(init_observer_info(left_sel_sink))
        obs.right_selector.observe(init_observer_info(right_sel_sink))

        self.left.on_next_single(1).subscribe(ack1)
        self.right.on_next_single(2).subscribe(ack2)

        self.assertIsInstance(self.measure_state(obs), ControlledZipStates.WaitOnLeft)
        self.assertIsInstance(left_sel_sink.received[0], SelectCompleted)

        self.left.on_next_single(2).subscribe(ack1)

        self.assertIsInstance(self.measure_state(obs), ControlledZipStates.WaitOnLeftRight)

        for instance_obj, class_obj in zip(left_sel_sink.received, [SelectCompleted, SelectNext, SelectCompleted]):
            self.assertIsInstance(instance_obj, class_obj)
Esempio n. 3
0
    def on_next(self, elem: ElementType):
        ack_subject = AckSubject()

        if isinstance(elem, list):
            elem = elem
        else:
            try:
                elem = list(elem)
            except Exception as exc:
                self.observer.on_error(exc)
                return stop_ack

        def action(_, __):
            inner_ack = self.observer.on_next(elem)

            if isinstance(inner_ack, ContinueAck):
                ack_subject.on_next(inner_ack)
            elif isinstance(inner_ack, StopAck):
                ack_subject.on_next(inner_ack)
            elif inner_ack is None:
                raise Exception(
                    f'observer {self.observer} returned None instead of an Ack'
                )
            else:
                inner_ack.subscribe(ack_subject)

        self.schedule_func(action)
        return ack_subject
    def on_next(self, elem: ElementType):
        if self.back_pressure is None:
            if len(self.queue) < self.buffer_size:
                return_ack = continue_ack

            else:
                return_ack = AckSubject()
                self.back_pressure = return_ack

        else:
            return_ack = self.back_pressure

        with self.lock:
            len_queue = len(self.queue)
            self.queue.append(elem)

        prev_meas_state = self.state.get_measured_state(bool(len_queue))

        if isinstance(prev_meas_state, BufferedStates.WaitingState):
            last_ack = prev_meas_state.last_ack

            self._start_loop(last_ack=last_ack, next_index=1)

            return return_ack

        elif isinstance(prev_meas_state, BufferedStates.RunningState):
            return return_ack

        else:
            return stop_ack
Esempio n. 5
0
        def gen_acks():
            for subscription in subscriptions:

                # synchronize on_next call to conform with Observer convention
                with self.lock:
                    ack = subscription.next_observer.on_next(
                        materialized_values)

                if isinstance(ack, StopAck):
                    with self.lock:
                        try:
                            self.subscriptions.remove(subscription)
                        except ValueError:
                            pass

                        n_subscriptions = len(self.subscriptions)

                    if n_subscriptions == 0:
                        return stop_ack

                elif isinstance(ack, AckSubject):

                    @dataclass
                    class SubscriptionSingle(Single):
                        subscriptions: List[
                            'PublishObservableSubject.InnerSubscription']
                        subscription: 'PublishObservableSubject.InnerSubscription'
                        out_ack: AckSubject
                        lock: threading.RLock

                        def on_next(self, elem):
                            if isinstance(ack, StopAck):
                                with self.lock:
                                    try:
                                        self.subscriptions.remove(subscription)
                                    except ValueError:
                                        pass

                                    n_subscriptions = len(self.subscriptions)

                                if n_subscriptions == 0:
                                    self.out_ack.on_next(stop_ack)
                                    return

                            else:
                                self.out_ack.on_next(elem)

                    out_ack = AckSubject()
                    ack.subscribe(
                        SubscriptionSingle(
                            subscriptions=self.subscriptions,
                            subscription=subscription,
                            out_ack=out_ack,
                            lock=self.lock,
                        ))
                    yield out_ack

                else:
                    yield ack
Esempio n. 6
0
class PromiseCounter:
    def __init__(self, value, initial):
        self.lock = threading.RLock()

        self.value = value
        self.counter = initial
        self.promise = AckSubject()

    def acquire(self):
        with self.lock:
            self.counter += 1

    def countdown(self):
        with self.lock:
            self.counter -= 1
            counter = self.counter

        if counter == 0:
            self.promise.on_next(self.value)
Esempio n. 7
0
    def on_next(self, elem: ElementType):
        self.on_next_counter += 1

        try:
            values = list(elem)
        except Exception as exc:
            self.exception = exc
            return stop_ack

        self.received += values
        if self.immediate_continue is None:
            return continue_ack
        elif 0 < self.immediate_continue:
            self.immediate_continue -= 1
            return continue_ack
        else:
            self.ack = AckSubject()
            return self.ack
    def on_next(self, elem: ElementType):
        ack_subject = AckSubject()

        if isinstance(elem, list):
            elem = elem
        else:
            try:
                elem = list(elem)
            except Exception as exc:
                self.next_observer.on_error(exc)
                return

        def action(_, __):
            def subscribe_action(_, __):
                self.next_observer.on_next(elem)

            self.source_scheduler.schedule(subscribe_action)

        self.schedule_func(action)
        return ack_subject
Esempio n. 9
0
    def on_next(self, elem: ElementType):
        if not self.is_connected:

            def __(v):
                if isinstance(v, ContinueAck):
                    ack = self.underlying.on_next(elem)
                    return ack
                else:
                    return StopAck()

            new_ack = AckSubject()
            # _merge_all(_map(_observe_on(self.connected_ack, scheduler=self.scheduler), func=__)).subscribe(new_ack)
            _merge_all(_map(self.connected_ack, func=__)).subscribe(new_ack)

            self.connected_ack = new_ack
            return self.connected_ack
        elif not self.was_canceled:
            ack = self.underlying.on_next(elem)
            return ack
        else:
            return stop_ack
    def _iterate_over_batch(
        self,
        elem: ElementType,
        is_left: bool,
    ) -> Ack:
        """ This function is called once elements are received from left and right observable

        Loop over received elements. Send elements downstream if the match function applies. Request new elements
        from left, right or left and right observable.

        :param elem: either elements received form left or right observable depending on is_left argument
        :param is_left: if True then the _iterate_over_batch is called on left elements received

        :return: acknowledgment that will be returned from `on_next` called from either left or right observable
        """

        upstream_ack = AckSubject()

        iterable = iter(elem)

        # empty iterable
        try:
            val = next(iterable)
        except StopIteration:
            return continue_ack

        next_state = RawControlledZipStates.ElementReceived(
            val=val,
            is_left=is_left,
            ack=upstream_ack,
            iter=iterable,
        )

        with self.lock:
            next_state.prev_raw_state = self.state
            next_state.prev_raw_termination_state = self.termination_state
            self.state = next_state

        raw_prev_termination_state = next_state.prev_raw_termination_state
        prev_raw_state = next_state.prev_raw_state
        prev_state = prev_raw_state.get_measured_state(
            raw_prev_termination_state)

        if isinstance(prev_state, ControlledZipStates.Stopped):
            return stop_ack

        elif isinstance(prev_state, ControlledZipStates.WaitOnLeftRight):
            return upstream_ack

        elif is_left and isinstance(prev_state,
                                    ControlledZipStates.WaitOnLeft):
            left_val = val
            left_iter = iterable
            left_in_ack = upstream_ack
            right_val = prev_state.right_val
            right_iter = prev_state.right_iter
            right_in_ack = prev_state.right_ack
            other_upstream_ack = prev_state.right_ack

        elif not is_left and isinstance(prev_state,
                                        ControlledZipStates.WaitOnRight):
            left_val = prev_state.left_val
            left_iter = prev_state.left_iter
            left_in_ack = prev_state.left_ack
            right_val = val
            right_iter = iterable
            right_in_ack = upstream_ack
            other_upstream_ack = prev_state.left_ack

        else:
            raise Exception('unknown state "{}", is_left {}'.format(
                prev_state, is_left))

        # keep elements to be sent in a buffer. Only when the incoming batch of elements is iterated over, the
        # elements in the buffer are sent.
        zipped_output_buffer = []

        request_new_elem_from_left = False
        request_new_elem_from_right = False

        while True:

            # iterate over next series of SelectComplete (if exists)
            # break loop when encountering SelectNext or end of list
            while True:
                # collect SelectComplete, because they don't appear on the right side
                if isinstance(left_val, SelectCompleted):
                    zipped_output_buffer.append(left_val)

                else:
                    break

                try:
                    left_val = next(left_iter)
                except StopIteration:
                    request_new_elem_from_left = True
                    break

            # break loop when there are no more right elements
            if request_new_elem_from_right or request_new_elem_from_left:
                break

            # left send SelectNext
            stop_right = False
            while True:
                if isinstance(right_val, SelectNext):
                    # add to buffer
                    zipped_output_buffer.append(left_val)

                elif isinstance(right_val, SelectCompleted):
                    stop_right = True

                # always read next right value
                try:
                    right_val = next(right_iter)
                except StopIteration:
                    request_new_elem_from_right = True
                    break

                if stop_right:
                    break

            try:
                left_val = next(left_iter)
            except StopIteration:
                request_new_elem_from_left = True
                break

        # only send elements downstream, if there are any to be sent
        if zipped_output_buffer:
            zip_out_ack = self.observer.on_next(zipped_output_buffer)
        else:
            zip_out_ack = continue_ack

        # all elements in the left and right iterable are send downstream
        if request_new_elem_from_left and request_new_elem_from_right:
            next_state = RawControlledZipStates.WaitOnLeftRight()

        elif request_new_elem_from_left:
            next_state = RawControlledZipStates.WaitOnLeft(
                right_val=right_val,
                right_iter=right_iter,
                right_ack=right_in_ack,
            )

        elif request_new_elem_from_right:
            next_state = RawControlledZipStates.WaitOnRight(
                left_val=left_val,
                left_iter=left_iter,
                left_ack=left_in_ack,
            )

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

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

            # set next state
            self.state = next_state

        prev_termination_state = raw_prev_termination_state.get_measured_state(
        )

        def stop_active_acks():
            other_upstream_ack.on_next(stop_ack)

        # stop back-pressuring both sources, because there is no need to request elements
        # from completed source
        if isinstance(prev_termination_state, TerminationStates.LeftCompletedState) \
                and request_new_elem_from_left:

            self._signal_on_complete_or_on_error(state=next_state)
            stop_active_acks()
            return stop_ack

        # stop back-pressuring both sources, because there is no need to request elements
        # from completed source
        elif isinstance(prev_termination_state, TerminationStates.RightCompletedState) \
                and request_new_elem_from_right:

            self._signal_on_complete_or_on_error(state=next_state)
            stop_active_acks()
            return stop_ack

        # in error state, stop back-pressuring both sources
        elif isinstance(prev_termination_state, TerminationStates.ErrorState):

            self._signal_on_complete_or_on_error(state=next_state,
                                                 ex=prev_termination_state.ex)
            stop_active_acks()
            return stop_ack

        # finish connecting ack only if not in Stopped or Error state
        else:

            if request_new_elem_from_left and request_new_elem_from_right:

                # directly return ack depending on whether left or right called `iterate_over_batch`
                if is_left:
                    zip_out_ack.subscribe(right_in_ack)

                else:
                    zip_out_ack.subscribe(left_in_ack)

                return zip_out_ack

            # all elements in the left buffer are send to the observer, back-pressure only left
            elif request_new_elem_from_left:

                if is_left:
                    return zip_out_ack

                else:
                    zip_out_ack.subscribe(left_in_ack)
                    return right_in_ack

            # all elements in the left buffer are send to the observer, back-pressure only right
            elif request_new_elem_from_right:

                if is_left:
                    zip_out_ack.subscribe(right_in_ack)
                    return left_in_ack

                else:
                    return zip_out_ack

            else:
                raise Exception('illegal case')
Esempio n. 11
0
 def __post_init__(self):
     self.root_ack = AckSubject()
     self.connected_ack = self.root_ack
     self.is_connected = False
     self.was_canceled = False
     self.is_volatile = False
Esempio n. 12
0
class ConnectableObserver(Observer):
    underlying: Observer

    # scheduler: Scheduler        # todo: remote this

    def __post_init__(self):
        self.root_ack = AckSubject()
        self.connected_ack = self.root_ack
        self.is_connected = False
        self.was_canceled = False
        self.is_volatile = False

    def connect(self):
        self.is_connected = True
        self.root_ack.on_next(continue_ack)

    def on_next(self, elem: ElementType):
        if not self.is_connected:

            def __(v):
                if isinstance(v, ContinueAck):
                    ack = self.underlying.on_next(elem)
                    return ack
                else:
                    return StopAck()

            new_ack = AckSubject()
            # _merge_all(_map(_observe_on(self.connected_ack, scheduler=self.scheduler), func=__)).subscribe(new_ack)
            _merge_all(_map(self.connected_ack, func=__)).subscribe(new_ack)

            self.connected_ack = new_ack
            return self.connected_ack
        elif not self.was_canceled:
            ack = self.underlying.on_next(elem)
            return ack
        else:
            return stop_ack

    def on_error(self, err):
        self.was_canceled = True

        if not self.is_connected:

            class ResultSingle(Single):
                def on_next(_, v):
                    if isinstance(v, ContinueAck):
                        self.underlying.on_error(err)

            # _observe_on(self.connected_ack, scheduler=self.scheduler).subscribe(ResultSingle())
            self.connected_ack.subscribe(ResultSingle())
        else:
            self.underlying.on_error(err)

    def on_completed(self):
        if not self.is_connected:

            class ResultSingle(Single):
                def on_next(_, v):
                    if isinstance(v, ContinueAck):
                        self.underlying.on_completed()

            # _observe_on(self.connected_ack, scheduler=self.scheduler).subscribe(ResultSingle())
            self.connected_ack.subscribe(ResultSingle())

        else:
            self.underlying.on_completed()
Esempio n. 13
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
Esempio n. 14
0
    def __init__(self, value, initial):
        self.lock = threading.RLock()

        self.value = value
        self.counter = initial
        self.promise = AckSubject()
Esempio n. 15
0
    def _iterate_over_batch(
        self,
        elem: ElementType,
        is_left: bool,
    ) -> Ack:
        """ This function is called once elements are received from left and right observable

        Loop over received elements. Send elements downstream if the match function applies. Request new elements
        from left, right or left and right observable.

        :param elem: either elements received form left or right observable depending on is_left argument
        :param is_left: if True then the _iterate_over_batch is called on left elements received

        :return: acknowledgment that will be returned from `on_next` called from either left or right observable
        """

        upstream_ack = AckSubject()

        iterable = iter(elem)

        # empty iterable
        try:
            val = next(iterable)
        except StopIteration:
            return continue_ack

        next_state = RawControlledZipStates.ElementReceived(
            val=val,
            is_left=is_left,
            ack=upstream_ack,
            iter=iterable,
        )

        with self.lock:
            next_state.prev_raw_state = self.state
            next_state.prev_raw_termination_state = self.termination_state
            self.state = next_state

        raw_prev_termination_state = next_state.prev_raw_termination_state
        prev_raw_state = next_state.prev_raw_state
        prev_state = prev_raw_state.get_measured_state(
            raw_prev_termination_state)

        if isinstance(prev_state, ControlledZipStates.Stopped):
            return stop_ack

        elif isinstance(prev_state, ControlledZipStates.WaitOnLeftRight):
            return upstream_ack

        elif is_left and isinstance(prev_state,
                                    ControlledZipStates.WaitOnLeft):
            left_val = val
            left_iter = iterable
            left_in_ack = upstream_ack
            # last_left_sel_ack = None
            right_val = prev_state.right_val
            right_iter = prev_state.right_iter
            right_in_ack = prev_state.right_ack
            # last_right_sel_ack = prev_state.right_sel_ack
            other_upstream_ack = prev_state.right_ack

        elif not is_left and isinstance(prev_state,
                                        ControlledZipStates.WaitOnRight):
            left_val = prev_state.left_val
            left_iter = prev_state.left_iter
            left_in_ack = prev_state.left_ack
            # last_left_sel_ack = prev_state.left_sel_ack
            right_val = val
            right_iter = iterable
            right_in_ack = upstream_ack
            # last_right_sel_ack = None
            other_upstream_ack = prev_state.left_ack

        else:
            raise Exception('unknown state "{}", is_left {}'.format(
                prev_state, is_left))

        # keep elements to be sent in a buffer. Only when the incoming batch of elements is iterated over, the
        # elements in the buffer are sent.
        left_index_buffer = [
        ]  # index of the elements from the left observable that got selected
        right_index_buffer = []  # by the match function
        zipped_output_buffer = []

        request_new_elem_from_left = False
        request_new_elem_from_right = False

        # iterate over two batches of elements from left and right observable
        # until one batch is empty
        while True:

            if self.match_func(left_val, right_val):
                left_index_buffer.append(select_next)
                right_index_buffer.append(select_next)

                # add to buffer
                zipped_output_buffer.append((left_val, right_val))

            request_left = self.request_left(left_val, right_val)
            request_right = self.request_right(left_val, right_val)

            if request_left:
                left_index_buffer.append(select_completed)

                try:
                    left_val = next(left_iter)
                except StopIteration:
                    request_new_elem_from_left = True

            if request_right:
                right_index_buffer.append(select_completed)

                try:
                    right_val = next(right_iter)
                except StopIteration:
                    request_new_elem_from_right = True
                    break

            elif not request_left:
                raise Exception(
                    'either `request_left` or `request_right` condition needs to be True'
                )

            if request_new_elem_from_left:
                break

        # only send elements downstream, if there are any to be sent
        if zipped_output_buffer:
            zip_out_ack = self.observer.on_next(zipped_output_buffer)
        else:
            zip_out_ack = continue_ack

        # only send elements over the left selector observer, if there are any to be sent
        if left_index_buffer:
            left_out_ack = self.left_selector.on_next(left_index_buffer)
        else:
            left_out_ack = continue_ack  #last_left_sel_ack or continue_ack

        # only send elements over the right selector observer, if there are any to be sent
        if right_index_buffer:
            right_out_ack = self.right_selector.on_next(right_index_buffer)
        else:
            right_out_ack = continue_ack  #last_right_sel_ack or continue_ack

        # all elements in the left and right iterable are send downstream
        if request_new_elem_from_left and request_new_elem_from_right:
            next_state = RawControlledZipStates.WaitOnLeftRight(
                # right_sel=right_sel,
                # left_sel=left_sel,
            )

        elif request_new_elem_from_left:
            next_state = RawControlledZipStates.WaitOnLeft(
                right_val=right_val,
                right_iter=right_iter,
                right_ack=right_in_ack,
                # right_sel=right_sel,
                # left_sel=left_sel,
                # right_sel_ack=right_out_ack,
            )

        elif request_new_elem_from_right:
            next_state = RawControlledZipStates.WaitOnRight(
                left_val=left_val,
                left_iter=left_iter,
                left_ack=left_in_ack,
                # right_sel=right_sel,
                # left_sel=left_sel,
                # left_sel_ack=left_out_ack,
            )

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

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

            # set next state
            self.state = next_state

        prev_termination_state = raw_prev_termination_state.get_measured_state(
        )

        def stop_active_acks():
            # if isinstance(last_right_sel_ack, AckSubject):
            #     last_right_sel_ack.on_next(stop_ack)
            # elif isinstance(last_left_sel_ack, AckSubject):
            #     last_left_sel_ack.on_next(stop_ack)
            other_upstream_ack.on_next(stop_ack)

        # def merge_ack(ack1: Ack, ack2: Ack) -> Ack:
        #     if isinstance(ack2, ContinueAck) or isinstance(ack1, StopAck):
        #         return_ack = ack1
        #
        #     elif isinstance(ack2, StopAck):
        #         return ack2
        #
        #     elif isinstance(ack1, ContinueAck):
        #         return_ack = _map(ack2, lambda _: continue_ack)
        #
        #     else:
        #         return_ack = _map(source=_zip(ack1, ack2), func=lambda t2: t2[0])
        #
        #     return return_ack

        # stop back-pressuring both sources, because there is no need to request elements
        # from completed source
        if isinstance(prev_termination_state, TerminationStates.LeftCompletedState) \
                and request_new_elem_from_left:

            self._signal_on_complete_or_on_error(state=next_state)
            stop_active_acks()
            return stop_ack

        # stop back-pressuring both sources, because there is no need to request elements
        # from completed source
        elif isinstance(prev_termination_state, TerminationStates.RightCompletedState) \
                and request_new_elem_from_right:
            self._signal_on_complete_or_on_error(state=next_state)
            stop_active_acks()
            return stop_ack

        # in error state, stop back-pressuring both sources
        elif isinstance(prev_termination_state, TerminationStates.ErrorState):
            self._signal_on_complete_or_on_error(state=next_state,
                                                 exc=prev_termination_state.ex)
            stop_active_acks()
            return stop_ack

        # finish connecting ack only if not in Stopped or Error state
        else:

            if request_new_elem_from_left and request_new_elem_from_right:

                # integrate selector acks
                result_ack_left = merge_ack(zip_out_ack, left_out_ack)
                result_ack_right = merge_ack(zip_out_ack, right_out_ack)

                # directly return ack depending on whether left or right called `iterate_over_batch`
                if is_left:
                    result_ack_right.subscribe(right_in_ack)
                    return result_ack_left

                else:
                    result_ack_left.subscribe(left_in_ack)
                    return result_ack_right

            # all elements in the left buffer are send to the observer, back-pressure only left
            elif request_new_elem_from_left:

                result_out_ack = merge_ack(
                    merge_ack(zip_out_ack, left_out_ack), right_out_ack)

                if is_left:
                    return result_out_ack

                else:
                    result_out_ack.subscribe(left_in_ack)
                    return right_in_ack

            # all elements in the left buffer are send to the observer, back-pressure only right
            elif request_new_elem_from_right:

                result_out_ack = merge_ack(
                    merge_ack(zip_out_ack, left_out_ack), right_out_ack)

                if is_left:
                    result_out_ack.subscribe(right_in_ack)
                    return left_in_ack

                else:
                    return result_out_ack

            else:
                raise Exception('illegal case')
Esempio n. 16
0
 def on_next(self, elem: ElementType):
     next_state = RawMergeStates.OnRightReceived(elem=elem,
                                                 ack=AckSubject())
     return source._on_next(next_state)
Esempio n. 17
0
    def on_next(self, outer_elem: ElementType):
        if isinstance(outer_elem, list):
            outer_vals = outer_elem
        else:
            try:
                # materialize received values immediately
                outer_vals = list(outer_elem)
            except Exception as exc:
                self.on_error(exc)
                return stop_ack

        if len(outer_vals) == 0:
            return continue_ack

        # the ack that might be returned by this `on_next`
        async_upstream_ack = AckSubject()

        def subscribe_action(_, __):

            conn_observer = None

            for val in reversed(outer_vals[1:]):

                # create a new `InnerObserver` for each inner observable
                inner_observer = FlatMapInnerObserver(
                    observer_info=self.observer_info,
                    next_conn_observer=conn_observer,
                    outer_upstream_ack=async_upstream_ack,
                    state=self.state,
                    lock=self.lock,
                )

                # add ConnectableObserver to observe all inner observables simultaneously, and
                # to get control of activating one after the other
                conn_observer = ConnectableObserver(
                    underlying=inner_observer, )

                # apply selector to get inner observable (per element received)
                try:
                    inner_observable = self.func(val)

                    # for mypy to type check correctly
                    assert isinstance(self.observer_info, ObserverInfo)

                    # observe inner observable
                    disposable = inner_observable.observe(
                        self.observer_info.copy(observer=conn_observer, ))
                    self.composite_disposable.add(disposable)
                except Exception as exc:
                    self.on_error(exc)
                    return stop_ack

            # create a new `InnerObserver` for each inner observable
            inner_observer = FlatMapInnerObserver(
                observer_info=self.observer_info,
                next_conn_observer=conn_observer,
                outer_upstream_ack=async_upstream_ack,
                state=self.state,
                lock=self.lock,
            )

            next_state = RawFlatMapStates.Active()

            with self.lock:
                next_state.raw_prev_state = self.state[0]
                self.state[0] = next_state

            meas_state = next_state.raw_prev_state.get_measured_state()

            if isinstance(meas_state, FlatMapStates.Stopped):
                return stop_ack

            # for mypy to type check correctly
            assert isinstance(self.observer_info, ObserverInfo)

            try:
                # apply selector to get inner observable (per element received)
                inner_observable = self.func(outer_vals[0])

                # observe inner observable
                disposable = inner_observable.observe(
                    self.observer_info.copy(observer=inner_observer, ))
                self.composite_disposable.add(disposable)
            except Exception as exc:
                self.observer_info.observer.on_error(exc)
                return stop_ack

        if self.subscribe_scheduler.idle:
            disposable = self.subscribe_scheduler.schedule(subscribe_action)
            self.composite_disposable.add(disposable)
        else:
            subscribe_action(None, None)

        return async_upstream_ack