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

Sente sessions persist over browser reloads #234

Closed
jwr opened this issue Jun 4, 2016 · 8 comments
Closed

Sente sessions persist over browser reloads #234

jwr opened this issue Jun 4, 2016 · 8 comments

Comments

@jwr
Copy link

jwr commented Jun 4, 2016

I wanted to check if this is expected behavior:

I just noticed that the connected-uids atom, while watchable, does not provide notifications about every connection change. In particular, uids get removed 1-2 seconds after a tab/page is closed. More worryingly, reloading the page in the browser, or even entering a different URL within the app and pressing enter, does not cause a change in connected-uids, even though the ClojureScript app on the client gets restarted, and starts with a completely clean state. Similarly, :chsk/uidport-open and :chsk/uidport-close` events do not provide full information about the app getting reloaded.

I expected to be able to notice reloads.

Is this expected behavior? If so, I will have to maintain information about connected client state separately, in my application, as the Sente mechanism does not provide sufficient information.

I'm using http-kit, in case it matters.

@danielcompton
Copy link
Collaborator

danielcompton commented Jun 4, 2016

The first part of your question sounds about right, the connections are closed by the client in an onclose event fired after the tab closes, I observe this takes a second or two.

I haven't seen the second part of the behaviour before. You should always see a :chsk/uidport-close when the connection closes and the user removed from connected-uids.

Similarly, :chsk/uidport-open and :chsk/uidport-close events do not provide full information about the app getting reloaded.

What do you mean by "not provide full information"?

Can you provide a small reproducible example of this, or does it occur on the example Sente app? I'm a few versions behind in Sente, so it is possible something has changed here.

@jwr
Copy link
Author

jwr commented Jun 5, 2016

Yes, I get the same behavior in the example app.

  • lein start

Connected uids change: {:ws #{}, :ajax #{:taoensso.sente/nil-uid}, :any #{:taoensso.sente/nil-uid}}

  • Enter "asdf" and press "Secure login"

Connected uids change: {:ws #{}, :ajax #{:taoensso.sente/nil-uid "asdf"}, :any #{:taoensso.sente/nil-uid "asdf"}}
Broadcasting server>user: 2 uids

  • Press Command-R, nothing appears in the server logs, except: Broadcasting server>user: 2 uids
  • Type "asdf2", press "secure login" and observe

Connected uids change: {:ws #{}, :ajax #{:taoensso.sente/nil-uid "asdf2" "asdf"}, :any #{:taoensso.sente/nil-uid "asdf2" "asdf"}}

Broadcasting server>user: 3 uids

So, from the server's point of view, we have asdf and asdf2 connections open, which doesn't seem to be true.

@ptaoussanis
Copy link
Member

ptaoussanis commented Jun 8, 2016

Hi Jan,

The behaviour you've described is intentional. Uid connection changes are officially announced only ~5 seconds after the event (this behaviour is the same whether you're watching the connected-uids atom or listening for uid-port event messages).

Without the 5 second buffer, you'd see a lot of dis/connect noise during Ajax repolling, momentary connection drops, browser refreshes, etc.

For example: if a user follows an href from page A to page B, that'll technically trigger a disconnect+reconnect in most browsers. But from Sente's API perspective, the user hasn't actually intended to disconnect; she's just followed an href.

So Sente smoothes over these implementation-level dis/connects with a 5 second window.

The connected-uids atom and related event messages aren't realtime. In fact, they may take as long as 30 seconds or more to reflect connection changes in the worst cases (e.g. a recently identified case of a mobile user suddenly turning on their airplane mode).

The TCP/WebSocket spec by design doesn't have provisions for quickly+reliably identifying these kinds of abnormally terminated connections, so Sente leans on a keep-alive ping.

tl;dr - disconnect notification resolution can vary from between <1 second to about <60 seconds depending on your keep-alive configuration. For an application-level API, that should be plenty quick in most cases and strikes a good balance between notification speed + noise IMO.

Does that help / make sense? Please feel free to close this issue if you're satisfied.

Cheers :-)

@ptaoussanis
Copy link
Member

Closing for now, please feel free to reopen if you still have any questions. Cheers :-)

@jwr
Copy link
Author

jwr commented Jun 10, 2016

My issue wasn't that much about the time it takes to notice a disconnect, but rather about being able to notice it at all. And as it turns out, the solution described in #235 solves my problem: using uids of the form [id (get-in req [:params :client-id])] means that browser reloads will eventually trigger a disconnect which will be reflected in connected-uids. This is fine — I never wanted to rely on connected-uids for real-time stuff, only for cleanup.

The reason I wasn't seeing disconnects was because if the uid was the same, a browser reload was treated as a non-event: a temporary communication failure. After all, the same uid was back several seconds later.

So, my problem is solved, though I am still not sure about the example I posted above: the 3 uids message after a browser reload and another login is at the very least confusing.

@ptaoussanis
Copy link
Member

ptaoussanis commented Jun 10, 2016

Hi Jan, thanks for the clarification.

And as it turns out, the solution described in #235 solves my problem: using uids of the form [id (get-in req [:params :client-id])]

User ids are probably the hardest thing in Sente to grok, mostly because developers normally have a notion in mind about how they'd like user identities to work - but it's a notion that conflicts with the actual capabilities of what browsers can do in a way that's reliable and secure. I.e. most of the problems turn out to be intrinsic to the mishmash of net infrastructure that we need to build on.

In case it's helpful, have tried to summarise some of the most important info at #118 (comment)

I am still not sure about the example I posted above: the 3 uids message after a browser reload and another login is at the very least confusing.

Sure; this is a good example because the current behaviour is indeed confusing. But any alternative behaviour would be just as confusing, plus also inherently troublesome in additional ways.

I'd recommend you think of things like so:

  1. A user-id is an arbitrary function of the Ring request plus client-id (an arbitrary identifier provided by each client). This definition means that one user-id may map to many clients. (E.g. one logged-in username with multiple tabs or devices).
  2. Sente's server tracks connected user-ids.
  3. Since tabs and devices can come and go, we choose to define a uid as "connected" the moment we see it, and "disconnected" only when we haven't observed any active connections with that uid in the last 5 seconds.

Point 3 above explains your 3 uids message. Seeing a "connected" uid doesn't mean that there's an active connection with that uid right this moment; it means that there's been at least one active connection with that uid recently.

The connected-uids atom can contain these ghost uids for up to a minute in the worst (rare) cases. That's by design, useful, and necessary because of the way the internet works.

It might be helpful to consider alternative behaviour: e.g. if we immediately show a uid as disconnected the moment its only active connection ends, then you're going to see dis/connection alerts every time a user refreshes a page, or clicks a link, or drops to Ajax, or a proxy delays a poll attempt, or the user drives through a tunnel w/o a net connection for 500 milliseconds, etc.

And in cases like a sudden airplane mode activation, we literally have no mechanism to immediately and reliably identify a closed connection. So we'll accept all these problems, only to gain inconsistencies we didn't have before. I.e. we still can't react to dis/connection events immediately with confidence.

The current behaviour saves you from an awful lot of headaches + keeps things simple:

  • uidport-open => At least one client has connected using this uid
  • uidport-close => All clients using this uid appear to have intentionally (or otherwise at least semi-permanently) disconnected.

Hope that helps, cheers!

@ptaoussanis ptaoussanis removed the bug? label Jun 10, 2016
@jwr
Copy link
Author

jwr commented Jun 10, 2016

Thank you, your explanations are (as usual) fantastic. Also, I checked the example again and indeed uids disappear from connected-uids, but after a while, and it isn't immediately apparent from the messages that the example app prints. Hence my confusion there.

As for the general behavior, like I said in my case using uids that also include a client-id solves the problem. My wording wasn't precise: it isn't necessarily that I wanted to notice disconnects — I wanted to notice when a new instance of my ClojureScript client app appeared. And I wanted to (eventually) clean up after old connections. All of this is possible right now.

Thanks again for the clear and extensive explanation!

@ptaoussanis
Copy link
Member

No problem :-)

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

No branches or pull requests

3 participants