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

http-kit channel reuse means chsk-send! occasionally responds to the incorrect HTTP request #260

Closed
osbert opened this issue Aug 19, 2016 · 7 comments
Assignees
Milestone

Comments

@osbert
Copy link

osbert commented Aug 19, 2016

I think this is what was being reported in #188, but let me try to offer a better explanation of what's happening and a couple different ideas for fixing, which I gladly welcome other ideas for.

Note that it is possible that this bug only occurs when using AJAX fallback and http-kit.

Because http-kit server channels are (at least I think) a 1:1 mapping to the underlying socket connection, they are reused as it processes HTTP requests. This means that a race condition is possible where interfaces/sch-send! attempts to send data down, but the channel is actually currently processing a request other than a sente-initiated AJAX long-poll.

From the client perspective the net result is that you issued an AJAX request for XYZ but received the result of a chsk-send! instead. Note that this is particularly prone to happening during handshake since there is no buffering and essentially closes the current long-poll socket immediately.

A full example looks something like this:

  • Server calls chsk-send! to CLIENT-ID, and pulls out a valid sch from conns_.
  • Sever responds to a handshake, which causes the current long-poll request to immediately close, previously using channel CHAN. Note that this fires on-close immediately, but send-buffered-server-evs>ajax-clients! continues using the old copy. Normally you can rely on interfaces/sch-send! to fail, but in the meantime ...
  • CLIENT-ID has received the handshake result and issues a non long-poll AJAX request for "/data", on the same underlying socket which on the server means CHAN now appears open/safe for sending, causing sch-send! to succeed, but this is in response to "/data". Note that this requires "/data" to take a little while to send a response in order for it to get preempted.

I have worked around this bug for now by building custom versions of sente and http-kit that expose the underlying http-kit HttpRequest object and hard-coding that the request URI is for the sente chsk endpoint. I have been able to conclusively prove that sente is calling sch-send! on a server channel with an associated HttpRequest for a URI other than the chsk endpoint.

But ... this does not seem like the best way to handle this. The best approach I've been able to come up with is to add a custom client header to sente long-poll and adding a method to IServerChan to check for this header in a server-specific way to detect if this is a sente AJAX long poll. Ideally I would prefer to be able to work with a ring request so that the logic could be uniform across all server adapters but I couldn't figure out a way to do that since the channels returned are designed to be opaque objects.

Let me know your thoughts and if this bug makes sense! I would be happy to clarify and try to provide logging/a more concrete example, but as you can see it's kind of difficult to illustrate properly.

Thanks for your time and a great library.

@osbert osbert changed the title http-kit channel reuse means chsk-send! occasionally respond to the incorrect HTTP request http-kit channel reuse means chsk-send! occasionally responds to the incorrect HTTP request Aug 19, 2016
@ptaoussanis
Copy link
Member

ptaoussanis commented Aug 20, 2016

Hi Osbert, thanks a lot for the clear, detailed report - this was very helpful.

Let me know your thoughts and if this bug makes sense!

It does, but I'll definitely need to dig some more to get a full handle on precisely what's happening + what the possible solutions look like.

Just have my hands very full for the next week or two, so it's unlikely I'll be able to look at this right away. Will try make it a priority soon as I can.

Some things you or someone else could do so long to assist:

  1. Produce some kind of repro example/test project that can reliably generate the problem (know this might be challenging, sorry).
  2. Confirm that this is an Ajax-only problem (i.e. doesn't affect http-kit WebSockets).
  3. Confirm that this is an http-kit-only problem (i.e. doesn't affect Immutant).

These would save me some significant time, so make it likelier that I can try get to a solution quicker if I have some random free moments.

Otherwise any other thoughts/observations very welcome!

Cheers :-)

@osbert
Copy link
Author

osbert commented Aug 30, 2016

I haven't looked at the other server adapters, but upon further reflection I think this should be considered a bug in http-kit (which I notice you're also the maintainer for so I'm comfortable starting the discussion here).

I think a better approach in http-kit would be to create a unique channel on a per-request basis to prevent reuse of the server channel. http-kit has to assume that library users will be holding onto these channels; that you can occasionally have this channel reference and send data back incorrectly seems like a bug. I think treating it as an http-kit bug would also make it much easier to reproduce.

Let me know if you agree and I can try to refile for http-kit and provide a suitable test case there. Thanks!

@ptaoussanis
Copy link
Member

Filing a bug + test with http-kit would be very helpful, thanks Osbert!

@ptaoussanis
Copy link
Member

Hi Osbert, any update on this? Thanks!

@osbert
Copy link
Author

osbert commented Sep 14, 2016

Sorry I've been too busy to look at this further. Will update when I can.

@ptaoussanis
Copy link
Member

No problem! Appreciate it, thanks :-)

