Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SockJS -> Infinite xhr-streaming calls after reconnect

im using Stomp over SockJS and Spring websockets as backend. Occasionally Im having problems after my stomp client reconnect (wifi loss, server down, other). The connection is perfectly restored but passed few seconds i can see in browser network how SockJS client try and try send xhr-streaming with the old session id. The backend responde with close frame c[1000, "Go Away!"] The application its still work but this problem load the CPU and slow the application.

I could play turning off and starting the server several times (Not always happen). I cant understand how SockJS once reconnected ( We destroy and create the Sockjs instance from 0 ) send xhr-streaming requests with the id of the old session ( Maintains the pre-reconnection id session? memory leak? ). I'd like to keep it Sockjs not any state after reconnection and always starting from scratch.

enter image description here

Backend log:

2016-05-02 21:45:01.943 DEBUG [http-nio-8090-exec-7] o.s.w.s.s.t.h.XhrStreamingTransportHandler - Connection already closed (but not removed yet) for XhrStreamingSockJsSession[id=ypsjtids]

Client log:

(New Sessión Id -> h2pystok, Old session Id -> ypsjtids)

<<< PONG
browser.js:120 sockjs-client:buffered-sender send +45ms "\n"
browser.js:120 sockjs-client:buffered-sender sendSchedule +0ms 1
browser.js:120 sockjs-client:ajax-based create ajax sender +2ms        http://localhost/eess-services/stomp/355/h2pystok ["\n"]
browser.js:120 sockjs-client:browser:xhr POST +0ms http://localhost/eess-services/stomp/355/h2pystok/xhr_send
ws.js:216 >>> PING
browser.js:120 sockjs-client:browser:xhr withCredentials +2ms
browser.js:120 sockjs-client:browser:xhr readyState +18ms 4
browser.js:120 sockjs-client:browser:xhr status +1ms 200
browser.js:120 sockjs-client:browser:xhr finish +0ms 200 hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
o
h
h
h
h
h

browser.js:120 sockjs-client:receiver:xhr finish +0ms 200 hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh

browser.js:120 sockjs-client:receiver:xhr _chunkHandler +0ms 200
browser.js:120 sockjs-client:receiver:xhr close +0ms network
browser.js:120 sockjs-client:polling close +1ms null network undefined
browser.js:120 sockjs-client:polling _scheduleReceiver +0ms
browser.js:120 sockjs-client:receiver:xhr http://localhost/eess-   services/stomp/266/ypsjtids/xhr_streaming +0ms
browser.js:120 sockjs-client:browser:xhr POST +1ms http://localhost/eess-services/stomp/266/ypsjtids/xhr_streaming

There is a issue in github: https://github.com/sockjs/sockjs-client/issues/308

I have this problem in production :( Regards.

EDIT:

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(); }

like image 430
Antonio Hernández Avatar asked Nov 20 '22 07:11

Antonio Hernández


1 Answers

I spent a lot of time debugging SockJS and Spring code to figure out with that issue and what I have found.

xhr.js file has next method to parse inbound text message:

XhrReceiver.prototype._chunkHandler = function(status, text) {
  debug('_chunkHandler', status);
  if (status !== 200 || !text) {
    return;
  }

  for (var idx = -1; ; this.bufferPosition += idx + 1) {
    var buf = text.slice(this.bufferPosition);
    idx = buf.indexOf('\n');
    if (idx === -1) {
      break;
    }
    var msg = buf.slice(0, idx);
    if (msg) {
      debug('message', msg);
      this.emit('message', msg);
    }
  }
};

That method expects \n at the end of the SockJS text frame.

But in case of "Connection already closed (but not removed yet) forsessionId" Spring Framework sends unformatted SockJsFrame.closeFrameGoAway() without ending \n.

AbstractHttpSendingTransportHandler.class


protected void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response,
            AbstractHttpSockJsSession sockJsSession) throws SockJsException {

        if (sockJsSession.isNew()) {
            if (logger.isDebugEnabled()) {
                logger.debug(request.getMethod() + " " + request.getURI());
            }
            sockJsSession.handleInitialRequest(request, response, getFrameFormat(request));
        }
        else if (sockJsSession.isClosed()) {
            if (logger.isDebugEnabled()) {
                logger.debug("Connection already closed (but not removed yet) for " + sockJsSession);
            }
            SockJsFrame frame = SockJsFrame.closeFrameGoAway();
            try {
                response.getBody().write(frame.getContentBytes());
            }
            catch (IOException ex) {
                throw new SockJsException("Failed to send " + frame, sockJsSession.getId(), ex);
            }
        }

SockJsFrame

public static SockJsFrame closeFrame(int code, @Nullable String reason) {
    return new SockJsFrame("c[" + code + ",\"" + (reason != null ? reason : "") + "\"]");
}

As a quick and dirty fix I just created copy of org.springframework.web.socket.sockjs.frame.SockJsFrame and put in my classpath to substitute the original file with modified one.

public static SockJsFrame closeFrame(int code, @Nullable String reason) {
    return new SockJsFrame("c[" + code + ",\"" + (reason != null ? reason : "") + "\"]\n");
}

But in reality AbstractHttpSendingTransportHandler class should be fixed to apply format to the SockJS frame.

SockJsFrameFormat frameFormat = this.getFrameFormat(request);
SockJsFrame frame = SockJsFrame.closeFrameGoAway();
String formattedFrame = frameFormat.format(frame);
try {
    response.getBody().write(formattedFrame.getBytes(SockJsFrame.CHARSET));
}

After that fix all works as expected and c[1000, "Go Away!"] successfully routes to the 'c' section of next switch

SockJS.prototype._transportMessage = function(msg) {
  ...
  switch (type) {
    case 'a':
      if (Array.isArray(payload)) {
        payload.forEach(function(p) {
          debug('message', self.transport, p);
          self.dispatchEvent(new TransportMessageEvent(p));
        });
      }
      break;
    case 'm':
      debug('message', this.transport, payload);
      this.dispatchEvent(new TransportMessageEvent(payload));
      break;
    case 'c':
      if (Array.isArray(payload) && payload.length === 2) {
        this._close(payload[0], payload[1], true);
      }
      break;
  }
};

Without that fix message is not parsed as close message.

https://github.com/spring-projects/spring-framework/pull/28000

like image 68
Ivan Zbykovskyi Avatar answered Dec 21 '22 15:12

Ivan Zbykovskyi