Skip to content

What should a Publisher do if it detects a duplicate subscription? #364

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

Closed
kjkrum opened this issue May 1, 2017 · 8 comments
Closed

What should a Publisher do if it detects a duplicate subscription? #364

kjkrum opened this issue May 1, 2017 · 8 comments
Assignees
Labels

Comments

@kjkrum
Copy link

kjkrum commented May 1, 2017

Rule 2.5 mandates a Subscriber's response to an out-of-sequence onSubscribe caused by a faulty Publisher or external misuse of the Subscriber.

There is no corresponding rule regarding a Publisher's response to a duplicate concurrent subscription caused by a faulty or misused Subscriber. A Publisher is not required to detect this because doing so would require the Publisher to be stateful. However, if a Publisher does detect it, what should it do?

Some possibilities:

  1. Call onSubscribe with a dummy Subscription.
  2. Call onSubscribe with the original Subscription.
  3. Signal onError, thereby canceling the original Subscription per 2.4.
  4. Silently ignore the duplicate subscription.

A strict reading of 2.12 prohibits the first two options. I propose that 2.12 be struck entirely, since it has evolved into a Publisher rule in the Subscriber section. Replace it with a new Publisher rule:

A Publisher MUST call Subscriber.onSubscribe exactly once per invocation of subscribe.

And amend 2.5 to read:

After receiving an onSubscribe signal, a Subscriber MUST call Subscription.cancel() on the given Subscription if it already has an active Subscription which is not equal to the given Subscription (based on object equality).

Related discussions:
#63
#178
#202

@viktorklang
Copy link
Contributor

Rule 2.5 mandates a Subscriber's response to an out-of-sequence onSubscribe caused by a faulty Publisher or external misuse of the Subscriber.

Yes. Do note, however, that a synchronous Subscriber may, because of visibility rules, not be able to detect double-subscriptions.

There is no corresponding rule regarding a Publisher's response to a duplicate concurrent subscription caused by a faulty or misused Subscriber. A Publisher is not required to detect this because doing so would require the Publisher to be stateful. However, if a Publisher does detect it, what should it do?

Great question!

Some possibilities:

Call onSubscribe with a dummy Subscription.
Call onSubscribe with the original Subscription.

Since 2:10 has been violated at this point, we are now operating in out-of-spec territory.
If the Subscriber is spec-compliant (which we have to assume), then if we pass a Subscription then it should cancel it. But then we are technically violating the spec.

Signal onError, thereby canceling the original Subscription per 2.4.

You need to signal onSubscribe first: 2:9.

Silently ignore the duplicate subscription.

This is borderline legal according to 2:9—depends on interpretation of except.

A strict reading of 2.12 prohibits the first two options. I propose that 2.12 be struck entirely, since it has evolved into a Publisher rule in the Subscriber section. Replace it with a new Publisher rule:

A Publisher MUST call Subscriber.onSubscribe exactly once per invocation of subscribe.

That doesn't take null into account specwise, perhaps you mean that it will throw an NPE by default?

And amend 2.5 to read:

After receiving an onSubscribe signal, a Subscriber MUST call Subscription.cancel() on the given Subscription if it already has an active Subscription which is not equal to the given Subscription (based on object equality).

Since "keeping track of existing Subscribers" will not solve the problem of infinite history, or the possibility of having the same Subscriber subscribed to different Publishers. Writing that out of the spec is probably backwards compatible.

The problem with memory visibility for synchronous Subscribers still exist tho.

I don't have time right now to check the TCK, what does it advise in the case of double-subscriber on the Publisher-side?

@ktoso
Copy link
Contributor

ktoso commented May 1, 2017

I don't have time right now to check the TCK, what does it advise in the case of double-subscriber on the Publisher-side?

This rule is not checked in the TCK since it's somewhat fuzzy as to what one should do if this situation is encountered (void untested_spec212_mustNotCallOnSubscribeMoreThanOnceBasedOnObjectEquality_specViolation() throws Throwable;), if we can find a blessed response to this we could add tests covering it.

I'm pretty sure the "4) ignore silently" is a bad idea, if a Publisher is in position to detect this, as you hint at @kjkrum, it should sound the alarms that a faulty part was detected IMO, one way or another.

@viktorklang
Copy link
Contributor

viktorklang commented May 1, 2017

@ktoso ~~IMO there are three options:~

1) Amend the spec to RECOMMEND "throw IllegalArgumentException("Subscriber already subscribed")" (Not backwards compatible)
2) Amend spec to "the caller MUST raise this error condition in a fashion that is adequate for the runtime environment." (Backwards compatible)
3) Rely on Subscriber to cancel the second subscription. (Backwards compatible)

I am personally leaning towards 3 since no2 cannot be relied on since it could be different Publisher instances.

@kjkrum Is right, if the Publish knows that Subscriber has already been subscribed, issuing onError is the right thing to do.

@kjkrum
Copy link
Author

kjkrum commented May 1, 2017

@viktorklang

You need to signal onSubscribe first: 2:9.

If the Publisher knows the Subscriber is a duplicate, I was thinking it can assume onSubscribe has already been called on it.

@viktorklang
Copy link
Contributor

@kjkrum Apologies, you are of course right, see my edited comment above.

@akarnokd
Copy link
Contributor

akarnokd commented May 1, 2017

@kjkrum

However, if a Publisher does detect it, what should it do?

It is generally impractical to detect this in an arbitrary Publisher but may happen with a Processor which if multicast-like will already have a list/set of subscribed Subscribers.

Call onSubscribe with a dummy Subscription.

This dummy instance would be cancelled by the Subscriber based on §2.5 (as tested by required_spec205_blackbox_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal())

Call onSubscribe with the original Subscription.

Same rule but with the consequence that the subscriber may left "hanging" because no further events will arrive from the Publisher side.

Signal onError, thereby canceling the original Subscription per 2.4.

This is a conservative way but also requires per-signal synchronization within the Publisher to ensure §1.3 (and can get expensive).

Silently ignore the duplicate subscription.

Silently ignoring is usually not recommended because the situation is likely due to implementation bugs anyway.

As an example, RxJava doesn't check for duplicates and treats all Subscriber coming through subscribe() as individual. Each Subscriber then will cancel the unique Subscription by the spec and then report the error case through RxJava's global error handler. Otherwise the previous Subscription is kept intact and the Subscriber will receive events through that connection only.

Allowing the same subscriber multiple times may be permissible provided the Subscriber synchronizes its onXXX methods. Often though, there is no actual need for synchronization, for example with concat, because the same instance will be served from different Publishers strictly one after the other has completed. This saves on allocation but can't be expected from any Subscriber thus the limits of the rule.

@viktorklang

Rely on Subscriber to cancel the second subscription. (Backwards compatible)

§2.5 already requires this.

@viktorklang
Copy link
Contributor

@akarnokd Re §2.5, that's what I meant. :)

@viktorklang
Copy link
Contributor

@kjkrum Closing due to inactivity, please reopen if you think it hasn't been answered. :)

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

No branches or pull requests

4 participants