def handle_websocket(): wsock = request.environ.get("wsgi.websocket") if not wsock: abort(400, "Expected WebSocket request.") stream = Subject() query = ( stream.map(lambda x: x["term"]).filter(lambda text: len( text) > 2) # Only if text is longer than 2 characters .debounce(0.750, scheduler=scheduler) # Pause for 750ms .distinct_until_changed()) # Only if the value has changed searcher = query.flat_map_latest(lambda term: WikiFinder(term)) def send_response(x): wsock.on_next(x) def on_error(ex): print(ex) searcher.subscribe(send_response, on_error) while True: try: message = wsock.receive() # like {'term': '<current textbox val>'} obj = json.loads(message) stream.on_next(obj) except WebSocketError: break
def __init__(self): QWidget.__init__(self) self.setWindowTitle("Rx for Python rocks") self.resize(600, 600) self.setMouseTracking(True) # This Subject is used to transmit mouse moves to labels self.mousemove = Subject()
def __init__(self): super().__init__() self.resize(600, 600) self.add_events(Gdk.EventMask.POINTER_MOTION_MASK) self.connect("motion-notify-event", self.on_mouse_move) self.mousemove = Subject()
def __init__(self): super(Frame, self).__init__(None) self.SetTitle("Rx for Python rocks") self.SetSize((600, 600)) # This Subject is used to transmit mouse moves to labels self.mousemove = Subject() self.Bind(wx.EVT_MOTION, self.OnMotion)
def __init__(self) -> None: self._subject = Subject() self._scheduler = ThreadPoolScheduler(max_workers=1) obs = self._subject.pipe(ops.observe_on(self._scheduler)) self._disposable = obs \ .pipe(ops.window_with_time_or_count(count=5, timespan=datetime.timedelta(milliseconds=10_000)), ops.flat_map(lambda x: self._window_to_group(x)), ops.map(mapper=lambda x: self._retryable(data=x, delay=self._jitter_delay(jitter_interval=1000))), ops.merge_all()) \ .subscribe(self._result, self._error, self._on_complete) pass
class Window(QWidget): def __init__(self): QWidget.__init__(self) self.setWindowTitle("Rx for Python rocks") self.resize(600, 600) self.setMouseTracking(True) # This Subject is used to transmit mouse moves to labels self.mousemove = Subject() def mouseMoveEvent(self, event): self.mousemove.on_next((event.x(), event.y()))
class Window(Gtk.Window): def __init__(self): super().__init__() self.resize(600, 600) self.add_events(Gdk.EventMask.POINTER_MOTION_MASK) self.connect("motion-notify-event", self.on_mouse_move) self.mousemove = Subject() def on_mouse_move(self, widget, event): self.mousemove.on_next((event.x, event.y))
def action(scheduler: abc.SchedulerBase, state: Any = None): s: Optional[Subject[_T]] = None if is_shift: s = Subject() queue.append(s) observer.on_next(add_ref(s, ref_count_disposable)) if is_span: s = queue.pop(0) s.on_completed() create_timer()
class Frame(wx.Frame): def __init__(self): super(Frame, self).__init__(None) self.SetTitle("Rx for Python rocks") self.SetSize((600, 600)) # This Subject is used to transmit mouse moves to labels self.mousemove = Subject() self.Bind(wx.EVT_MOTION, self.OnMotion) def OnMotion(self, event): self.mousemove.on_next((event.GetX(), event.GetY()))
def publish_( mapper: Optional[Mapper[Observable[_TSource], Observable[_TResult]]] = None, ) -> Callable[ [Observable[_TSource]], Union[Observable[_TResult], ConnectableObservable[_TSource]] ]: """Returns an observable sequence that is the result of invoking the mapper on a connectable observable sequence that shares a single subscription to the underlying sequence. This operator is a specialization of Multicast using a regular Subject. Example: >>> res = publish() >>> res = publish(lambda x: x) mapper: [Optional] Selector function which can use the multicasted source sequence as many times as needed, without causing multiple subscriptions to the source sequence. Subscribers to the given source will receive all notifications of the source from the time of the subscription on. Returns: An observable sequence that contains the elements of a sequence produced by multicasting the source sequence within a mapper function. """ if mapper: def factory(scheduler: Optional[abc.SchedulerBase] = None) -> Subject[_TSource]: return Subject() return ops.multicast(subject_factory=factory, mapper=mapper) subject: Subject[_TSource] = Subject() return ops.multicast(subject=subject)
def open(self): scheduler = AsyncIOScheduler(asyncio.get_event_loop()) print("WebSocket opened") # A Subject is both an observable and observer, so we can both subscribe # to it and also feed (send) it with new values self.subject: Subject[Dict[str, str]] = Subject() # Get all distinct key up events from the input and only fire if long enough and distinct searcher = self.subject.pipe( ops.map(lambda x: x["term"]), ops.filter(lambda text: len(text) > 2 ), # Only if the text is longer than 2 characters ops.debounce(0.750), # Pause for 750ms ops.distinct_until_changed(), # Only if the value has changed ops.flat_map_latest(search_wikipedia), ) def send_response(x: HTTPResponse) -> None: self.write_message(x.body) def on_error(ex: Exception): print(ex) searcher.subscribe(on_next=send_response, on_error=on_error, scheduler=scheduler)
async def test_flat_map(): x: Subject[int] = Subject() x.pipe(ops.flat_map(mapper)).subscribe(on_next, on_error, scheduler=scheduler) x.on_next(10) await asyncio.sleep(0.1)
def main() -> None: root = Tk() root.title("Rx for Python rocks") scheduler = TkinterScheduler(root) mousemoves: Subject[Event[Any]] = Subject() frame = Frame(root, width=600, height=600) frame.bind("<Motion>", mousemoves.on_next) text = "TIME FLIES LIKE AN ARROW" def on_next(info: Tuple[tkinter.Label, "Event[Frame]", int]) -> None: label, ev, i = info label.place(x=ev.x + i * 12 + 15, y=ev.y) def label2stream( label: tkinter.Label, index: int ) -> Observable[Tuple[tkinter.Label, "Event[Frame]", int]]: return mousemoves.pipe( ops.map(lambda ev: (label, ev, index)), ops.delay(index * 0.1), ) def char2label(char: str) -> Label: return Label(frame, text=char, borderwidth=0, padx=0, pady=0) reactivex.from_(text).pipe( ops.map(char2label), ops.flat_map_indexed(label2stream), ).subscribe(on_next, on_error=print, scheduler=scheduler) frame.pack() root.mainloop()
def test_multicast_hot_4(self): c = [None] d1 = [None] d2 = [None] ex = "ex" scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(40, 0), on_next(90, 1), on_next(150, 2), on_next(210, 3), on_next(240, 4), on_next(270, 5), on_next(330, 6), on_next(340, 7), on_error(390, ex), ) s = Subject() o = scheduler.create_observer() def action0(scheduler: abc.SchedulerBase, state: Any = None): c[0] = xs.pipe(ops.multicast(s)) scheduler.schedule_absolute(50, action0) def action1(scheduler: abc.SchedulerBase, state: Any = None): d2[0] = c[0].connect(scheduler) scheduler.schedule_absolute(100, action1) def action2(scheduler: abc.SchedulerBase, state: Any = None): d1[0] = c[0].subscribe(o, scheduler) scheduler.schedule_absolute(200, action2) def action3(scheduler: abc.SchedulerBase, state: Any = None): d2[0].dispose() scheduler.schedule_absolute(300, action3) def action4(scheduler: abc.SchedulerBase, state: Any = None): d2[0] = c[0].connect(scheduler) scheduler.schedule_absolute(335, action4) scheduler.start() assert o.messages == [ on_next(210, 3), on_next(240, 4), on_next(270, 5), on_next(340, 7), on_error(390, ex), ] assert xs.subscriptions == [subscribe(100, 300), subscribe(335, 390)]
def action(scheduler: abc.SchedulerBase, state: Any = None): nonlocal n, s, window_id if _id != window_id: return n = 0 window_id += 1 new_id = window_id s.on_completed() s = Subject() observer.on_next(add_ref(s, ref_count_disposable)) create_timer(new_id)
def on_next_left(value: _TLeft) -> None: subject: Subject[_TRight] = Subject() with left.lock: _id = left_id[0] left_id[0] += 1 left_map[_id] = subject try: result = (value, add_ref(subject, rcd)) except Exception as e: log.error("*** Exception: %s" % e) for left_value in left_map.values(): left_value.on_error(e) observer.on_error(e) return observer.on_next(result) for right_value in right_map.values(): subject.on_next(right_value) md = SingleAssignmentDisposable() group.add(md) def expire(): if _id in left_map: del left_map[_id] subject.on_completed() group.remove(md) try: duration = left_duration_mapper(value) except Exception as e: for left_value in left_map.values(): left_value.on_error(e) observer.on_error(e) return def on_error(error: Exception) -> Any: for left_value in left_map.values(): left_value.on_error(error) observer.on_error(error) md.disposable = duration.pipe(ops.take(1)).subscribe( nothing, on_error, expire, scheduler=scheduler)
def test_connectable_observable_creation(self): y = [0] s2 = Subject() co2 = ConnectableObservable(reactivex.return_value(1), s2) def on_next(x): y[0] = x co2.subscribe(on_next=on_next) self.assertNotEqual(1, y[0]) co2.connect() self.assertEqual(1, y[0])
def test_multicast_hot_1(self): scheduler = TestScheduler() s: Subject[int] = Subject() xs = scheduler.create_hot_observable( on_next(40, 0), on_next(90, 1), on_next(150, 2), on_next(210, 3), on_next(240, 4), on_next(270, 5), on_next(330, 6), on_next(340, 7), on_completed(390), ) obv = scheduler.create_observer() d1: List[Optional[abc.DisposableBase]] = [None] d2: List[Optional[abc.DisposableBase]] = [None] c: List[Optional[ConnectableObservable[int]]] = [None] def action(scheduler: abc.SchedulerBase, state: Any = None): c[0] = xs.pipe(ops.multicast(s)) scheduler.schedule_absolute(50, action) def action0(scheduler: abc.SchedulerBase, state: Any = None): assert c[0] d1[0] = c[0].subscribe(obv, scheduler=scheduler) scheduler.schedule_absolute(100, action0) def action1(scheduler: abc.SchedulerBase, state: Any = None): assert c[0] d2[0] = c[0].connect(scheduler) scheduler.schedule_absolute(200, action1) def action2(scheduler: abc.SchedulerBase, state: Any = None): assert d1[0] d1[0].dispose() scheduler.schedule_absolute(300, action2) scheduler.start() assert obv.messages == [on_next(210, 3), on_next(240, 4), on_next(270, 5)] assert xs.subscriptions == [subscribe(200, 390)]
def subscribe( observer: abc.ObserverBase[Observable[_T]], scheduler: Optional[abc.SchedulerBase] = None, ): m = SerialDisposable() d = CompositeDisposable(m) r = RefCountDisposable(d) window: Subject[_T] = Subject() observer.on_next(add_ref(window, r)) def on_next(value: _T) -> None: window.on_next(value) def on_error(error: Exception) -> None: window.on_error(error) observer.on_error(error) def on_completed() -> None: window.on_completed() observer.on_completed() d.add( source.subscribe(on_next, on_error, on_completed, scheduler=scheduler)) def create_window_on_completed(): try: window_close = closing_mapper() except Exception as exception: observer.on_error(exception) return def on_completed(): nonlocal window window.on_completed() window = Subject() observer.on_next(add_ref(window, r)) create_window_on_completed() m1 = SingleAssignmentDisposable() m.disposable = m1 m1.disposable = window_close.pipe(ops.take(1)).subscribe( noop, on_error, on_completed, scheduler=scheduler) create_window_on_completed() return r
def test_dispose_on_cancel(self): loop = asyncio.get_event_loop() sub = Subject() async def using_sub(): # Since the subject never completes, this await statement # will never be complete either. We wait forever. await reactivex.using(lambda: sub, lambda s: s) async def go(): await asyncio.wait_for(using_sub(), 0.1) self.assertRaises(asyncio.TimeoutError, loop.run_until_complete, go()) # When we cancel the future (due to the time-out), the future # automatically disposes the underlying subject. self.assertTrue(sub.is_disposed)
def on_next(x: _T) -> None: nonlocal n, s, window_id new_window = False new_id = 0 s.on_next(x) n += 1 if n == count: new_window = True n = 0 window_id += 1 new_id = window_id s.on_completed() s = Subject() observer.on_next(add_ref(s, ref_count_disposable)) if new_window: create_timer(new_id)
def open(self): print("WebSocket opened") # A Subject is both an observable and observer, so we can both subscribe # to it and also feed (on_next) it with new values self.subject: Subject[Dict[str, int]] = Subject() # Now we take on our magic glasses and project the stream of bytes into # a ... query = self.subject.pipe( # 1. stream of keycodes ops.map(lambda obj: obj["keycode"]), # 2. stream of windows (10 ints long) ops.window_with_count(10, 1), # 3. stream of booleans, True or False ops.flat_map(lambda win: win.pipe(ops.sequence_equal(codes))), # 4. stream of Trues ops.filter(lambda equal: equal), ) # 4. we then subscribe to the Trues, and signal Konami! if we see any query.subscribe(on_next=lambda x: self.write_message("Konami!"))
def subscribe( observer: abc.ObserverBase[Observable[_T]], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: window_subject: Subject[_T] = Subject() d = CompositeDisposable() r = RefCountDisposable(d) observer.on_next(add_ref(window_subject, r)) def on_next_window(x: _T) -> None: window_subject.on_next(x) def on_error(err: Exception) -> None: window_subject.on_error(err) observer.on_error(err) def on_completed() -> None: window_subject.on_completed() observer.on_completed() d.add( source.subscribe(on_next_window, on_error, on_completed, scheduler=scheduler)) def on_next_observer(w: Observable[_T]): nonlocal window_subject window_subject.on_completed() window_subject = Subject() observer.on_next(add_ref(window_subject, r)) d.add( boundaries.subscribe(on_next_observer, on_error, on_completed, scheduler=scheduler)) return r
class _RxWriter(object): success_count = 0 failed_count = 0 raise_retry_exception = 0 def __init__(self) -> None: self._subject = Subject() self._scheduler = ThreadPoolScheduler(max_workers=1) obs = self._subject.pipe(ops.observe_on(self._scheduler)) self._disposable = obs \ .pipe(ops.window_with_time_or_count(count=5, timespan=datetime.timedelta(milliseconds=10_000)), ops.flat_map(lambda x: self._window_to_group(x)), ops.map(mapper=lambda x: self._retryable(data=x, delay=self._jitter_delay(jitter_interval=1000))), ops.merge_all()) \ .subscribe(self._result, self._error, self._on_complete) pass def close(self): self.__del__() def __del__(self): if self._subject: self._subject.on_completed() self._subject.dispose() self._subject = None while not self._disposable.is_disposed: time.sleep(0.1) if self._disposable: self._disposable = None pass def _window_to_group(self, value): return value.pipe( ops.to_iterable(), ops.map( lambda x: rx.from_iterable(x).pipe(ops.group_by( _group_by), ops.map(_group_to_batch), ops.merge_all())), ops.merge_all()) def _retryable(self, data: str, delay: datetime.timedelta): return rx.of(data).pipe( ops.delay(duetime=delay, scheduler=self._scheduler), ops.map(lambda x: self._http(x)), ops.catch(handler=lambda exception, source: self._retry_handler( exception, source, data)), ) def _http(self, data: str): if "gamma" in data: print('bad request[{}]: {}'.format(current_thread().name, data)) raise Exception('unexpected token: {}'.format(data)) pass if "alpha" in data: if self.raise_retry_exception < 2: self.raise_retry_exception += 1 print( 'server is temporarily unavailable to accept writes[{}]: {}' .format(current_thread().name, data)) raise Exception( 'server is temporarily unavailable to accept writes: {}'. format(data)) else: print("server is OK: {}".format(datetime.datetime.now())) pass print("http[" + current_thread().name + "]: " + data) return _Notification(data=data) def write(self, data: str): print("write[" + current_thread().name + "]") self._subject.on_next(data) pass def _result(self, data: _Notification): print("result[" + current_thread().name + "]: " + str(data)) if data.exception: self.failed_count += 1 else: self.success_count += 1 pass def _error(self, error): print(error) def _on_complete(self): self._disposable.dispose() print("on complete") def _jitter_delay(self, jitter_interval=0): _jitter = datetime.timedelta(milliseconds=random() * jitter_interval) print('jitter: {}'.format(_jitter)) return _jitter def _retry_handler(self, exception, source, data): print('retry_handler: {}, source: {}'.format(exception, source)) if "server is temporarily" in str(exception): print("RETRY!!!: {}".format(datetime.datetime.now())) return self._retryable(data, delay=datetime.timedelta(seconds=2)) notification = _Notification(exception=exception, data=data) return rx.just(notification)
def test_connectable_observable_multiple_non_overlapped_connections(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(210, 1), on_next(220, 2), on_next(230, 3), on_next(240, 4), on_next(250, 5), on_next(260, 6), on_next(270, 7), on_next(280, 8), on_next(290, 9), on_completed(300), ) subject = Subject() conn = xs.pipe(ops.multicast(subject)) c1 = [None] def action10(scheduler, state): c1[0] = conn.connect(scheduler) scheduler.schedule_absolute(225, action10) def action11(scheduler, state): c1[0].dispose() scheduler.schedule_absolute(241, action11) def action12(scheduler, state): c1[0].dispose() # idempotency test scheduler.schedule_absolute(245, action12) def action13(scheduler, state): c1[0].dispose() # idempotency test scheduler.schedule_absolute(251, action13) def action14(scheduler, state): c1[0].dispose() # idempotency test scheduler.schedule_absolute(260, action14) c2 = [None] def action20(scheduler, state): c2[0] = conn.connect(scheduler) scheduler.schedule_absolute(249, action20) def action21(scheduler, state): c2[0].dispose() scheduler.schedule_absolute(255, action21) def action22(scheduler, state): c2[0].dispose() # idempotency test scheduler.schedule_absolute(265, action22) def action23(scheduler, state): c2[0].dispose() # idempotency test scheduler.schedule_absolute(280, action23) c3 = [None] def action30(scheduler, state): c3[0] = conn.connect(scheduler) scheduler.schedule_absolute(275, action30) def action31(scheduler, state): c3[0].dispose() scheduler.schedule_absolute(295, action31) res = scheduler.start(lambda: conn) assert res.messages == [ on_next(230, 3), on_next(240, 4), on_next(250, 5), on_next(280, 8), on_next(290, 9), ] assert xs.subscriptions == [ subscribe(225, 241), subscribe(249, 255), subscribe(275, 295), ]
def main(): pygame.init() size = 500, 500 screen = pygame.display.set_mode(size) pygame.display.set_caption("Rx for Python rocks") black = 0, 0, 0 background = pygame.Surface(screen.get_size()) background.fill(black) # fill the background black background = background.convert() # prepare for faster blitting scheduler = PyGameScheduler(pygame) mousemove = Subject() color = "white" base = dirname(__file__) files = [ join(base, img % color) for img in [ "chess_rook_%s.png", "chess_knight_%s.png", "chess_bishop_%s.png", "chess_king_%s.png", "chess_queen_%s.png", "chess_bishop_%s.png", "chess_knight_%s.png", "chess_rook_%s.png", ] ] images = [pygame.image.load(image).convert_alpha() for image in files] old = [None] * len(images) draw = [] erase = [] def handle_image(i, image): imagerect = image.get_rect() def on_next(ev): imagerect.top = ev[1] imagerect.left = ev[0] + i * 32 if old[i]: erase.append(old[i]) old[i] = imagerect.copy() draw.append((image, imagerect.copy())) def on_error(err): print("Got error: %s" % err) sys.exit() mousemove.pipe(ops.delay(0.1 * i, scheduler=scheduler)).subscribe( on_next, on_error=on_error) for i, image in enumerate(images): handle_image(i, image) while True: for event in pygame.event.get(): if event.type == pygame.MOUSEMOTION: pos = event.pos mousemove.on_next(pos) elif event.type == pygame.QUIT: sys.exit() if len(draw): update = [] for rect in erase: screen.blit(background, (rect.x, rect.y), rect) update.append(rect) for image, rect in draw: screen.blit(image, rect) update.append(rect) pygame.display.update(update) pygame.display.flip() draw = [] erase = [] scheduler.run()
def action1(scheduler, state=None): s[0] = Subject()
def group_by_until_( key_mapper: Mapper[_T, _TKey], element_mapper: Optional[Mapper[_T, _TValue]], duration_mapper: Callable[[GroupedObservable[_TKey, _TValue]], Observable[Any]], subject_mapper: Optional[Callable[[], Subject[_TValue]]] = None, ) -> Callable[[Observable[_T]], Observable[GroupedObservable[_TKey, _TValue]]]: """Groups the elements of an observable sequence according to a specified key mapper function. A duration mapper function is used to control the lifetime of groups. When a group expires, it receives an OnCompleted notification. When a new element with the same key value as a reclaimed group occurs, the group will be reborn with a new lifetime request. Examples: >>> group_by_until(lambda x: x.id, None, lambda : reactivex.never()) >>> group_by_until( lambda x: x.id,lambda x: x.name, lambda grp: reactivex.never() ) >>> group_by_until( lambda x: x.id, lambda x: x.name, lambda grp: reactivex.never(), lambda: ReplaySubject() ) Args: key_mapper: A function to extract the key for each element. duration_mapper: A function to signal the expiration of a group. subject_mapper: A function that returns a subject used to initiate a grouped observable. Default mapper returns a Subject object. Returns: a sequence of observable groups, each of which corresponds to a unique key value, containing all elements that share that same key value. If a group's lifetime expires, a new group with the same key value can be created once an element with such a key value is encountered. """ element_mapper_ = element_mapper or cast(Mapper[_T, _TValue], identity) default_subject_mapper: Callable[[], Subject[_TValue]] = lambda: Subject() subject_mapper_ = subject_mapper or default_subject_mapper def group_by_until( source: Observable[_T], ) -> Observable[GroupedObservable[_TKey, _TValue]]: def subscribe( observer: abc.ObserverBase[GroupedObservable[_TKey, _TValue]], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: writers: OrderedDict[_TKey, Subject[_TValue]] = OrderedDict() group_disposable = CompositeDisposable() ref_count_disposable = RefCountDisposable(group_disposable) def on_next(x: _T) -> None: writer = None key = None try: key = key_mapper(x) except Exception as e: for wrt in writers.values(): wrt.on_error(e) observer.on_error(e) return fire_new_map_entry = False writer = writers.get(key) if not writer: try: writer = subject_mapper_() except Exception as e: for wrt in writers.values(): wrt.on_error(e) observer.on_error(e) return writers[key] = writer fire_new_map_entry = True if fire_new_map_entry: group: GroupedObservable[_TKey, _TValue] = GroupedObservable( key, writer, ref_count_disposable) duration_group: GroupedObservable[_TKey, Any] = GroupedObservable( key, writer) try: duration = duration_mapper(duration_group) except Exception as e: for wrt in writers.values(): wrt.on_error(e) observer.on_error(e) return observer.on_next(group) sad = SingleAssignmentDisposable() group_disposable.add(sad) def expire(): if writers[key]: del writers[key] writer.on_completed() group_disposable.remove(sad) def on_next(value: Any) -> None: pass def on_error(exn: Exception) -> None: for wrt in writers.values(): wrt.on_error(exn) observer.on_error(exn) def on_completed(): expire() sad.disposable = duration.pipe(ops.take(1)).subscribe( on_next, on_error, on_completed, scheduler=scheduler) try: element = element_mapper_(x) except Exception as error: for wrt in writers.values(): wrt.on_error(error) observer.on_error(error) return writer.on_next(element) def on_error(ex: Exception) -> None: for wrt in writers.values(): wrt.on_error(ex) observer.on_error(ex) def on_completed() -> None: for wrt in writers.values(): wrt.on_completed() observer.on_completed() group_disposable.add( source.subscribe(on_next, on_error, on_completed, scheduler=scheduler)) return ref_count_disposable return Observable(subscribe) return group_by_until
def subject_factory(scheduler): return Subject()
def subscribe( observer: abc.ObserverBase[Observable[_T]], scheduler_: Optional[abc.SchedulerBase] = None, ): _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton( ) timer_d = SerialDisposable() next_shift = [timeshift] next_span = [timespan] total_time = [DELTA_ZERO] queue: List[Subject[_T]] = [] group_disposable = CompositeDisposable(timer_d) ref_count_disposable = RefCountDisposable(group_disposable) def create_timer(): m = SingleAssignmentDisposable() timer_d.disposable = m is_span = False is_shift = False if next_span[0] == next_shift[0]: is_span = True is_shift = True elif next_span[0] < next_shift[0]: is_span = True else: is_shift = True new_total_time = next_span[0] if is_span else next_shift[0] ts = new_total_time - total_time[0] total_time[0] = new_total_time if is_span: next_span[0] += timeshift if is_shift: next_shift[0] += timeshift @synchronized(source.lock) def action(scheduler: abc.SchedulerBase, state: Any = None): s: Optional[Subject[_T]] = None if is_shift: s = Subject() queue.append(s) observer.on_next(add_ref(s, ref_count_disposable)) if is_span: s = queue.pop(0) s.on_completed() create_timer() m.disposable = _scheduler.schedule_relative(ts, action) queue.append(Subject()) observer.on_next(add_ref(queue[0], ref_count_disposable)) create_timer() def on_next(x: _T) -> None: with source.lock: for s in queue: s.on_next(x) @synchronized(source.lock) def on_error(e: Exception) -> None: for s in queue: s.on_error(e) observer.on_error(e) @synchronized(source.lock) def on_completed() -> None: for s in queue: s.on_completed() observer.on_completed() group_disposable.add( source.subscribe(on_next, on_error, on_completed, scheduler=scheduler_)) return ref_count_disposable