-
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 issue #1522 #1523
Fix issue #1522 #1523
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -108,11 +108,21 @@ void startEmitting() { | |
|
||
@Override | ||
public void request(long n) { | ||
long _c = 0; | ||
long _c; | ||
if (n == Long.MAX_VALUE) { | ||
requested = Long.MAX_VALUE; | ||
_c = REQUESTED_UPDATER.getAndSet(this, Long.MAX_VALUE); | ||
} else { | ||
_c = REQUESTED_UPDATER.getAndAdd(this, n); | ||
for (;;) { | ||
_c = requested; | ||
if (_c == Long.MAX_VALUE) { | ||
// If `requested` is Long.MAX_VALUE, `c+n` will be overflow. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It could be overflow even if not Long.MAX_VALUE |
||
// Therefore, always check before setting to `c+n` | ||
return; | ||
} | ||
if (REQUESTED_UPDATER.compareAndSet(this, _c, _c + n)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can still just use getAndAdd without the CAS retry loop. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here I try to ignore the further requests after There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the overflow, I mean sending a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we keep the fast path we can just check that request is MAX_VALUE then skip. For example: https://github.com/Netflix/RxJava/blob/bb56093c00185dbd39785a95197abe4b66f3bbca/rxjava-core/src/main/java/rx/internal/operators/OnSubscribeRange.java#L59 |
||
break; | ||
} | ||
} | ||
} | ||
if (!emittingStarted) { | ||
// we haven't started yet, so record what was requested and return | ||
|
@@ -122,16 +132,20 @@ public void request(long n) { | |
} | ||
|
||
void emit(long previousRequested) { | ||
if (requested < 0) { | ||
if (requested == Long.MAX_VALUE) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ha! That's a fun bug, totally missed that when we went from -1 to MAX_VALUE. I bet it isn't even worth having the complexity of a fast-path because the cost of queue/deque is far worse than the counter, and we only hit the counter at the very end anyways. |
||
// fast-path without backpressure | ||
try { | ||
for (Object value : deque) { | ||
notification.accept(subscriber, value); | ||
if (previousRequested == 0) { | ||
try { | ||
for (Object value : deque) { | ||
notification.accept(subscriber, value); | ||
} | ||
} catch (Throwable e) { | ||
subscriber.onError(e); | ||
} finally { | ||
deque.clear(); | ||
} | ||
} catch (Throwable e) { | ||
subscriber.onError(e); | ||
} finally { | ||
deque.clear(); | ||
} else { | ||
// backpressure path will handle Long.MAX_VALUE and emit the rest events. | ||
} | ||
} else { | ||
// backpressure is requested | ||
|
@@ -160,7 +174,6 @@ void emit(long previousRequested) { | |
// we're done emitting the number requested so return | ||
return; | ||
} | ||
|
||
} | ||
} | ||
} | ||
|
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 is the critical part as our fast-path in emit will never decrement.
I'm curious in this particular operator if the fast-path even matters and warrants the complexity.
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 think we can remove the fast-path in
emit
. But we still need to handleLong.MAX_VALUE
inrequest
explicitly because the overflow problem.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.
Downstream should never request more unless it has received something, so as long as we are decrementing when we emit then we shouldn't overflow.
It will be a fine safety check, but well behaved subscribers should never result in overflow.
The
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.
Whoops .... the only reason we had overflows here is because of the fast-path that didn't decrement when we emitted.
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.
Actually, the following code is the reason:
Assume we have the following codes:
If the order is:
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 just realized my PR didn't handle this case. I should also put CAS loop in
if (REQUESTED_UPDATER.addAndGet(this, -emitted) == 0)