-
Notifications
You must be signed in to change notification settings - Fork 7.6k
2.x Design: Subject #3349
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 Design: Subject #3349
Conversation
Clarification of `Subject` that affects implementation. Related to discussion in #3345.
Relation to Reactive Streams | ||
|
||
- It can not implement Reactive Streams `Publisher` unless it is created with a default flow control strategy. | ||
- It can not implement `Processor` since a `Processor` must compose `request(n)` which can not be done with multicasting or pure push. |
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've read the spec but I haven't seen an implication of this. The only rule I can deduce is that Processor
may not overflow its subscribers. Nothing about request coordination requirement. This is what publish()
is for.
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.
4.1: A Processor represents a processing stage—which is both a Subscriber and a Publisher and MUST obey the contracts of both.
Thus, it has to obey the rules of a Publisher
which can't overflow its subscribers as per rule 1.1:
1.1 The total number of onNext signals sent by a Publisher to a Subscriber MUST be less than or equal to the total number of elements requested by that Subscriber´s Subscription at all times.
I strongly argued against Processor
as it looks like a Subject
but can't be used as one. It is actually more like the RxJava Operator
, but requires subscription rather than lifting into the observer chain.
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.
Note that this is exactly why we stopped using Subjects inside RxJava v1 Observable
operators, such as publish()
, and why we removed multicast(Subject s)
.
A Subject
can not compose request(n)
. That's why v1 Observable.publish()
behaves very differently than a PublishSubject
and will slow down to the slowest consumer.
Ping @akarnokd |
@ReactiveX/rxjava-committers Can this PR be merged? |
I disagree with the two statements. |
Please explain why you disagree and your proposed solution. |
Subjects can implement In other words, there could be What we call
Again, I don't see any mention of composing requests through a processor in the spec, but you could ask them (they're less likely to answer me lately). |
There is a very, very, long discussion about multicasting here: reactive-streams/reactive-streams-jvm#19 (comment) Other related discussions in reactive-streams/reactive-streams-jvm#22 and reactive-streams/reactive-streams-jvm#37 In short, Reactive Streams chose to not say anything about whether a This means some variants of an Rx
Yup, but since 'Subjects' are "hot", and |
Yet it does and works fine. It does not overflow the client and doesn't drop values silently either; if the client can't keep up, it will receive an error. The developer must think about what should happen in case the client can't keep up, perhaps increase the buffer size in I've read through the linked discussion and it feels there is a lot of self-handcuffing going on. Restricting a So if you think |
A Here is the example: package io.reactivesocket;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import io.reactivex.subjects.PublishSubject;
public class SubjectContract {
public static void main(String... args) {
PublishSubject<String> s = PublishSubject.create();
s.subscribe(new Subscriber<String>() {
@Override
public void onSubscribe(Subscription s) {
s.request(10);
}
@Override
public void onNext(String t) {
System.out.println("A Received: " + t);
}
@Override
public void onError(Throwable t) {
t.printStackTrace();
}
@Override
public void onComplete() {
}
});
s.subscribe(new Subscriber<String>() {
@Override
public void onSubscribe(Subscription s) {
s.request(50);
}
@Override
public void onNext(String t) {
System.out.println("B Received: " + t);
}
@Override
public void onError(Throwable t) {
t.printStackTrace();
}
@Override
public void onComplete() {
}
});
for(int i=0; i < 1000; i++) {
s.onNext(String.valueOf(i));
}
}
} This results in an error on both
A However, the intent and design of a PublishSubject<String> s = PublishSubject.create();
for(int i=0; i < 1000; i++) {
s.onNext(String.valueOf(i));
} So let's see what happens when I do use package io.reactivesocket;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import io.reactivex.subjects.PublishSubject;
public class SubjectContract {
public static void main(String... args) {
PublishSubject<String> s = PublishSubject.create();
s.onSubscribe(new Subscription() {
@Override
public void request(long n) {
System.out.println("Requested: " + n);
}
@Override
public void cancel() {
// what does it mean to cancel here?
}
});
s.subscribe(new Subscriber<String>() {
@Override
public void onSubscribe(Subscription s) {
s.request(10);
}
@Override
public void onNext(String t) {
System.out.println("A Received: " + t);
}
@Override
public void onError(Throwable t) {
t.printStackTrace();
}
@Override
public void onComplete() {
}
});
s.subscribe(new Subscriber<String>() {
@Override
public void onSubscribe(Subscription s) {
s.request(50);
}
@Override
public void onNext(String t) {
System.out.println("B Received: " + t);
}
@Override
public void onError(Throwable t) {
t.printStackTrace();
}
@Override
public void onComplete() {
}
});
for(int i=0; i < 1000; i++) {
s.onNext(String.valueOf(i));
}
}
} I received
So, rule 1.9 is broken. And what does a
Thus, rule 1.8 is also broken. A |
Related to this is how we determined that |
If your interpretation is true, no operator can implement RS at all and no source, hot or cold is allowed.
|
I am convinced that the PublishSubject cannot be a Processor. In order for a thing to offer stream fan-in/fan-out functionality then a new |
Additionally:
No, because Thus, a fully unbounded
Huh? 1.8 does not say
|
Let's assume there is a non-backpressure PublishSubject, not Flowable and not implementing RS but it is a NbpObservable. I then take this subject, cast it to NbpObservable, call toFlowable(BackpressureStrategy.FAIL) and I have a Flowable. Is this Flowable conforming with the RS spec? If not, there any legal way of implementing any toFlowable at all? |
That example is now okay, because when you converted from |
The issue with public void subscribe(Subscriber<? super T> s); If it was instead: public void subscribe(Subscriber<? super T> s, BackpressureStrategy strategy); ... then it would be okay, and that is the same as |
@benjchristensen Then why is it wrong if PublishSubject does what you just replied: sends an error outside requested amount, decouples the flow and doesn't call cancel to upstream? |
Because you are calling an error on the In the |
Also, do you recognize that what I described in the NbpObservable->Flowable example is what the current 2.0 PublishSubject does in one step? |
Yes I do, but you decide a default backpressure strategy of "FAIL", which is not okay to do. It means you are ignoring the public void subscribe(Subscriber<? super T> s); Ignoring the We can not choose a default flow control strategy that defeats the purpose of |
The default can be overruled by any of the onBackpressureXXX operators. Do error(), empty(), never() and just() ignore request(n)? If yes, then we can't have them as flowables. |
That's missing the point. The user is opting into those forms of flow control. The user can choose whatever A |
Don't ignore the other side of this as well, the purpose of a |
If this is about the lack of call to PublishSubject.onSubscribe then one should call it with something. Such call is practically no-op because PublishSubject is an unbounded Subscriber. Is being an unbounded Subscriber a violation in general or only if said Subscriber is a Subject? If the latter, then all Subjects should be banned and neither groupBy() nor window() should be allowed. |
Being an unbounded subscriber is fine. Being an unbounded Publisher is not. Subjects are hot, push. They do not participate in backpressure. Stop trying to apply the same rules to all types, that is why we are splitting the types in #3348. We will have a |
I can't disprove a definition which this thread has become now. In addition, reviewing anything is now quite difficult because I can't predict which rule applies when and when will a rule suddenly mean its opposite. |
I see no logical fault in reasoning 👍. We can debate exactly what subjects are later but it is clear to me that the assumptions established by the RS Processor do not allow for multicast subjects (as they operate today). |
Thanks @stealthcode for reviewing and weighing in. As you allude to, this PR is simply attempting to document what a |
@akarnokd Any further thoughts on the text of this PR? I'd like to move forward to other items of design if we can move past this one. |
I disagree with the text. No further comments on this topic. |
I think this PR makes the distinction between Subject and reactive streams Publisher more apparent which is good. Regarding the argument, I'm convinced that PublishSubject can't be a Processor without implementing a backpressure strategy. |
@akarnokd You can't disagree and then abstain from providing alternatives when evidence has been provided supporting this text. Are you removing yourself from the design discussion and letting the rest of us make the decision? |
@stevegury thank you for your review of the topic and weighing in. |
Don't put is on me. In my understanding, there is no problem with subject.onErrorResumeNext(
e -> e instanceof MissingBackpressureException
? subject.onBackpressureDrop() : Observable.error(e)); This will resubscribe with a drop strategy to the subject to avoid any further errors. But if your problem is that The final alternative I can offer is something like the
I'm outnumbered anyways. Does this mean whenever I disagree with something in the future, you either keep asking until I change my mind or simply outrule me? Perhaps this is a good time to ask for insight from the guys who maintain the RS spec or the persons who are not Neflix or me. |
I'm one of them => https://github.com/reactive-streams/reactive-streams-jvm/blob/v1.0.0/CONTRIBUTING.md#gatekeepers But if we can get someone else to spend time on this with us, we can get others involved. Perhaps @smaldini could give us some time.
This is the key problem so let me summarize this for anyone else catching up on this thread. A |
I'm merging this based on feedback from @stevegury and @stealthcode and my understanding of Reactive Streams based on my involvement in defining that contract. The Design.md document still has a long way to go and we'll have many more weeks of discussions on design. This is but one small point. |
Clarification of
Subject
that affects implementation.Related to discussion in #3345.