socket.io: JS exceptions thrown while just idling (WebSocket is already in CLOSING or CLOSED state.)

You want to:

  • report a bug
  • request a feature

Current behaviour

While leaving a socket connected, but doing no other activity in my program, I see intermittently (but fairly consistently) an exception thrown in browser console, from inside of the socket.io machinery (specifically in backo2/index.js line 83. That error is:

WebSocket is already in CLOSING or CLOSED state.

socket-io-errors

Steps to reproduce (if the current behaviour is a bug)

I have two tabs open with client sockets connected to the same server (localhost) via https. Both clients are sitting idle with nothing else happening in browser or server (except for whatever keep-alive pings socket.io is doing). Both of them are joined to a single channel (via join(..) on the server). Otherwise, nothing else special.

Here’s how I create the server socket instance:

var httpsServer = https.createServer(..);
var io = require("socket.io")(httpsServer);
io.on("connection",onSocketConnection);

And in the client:

socket = io();
socket.on("connect",function(){
   console.log("socket connected");
});
socket.on("disconnect",function(){
   console.log("socket disconnected");
});

Expected behaviour

I expect disconnections and reconnections from time to time, but I don’t expect spurious JS exceptions thrown by the library when I’m not doing anything else over the connections.

Setup

  • OS: Mac OSX
  • browser: Chrome 66, node 10.2.1
  • socket.io version: 2.1.1

Other information (e.g. stacktraces, related issues, suggestions how to fix)

Expanded stack trace:

index.js:83 WebSocket is already in CLOSING or CLOSED state.
(anonymous) @ index.js:83
e.encodePacket @ index.js:83
(anonymous) @ index.js:83
r.write @ index.js:83
r.send @ index.js:83
r.flush @ index.js:83
r.sendPacket @ index.js:83
r.ping @ index.js:83
(anonymous) @ index.js:83
setTimeout (async)
r.setPing @ index.js:83
r.onPacket @ index.js:83
(anonymous) @ index.js:83
r.emit @ index.js:83
r.onPacket @ index.js:83
r.onData @ index.js:83
ws.onmessage @ index.js:83

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 86
  • Comments: 60 (3 by maintainers)

Commits related to this issue

Most upvoted comments

There is a change for the default pingTimeout from 60000(v2.0.4) to 5000 (v2.1.0+) which is not enough for some browsers like Chrome.

The solution to this issue on v2.1.0+ including the latest v2.2.0 is to override the default pingTimeout on your server to a large value as follows:

const http = require('http');
const server = http.createServer();
const io = require('socket.io')(server, {
  pingTimeout: 60000,
});

OR

const io = require('socket.io')();
const http = require('http');
const server = http.createServer();
io.attach(server, {
  pingTimeout: 60000,
});

I fixed it for me:

Previously I used this:

socket.on('ping', alert);

But then I changed it to this:

socket.on('ping', msg => {
    alert(msg);
});

And it worked!

Thx for getting this thread started @getify (I’m on socket.io v2.2.0)

To anyone that is also experiencing this - ME TOO COMMENTS DON’T HELP 😃 please vote up the issue with a thumb to show your support 👍

Thx @omardoma for discovering the pingTimeout and I agree with @LunarMist that the we should investigate the ping/pong event system.

I did some preliminary testing with Chrome, Firefox, & Safari (all on MacOS).

Chrome & Safari both appear to throttle ws activity with different behavior & constraints:

  • Chrome, after ~5min of inactivity when the tab is focused or immediately when the tab goes into the background. This is intentional by the chromium team (https://developers.google.com/web/updates/2017/03/background_tabs)
  • Safari, almost immediately when app focus is changed to another desktop app (MacOS) or when the tab goes into the background. Related to #2924
  • Firefox, *never has any issues mentioned in this thread

Solutions

I tested for several hours with all 3 of these browsers, changing the pingTimeout & pingInterval values. What I found to be solutions:

  1. Setting pingTimeout >= 30000ms
  • or -
  1. Setting pingInterval <= 10000ms

I believe the best solution to be changing pingTimeout = 30000. The default pingInterval is 25000ms and increasing the frequency of the clients pinging the server to every 10s can be too chatty for at scale projects.


Are @darrachequesne or any other repo members in favor of increasing the default pingTimeout to 30000 from the current default of 5000ms which was changed in v2.1 from 60000ms?

Downgrading to 2.0.3 “fixed” this issue for me.

I think that’s because the current version of socket.io relies on the setTimeout on the client side, which may not be as reliable as expected.

I think increasing the pingTimeout should temporarily fix the issue, but we’ll use ping/pong from the server -> client (instead of the current client -> server) in v3.

just use websocket- node, it is easy to use and works without any errors and plus you don’t need an external library for that: https://codeburst.io/why-you-don-t-need-socket-io-6848f1c871cd https://medium.com/@martin.sikora/node-js-websocket-simple-chat-tutorial-2def3a841b61 https://www.npmjs.com/package/websocket

Experiencing absolutely the same issue and the code is correct. This only happens in Chrome for me. Mozilla is clean. In chrome, this err is raining over and over and duplicating all the chats I have. I tried using this method socket.on('disconnect', () =>{ socket.disconnect(); }); It doesn’t disconnect the client from the server. Repo in case needed https://github.com/antoniab123456/Chat_app

Just use the following code at the client end.

socket.on(‘ping’, () => { socket.emit(data); });

This seems to be an ongoing issue for several people and there has not been any solution to this. Any updates from anyone?

Same here.

socket.io & socket.io-client: “^2.1.1” MacOS Google Chrome is up to date Version 68.0.3440.106 (Official Build) (64-bit)

Here are the defaults from the documentation. Looks like a ping is sent every 25 seconds and each ping has 5 seconds to complete.

pingTimeout 5000 how many ms without a pong packet to consider the connection closed
pingInterval 25000 how many ms before sending a new ping packet

You use Safari for localhost webdev? 🤔

Please let us know once this is fixed, same issue for me, my versions r below:

"socket.io": "2.2.0",
"socket.io-adapter": "~1.1.1",
"socket.io-client": "2.2.0",
"socket.io-parser": "~3.2.0",
"socket.io-redis": "^5.2.0",

My Code: server, { path: ‘/socket.io’, serveClient: true, // below are engine.IO options pingInterval: 40000, pingTimeout: 25000, upgradeTimeout: 30000, // default value is 10000ms, try changing it to 20k or more agent: false, cookie: false, rejectUnauthorized: false, reconnectionDelay: 1000, reconnectionDelayMax: 5000 }

my client:

reconnection: true, reconnectionDelay: 1000, reconnectionDelayMax: 5000, reconnectionAttempts: Infinity, //our site options transports: [“polling”, “websocket”], secure: true, rejectUnauthorized: false, forceNew: true, timeout: 60000

Why is 5000 not enough? I am seeing this issue on my local dev server, and the latency between the client and server is most certainly under that threshold. 5000ms seems like a reasonable timeout. Smells like there could be an issue with the ping-pong system itself, and it only manifested when it was lowered.

2.1.1 still facing the same issues. Tried to incrase ping and timeout but nothing changed

downgrade to v 2.0.3

the same problem here image

browser is just idling, then the errors come up.

I used Chrome and mac OS

Possibly a workaround on client side if anybody is interested till the issue is fixed. This will cause browser to not free up memory and may cause excessive memory usage.

If anybody has Chrome workaround, that would help

Reacting to your own comment also doesn’t help.

The clients are the ones pinging the the server and not the other way around.

I have the same problem with socket.io and Chrome Mac OS 10.13.5 Chrome Version 67.0.3396.87 (Official Build) (64-bit) Node: 10.3.0 Express: 4.16.3 socket.io: 2.1.1

In Firefox everything is fine.

Error details below:

index.js:83 WebSocket is already in CLOSING or CLOSED state.
(anonymous) | @ | index.js:83
  | e.encodePacket | @ | index.js:83
  | (anonymous) | @ | index.js:83
  | r.write | @ | index.js:83
  | r.send | @ | index.js:83
  | r.flush | @ | index.js:83
  | r.sendPacket | @ | index.js:83
  | r.ping | @ | index.js:83
  | (anonymous) | @ | index.js:83
  | setTimeout (async) |   |  
  | r.setPing | @ | index.js:83
  | r.onPacket | @ | index.js:83
  | (anonymous) | @ | index.js:83
  | r.emit | @ | index.js:83
  | r.onPacket | @ | index.js:83
  | r.onData | @ | index.js:83
  | ws.onmessage | @ | index.js:83

When I click on the index.js:83 it takes me to this module:

/**
 * Expose `Backoff`.
 */

module.exports = Backoff;

/**
 * Initialize backoff timer with `opts`.
 *
 * - `min` initial timeout in milliseconds [100]
 * - `max` max timeout [10000]
 * - `jitter` [0]
 * - `factor` [2]
 *
 * @param {Object} opts
 * @api public
 */

function Backoff(opts) {
  opts = opts || {};
  this.ms = opts.min || 100;
  this.max = opts.max || 10000;
  this.factor = opts.factor || 2;
  this.jitter = opts.jitter > 0 && opts.jitter <= 1 ? opts.jitter : 0;
  this.attempts = 0;
}

/**
 * Return the backoff duration.
 *
 * @return {Number}
 * @api public
 */

Backoff.prototype.duration = function(){
  var ms = this.ms * Math.pow(this.factor, this.attempts++);
  if (this.jitter) {
    var rand =  Math.random();
    var deviation = Math.floor(rand * this.jitter * ms);
    ms = (Math.floor(rand * 10) & 1) == 0  ? ms - deviation : ms + deviation;
  }
  return Math.min(ms, this.max) | 0;
};

/**
 * Reset the number of attempts.
 *
 * @api public
 */

Backoff.prototype.reset = function(){
  this.attempts = 0;
};

/**
 * Set the minimum duration
 *
 * @api public
 */

Backoff.prototype.setMin = function(min){
  this.ms = min;
};

/**
 * Set the maximum duration
 *
 * @api public
 */

Backoff.prototype.setMax = function(max){
  this.max = max;
};

/**
 * Set the jitter
 *
 * @api public
 */

Backoff.prototype.setJitter = function(jitter){
  this.jitter = jitter;
};




//////////////////
// WEBPACK FOOTER
// ./~/backo2/index.js
// module id = 41
// module chunks = 0

Line 83 is:

this.jitter = jitter;

Hello, I am facing the same kind of issue but with two errors. I tried @omardoma solution. Can anyone help? Os - Ubuntu 18.04 Node - 12.16.2 npm - 6.14.5 socket.io - 2.3.0 my code -

app.js


const speech = require('@google-cloud/speech');
const speechClient = new speech.SpeechClient(); // Creates a client
const environmentVars = require('dotenv').config();
const io = require('socket.io')();
const http = require('http');
const server = http.createServer();
io.attach(server, {
  pingTimeout: 60000,
});



console.log(io)



// =========================== SOCKET.IO ================================ //

io.on('connection', function (client) {
  console.log('Client Connected to server');
  let recognizeStream = null;

ejs file

<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.3.0/socket.io.js" integrity="sha256-bQmrZe4yPnQrLTY+1gYylfNMBuGfnT/HKsCGX+9Xuqo=" crossorigin="anonymous"></script>
<script src="/js/client.js"></script>

js file

const socket = io.connect("http://localhost:8080");

I noticed this same error but in a different context so I’m going to do a quick write-up with my problem and solution for anyone else ending up here.

My scenario was on Chrome for Android where the connection is dropped after locking the screen.

I’m using a token to identify the same user across different connections, which I send from client on connect using the query option approach and I update the token with a new one received from server:

// client
io('myAppUrl', {
  query: {
    token: localStorage.getItem('myKey') || ''
  }
});

io.on('reconnect_attempt', () => {
  io.opts.query = {
    token: localStorage.getItem('myKey') || ''
  };
});

io.on('my_custom_connection_successful_event', (token) => {
  localStorage.setItem('myKey', token);
});

The problem is that the Chrome for Android client would send to the server an empty string token on reconnect. Which I wouldn’t expect since I added the 'reconnect_attempt' listener which makes sure to set the latest token from localStorage.

Well after a couple of hours of debugging with console logging all the possible client events I realized that the 'reconnect_attempt' event is not fired at all in my scenario of reconnecting. After unlocking the screen I get the following sequence of events:

  • disconnect (reason: ‘transport close’)
  • reconnecting (attempt: 1)
  • reconnect
  • ping/pong starts again

So no 'reconnect_attempt' event is being fired which explains why the token is still empty string (the initial value set on connect).

Long story short, the solution for me is to immediately update the io.opts.query instance variable on my custom “connection successful” event received form the server with the new token:

// client
io('myAppUrl', {
  query: {
    token: localStorage.getItem('myKey') || ''
  }
});

io.on('my_custom_connection_successful_event', (token) => {
  localStorage.setItem('myKey', token);
  io.opts.query = {
    token: token
  };
});

Now I understand that io.opts.query is an instance variable which is used on connect and all subsequent reconnects, so I can update it as soon as I want to. I don’t have to wait for any reconnect-related event.

I feel a bit misled by the With query options docs. Maybe I misunderstood the use case in that example? But if it’s similar to my use case, instead of the 'reconnect_attempt' example the docs could explain that io.opts.query is an instance variable that can be mutated and its current value is used on all subsequent reconnects. So it can be changed whenever you want to refresh the token, even on a 'refresh_token' custom event for example. I can do a PR if you think it’s a good idea for improving the docs.

Edit: Upon further investigation I realized my mistake was that the 'reconnect_attempt' listener was being removed from another part of my code… 🤦‍♂️ That’s why I was not seeing it in my logging. So yes, the events sequence for my reconnect scenario is in fact:

  • disconnect (reason: ‘transport close’)
  • reconnect_attempt (attempt: 1)
  • reconnecting (attempt: 1)
  • reconnect
  • ping/pong starts again

Still, my realization that io.opts.query can be changed whenever you want is still valid. 😅

Downgrading to 2.0.3 “fixed” this issue for me.

seems to be doing the trick. Tested for 4 mins and no errors. Let see how long it holds!

@cozuya thanks for posting this!

You use Safari for localhost webdev? 🤔

Not really, but it is annoying with constant disconnects to test my app. This provides a temporary workaround till it is fixed in 3.0. This is not a solution. I posted if it will help somebody during dev.

Same problem. Can someone please respond to this?