http.request can throw immediately but still connect a socket, cause unhandled error

  • Version: v10.15.0, v12.0.0-nightly201902158e68dc53b3
  • Platform: Win10 x64, Linux x64 (4.15.0-1027-gcp Ubuntu 16)
  • Subsystem: http

A call to http.request can throw an error immediately but still connect a socket. When that socket is closed by the other party (e.g. due to timeout), I don't think there's a way to handle the socket hangup error without digging into the http.Agent. Instead, it becomes an unhandled exception.

An edge case, but still seems like a bug. Not sure if there are other ways to cause exceptions besides an invalid timeout type.

const http = require("http");
const {fork} = require("child_process");

if (!process.argv.includes("--server")) {
  const server = fork(__filename, ["--server"]);

  server.on("message", () => { // server is listening
    try {
      http.get({
        port: 3333,
        timeout: null // this causes an error to be thrown immediately
      });
    } catch (ex) {
      // TypeError [ERR_INVALID_ARG_TYPE]: The "msecs" argument must be of type number.
      console.log("caught:", ex);

      // There's a socket here, which will timeout and create an uncaught exception
      console.log("after:", http.globalAgent.sockets);
    }
  });

} else {
  const server = http.createServer();
  server.listen(3333, () => {
    process.send("listening");
  });
  server.timeout = 1000;
}
output
caught: TypeError [ERR_INVALID_ARG_TYPE]: The "msecs" argument must be of type number. Received type object
    at validateNumber (internal/validators.js:130:11)
    at validateTimerDuration (internal/timers.js:146:3)
    at Socket.setTimeout (net.js:418:11)
    at setRequestSocket (_http_agent.js:361:10)
    at handleSocketCreation_Inner (_http_agent.js:349:7)
    at oncreate (_http_agent.js:224:5)
    at Agent.createSocket (_http_agent.js:229:5)
    at Agent.addRequest (_http_agent.js:185:10)
    at new ClientRequest (_http_client.js:249:16)
    at request (http.js:42:10)
after: { 'localhost:3333:':
   [ Socket {
       connecting: true,
       _hadError: false,
       _handle: [TCP],
       _parent: null,
       _host: 'localhost',
       _readableState: [ReadableState],
       readable: false,
       _events: [Object],
       _eventsCount: 5,
       _maxListeners: undefined,
       _writableState: [WritableState],
       writable: true,
       allowHalfOpen: false,
       _sockname: null,
       _pendingData: null,
       _pendingEncoding: '',
       server: null,
       _server: null,
       timeout: null,
       [Symbol(asyncId)]: 8,
       [Symbol(lastWriteQueueSize)]: 0,
       [Symbol(timeout)]: null,
       [Symbol(kBytesRead)]: 0,
       [Symbol(kBytesWritten)]: 0 } ] }
events.js:167
      throw er; // Unhandled 'error' event
      ^

Error: socket hang up
    at createHangUpError (_http_client.js:323:15)
    at Socket.socketOnEnd (_http_client.js:426:23)
    at Socket.emit (events.js:187:15)
    at endReadableNT (_stream_readable.js:1094:12)
    at process._tickCallback (internal/process/next_tick.js:63:19)
Emitted 'error' event at:
    at Socket.socketOnEnd (_http_client.js:426:9)
    at Socket.emit (events.js:187:15)
    at endReadableNT (_stream_readable.js:1094:12)
    at process._tickCallback (internal/process/next_tick.js:63:19)