Skip to content

EPIPE which cannot be caught by try/catch and terminates the process #462

@hills

Description

@hills

If a client closes very early, the server's write() of its part of the handshake fails with EPIPE.

This happens before the underlying socket 'error' handler has been put in place, causing the default action (terminate the process)

Because the 'socket' hides behind an abstraction there's no way for a user of the API to mitigate this error; wrapping the call to accept() in a regular try/catch does not help.

This sort of early termination has some history in #167 #168.

The test case below readily reproduces this using a unix socket forward from nginx.

My draft of a patch is something like the below. Replacing the default handler at the earliest opportunity may be the clearest solution?

Request arrived, going into busy-wait
Accepting connection
1006: TCP connection lost before handshake completed.
node:events:502
      throw er; // Unhandled 'error' event
      ^

Error: write EPIPE
    at afterWriteDispatched (node:internal/stream_base_commons:159:15)
    at writeGeneric (node:internal/stream_base_commons:150:3)
    at Socket._writeGeneric (node:net:971:11)
    at Socket._write (node:net:983:8)
    at writeOrBuffer (node:internal/streams/writable:572:12)
    at _write (node:internal/streams/writable:501:10)
    at Writable.write (node:internal/streams/writable:510:10)
    at WebSocketRequest.accept (/home/mark/proj/websocket/node_modules/websocket/lib/WebSocketRequest.js:458:21)
    at WebSocketServer.<anonymous> (/home/mark/proj/websocket/test-epipe.js:15:36)
    at WebSocketServer.emit (node:events:524:28)
Emitted 'error' event on Socket instance at:
    at emitErrorNT (node:internal/streams/destroy:170:8)
    at emitErrorCloseNT (node:internal/streams/destroy:129:3)
    at process.processTicksAndRejections (node:internal/process/task_queues:90:21) {
  errno: -32,
  code: 'EPIPE',
  syscall: 'write'
}

Node.js v22.13.1
const url = require('node:url');
const http = require('node:http');
const websocket = require('websocket');

const server = http.createServer();
const wss = new websocket.server({httpServer: server});

wss.on('request', request => {
    try {
        console.warn(`Request arrived, going into busy-wait`);
        const begin = new Date;
        while (new Date < begin.valueOf() + 10 * 1000);

        console.warn(`Accepting connection`);
        const connection = request.accept();

        connection.on('close', (reason, description) => {
            console.warn(`${reason}: ${description}`);
        });

    } catch (error) {
        console.warn(`Caught an error in the websocket request handler`);
        console.error(error);
    }
});

server.listen('test.socket');
--- a/lib/WebSocketRequest.js
+++ b/lib/WebSocketRequest.js
@@ -443,6 +443,7 @@ WebSocketRequest.prototype.accept = function(acceptedProtocol, allowedOrigin, co
     response += '\r\n';

     var connection = new WebSocketConnection(this.socket, [], acceptedProtocol, false, this.serverConfig);
+    connection._addSocketEventListeners();
     connection.webSocketVersion = this.webSocketVersion;
     connection.remoteAddress = this.remoteAddress;
     connection.remoteAddresses = this.remoteAddresses;
@@ -462,7 +463,6 @@ WebSocketRequest.prototype.accept = function(acceptedProtocol, allowedOrigin, co
             }

             self._removeSocketCloseListeners();
-            connection._addSocketEventListeners();
         });
     }
--

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions