0

I've implemented an FlowableOperator as described in the RxJava2 wiki (https://github.com/ReactiveX/RxJava/wiki/Writing-operators-for-2.0#operator-targeting-lift) except that I perform some testing in the onNext() operation something like that:

public final class MyOperator implements FlowableOperator<Integer, Integer> {

...

static final class Op implements FlowableSubscriber<Integer>, Subscription {

    @Override
    public void onNext(Integer v) {
        if (v % 2 == 0) {
          child.onNext(v * v);
        }  
    }
   ...
  }
}

This operator is part of a chain where I have a Flowable created with a backpressure drop. In essence, it looks almost like this:

Flowable.<Integer>create(emitter -> myAction(), DROP)
        .filter(v -> v > 2)
        .lift(new MyOperator())
        .subscribe(n -> doSomething(n));

I've met the following issue:

  • backpressure occurs, so doSomething(n) cannot handle the upcoming upstream
  • items are dropped due to the Backpressure strategy chosen
  • but doSomething(n) never receives back new item after the drop has been performed and while doSomething(n) was ready to deal with new items

Reading back the excellent blog post http://akarnokd.blogspot.fr/2015/05/pitfalls-of-operator-implementations.html of David Karnok, it's seems that I need to add a request(1) in the onNext() method. But that was with RxJava1...

So, my question is: is this fix enough in RxJava2 to deal with my backpressure issue? Or do my operator have to implement all the stuff about Atomics, drain stuff described in https://github.com/ReactiveX/RxJava/wiki/Writing-operators-for-2.0#atomics-serialization-deferred-actions to properly handle my backpressure issue?

Note: I've added the request(1) and it seems to work. But I can't figure out whether it's enough or whether my operator needs the tricky stuff of queue-drain and atomics.

Thanks in advance!

ctranxuan
  • 785
  • 5
  • 9

2 Answers2

1

Does a FlowableOperator inherently supports backpressure?

FlowableOperator is an interface that is called for a given downstream Subscriber and should return a new Subscriber that wraps the downstream and modulates the Reactive Streams events passing in one or both directions. Backpressure support is the responsibility of the Subscriber implementation, not this particular functional interface. It could have been Function<Subscriber, Subscriber> but a separate named interface was deemed more usable and less prone to overload conflicts.

need to add a request(1) in the onNext() [...] But I can't figure out whether it's enough or whether my operator needs the tricky stuff of queue-drain and atomics.

Yes, you have to do that in RxJava 2 as well. Since RxJava 2's Subscriber is not a class, it doesn't have v1's convenience request method. You have to save the Subscription in onSubscribe and call upstream.request(1) on the appropriate path in onNext. For your case, it should be quite enough.

I've updated the wiki with a new section explaining this case explicitly:

https://github.com/ReactiveX/RxJava/wiki/Writing-operators-for-2.0#replenishing

final class FilterOddSubscriber implements FlowableSubscriber<Integer>, Subscription {

    final Subscriber<? super Integer> downstream;

    Subscription upstream;

    // ...

    @Override
    public void onSubscribe(Subscription s) {
        if (upstream != null) {
            s.cancel();
        } else {
            upstream = s;                    // <-------------------------
            downstream.onSubscribe(this);
        }
    }

    @Override
    public void onNext(Integer item) {
        if (item % 2 != 0) {
           downstream.onNext(item);
        } else {
           upstream.request(1);              // <-------------------------
        }
    }

    @Override
    public void request(long n) {
        upstream.request(n);
    }

    // the rest omitted for brevity
}
akarnokd
  • 69,132
  • 14
  • 157
  • 192
  • Thanks for the reply and the wiki update! So, from my understanding adding `upstream.request(1)` would be enough to support backpressure in my case. It wouldn't be enough if the downstream would request more than 1 element. In this case, one would have to implement the drain-queue stuff described in the wiki. Am I right? – ctranxuan Feb 22 '18 at 17:53
  • It depends on the operator's behavior, not the request pattern of the downstream. If you drop 1 item, you have to request 1 more item from upstream in this middle operator. Queue-drain comes into play when there is an async boundary, not in your plain sequential operator. – akarnokd Feb 22 '18 at 18:12
0

Yes you have to do the tricky stuff...

I would avoid writing operators, except if you are very sure what you are doing? Nearly everything can be achieved with the default operators...

Writing operators, source-like (fromEmitter) or intermediate-like (flatMap) has always been a hard task to do in RxJava. There are many rules to obey, many cases to consider but at the same time, many (legal) shortcuts to take to build a well performing code. Now writing an operator specifically for 2.x is 10 times harder than for 1.x. If you want to exploit all the advanced, 4th generation features, that's even 2-3 times harder on top (so 30 times harder in total).

There is the tricky stuff explained: https://github.com/ReactiveX/RxJava/wiki/Writing-operators-for-2.0

Appyx
  • 1,145
  • 1
  • 12
  • 21