Skip to content

Proposal for a subscribe scheduler #309

Description

@MichaelSchneeberger

When I started with RxPY, I often run into the problem of missing a message when subscribing to an observable. This is especially true when working with the shared operator. This can be demonstrated by the following example, ...

import rx

from rx import operators as rx_op
from rx.concurrency import CurrentThreadScheduler

my_scheduler = CurrentThreadScheduler()

# create an shared observable emitting a single element "3"
shared_obs = rx.just(3, scheduler=my_scheduler) \
    .pipe(rx_op.share())

# take the first element of the shared observable
left_obs = shared_obs.pipe(rx_op.first())
right_obs = shared_obs.pipe(rx_op.first())

# zip the two single element observables
out_obs = rx.zip(left_obs, right_obs)

out_obs.subscribe()

... which raises the following exception.

  File "/home/mike/workspace/python/RxPY/rx/core/observer/autodetachobserver.py", line 45, in on_completed
    self._on_completed()
  File "/home/mike/workspace/python/RxPY/rx/core/operators/firstordefault.py", line 18, in on_completed
    observer.on_error(SequenceContainsNoElementsError())
  File "/home/mike/workspace/python/RxPY/rx/core/observer/autodetachobserver.py", line 35, in on_error
    self._on_error(error)
  File "/home/mike/workspace/python/RxPY/rx/core/observer/autodetachobserver.py", line 35, in on_error
    self._on_error(error)
  File "/home/mike/workspace/python/RxPY/rx/internal/basic.py", line 34, in default_error
    raise err
rx.internal.exceptions.SequenceContainsNoElementsError: Sequence contains no elements

What happens?

  1. the zip operator subscribes first left_obs observable
  2. left_obs observable subscribes shared_obs observable
  3. shared_obs observable subscribes just observable, which schedules the element "3" on the my_scheduler. Note that my_scheduler could also be a scheduler running on another thread. We then can not predict when the element "3" is exactly sent. In case of an "inactive" CurrentThreadScheduler, however, the element "3" is sent before subscribe method of the just observable returns.
  4. Element "3" is sent to left_obs observer and then to zip observer
  5. just observable completes
  6. right_obs observable subscribes to shared_obs observable, but does not receive any elements, and therefore raises a SequenceContainsNoElementsError exception

The problem is that two different kind of actions (subscribing and emitting elements) are getting scheduled on the same scheduler. This leads to situations where it becomes almost impossible to determine, in which order actions are scheduled. And anyway, it should not be the responsibility of the RxPY user to keep track in which order subscriptions and emissions of elements happen.

Update: The problem is that the emission of elements are scheduled during the "subscribe" process. That way, the source observable might send (depending on the scheduler that is used) an element via on_next before all downstream observables subscribed to the source observable. Hence, some downstream observables will miss one or more elements.

My proposal is to extend the subscribe_ method with a third argument that represents the subscribe scheduler. The subscribe_ method of an Observable would then look as follows.

Update: Changed subscribe to subscribe_

def subscribe_(self, observer: Observer, scheduler: Scheduler, subscribe_scheduler: Scheduler):
    """"
    :param subscribe_scheduler: used to schedule "upstream" observables to start emitting their elements
    :param scheduler: used to schedule the elements emitted by a source observable
    """"

A subscribe_ makes sure that all "upstream" observables (e.g. observables connected to the observable that is getting subscribed to) are subscribed before the first element is emitted.

Another effect, is that the global variable current_thread_scheduler would become obsolete, because it is replaced by the "subscribe" scheduler.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions