-
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
2.x: add limit() to limit both item count and request amount #5655
Conversation
Codecov Report
@@ Coverage Diff @@
## 2.x #5655 +/- ##
============================================
- Coverage 96.24% 96.21% -0.04%
+ Complexity 5853 5850 -3
============================================
Files 635 636 +1
Lines 41651 41703 +52
Branches 5768 5778 +10
============================================
+ Hits 40089 40123 +34
- Misses 616 623 +7
- Partials 946 957 +11
Continue to review full report at Codecov.
|
@BackpressureSupport(BackpressureKind.SPECIAL) | ||
@SchedulerSupport(SchedulerSupport.NONE) | ||
@CheckReturnValue | ||
public final Flowable<T> limit(long count) { |
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 like this strict version of take()
, but thinking about API design I'm afraid that this additional operator could add unnecessary confusion for the users.
Maybe an overload of take()
with boolean flag that controls "strictness" of the upstream requests would work? (or something better if you have it on your mind)
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.
Possibly.
|
||
@Override | ||
public void onNext(T t) { | ||
long r = remaining; |
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.
what's the point of local r
? It looks like you can just do everything directly on remaining
here
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 avoid re-reading fields.
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.
hm, it's not volatile, so it should be normally in CPU register/cache, but yeah local var has slightly more chances to be there
Although local var creates indirection between read/writes, so overall I think it's neither win nor loss in performance :)
There is interesting question on SO, but answers seem to only compare speed of access to the field when it's already initialized, not taking overall overhead of additional variable https://stackoverflow.com/questions/21613098/java-local-vs-instance-variable-access-speed
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.
The JIT may load it into a register but not everything runs on HotSpot.
remaining = --r; | ||
actual.onNext(t); | ||
if (r == 0L) { | ||
upstream.cancel(); |
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.
What about this:
if (r > 0L) {
remaining = --r;
final boolean completed = r == 0L;
if (completed) {
upstream.cancel();
}
actual.onNext(t);
if (completed) {
actual.onComplete();
}
}
This way we cancel upstream before delivering onNext
notification, it could be important in case if onNext
triggers long running chain of computations thus delaying cancellation of the upstream (in current version).
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.
Though, current implementations of take()
also does this so it might be unwanted behavior change, up2u, we can move this change to 3.x
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.
Or it could trigger an interrupted thread and fail code in the downstream unexpectedly.
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.
Good point!
But doesn't it mean that actual.onComplete()
should happen before upstream.cancel()
then?
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.
It's way less likely to happen. There were issues around onNext
before.
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.
ok, although I think probability is the same here, upstream.cancel()
has to be called in both ways of call ordering and possibly cause problems. But in current order we guarantee delivery of onNext which is probably good :)
|
||
public class FlowableLimitTest implements LongConsumer, Action { | ||
|
||
final List<Long> requests = Collections.synchronizedList(new ArrayList<Long>()); |
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.
nit: I don't think you need synchronization here, I don't see any concurrent access to this list
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.
Just in case.
|
||
@Override | ||
public void run() throws Exception { | ||
requests.add(-100L); |
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.
super nit: List<Object>
, private static final Object CANCELLED = new Object()
, requests.add(CANCELLED)
assertEquals(5, requests.get(0));
assertEquals(CANCELLED, requests.get(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.
-100 is fine.
|
||
assertEquals(2, requests.size()); | ||
assertEquals(5, requests.get(0).intValue()); | ||
assertEquals(-100, requests.get(1).intValue()); |
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.
let's use the constant here?
Updated. I'm not too fond of a |
I also prefer to have a dedicated method instead of magic Boolean operators. |
This PR adds the operator
limit
to limit the number of items as well as the total request amount.Some asynchronous boundary-like sources, such as network libraries that translate from/to
Flowable
/Publisher
, may not modulate or limit downstream requests and thus the other side may produce items unnecessarily.Alternatives, such as
take(N)
is designed to go unbounded if its downstream requests more than N to improve performance of local flows andrebatchRequests(M)
will keep requesting once 75% of M has been received.Note that requests are negotiated on an operator boundary and
limit
's amount may not be preserved further upstream. For example,source.observeOn(Schedulers.computation()).limit(5)
will still request the default (128) elements from the givensource
.Related discussion: #5077.
Blog: Java 9 Flow API: taking and skipping - Limiting the request amount