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, ))
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