def test_map_disposeinsidemapper(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(100, 1), on_next(200, 2), on_next(500, 3), on_next(600, 4) ) results = scheduler.create_observer() d = SerialDisposable() invoked = [0] def projection(x, *args, **kw): invoked[0] += 1 if scheduler.clock > 400: d.dispose() return x d.disposable = xs.pipe(map(projection)).subscribe(results, scheduler) def action(scheduler, state): return d.dispose() scheduler.schedule_absolute(ReactiveTest.disposed, action) scheduler.start() assert results.messages == [on_next(100, 1), on_next(200, 2)] assert xs.subscriptions == [ReactiveTest.subscribe(0, 500)] assert invoked[0] == 3
def subscribe( observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: scheduler = scheduler or CurrentThreadScheduler.singleton() subscription = SerialDisposable() cancelable = SerialDisposable() def action(scheduler: abc.SchedulerBase, state: Optional[Exception] = None) -> None: try: source = next(sources_) except StopIteration: observer.on_completed() return # Allow source to be a factory method taking an error source = source(state) if callable(source) else source current = (reactivex.from_future(source) if isinstance( source, Future) else source) d = SingleAssignmentDisposable() subscription.disposable = d def on_resume(state: Optional[Exception] = None) -> None: scheduler.schedule(action, state) d.disposable = current.subscribe(observer.on_next, on_resume, on_resume, scheduler=scheduler) cancelable.disposable = scheduler.schedule(action) return CompositeDisposable(subscription, cancelable)
def test_map_with_index_dispose_inside_mapper(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( on_next(100, 4), on_next(200, 3), on_next(500, 2), on_next(600, 1) ) invoked = [0] results = scheduler.create_observer() d = SerialDisposable() def projection(x, index): invoked[0] += 1 if scheduler.clock > 400: d.dispose() return x + index * 10 d.disposable = xs.pipe(map_indexed(projection)).subscribe(results) def action(scheduler, state): return d.dispose() scheduler.schedule_absolute(disposed, action) scheduler.start() assert results.messages == [on_next(100, 4), on_next(200, 13)] assert xs.subscriptions == [subscribe(0, 500)] assert invoked[0] == 3
def test_starmap_dispose_inside_mapper(self): scheduler = TestScheduler() xs = scheduler.create_hot_observable( # 100 create on_next(110, (1, 10)), # 200 subscribe on_next(210, (2, 20)), on_next(310, (3, 30)), on_next(410, (4, 40)), ) results = scheduler.create_observer() d = SerialDisposable() invoked = [0] def mapper(x, y): invoked[0] += 1 if scheduler._clock > 250: d.dispose() return x + y d.disposable = xs.pipe(ops.starmap(mapper)).subscribe(results, scheduler) def action(scheduler, state): return d.dispose() scheduler.schedule_absolute(ReactiveTest.disposed, action) scheduler.start() assert results.messages == [on_next(110, 11), on_next(210, 22)] assert xs.subscriptions == [ReactiveTest.subscribe(0, 310)] assert invoked[0] == 3
def subscribe( observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: d1 = SingleAssignmentDisposable() subscription = SerialDisposable() subscription.disposable = d1 def on_error(exception: Exception) -> None: try: result = handler(exception, source) except Exception as ex: # By design. pylint: disable=W0703 observer.on_error(ex) return result = (reactivex.from_future(result) if isinstance( result, Future) else result) d = SingleAssignmentDisposable() subscription.disposable = d d.disposable = result.subscribe(observer, scheduler=scheduler) d1.disposable = source.subscribe(observer.on_next, on_error, observer.on_completed, scheduler=scheduler) return subscription
def subscribe( observer: abc.ObserverBase[_T], scheduler_: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton( ) switched = [False] _id = [0] original = SingleAssignmentDisposable() subscription = SerialDisposable() timer = SerialDisposable() subscription.disposable = original def create_timer() -> None: my_id = _id[0] def action(scheduler: abc.SchedulerBase, state: Any = None): switched[0] = _id[0] == my_id timer_wins = switched[0] if timer_wins: subscription.disposable = obs.subscribe( observer, scheduler=scheduler) if isinstance(duetime, datetime): timer.disposable = _scheduler.schedule_absolute( duetime, action) else: timer.disposable = _scheduler.schedule_relative( duetime, action) create_timer() def on_next(value: _T) -> None: send_wins = not switched[0] if send_wins: _id[0] += 1 observer.on_next(value) create_timer() def on_error(error: Exception) -> None: on_error_wins = not switched[0] if on_error_wins: _id[0] += 1 observer.on_error(error) def on_completed() -> None: on_completed_wins = not switched[0] if on_completed_wins: _id[0] += 1 observer.on_completed() original.disposable = source.subscribe(on_next, on_error, on_completed, scheduler=scheduler_) return CompositeDisposable(subscription, timer)
def subscribe( observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: delays = CompositeDisposable() at_end = [False] def done(): if at_end[0] and delays.length == 0: observer.on_completed() subscription = SerialDisposable() def start(): def on_next(x: _T) -> None: try: assert mapper delay = mapper(x) except Exception as error: # pylint: disable=broad-except observer.on_error(error) return d = SingleAssignmentDisposable() delays.add(d) def on_next(_: Any) -> None: observer.on_next(x) delays.remove(d) done() def on_completed() -> None: observer.on_next(x) delays.remove(d) done() d.disposable = delay.subscribe(on_next, observer.on_error, on_completed, scheduler=scheduler) def on_completed() -> None: at_end[0] = True subscription.dispose() done() subscription.disposable = source.subscribe(on_next, observer.on_error, on_completed, scheduler=scheduler) if not sub_delay: start() else: subscription.disposable = sub_delay.subscribe( lambda _: start(), observer.on_error, start) return CompositeDisposable(subscription, delays)
def __init__(self, scheduler: abc.SchedulerBase, observer: abc.ObserverBase[_T_in]) -> None: super().__init__() self.scheduler = scheduler self.observer = observer self.lock = threading.RLock() self.is_acquired = False self.has_faulted = False self.queue: List[typing.Action] = [] self.disposable = SerialDisposable()
def subscribe( observer: abc.ObserverBase[_T], _: Optional[abc.SchedulerBase] = None ): m = SingleAssignmentDisposable() d = SerialDisposable() d.disposable = m def action(scheduler: abc.SchedulerBase, state: Optional[Any] = None): d.disposable = ScheduledDisposable( scheduler, source.subscribe(observer) ) m.disposable = scheduler.schedule(action) return d
def test_mutabledisposable_dispose(): disp = [False] m = SerialDisposable() def action(): disp[0] = True d = Disposable(action) m.disposable = d assert d == m.disposable assert not disp[0] m.dispose() assert disp[0] assert m.disposable == None
def test_return_disposed_after_next(self): scheduler = TestScheduler() d = SerialDisposable() xs = reactivex.return_value(42) results = scheduler.create_observer() def action(scheduler, state): def on_next(x): d.dispose() results.on_next(x) def on_error(e): results.on_error(e) def on_completed(): results.on_completed() d.disposable = xs.subscribe(on_next, on_error, on_completed, scheduler=scheduler) return d.disposable scheduler.schedule_absolute(100, action) scheduler.start() assert results.messages == [on_next(100, 42)]
def subscribe( observer: abc.ObserverBase[_T], scheduler_: Optional[abc.SchedulerBase] = None ) -> abc.DisposableBase: _scheduler = scheduler_ or CurrentThreadScheduler.singleton() subscription = SerialDisposable() cancelable = SerialDisposable() last_exception = None is_disposed = False def action(scheduler: abc.SchedulerBase, state: Any = None) -> None: def on_error(exn: Exception) -> None: nonlocal last_exception last_exception = exn cancelable.disposable = _scheduler.schedule(action) if is_disposed: return try: current = next(sources_) except StopIteration: if last_exception: observer.on_error(last_exception) else: observer.on_completed() except Exception as ex: # pylint: disable=broad-except observer.on_error(ex) else: d = SingleAssignmentDisposable() subscription.disposable = d d.disposable = current.subscribe( observer.on_next, on_error, observer.on_completed, scheduler=scheduler_, ) cancelable.disposable = _scheduler.schedule(action) def dispose() -> None: nonlocal is_disposed is_disposed = True return CompositeDisposable(subscription, cancelable, Disposable(dispose))
def subscribe( observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: inner_subscription = SerialDisposable() has_latest = [False] is_stopped = [False] latest = [0] def on_next(inner_source: Union[Observable[_T], "Future[_T]"]) -> None: nonlocal source d = SingleAssignmentDisposable() with source.lock: latest[0] += 1 _id = latest[0] has_latest[0] = True inner_subscription.disposable = d # Check if Future or Observable if isinstance(inner_source, Future): obs = from_future(inner_source) else: obs = inner_source def on_next(x: Any) -> None: if latest[0] == _id: observer.on_next(x) def on_error(e: Exception) -> None: if latest[0] == _id: observer.on_error(e) def on_completed() -> None: if latest[0] == _id: has_latest[0] = False if is_stopped[0]: observer.on_completed() d.disposable = obs.subscribe( on_next, on_error, on_completed, scheduler=scheduler ) def on_completed() -> None: is_stopped[0] = True if not has_latest[0]: observer.on_completed() subscription = source.subscribe( on_next, observer.on_error, on_completed, scheduler=scheduler ) return CompositeDisposable(subscription, inner_subscription)
def test_filter_dispose_in_predicate(self): scheduler = TestScheduler() invoked = [0] ys = [None] xs = scheduler.create_hot_observable( on_next(110, 1), on_next(180, 2), on_next(230, 3), on_next(270, 4), on_next(340, 5), on_next(380, 6), on_next(390, 7), on_next(450, 8), on_next(470, 9), on_next(560, 10), on_next(580, 11), on_completed(600), on_next(610, 12), on_error(620, "ex"), on_completed(630), ) results = scheduler.create_observer() d = SerialDisposable() def action(scheduler, state): def predicate(x): invoked[0] += 1 if x == 8: d.dispose() return is_prime(x) ys[0] = xs.pipe(filter(predicate)) return ys[0] scheduler.schedule_absolute(created, action) def action1(scheduler, state): d.disposable = ys[0].subscribe(results) scheduler.schedule_absolute(subscribed, action1) def action2(scheduler, state): d.dispose() scheduler.schedule_absolute(disposed, action2) scheduler.start() assert results.messages == [on_next(230, 3), on_next(340, 5), on_next(390, 7)] assert xs.subscriptions == [subscribe(200, 450)] assert invoked[0] == 6
def test_mutabledisposable_replacebeforedispose(): disp1 = [False] disp2 = [False] m = SerialDisposable() def action1(): disp1[0] = True d1 = Disposable(action1) m.disposable = d1 assert d1 == m.disposable assert not disp1[0] def action2(): disp2[0] = True d2 = Disposable(action2) m.disposable = d2 assert d2 == m.disposable assert disp1[0] assert not disp2[0]
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 subscribe( observer: abc.ObserverBase[_T], scheduler_: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton( ) cancelable = SerialDisposable() has_value = [False] value: List[_T] = [cast(_T, None)] _id: List[int] = [0] def on_next(x: _T) -> None: has_value[0] = True value[0] = x _id[0] += 1 current_id = _id[0] d = SingleAssignmentDisposable() cancelable.disposable = d def action(scheduler: abc.SchedulerBase, state: Any = None) -> None: if has_value[0] and _id[0] == current_id: observer.on_next(value[0]) has_value[0] = False d.disposable = _scheduler.schedule_relative(duetime, action) def on_error(exception: Exception) -> None: cancelable.dispose() observer.on_error(exception) has_value[0] = False _id[0] += 1 def on_completed() -> None: cancelable.dispose() if has_value[0]: observer.on_next(value[0]) observer.on_completed() has_value[0] = False _id[0] += 1 subscription = source.subscribe(on_next, on_error, on_completed, scheduler=scheduler_) return CompositeDisposable(subscription, cancelable)
def test_mutabledisposable_replaceafterdispose(): disp1 = [False] disp2 = [False] m = SerialDisposable() m.dispose() def action1(): disp1[0] = True d1 = Disposable(action1) m.disposable = d1 assert m.disposable == None assert disp1[0] def action2(): disp2[0] = True d2 = Disposable(action2) m.disposable = d2 assert m.disposable == None assert disp2[0]
def subscribe( observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: scheduler = scheduler or ImmediateScheduler.singleton() queue: List[Observable[_T]] = [] m = SerialDisposable() d = CompositeDisposable(m) active_count = 0 is_acquired = False def ensure_active(): nonlocal is_acquired is_owner = False if queue: is_owner = not is_acquired is_acquired = True def action(scheduler: abc.SchedulerBase, state: Any = None): nonlocal is_acquired, active_count if queue: work = queue.pop(0) else: is_acquired = False return sad = SingleAssignmentDisposable() d.add(sad) def on_next(value: _T) -> None: nonlocal active_count observer.on_next(value) result = None try: result = mapper(value) except Exception as ex: observer.on_error(ex) return queue.append(result) active_count += 1 ensure_active() def on_complete() -> None: nonlocal active_count d.remove(sad) active_count -= 1 if active_count == 0: observer.on_completed() sad.disposable = work.subscribe(on_next, observer.on_error, on_complete, scheduler=scheduler) m.disposable = scheduler.schedule(action) if is_owner: m.disposable = scheduler.schedule(action) queue.append(source) active_count += 1 ensure_active() return d
def subscribe( observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: subscription = SerialDisposable() timer = SerialDisposable() original = SingleAssignmentDisposable() subscription.disposable = original switched = False _id = [0] def set_timer(timeout: Observable[Any]) -> None: my_id = _id[0] def timer_wins(): return _id[0] == my_id d = SingleAssignmentDisposable() timer.disposable = d def on_next(x: Any) -> None: if timer_wins(): subscription.disposable = other_.subscribe( observer, scheduler=scheduler) d.dispose() def on_error(e: Exception) -> None: if timer_wins(): observer.on_error(e) def on_completed() -> None: if timer_wins(): subscription.disposable = other_.subscribe(observer) d.disposable = timeout.subscribe(on_next, on_error, on_completed, scheduler=scheduler) set_timer(first_timeout_) def observer_wins(): res = not switched if res: _id[0] += 1 return res def on_next(x: _T) -> None: if observer_wins(): observer.on_next(x) timeout = None try: timeout = (timeout_duration_mapper(x) if timeout_duration_mapper else reactivex.never()) except Exception as e: observer.on_error(e) return set_timer(timeout) def on_error(error: Exception) -> None: if observer_wins(): observer.on_error(error) def on_completed() -> None: if observer_wins(): observer.on_completed() original.disposable = source.subscribe(on_next, on_error, on_completed, scheduler=scheduler) return CompositeDisposable(subscription, timer)
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
def subscribe(observer: abc.ObserverBase[_T], scheduler_: Optional[abc.SchedulerBase] = None): nonlocal duetime _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton() if isinstance(duetime, datetime): duetime_ = _scheduler.to_datetime(duetime) - _scheduler.now else: duetime_ = _scheduler.to_timedelta(duetime) cancelable = SerialDisposable() exception: Optional[Exception] = None active = [False] running = [False] queue: List[Timestamp[Notification[_T]]] = [] def on_next(notification: Timestamp[Notification[_T]]) -> None: nonlocal exception should_run = False with source.lock: if isinstance(notification.value, OnError): del queue[:] queue.append(notification) exception = notification.value.exception should_run = not running[0] else: queue.append( Timestamp( value=notification.value, timestamp=notification.timestamp + duetime_, )) should_run = not active[0] active[0] = True if should_run: if exception: observer.on_error(exception) else: mad = MultipleAssignmentDisposable() cancelable.disposable = mad def action(scheduler: abc.SchedulerBase, state: Any = None): if exception: return with source.lock: running[0] = True while True: result = None if queue and queue[ 0].timestamp <= scheduler.now: result = queue.pop(0).value if result: result.accept(observer) if not result: break should_continue = False recurse_duetime: typing.RelativeTime = 0 if queue: should_continue = True diff = queue[0].timestamp - scheduler.now recurse_duetime = max(DELTA_ZERO, diff) else: active[0] = False ex = exception running[0] = False if ex: observer.on_error(ex) elif should_continue: mad.disposable = scheduler.schedule_relative( recurse_duetime, action) mad.disposable = _scheduler.schedule_relative( duetime_, action) subscription = source.pipe( ops.materialize(), ops.timestamp(), ).subscribe(on_next, scheduler=_scheduler) return CompositeDisposable(subscription, cancelable)
def subscribe( observer: abc.ObserverBase[_T], scheduler: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: cancelable = SerialDisposable() has_value: bool = False value: _T = cast(_T, None) _id = [0] def on_next(x: _T) -> None: nonlocal value, has_value throttle = None try: throttle = throttle_duration_mapper(x) except Exception as e: # pylint: disable=broad-except observer.on_error(e) return has_value = True value = x _id[0] += 1 current_id = _id[0] d = SingleAssignmentDisposable() cancelable.disposable = d def on_next(x: Any) -> None: nonlocal has_value if has_value and _id[0] == current_id: observer.on_next(value) has_value = False d.dispose() def on_completed() -> None: nonlocal has_value if has_value and _id[0] == current_id: observer.on_next(value) has_value = False d.dispose() d.disposable = throttle.subscribe(on_next, observer.on_error, on_completed, scheduler=scheduler) def on_error(e: Exception) -> None: nonlocal has_value cancelable.dispose() observer.on_error(e) has_value = False _id[0] += 1 def on_completed() -> None: nonlocal has_value cancelable.dispose() if has_value: observer.on_next(value) observer.on_completed() has_value = False _id[0] += 1 subscription = source.subscribe(on_next, on_error, on_completed, scheduler=scheduler) return CompositeDisposable(subscription, cancelable)
def test_mutabledisposable_ctor_prop(): m = SerialDisposable() assert not m.disposable
def subscribe( observer: abc.ObserverBase[Observable[_T]], scheduler_: Optional[abc.SchedulerBase] = None, ) -> abc.DisposableBase: _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton( ) n: int = 0 s: Subject[_T] = Subject() timer_d = SerialDisposable() window_id = 0 group_disposable = CompositeDisposable(timer_d) ref_count_disposable = RefCountDisposable(group_disposable) def create_timer(_id: int): nonlocal n, s, window_id m = SingleAssignmentDisposable() timer_d.disposable = m 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) m.disposable = _scheduler.schedule_relative(timespan, action) observer.on_next(add_ref(s, ref_count_disposable)) create_timer(0) 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 on_error(e: Exception) -> None: s.on_error(e) observer.on_error(e) def on_completed() -> None: s.on_completed() observer.on_completed() group_disposable.add( source.subscribe(on_next, on_error, on_completed, scheduler=scheduler_)) return ref_count_disposable
class ScheduledObserver(Observer[_T_in]): def __init__(self, scheduler: abc.SchedulerBase, observer: abc.ObserverBase[_T_in]) -> None: super().__init__() self.scheduler = scheduler self.observer = observer self.lock = threading.RLock() self.is_acquired = False self.has_faulted = False self.queue: List[typing.Action] = [] self.disposable = SerialDisposable() # Note to self: list append is thread safe # http://effbot.org/pyfaq/what-kinds-of-global-value-mutation-are-thread-safe.htm def _on_next_core(self, value: Any) -> None: def action() -> None: self.observer.on_next(value) self.queue.append(action) def _on_error_core(self, error: Exception) -> None: def action() -> None: self.observer.on_error(error) self.queue.append(action) def _on_completed_core(self) -> None: def action() -> None: self.observer.on_completed() self.queue.append(action) def ensure_active(self) -> None: is_owner = False with self.lock: if not self.has_faulted and self.queue: is_owner = not self.is_acquired self.is_acquired = True if is_owner: self.disposable.disposable = self.scheduler.schedule(self.run) def run(self, scheduler: abc.SchedulerBase, state: Any) -> None: parent = self with self.lock: if parent.queue: work = parent.queue.pop(0) else: parent.is_acquired = False return try: work() except Exception: with self.lock: parent.queue = [] parent.has_faulted = True raise self.scheduler.schedule(self.run) def dispose(self) -> None: super().dispose() self.disposable.dispose()