-
Notifications
You must be signed in to change notification settings - Fork 7.6k
IO Scheduler breaks long running Subscriber onNext #3152
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Did you try to add a |
What @headinthebox is getting at is that the |
That makes sense. I'll try to duplicate my issue tomorrow then. It's on Android and I was trying to avoid requiring a device for the test, so it wasn't exactly my scenario. |
Here's another test that confuses me, although I'm not sure it's related to the problem in my Android app. This test counts to 16 instead of 20. If I remove Observable.create() in the flatMap() call and change it to Observable.just(), then it works as expected. Integer[] source = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
Observable.from(source)
.subscribeOn(Schedulers.io())
.flatMap(new Func1<Integer, Observable<Integer>>() {
@Override
public Observable<Integer> call(final Integer integer) {
return Observable.create(new Observable.OnSubscribe<Integer>() {
@Override
public void call(Subscriber<? super Integer> subscriber) {
subscriber.onNext(integer);
}
});
}
})
.subscribe(new Subscriber<Integer>() {
@Override public void onCompleted() { }
@Override
public void onError(Throwable e) {
Assert.fail("error: " + e.getMessage());
}
@Override
public void onNext(Integer integer) {
try {
System.out.print(integer.toString() + " ");
Thread.sleep(50);
} catch (InterruptedException e) {
System.out.print("interrupted");
Assert.fail(e.getMessage());
}
}
});
Thread.sleep(10000); |
flatMap defaults to a concurrency level equal to the platform buffer size. Since you don't onComplete the created observable, flatMap won't continue with newer sources. |
Nice little sample that I can use in https://www.youtube.com/watch?v=pOl4E8x3fmw to show that with back pressure RxJava is essentially pull rather than push. |
@thasmin you should try retrolambda, 2 minutes to install will save you 50% or more on typing effort (in both meanings of the word typing) |
I'll check out retrolambda. That's not the problem with my app if it's a buffer issue, although I'm not using onComplete, I'm only getting one item out of the chain before hitting the backpressure exception. I'll watch the video and try again tonight. |
@thasmin |
Say I am writing a game, and I have a stream of game entities that each generate events, and I want to flatMap that stream of streams into a single stream (say to update the screen). At any point in time there are n active characters (i.e. that have not completed their inner streams) but that n is varying. Of course I want to receive the events from all active characters. How do I solve that using |
@headinthebox if n is always less than the platform default buffer size, the flatMap(mapper) will happily merge the values. Otherwise, the excess sources will be dealt with according to how the main source reacts to backpressure (drop, buffer, latest, etc). |
Since n varies, it can be below or above the default buffer size. I can not drop or buffer them because then I would only see a small portion of my game being updated. |
@headinthebox increase buffer size to max possible value? |
@headinthebox System property |
@artem-zinnatullin in which case why have back pressure at all? |
It's a tricky question, personally I'd prefer if back pressure detection will be turned of by default and if we would have ability to use it via separate operator applied to the But probably I'm just not in the target audience of the back pressure feature in RxJava. As alternative — what about operator |
I know the issue title is wrong and the conversation has strayed, but I think I found my problem. The ordering of my operators was messing things up. When I called .flatMap().observeOn().onBackpressureBuffer() in that order, I wasn't actually getting a backpressure buffer. Changing the order to .flatMap().onBackpressureBuffer().observeOn() made it work. I didn't expect that to make a difference. I only switched it randomly while testing to see whether another of the flatMap calls in the chain was causing the backpressure. |
That is onBackpressureBuffer if you want to opt out of backpressure. |
@akarnokd I don't understand the statement you made earlier:
It doesn't default to the buffer size, it has unbounded concurrency (well, up to Integer.MAX_VALUE): https://github.com/ReactiveX/RxJava/blob/1.x/src/main/java/rx/internal/operators/OperatorMerge.java#L57 If it didn't, we could deadlock the system. Only when the overloaded flatMap/merge operators that inject |
@thasmin I'd like to understand the mental model that led to thinking the ordering of The order of operators definitely matters, as events flow down the pipe, each operator affects them. Since Can you help me understand how we need to better educate, document and communicate things to make this clear? |
We run into this mental model all the time. In my experience it's almost as if people think they are in a builder of sorts. From randomly plugging in I'm giving a (recorded) talk soon dedicated to subscriptions, how they link together to create the chain, and how operators alter the behavior of each link. |
Here's some advice, but of course it's my personal opinion and I barely know how Rx works. This is a few ways of saying the same thing, but I'm trying to be explicit in my thinking to paint the best picture I could for you guys.
Let me know if I can clarify anything. |
Subscriptions go "upward". That is, they start where you call Conversely, data goes "downward". When an observable emits data, the subscriber observes it in the form of a callback. By default this happens synchronously (like a normal callback) on the same thread as the work is being done inside the observer. If you understand these two facts, it should start to make sense why order matters and why you can do things like call these operators multiple times in a single stream. For example, Observable.create(s -> {
// This runs on whatever thread calls 'subscribe' because there is no subscribeOn
String name = readNameFromDb();
s.onNext(name);
s.onComplete();
})
// Move data from the above observable to "io" scheduler.
.observeOn(io())
// This map (and thus DB call) happens on "io" scheduler.
.map(v -> readAddressForNameFromDb(v))
// Move data from the above map to "mainThread" scheduler.
.observeOn(mainThread())
// This happens on "mainThread" scheduler.
.subscribe(d -> System.out.println(d)); This stream is created synchronously and it reads and emits the first name synchronously when If you wanted the first DB read on a background thread (as you would in Android), you need to tell the stream to do the work which happens when you subscribe to the top observable on a background scheduler. This is done by putting Observable.create(s -> {
// This now runs on the "io" scheduler
String name = readNameFromDb();
s.onNext(name);
s.onComplete();
})
// When hooking up everything below to the observable above, do it on "io" scheduler.
.subscribeOn(io())
// Move data from the above observable to "io" scheduler.
.observeOn(io())
// This map (and thus DB call) happens on "io" scheduler.
.map(v -> readAddressForNameFromDb(v))
// Move data from the above map to "mainThread" scheduler.
.observeOn(mainThread())
// This happens on "mainThread" scheduler.
.subscribe(d -> System.out.println(d)); Now the first DB read inside the observable happens on a background thread. Now hopefully you notice from the comments that we are doing the first DB read on the "io" scheduler and then we are telling it to observe that data on the "io" scheduler. This is redundant and can be removed: Observable.create(s -> {
// This now runs on the "io" scheduler
String name = readNameFromDb();
s.onNext(name);
s.onComplete();
})
// When hooking up everything below to the observable above, do it on "io" scheduler.
.subscribeOn(io())
// This map still happens on "io" scheduler because that's the thread the data is emitted on.
.map(v -> readAddressForNameFromDb(v))
// Move data from the above map to "mainThread" scheduler.
.observeOn(mainThread())
// This happens on "mainThread" scheduler.
.subscribe(d -> System.out.println(d)); Hope this helps. Like I said, I'm preparing a 45m talk on these concepts with some (hopefully) nice visualizations of the concepts. |
That explains it very well. So subscribeOn only applies to the first operation and observeOn affects all the following operations until observeOn is called again. That makes sense. Is this because there's no way to set the scheduler before calling create? Perhaps the subscribeOn call can be removed by adding an Observable.createOn(Func1, Scheduler) call. This would add a lot of methods. Maybe Observable.observeOn(io).create would also be an acceptable API. It may be a good idea if you can remove the subscribeOn call completely and have one linear way to handle schedulers. Just some ideas for you guys. |
The reason that |
Here's something to answer in your talk: does subscribeOn go all the way to the top of the chain, does it affect only the preceding operation, does it go up until it hits a previous subscribeOn, or does it go up until it hits a previous observeOn? |
I like to explain
that is why Conversely
It is important to keep in mind that a |
Oooo that's clever. Very nice! On Sat, Aug 15, 2015, 2:25 AM George Campbell [email protected]
|
It's more like startOn and continueOn. I'd say subscribeOn and observeOn are not newbie friendly. It still doesn't explain the part where you lose the backpressure buffer and probably other things. |
You start when you subscribe, and you continue when you observe. |
fixes #135 see also ReactiveX/RxJava#3152
I'm new to Rx and I'm having trouble finding a solution with a scenario. I'm trying to make a network call, parse the result into objects, and send it to the UI. Parsing the response is quick and updating the UI is slower. The problem is that there's backpressure. Using onBackpressureBuffer doesn't fix the problem, and it doesn't even get rid of the MissingBackpressureException. When I tried to reduce the problem in a unit test, I found that I was able to get it to work by removing the subscribeOn(Schedulers.io()) line. I would expect this unit test to either fail or write "12345" to the consooe. Instead, it writes "1" and stops without any exceptions.
I'm also not sure why using a backpressure buffer isn't working, so there may be two problems. At this point, all I know is that I'm in over my head and I can't find out any more information on the web. Am I doing this wrong or are there bugs here?
The text was updated successfully, but these errors were encountered: