ws: read ECONNRESET error on disconnect (ws 3.3.3)

  • I’ve searched for any related issues and avoided creating a duplicate issue.

Description

Upon updating to ws 3.3.3, when a client disconnects the following exception is thrown:

events.js:183
      throw er; // Unhandled 'error' event
      ^

Error: read ECONNRESET
    at _errnoException (util.js:1024:11)
    at TCP.onread (net.js:615:25)

Code works fine in previous version.

Reproducible in:

version: 3.3.3 Node.js version(s): v8.9.1 OS version(s): Windows 10 Pro, Fall update

Steps to reproduce:

  1. Create a websocket server
  2. Connect to the websocket server via Chrome (`new WebSocket(‘ws://localhost:3000’))
  3. Disconnect from the websocket server (via refreshing the page in Chrome)

Expected result:

The disconnect is handled gracefully, with the ‘close’ event being called.

Actual result:

An exception is thrown, which crashes the app:

events.js:183
      throw er; // Unhandled 'error' event
      ^

Error: read ECONNRESET
    at _errnoException (util.js:1024:11)
    at TCP.onread (net.js:615:25)

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 68
  • Comments: 51 (18 by maintainers)

Commits related to this issue

Most upvoted comments

Issue is caused by lack of error listener on the socket, and can be fixed like so:

ws.on('error', () => console.log('errored'));

This isn’t documented in the README.md, and seems to be a pretty big breaking change.

@lpinca You document that that event is where any errors from underlying net.Socket are forwarded to, but not the fact that a simple client disconnection will cause such an exception.

To me, because you have a 'close' event, I’d assume that exceptions caused as a result of a client disconnecting would be handled by this library, since it’s something that the library seems to be able to detect and act on.

I think is a reasonable assumption to make, given that clients disconnecting is a perfectly normal event for a socket server.

I’m in the same boat.

ws.on(‘error’, () => console.log(‘errored’));

this doesn’t solve the problem.

I’m in the same boat.

ws.on('error', () => console.log('errored'));

this doesn’t solve the problem.

I can confirm this statement, when the handler is applied to the server. No more crashes when applied to each connection individually.

These errors occur quite often on Chrome V 63.0.3239.84 (=latest for my distribution).

FF 57.0.3 doesn’t produce this error at all. Same with Edge on Windows clients.

Node v8.9.3 ws 3.3.3 ubuntu1~16.04.5 Linux 4.4.0-21-generic (x86_64)

According to https://bugs.chromium.org/p/chromium/issues/detail?id=798194#c6 this works as intended.

What we can do here is adding a default 'error' listener which is a noop, so people are not forced to add one. Users can still get errors by adding their own 'error' listener(s).

Thoughts?

@endel the error is not a bug in the library and https://github.com/websockets/ws/issues/1256#issuecomment-354305677 is the proof. The fact that it only happens on Chrome is another proof.

You get the error only on ws@>=3.3.3 because it is the first version that does not swallow the net.Socket errors.

Another possible workaround for Chrome is calling close() from a listener of the 'beforeunload' event:

window.addEventListener('beforeunload', function () {
  ws.close();
});

Everyone please add a listener for the 'error' event on the WebSocket instance.

// Server example.
wss.on('connection', (ws) => {
  ws.on('error', (err) => {
    // Ignore network errors like `ECONNRESET`, `EPIPE`, etc.
    if (err.errno) return;
    throw err;
  });
});
// Client example.
const ws = new WebSocket(url);

ws.on('error', handleError);

Nevermind, here is a test case which doesn’t use ws:

const assert = require('assert');
const crypto = require('crypto');
const http = require('http');

const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';

const data = `<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
  </head>
  <body>
    <script>
      (function () {
        var ws = new WebSocket('ws://localhost:8080');

        ws.onopen = function () {
          console.log('open');
        };
      })();
    </script>
  </body>
</html>`;

const server = http.createServer();

server.on('request', (req, res) => {
  res.setHeader('Content-Type', 'text/html');
  res.end(data);
});

server.on('upgrade', (req, socket) => {
  const key = crypto.createHash('sha1')
    .update(req.headers['sec-websocket-key'] + GUID)
    .digest('base64');

  socket.on('error', console.error);
  socket.on('data', (data) => {
    assert.ok(data.slice(0, 2).equals(Buffer.from([0x88, 0x82])));
    socket.write(Buffer.from([0x88, 0x00]));
  });

  socket.write([
    'HTTP/1.1 101 Switching Protocols',
    'Upgrade: websocket',
    'Connection: Upgrade',
    `Sec-WebSocket-Accept: ${key}`,
    '\r\n'
  ].join('\r\n'));
});

server.listen(8080, () => console.log('Listening on *:8080'));

This still happens on 4.0.0, adding both error and close event handlers on ws instance doesn’t help.

@ProgrammingLife it should. In https://github.com/websockets/ws/issues/1266#issue-285162876 you added the listener on the server so make sure you are adding it to the WebSocket instance instead.

@G-Rath

but not the fact that a simple client disconnection will cause such an exception.

Yes because it shouldn’t, the issue seems to happen only on Chrome 63.

To me, because you have a ‘close’ event, I’d assume that exceptions caused as a result of a client disconnecting would be handled by this library, since it’s something that the library seems to be able to detect and act on.

I think is a reasonable assumption to make, given that clients disconnecting is a perfectly normal event for a socket server.

Swallowing errors is never a good idea. Developers can ignore errors but they may want to know if the connection was closed cleanly or not.

@quigleyj97

I didn’t check but maybe latest Chrome sends the close frame and abruptly closes the connection immediately after that.

Closing as we are no longer re-emitting net.Socket errors on version 5.

I didn’t expect to create so much disruption by re-emitting net.Socket errors as I assumed that everyone already had 'error' listeners set up but I was wrong and it seems that these errors are ignored anyway, so I’m thinking to not re-emit them again, restoring behavior of ws@<=3.3.2.

What do you think? It would be great if you could test upcoming changes installing from GitHub.

npm i websockets/ws#subclass/stream-writable

Thanks.

No, there are no side effects, the WebSocket is always closed when an error occurs, so you can use a noop as listener if you don’t care about errors.

ws.on('error', () => {});

idk then. works fine for me.

my code(server):

const WebSocket = require(‘ws’);

const wss = new WebSocket.Server({ port: 8080 });

wss.on(‘connection’, function(ws) { ws.on(‘message’, function(message) { console.log("msg: " + message); });

ws.on(‘close’, function(){ console.log(“client closed connection!”); });

ws.on(‘error’, function(e){ console.log("error!: " + e); })

ws.send(‘something’); });

just add onerror and onclose (inside of wss.on.(‘connection’,…)) to your server :

ws.on(‘close’, function(){ console.log(“client closed connection!”); });

ws.on(‘error’, function(e){ console.log("error!: " + e); })

Apologies for commenting on an old issue, but this is still a problem.

Error: write EPIPE
    at afterWriteDispatched (internal/stream_base_commons.js:156:25)
    at writevGeneric (internal/stream_base_commons.js:139:3)
    at Socket._writeGeneric (net.js:783:11)
    at Socket._writev (net.js:792:8)
    at doWrite (internal/streams/writable.js:375:12)
    at clearBuffer (internal/streams/writable.js:521:5)
    at Socket.Writable.uncork (internal/streams/writable.js:317:7)
    at Sender.sendFrame (/home/harold/projects/mercury/server/mercury.js:34612:22)
    at Sender.send (/home/harold/projects/mercury/server/mercury.js:34507:12)
    at WebSocket.send (/home/harold/projects/mercury/server/mercury.js:35382:18)

Whenever a client disconnects while a large amount of data is being sent, I consistently get full server crashes. Everywhere in my code, I’ve added listeners to the error event where possible; on the http server, request and response object, the socket server and each individual client. I’ve even gone as far as ws._socket.on('error', () => ...), but to no avail.

Using ws 7.4.2, running on Node v14.15.4 on Ubuntu 20.04.

I’m actually unsure if this is a node internal error or has something to do with this library, since I also get ECONNRESET errors that I can’t seem to catch from time to time.

edit:

Right as I finished up writing this post, I figured the listener was attached too late to the error event. My server runs inside a worker thread and the issue only seems to occur when my CPU load is reasonably high. The crash happened when the HTTP-server connection event is fired, then the client disconnects, and then the request should be fired. It’s not a problem with this library, but the fact the error event is emitted from the socket before an event listener is attached to it.

For anyone running into similar issues: The fix for me was to attach a listener to the error event immediately as the client connects, and not a ‘tick’ later, because the client disconnects after the connection event, but before the upgrade event.

So, instead of:

httpServer.on('upgrade', (request: IncomingMessage, socket: Socket, head: Buffer) => {
    socket.on('error', () => console.log('poof!'));
});

All you have to do is bind the listener during the connection event.

httpServer.on('connection', (socket: Socket) => {
    socket.on('error', () => console.log('poof!'));
});

Haha, negative. I was using ws for a project of mine, so was figuring stuff out when I ran across the issue. All good now though!

First, thanks for the great library. I am testing it out on a really cool IoT prototype. I love that it does one thing good. I ran into this error the last couple days. I am guessing because my headless client timed out it’s http connection for whatever reason (don’t have ping pong going yet, just a reconnect on the heartbeat.)

Anyways, I had already set up the error handler in connection event for the connected socket. So I was a little surprised that the server halted on this error. I realize that the error is not going to be re-emitted, but I guess I am confused by that. Why wouldn’t that error get handled the way it was intended?

Looks like you’re right.

According to your message it seems like error is happening on connected client instance, not whole server. That ws and wss wasn’t distinguishable enough in the example. That’s why I am always using client as name inside connection.

So to resolve the issue we should use:

ws.on("connection", (client) => {
  client.on("error", () => {
    // do something or ignore
  });
});

This is perfectly fine, I’ll always vote for explicit code - this just needs to be explained in README.

Spent some time figuring this one out in my unit tests. I had code like this to finish the tests:

wsClient.close();
server.close(done);

The problem is that the server tries to close before the event from the client reaches it, hence forcing the close connection and causing the error.

So I’m doing this now:

        if (server.clients.size) {
            server.clients.forEach(ws => {
                ws.on('close', () => {
                    if (!server.clients.size) { // last client
                        server.close(done);
                    }
                });

                wsClient.close();
            });
        } else {
            server.close(done);
        }

I don’t know how you are using it but if the client and server are two separate processes then ofc the server does not crash because of the client. From I what I can see puppeteer uses ws only as client.

Socket.IO seems to correctly add the 'error' listener on both the server and client. Same is for puppeteer. I’ve only checked the latest versions though.

The read ECONNRESET error can be emitted also on the client not only the server.

@jkasun well all browsers sends a close frame when you refresh the page if you didn’t already close the WebSocket. The problem is that Chrome closes the TCP socket while the server response is on the wire.

To be honest I don’t know why it doesn’t do the same when the WebSocket is closed from a listener of the 'beforeunload' event. Maybe it does but the small timing difference is sufficient (at least for the above example) to prevent the issue from happening.