@ptaoussanis
Copy link
Member

@osbert Hi Osbert, thanks again for the really detailed work on this!
I'm really sorry for the huge delay taking action.

Am going to try address this from 2x directions:

  1. I'll review your http-kit PR for the next upcoming http-kit release (again, huge thanks for this!).
  2. Forthcoming Sente v1.18 will include some tweaks to the connection handling to try minimize the likelihood of unintentional connection reuse.

ptaoussanis added a commit to http-kit/http-kit that referenced this issue Mar 1, 2023
…of channels (@osbert)

It can be dangerous to re-use channels for new HTTP requests.

A user might hold on to a channel, and reasonably expect it to *stay closed* after
use. By re-using channels, we risk a user unintentionally using a channel held from
req1 that's since been RE-OPENED for req2 (e.g. a completely unrelated request).

This is more than theoretical- the issue affected Sente,
  Ref. taoensso/sente#260

Big thanks to @osbert for identifying this risk, and for the proposed test and fix.

Commit marked as EXPERIMENTAL since possible performance implications of this change
have not yet been measured, and alternative solutions not yet exhaustively considered.

Still, since the behaviour prior to this commit is dangerous - it seems reasonable
to adopt the present fix as preliminary until further work can be done to either:

  1. Establish that this approach has limited or acceptable downsides, or
  2. A better alternative can be found
ptaoussanis added a commit that referenced this issue Mar 7, 2023
… `conns_` during handshake

Before this commit:

  Server channels (schs) for Ajax GET requests destined for handshaking are
  unnecessarily added to `conns_` before being closed to issue handshake.

  Sente presumed that once used, such schs would be permanently closed and
  all future send attempts against the sch would just fail.

  But as it turns out, @osbert identified [1][2] that at least http-kit may
  re-use (and re-open) schs for unrelated future requests.

  This could lead to cases like the following:

    - Handshake `req1` comes in, `sch1` gets added to `conns_` then closed.
    - `sch1` gets re-used (and re-opened) by http-kit for unrelated `req2`.
    - Before `req2` can respond, a broadcast call is made against `sch1`,
      which is still in `conns_`.

  The effect: random broadcast data incorrectly being sent to an unrelated
  request.

After this commit:

  Server channels (schs) for Ajax GET requests destined for handshaking are
  never added to `conns_`, preventing the possibility of handshake schs from
  being held for broadcast after closing.

  Instead, `conns_` contains only schs intended for long-polling (broadcast),
  and atomically removed from `conns_` on first use+close.

  This should prevent the issue described above.

  Separately, a change [2] is also being introduced upstream to http-kit for
  server-side prevention of this kind of unintentional re-use of channels.

[1] #260
[2] http-kit/http-kit#375
ptaoussanis added a commit that referenced this issue Mar 7, 2023
… `conns_` during handshake

Before this commit:

  Server channels (schs) for Ajax GET requests destined for handshaking are
  unnecessarily added to `conns_` before being closed to issue handshake.

  Sente presumed that once used, such schs would be permanently closed and
  all future send attempts against the sch would just fail.

  But as it turns out, @osbert identified [1][2] that at least http-kit may
  re-use (and re-open) schs for unrelated future requests.

  This could lead to cases like the following:

    - Handshake `req1` comes in, `sch1` gets added to `conns_` then closed.
    - `sch1` gets re-used (and re-opened) by http-kit for unrelated `req2`.
    - Before `req2` can respond, a broadcast call is made against `sch1`,
      which is still in `conns_`.

  The effect: random broadcast data incorrectly being sent to an unrelated
  request.

After this commit:

  Server channels (schs) for Ajax GET requests destined for handshaking are
  never added to `conns_`, preventing the possibility of handshake schs from
  being held for broadcast after closing.

  Instead, `conns_` contains only schs intended for long-polling (broadcast),
  and atomically removed from `conns_` on first use+close.

  This should prevent the issue described above.

  Separately, a change [2] is also being introduced upstream to http-kit for
  server-side prevention of this kind of unintentional re-use of channels.

[1] #260
[2] http-kit/http-kit#375
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants