Skip to content
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

1.x: OnBackpressureBuffer: DROP_LATEST and DROP_OLDEST #3487

Merged
merged 1 commit into from
Mar 17, 2016
Merged

1.x: OnBackpressureBuffer: DROP_LATEST and DROP_OLDEST #3487

merged 1 commit into from
Mar 17, 2016

Conversation

srvaroa
Copy link
Contributor

@srvaroa srvaroa commented Nov 2, 2015

Introduce a new interface BackpressureOverflowStrategy that allows implementing different handlers for an overflow situation. This patch adds three implementations, reachable via OverflowStrategy:

static class OverflowStrategy {
    static final BackpressureOverflowStrategy DEFAULT = Error.INSTANCE;
    static final Error ERROR = Error.INSTANCE;
    static final DropOldest DROP_OLDEST = DropOldest.INSTANCE;
    static final DropLatest DROP_LATEST = DropLatest.INSTANCE;
}

The behavior for each is the following:

  • ERROR remains the default as the existing implementation.
  • DROP_LATEST will drop newly produced items after the buffer fills up.
  • DROP_OLDEST will drop the oldest elements in the buffer, making room for
    newer ones.

In all cases, a drop will result in a notification to the producer by invoking the onOverflow callback.

None of the two new behaviours (DROP_*) will unsubscribe from the source nor onError.

@@ -20,6 +20,7 @@
import rx.functions.*;
import rx.internal.operators.*;
import rx.internal.producers.SingleProducer;
import rx.internal.operators.OperatorOnBackpressureBuffer.OnOverflow;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This enumeration should be part of a more public part of the library instead of the internal part.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense.

Any preference rather than in Observable.java? This seems to be the first case of an enum that's part of the public API (other than Notification.Kind, which is in Notification itself).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be moved under rx and I'd rename it to BackpressureOverflowStrategy

@akarnokd
Copy link
Member

akarnokd commented Nov 2, 2015

Otherwise, it looks okay. 👍

@srvaroa
Copy link
Contributor Author

srvaroa commented Nov 3, 2015

Thanks for the review @akarnokd

@@ -0,0 +1,5 @@
package rx;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add the copyright header.

@srvaroa
Copy link
Contributor Author

srvaroa commented Nov 10, 2015

Rebased - @akarnokd let me know if there are any fixes pending.

@akarnokd
Copy link
Member

Excellent. 👍. I don't think there are conflicting PRs right now.

*/
@Experimental
public final Observable<T> onBackpressureBuffer(long capacity, Action0 onOverflow, BackpressureOverflowStrategy overflowStrategy) {
return lift(new OperatorOnBackpressureBuffer<T>(capacity, onOverflow, overflowStrategy));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Knowing that some RxJava users like to pass null :trollface: in places where RxJava developers do not expect that I'd add null checks for onOverflow and overflowStrategy or at least mention it in javadoc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@artem-zinnatullin
Copy link
Contributor

Great 👍, just a few nits.

@srvaroa
Copy link
Contributor Author

srvaroa commented Nov 11, 2015

Thanks @artem-zinnatullin & @akarnokd. Addressed Artem's comments.

@akarnokd
Copy link
Member

👍

@stealthcode
Copy link

@abersnaze can you take a look and verify that it solves the issue raised in #3233?

@akarnokd akarnokd changed the title OnBackpressureBuffer: DROP_LATEST and DROP_OLDEST 1.x: OnBackpressureBuffer: DROP_LATEST and DROP_OLDEST Nov 12, 2015
@srvaroa
Copy link
Contributor Author

srvaroa commented Nov 22, 2015

Rebased on 1.x

@stealthcode
Copy link

The only concern that @abersnaze and I have had with this is that this uses enums to determine the strategy of operator functionality. This pattern hasn't been used much in the public API as of yet. @akarnokd @zsxwing do you guys have any opinion? We could just convert the strategies to instance methods (for consistency) or is this an emerging case that should be encouraged?

@akarnokd
Copy link
Member

I wouldn't turn a the whole set of onBackpressureXXX into enums of Drop|Buffer|Latest because the pattern doesn't allow parametrizing just the Buffer variant. In this case, however, having 3-9 new overload feels unnecessary expansion of the public API. I'm in for the use of enums here, but we should store them in a reasonable subpackage.

@srvaroa
Copy link
Contributor Author

srvaroa commented Dec 16, 2015

@akarnokd @stealthcode I don't love using enums in this way, but I agree with @akarnokd that this can lead to excessive API expansion. But happy to move to methods if you guys prefer. Ditto for moving the enums package from rx to rx.strategies or some similar alternative.

@zsxwing
Copy link
Member

zsxwing commented Dec 16, 2015

Agree with @akarnokd

I prefer to add a new interface BackpressureOverflowStrategy instead of enum.

@stealthcode
Copy link

I agree with @zsxwing on the use of an empty interface and impls to compare instanceof instead of using an enum. Eventually they might be usable to generalize the strategies.

@srvaroa
Copy link
Contributor Author

srvaroa commented Dec 16, 2015

Sounds good, I'll update the patch soon. Any suggestion on the package for the strategy?

@srvaroa
Copy link
Contributor Author

srvaroa commented Feb 1, 2016

Rebased

* @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number)
*/
@Experimental
public final Observable<T> onBackpressureBuffer(long capacity, Action0 onOverflow, BackpressureOverflowStrategy overflowStrategy) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This points to an internal interface from the public API, plus the allowed values are also internal classes and the BufferSubscriber is a private class.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still a problem? I'm not sure that I understand this comment.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BackpressureOverflowStrategy is still in the internals package, it should be moved up to the rx package.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, BackpressureOverflowStrategy is now in rx package.

