sockjs-client: xhr_streaming infinite loop after connection closed

Steps to reproduce:

  1. Turn WebSockets off in NodeJS server (websocket: false in options);
  2. On the server, close all SockJS connections after a delay of 500 ms using conn.close(1000, 'init_message_timeout') (the exact code and message don’t matter);
  3. Connect to that server using SockJS 1.0.1 (latest) or 0.3.4 from the latest Chrome.

During the first 500 ms, the connection stays alive. After that, it gets closed as expected. However, the xhr_streaming transport starts an infinite loop: it tries to connect to the server, then SockJS server (not the app logic) immediately closes that connection with the code/reason provided in the step 2, and, after that, xhr_streaming attempts to connect again, and so on:

image

Here is HTTP log from the server:

image

This infinite loop takes significant amount of CPU on the client (up to 70%) and on the server and load balancers, effectively causing DDOS when there are multiple such forcefully-closed clients.

It seems to me that xhr_streaming should not attempt to reconnect after it gets user-provided close reason from the server. Or am I misunderstanding something?

This issue hits us in production, and we need to resolve it as fast as possible.

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 36 (4 by maintainers)

Commits related to this issue

Most upvoted comments

Still having this issue even on 1.1.2.

I got this because of my shadowsocks, I turned it off and no more errors.

Here is our workaround for this issue:

var sockJs = new SockJS(...);
var _transportClose = sockJs._transportClose;
sockJs._transportClose = function(code, reason) {
  if (this._transport && this._transport.close) {
    this._transport.close();
  }
  _transportClose.call(this, code, reason);
}

In our case, loop was triggered by (occasional) initialization timeout in xhr-polling transport. After the timeout, function _transportTimeout calls _transportClose, and _transportClose removes all listeners from transport, however, transport remains active. When transport receives message from spring with close request (like c[3000,Go away!]), no one will process this message (because there are no listeners!), and transport will loop (because response code is 200).

Update: I see this is already fixed in master, but not in 1.1.4

Same problem with sockjs 1.1.4, spring boot websocket

Hey folks, can you try 1.1.2 and let me know if you can still reproduce the problem?

I found where is the possible error. In polling.js i have see a special treatment when the close reason is ‘network’. When is network again call the function _scheduleReceiver(). When we reconnect the infinite loop occurs. I dont know what is the reason for this treatment but I could try this deleting the special treatment of ‘network’ and everything works correctly. @skozin Can you try?

if (!self.pollIsClosing) { if (reason === 'network') { self._scheduleReceiver(); } else { self.emit('close', code || 1006, reason); self.removeAllListeners(); } }

the workaround is:

if (!self.pollIsClosing) { self.emit('close', code || 1006, reason); self.removeAllListeners(); }

@brycekahle any chance of a release? I encountered this problem using sockjs via Primus (https://github.com/primus/primus/issues/635#issuecomment-38507835), hand-patching with https://github.com/sockjs/sockjs-client/commit/eef1957a4d8007a64e170a4bbb2c635e40d870b2 fixed it but it would be nice to be able to rebuild Primus and depend on an official sockjs version.