-
Notifications
You must be signed in to change notification settings - Fork 7.6k
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
fix Amb backpressure bug #2961
fix Amb backpressure bug #2961
Conversation
} else { | ||
//subscriptions already happened so propagate the request to all the | ||
//amb subscribers | ||
for (AmbSubscriber<T> ambSubscriber: selection.ambSubscribers) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This may still run concurrently with the subscription loop and some subscribers won't get the newer requests during that time. The simplest fix is to subscribe all AmbSubscriber in the call() of the operator with 0 initial requests so all subsequent request() can target all or the winner without any additional race. The other way is to get into some complicated serialization logic.
yeah I like your first suggestion, I'll look at that |
I've moved the subscription to sources loop into the call method as suggested. |
if (!ambSubscriber.isUnsubscribed()) { | ||
// make a best endeavours check to not waste requests | ||
// if first emission has already occurred | ||
if (selection.choice.get() == ambSubscriber) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd move selection.choice
into a final variable at the beginning of the method so we don't re-read two fields unnecessarily with all those memory barriers around.
I've done that and reduced volatile reads a bit too. Given that |
Variables tend to be re-read around volatiles and the JIT may be smart enough to optimize it and pull them upfront, but you can't be sure. |
// there is a single winner so we unsubscribe it | ||
selection.choice.get().unsubscribe(); | ||
c.unsubscribe(); | ||
} | ||
// if we are racing with others still existing, we'll also unsubscribe them | ||
if(!selection.ambSubscribers.isEmpty()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd read selection.ambSubscribers
into a local variable as well.
added |
final Iterable<? extends Observable<? extends T>> sources; | ||
final Selection<T> selection = new Selection<T>(); | ||
final AtomicReference<AmbSubscriber<T>> choice = selection.choice; | ||
final Collection<AmbSubscriber<T>> ambSubscribers = selection.ambSubscribers; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant read them into a method-local variable.
ok, done |
I'd like to review the whole operator but I don't have the time and the focus to do it now. I'll come back to this in a few hours. |
Maybe I missed some discussion about this case. So now we support calling |
Yes we do, multiple requests in |
.delay(100, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.computation()); | ||
TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); | ||
Observable.amb(o1, o2).subscribe(ts); | ||
ts.requestMore(1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be forbidden in the real case. Right? request
should be called in Subscriber
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So testSubscriptionOnlyHappensOnce
is a test case that requesting twice before first emission. Right? The other request is in subscribe
.
Interesting point but I assume async operators downstream of the Saying that requests between between |
I don't mean it. I mean |
|
||
//give default access instead of private as a micro-optimization | ||
//for access from anonymous classes below | ||
final Iterable<? extends Observable<? extends T>> sources; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool. Just learnt that accessing a private field of the outer class uses invokestatic
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And generates an accessor method in that outer class, the count of which (methods) is a big deal on Android. So 👍.
LGTM for the fix. Just some minor comments for the tests. |
@zsxwing I've updated the test that called |
LGTM. Let's wait for @akarnokd to take a final look. |
I found a few places for possible optimizations:
|
47ebbbf
to
bb659b6
Compare
I like the optimization for |
5d5d929
to
b937fdd
Compare
I've implemented this suggestion @akarnokd. Ready for (another) review, thanks. |
Looks good. Thanks! |
The Amb operator has a backpressure bug:
m
requests were made before the first emission thenm
subscriptions were started on each source observable. We only want once subscription on each.This PR adds fix code to
OnSubscribeAmb
and two unit tests.