-
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
Bind Operator #770
Bind Operator #770
Conversation
- new create signature - new bind operator - new subscribe overload - OperationReplay is failing unit tests, all others are passing
- also simplified implementation to not worry about thread-safety as per Rx contract - performance improvement from 4,033,468 ops/sec -> 6,731,287 ops/sec
- even though unit tests don’t see it because of SafeObserver, the Take operator should not emit onCompleted more than once
- it’s working … but I can’t figure out the co/contra-variance for the generics! anyone have ideas?
- Changed `bind` signature to match the variant discussed at ReactiveX#746 (comment) - Updated code to new signature. - Re-implemented GroupBy operator with `bind`
Found bug while doing Parallel. It was completing prematurely when child groups were asynchronous and delayed.
- forgot to add earlier
It had not been successfully migrated before … this now passes unit tests. This is the bridge until we port it to the new “bind” model.
RxJava-pull-requests #681 FAILURE |
I'll try to take a look at the Clojure interop tomorrow. Is |
@daveray Yes, It is like this: new Action1<Operator<? super String>>() {
@Override
public void call(Operator<? super String> t1) {
}
} I'm open to giving an interface name for it if there is agreement. For example: public static interface OnSubscribe<T> extends Action1<Operator<? super T>> new OnSubscribe<String>() {
@Override
public void call(Operator<? super String> t1) {
}
} This was done for In other words, it is currently this: Action1<Operator<? super String>> but could be this: OnSubscribe<String> Anyone have strong opinions on this? |
RxJava-pull-requests #682 SUCCESS |
Signature items for discussion: 1)
|
public void call(Operator<? super T> o) { | ||
for (T i : is) { | ||
if (o.isUnsubscribed()) { | ||
break; |
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.
Do we want to emit onCompleted when unsubscription happened?
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 debated back and forth with myself but I think you're right.
The take
operator will invoke onCompleted
downwards, and unsubscribe
upwards. Anything that needs to cleanup or stop should pay attention to unsubscribe
, others like map
don't care about unsubscribe
or onComplete
.
Thus I agree with you and will remove the onCompleted
.
It's more complicated on groupBy
as it is managing multiple subscriptions, and once the inner are unsubscribed the outer must onComplete
. The outer being unsubscribed on the other hand wouldn't emit onComplete
.
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.
To be clear:
for (T i : is) {
if (o.isUnsubscribed()) {
return;
}
o.onNext(i);
}
if (!o.isUnsubscribed()) {
o.onCompleted();
}
@duarten You are correct. Thanks for the review. I'll post an updated commit shortly. It's clearer without closing over it. |
As per discussion at ReactiveX#770 (comment)
… ready to be re-written by @DavidMGross :-)
Can you elaborate on this? Unless I'm missing something, this would mean that there needs to be some other mechanism to actually propagate the unsubscription, because the call stack is lost and so are the exception handlers. The problem with returning a boolean is the same as propagating exceptions. Because there is no constraint on where the onNext call is executed, we can't assume we will be able to use the stack to communicate with the upstream operators. I like the current approach of Subscriptions as cancellation tokens. |
interface Observer { That does not sound right at all: >> boolean << onNext(T value); |
I meant if you use Otherwise, you are right, one would need to move the boolean return value into the heap, so it basically becomes the subscription. class ObserveOn.Observer {
Observer original;
CompositeSubscription csub = ...
boolean onNext(T t) {
csub.add(scheduler.schedule(() -> {
if (!original.onNext(t)) {
csub.unsubscribe();
}
}));
return !csub.isUnsubscribed();
}
}
I would say it is the dualization of |
@akarnokd It took me a bit to get used to the new mental model as well while implementing operators as the flow is inverted from before. However, the benefits are pretty convincing and I have come to prefer the model after implementing several operators. |
This should already be working due to how we catch errors and propagate via That said, what benefit does this give us as a programming model? Using the composed
Similarly, what does this give us that the On both of these, I'm not quite understanding what problems you are seeking to solve. Can you elaborate? |
RxJava-pull-requests #690 FAILURE |
The same issue as this new model: to stop from() or repeat() like producers in downstream operators like take() or one's Observer at the end of the chain. Edit: i suggest removing the failing test for the time being until a more load-insensitive variant is made. |
By overhead are you referring to object allocation or memory? To achieve composition do you envision some operators would need to Generally though, I don't like the idea of using exceptions for control flow. Unsubscribe is not an uncommon event (such as with |
I meant the boolean onNext() here. For the exception case, there is no need for extra try catch as exceptions will propagate back down on the onError wire, breaking out of loops and triggering unsubscriptions along the way. In terms of a TakeObserver: onNext { if (count >= limit) { o.onCompleted(); throw CancellationException(); } o.onNext(v); } But Exceptions are quite expensive compared to a simple boolean return value indeed. |
The
I played with these types of return signatures for a while trying to see if I could compose the back pressure channel but found it didn't compose well in things like The problem I see with using |
Merging. Discussion can continue here as the code will surely evolve on the master branch before we release. I have opened issue #775 for discussing the naming and type signatures. |
Practically, the signature |
The difference is that |
Really it is a composite subscription. Which is probably fine to have a the base class/interface for subscriptions. (one of the side effects of this exercise is that I hope we can junk most of the subscription types ...) |
Yes it is a |
Wouldn't we need the remove(), for example, in groupByUntil to unsubscribe and remove groups? I guess one could always have its on Composite and use add(), but how often do we need the ability to add multiple sub-subscriptions in the first place. I think Rx.NET's Sink class, the closest equivalent uses SingleAssignmentSubscription for it. |
I'm arguing for the fact that |
I'd say there are 4 scenarios for API users (as opposed to API implementors) (a) you pass in the individual methods and we create an observer for you. |
SingleAssignmentSubscription is not a friendly type. I bet you can replace SingleAssignmentDisposable with MultipleAssignmentDisposable in the whole Rx.NET code base and it will still work (caveat emptor) |
Excuse my short memory, but what problems do we want to solve with this new create/bind/subscribe approach? |
|
Thanks. I was looking at ways to implement repeat() but I've stumbled upon a problem: return from(Arrays.asList(this)).bind((u, k) -> new Observer<Obsurvable<T>>() {
@Override
public void onNext(Obsurvable<T> args) {
while (!k.isUnsubscribed()) {
args.subscribe(new Observer<T>() {
@Override
public void onNext(T args) {
u.onNext(args);
}
@Override
public void onError(Throwable e) {
u.onError(e);
}
@Override
public void onCompleted() {
}
}, k);
}
}
@Override
public void onError(Throwable e) {
u.onError(e);
}
@Override
public void onCompleted() {
}
}); It works if the source is a synchronous from: from(Arrays.asList(1)).repeat().take(10).subscribe(System.out::println); But starts to spin rapidly if one uses subscribeOn (which may lead to OOM and is just wasteful): from(Arrays.asList(1)).subscribeOn(Schedulers.computation()).repeat().take(10).subscribe(System.out::println); I did some implementation experiments and it appears one would always need some queue to avoid recursion. In addition, I felt I don't need to combine Observer & Subscription but to close over a CompositeSubscription, where I can add/remove dependent subscriptions at will. |
I haven't spent the time to play with it, but it seems to me that the repeat should not occur until |
Implementation of
bind
operator along with newcreate
andsubscribe
methods and several reimplemented operators:fromIterable
,toList
,toSortedList
,map
,cast
,timestamp
,merge
,flatMap
,mergeMap
,groupBy
,parallel
.The
bind
signature is:The new
Operator
class signature is:The
create
method now looks like:A new
subscribe
signature is:The use of these three new methods allows for chaining operators together and supporting unsubscription on both synchronous and asynchronous sources.
Status and Plan
create
method from the deprecated one.OperatorMerge
so am using it in non-typesafe manner right nowThis design has been reviewed with several people on Github and in person. Besides resolving the items listed above, my intention is as follows:
bind
signaturesCurrentThreadScheduler
(if it needs to exist at all, perhaps rename toTrampolineScheduler
to remove confusion betweenImmediate
andCurrentThread
)Scheduler
changes)See #746 for background on the proposal, reasons and design discussion.