-
Notifications
You must be signed in to change notification settings - Fork 140
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
Fix onClose calls for shared subscribers #117
base: master
Are you sure you want to change the base?
Fix onClose calls for shared subscribers #117
Conversation
2dfe379
to
072fd8e
Compare
Thank you @davilima6 for taking the time to put together reproducible examples, along with updating unit tests -- I will take a look later this evening. I'm really sorry for the late reply. |
Ok, so you are absolutely right in your identification of the issue in 1., but I don't think the negative outcome is that the child components (in your example) are not logging the close event, but arguably that anything is logging the close event at all. When a component that is sharing a websocket does one of the following things:
It is ultimately cutting ties with that websocket, and does not care what happens to it in the future (even if that future can be measured in milliseconds). The only reason the websocket closes at all is because there are no subscribers anymore that care what happens to it anymore. Imagine a use-case where your child components all set the There are definitely some inconsistencies here, however, that I hadn't really thought carefully enough about:
My original justification for 1. was pretty philosophical (and perhaps arbitrary): when a component with an unshared websocket does any of the three actions above, it knows that the websocket will close, and thus more akin to a request to close it than a request to 'unsubscribe' from it. For a component that has only participated as one of (potentially) many subscribers, opting to unsubscribe is less explicitly a request to close (even if in many typical use-cases, if one subscriber disconnects, all are). I'm open to arguments that this is nevertheless confusing and inconsistent -- I have a tendency to rationalize after-the-fact, and usually the longer my explanation, the more uncertain I am. For 2. this is largely caused by the fact that it is faster for all components to subscribe than it is for the websocket to 'open', and so all subscribers are lined up in time for the open event (even though the open event was initiated immediately after the first subscriber). This is actually the same in the case of closing a websocket: it is faster to unsubscribe than it is for the websocket to completely close, and so it would be awkward to differentiate between a component that is unsubscribing individually (and arguably does not care what happens to the websocket) and one that is unsubscribing at the same time as all other subscribers (and thus is more similar to the unshared websocket). As for your pull request, I don't think I can approve it (even if I agreed that my current implementation is wrong) -- the way I read it, if:
Nevertheless, I'm curious what you (or anyone else) thinks about the current behavior that the last component to unsubscribe has the if (!hasSubscribers(url)) {
try {
const socketLike = sharedWebSockets[url];
if (socketLike instanceof WebSocket) {
socketLike.onclose = (event: WebSocketEventMap['close']) => {
if (optionsRef.current.onClose) {
optionsRef.current.onClose(event);
}
setReadyState(ReadyState.CLOSED);
};
}
socketLike.close();
} to if (!hasSubscribers(url)) {
try {
const socketLike = sharedWebSockets[url];
if (socketLike instanceof WebSocket) {
socketLike.onclose = null;
}
socketLike.close();
} Does that actually benefit any possible use-case? As the last subscriber, there is no risk of the websocket closing unexpectedly in the future, and it's possible that a user is relying on the |
I appreciate a lot the care you put in your explanations, thank you very much! I will answer properly soon but I agree this PR cannot be merged as-is. I was looking only at my current use case, a single shared connection/url with multiple subscribing components, and missed the issue of the unexpected "stale" call(s) to |
…ent is invoked when all subscribers disconnect
… on last unsubscription also use addEventListener instead of reassigning and losing the previous socket-like close handlers this was causing a bug where only the first subscriber of a shared socket would have its handler invoked, since its listener was only being registered when cleaning unsubscribers for the last subscription for a url
072fd8e
to
3334944
Compare
My only other concern is with using I wonder if there might be another way to solve your use-case? https://stackblitz.com/edit/react-cfpqjj?file=index.js |
The missing calls to the option-based
onClose
handlers happen when all shared subscribers that once had theconnect
flag enabled (thus that had mounted the mainuseEffect
) later change it tofalse
(thus requesting unsubscription).In that case
useEffect
is re-run, performing the cleanup for theconnect
branch, where 2 issues seem to take place:onClose
handler for each subscriber happened only for the last component unsubscribing for a URL (i.e. only after!hasSubscribers(url)
incleanSubscribers
). The order depends on which components un-mounts last, e.g. only a parent'sonClose
listener would be registered, and later run aftersocketLike.close()
is called.addEventListener
It can be reproduced either in:
PS: I didn't understand why a complementary mechanism is needed to register the
onClose
handler, apart from the notifications-based one registered inattach-shared-listeners#bindCloseHandler
, like it works for all other listeners.PS 2: There is no issue related to missing calls when disconnects happen due to server closing.