@srvaroa
Copy link
Contributor Author

srvaroa commented Feb 11, 2016

@stealthcode I've been busy these days, just returned to this. I had the same doubt re. BufferSubscribe reference, @akarnokd mind to clarify where it happens?

I've fixed the visibility of OverflowStrategy members (new commit on the way)

@@ -71,6 +175,7 @@ public OperatorOnBackpressureBuffer(long capacity, Action0 onOverflow) {

return parent;
}

private static final class BufferSubscriber<T> extends Subscriber<T> implements BackpressureDrainManager.BackpressureQueueCallback {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BufferSubscriber is private here but the public BackpressureOverflowStrategy exposes it, which makes it not extendable outside this package, which is not better than having a BackpressureOverflowStrategy enum in the first place. Plus, leaving BufferSubscriber private generates accessibility bridges.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I replaced with the parent interface implemented by BufferSubscriber, which is public.

@stevegury
Copy link
Member

Thanks for the addition.
Could you also update the PR description?

@srvaroa
Copy link
Contributor Author

srvaroa commented Feb 15, 2016

@stevegury thanks for the review. PR fixed.

* A convenience class to provide singleton instances of available {@code BackpressureOverflowStrategy}.
*/
@SuppressWarnings("unused")
public static class OverflowStrategy {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once BackpressureOverflowStrategy has been moved to rx package, the values of this class can become its constants, but all of them should have the type BackpressureOverflowStrategy. The type after the assignment can stay the same as now and can point to the current internal location.

@stevegury
Copy link
Member

If I'm right the only missing thing is the header in BackpressureOverflowStrategy.java.
@srvaroa could you add that?
👍

@akarnokd
Copy link
Member

Wait, it has still the problem of exposing internal classes and types into the public API:

boolean triggerOn(rx.internal.util.BackpressureDrainManager.BackpressureQueueCallback buffer) throws MissingBackpressureException;

In addition, for a proper instance of BackpressureOverflowStrategy, you have to reach into

rx.internal.operators.OperatorOnBackpressureBuffer.ON_OVERFLOW_DEFAULT

for example.

@srvaroa
Copy link
Contributor Author

srvaroa commented Mar 14, 2016

Thanks @akarnokd @stevegury

I'm submitting a patch that eliminates the internal API leak.

While doing it I'm starting to realize that without exposing the buffer or callback the motivation for a strategy vs a plain enum practically dissapears. The point of using a strategy was to give users ability to implement their own strategies, this led me to expose the internal buffer (which is bad, obviously). However, without it, we're basically doing the same as an enum as the real overflow behaviour is not really in the Strategy. It actually feels redundant with the onOverflow callback.

To make the strategy worth the extra code we'd need to make BufferSubscriber implement some public API interface defined by BackpressureOverflow through which we allow the user to actually interact with the buffer without leaking the internal API. Something like:

BPBuffer {
  boolean dropOldest()
  ...
}

And then:

BPStrategy {
  boolean triggerOn(BPBuffer buffer)  
}

This would become similar to my current patch (https://github.com/srvaroa/RxJava/commit/b09061048f34f539bbc022e846e7361ae6e38606) and enable meaningful strategies without leaking APIs. That said, I think that would be overengineering this patch.

Options I see:

a) revert to enums (problem: we're introducing an enum-based public API).
b) leave the patch I'm submitting next (problem: we're committing to a public API based on a Strategy that doesn't really define any behaviour).
c) define BPBuffer and BPStrategy for the sake of introducing a meaningful strategy-based API. I ca call triggerOn() with a null for now (so it effectively behaves as -b-) and proceed with making BufferSubscriber extend BPBuffer in a later patch.

I'm leaning for (a), but let me know what you prefer.

/**
* Drop oldest items from the buffer making room for newer ones.
*/
public static class DropOldest implements BackpressureOverflow.Strategy {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's have these package private at least; users should use the ON_OVERFLOW_* constants.

@akarnokd
Copy link
Member

The current API looks good enough to me, only some small visibility and annotation issues.

Introduce a new interface BackpressureOverflow.Strategy that allows
implementing different handlers for an overflow situation.  This patch
adds three implementations:

- ON_OVERFLOW_ERROR remains the default as the existing implementation.
- ON_OVERFLOW_DROP_LATEST will drop newly produced items after the buffer fills
  up.
- ON_OVERFLOW_DROP_OLDEST will drop the oldest elements in the buffer, making
  room for newer ones.

The default strategy remains ON_OVERFLOW_ERROR.

In all cases, a drop will result in a notification to the producer by invoking
the onOverflow callback.

None of the two new behaviours (ON_OVERFLOW_DROP_*) will unsubscribe from the
source nor onError.

Fixes: #3233
@srvaroa
Copy link
Contributor Author

srvaroa commented Mar 16, 2016

Thanks for the reviews, I updated the patch.

@akarnokd
Copy link
Member

👍

@stevegury
Copy link
Member

Thanks @srvaroa for taking the time of writing a good PR!
👍

akarnokd added a commit that referenced this pull request Mar 17, 2016
1.x: OnBackpressureBuffer: DROP_LATEST and DROP_OLDEST
@akarnokd akarnokd merged commit df963fa into ReactiveX:1.x Mar 17, 2016
@stealthcode
Copy link

👍 thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants