Skip to content

API for Creating Safe Back Pressure Observables #3003

Closed
@stealthcode

Description

@stealthcode

The Problem:

Creating Back-Pressure Enabled Observables that produce data from a non-blocking source is hard and our current APIs for creating arbitrary observables do not lend towards creating observables that respect the back pressure rules. Users have a choice between calling one of the Observable static method to generate standard case observables using well known patterns or they can attempt to implement a responsible observable out of an OnSubscribe<T>. I will define a "responsible Observable" to be an observable that does the following:

  • calls subscriber.onNext(t) only when the subscriberCapacity > 0 where subscriberCapacity is the sum of the integer arguments for all invocations of producer.request(n) minus the number of calls made to subscriber.onNext(t).
  • calls subscriber.onCompleted() or subscriber.onError(e) only once.
  • does not call subscriber.onNext(t) concurrently.

The onSubscribe lifecycle of a responsible observable should fit within the following lifecycle.

  1. The onSubscribe.call(Subscriber<T>) method is invoked by the Observable.subscribe(Subscriber<T>) method.
  2. The OnSubscribe func creates a Producer via the subscriber.setProducer(Producer<T>) method. At some point in the (potentially distant) future the subscriber may call producer.request(n). This signals to the observable that capacity is allocated in the subscriber.
    3a. Values are produced via a call to subscriber.onNext(t).
    3b. At some point a terminal event is emitted via onCompleted or onError.

This allows for many different variations of control flow given that onNexts could possibly be scheduled, data could be pre-fetched, excess data could be batched or dropped, etc...

Proposal

With this ground work in mind I would like to start a discussion about a general framework that correctly models the various use cases, particularly with respects to the following requirements:

  • capable of paginating data across multiple requests
  • batching requests
  • hot and cold data sources
  • async and blocking onNexts

Naive Implementation

User provides callbacks that the framework will call between each of the steps above. That is

  1. Observable calls onSubscribe.call(s)
  2. The framework's onSubscribe calls: S dataState = onSubscribeGenerator.call()
  3. Next the onSubscribe calls: subscriber.setProducer(p) setting a framework Producer
  4. At some point the request is received by our producer p.request(n)
  5. Then the framework will call the user provided request callback and store the new resultant dataState for future requests. dataState = onRequestCallback.call(dataState, n, s)

Obvious short comings of this approach: Order may not be preserved across multiple requests if the onRequestCallback schedules the onNext. Also onNexts may be concurrent so the subscriber must be wrapped with a serializing subscriber. Also, a case could be made that many observables created should specify the backpressure strategy. Any thoughts are welcome.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions