nodejs / node

Node.js JavaScript runtime ✨🐢🚀✨
https://nodejs.org
Other
107.82k stars 29.71k forks source link

fetch() throws uncatachable UND_ERR_SOCKET exception #54960

Open neoncube2 opened 2 months ago

neoncube2 commented 2 months ago

Version

v22.8.0

Platform

Microsoft Windows NT 10.0.22631.0 x64

Subsystem

No response

What steps will reproduce the bug?

While using fetch(), I occasionally get this exception:

node:internal/deps/undici/undici:13185
      Error.captureStackTrace(err);
            ^

TypeError: fetch failed
    at node:internal/deps/undici/undici:13185:13
    at async getOldCourse (file:///E:/memrise-community-courses/fix-column-types.mjs:27:30) {
  [cause]: SocketError: other side closed
      at TLSSocket.<anonymous> (node:internal/deps/undici/undici:6040:28)
      at TLSSocket.emit (node:events:532:35)
      at endReadableNT (node:internal/streams/readable:1696:12)
      at process.processTicksAndRejections (node:internal/process/task_queues:90:21) {
    code: 'UND_ERR_SOCKET',
    socket: {
      localAddress: '127.0.0.1',
      localPort: 52533,
      remoteAddress: '127.0.0.1',
      remotePort: 443,
      remoteFamily: 'IPv4',
      timeout: undefined,
      bytesWritten: 12008,
      bytesRead: 4044144
    }
  }
}

Node.js v22.8.0

The problem is that the exception is uncatchable. I've already wrapped my fetch() called in try/catch, but when this UND_ERR_SOCKET error occurs, it crashes the entire process.

How often does it reproduce? Is there a required condition?

Once in every ~100 fetch() calls.

What is the expected behavior? Why is that the expected behavior?

Error should be catch

What do you see instead?

Error is uncatchable and crashes process

Additional information

My code is basically this:

process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0;

try {
   await fetch(
        'https://example.com/api/course/123/entries',
        {
            method: 'GET'
        }
    );
} catch(exception) {
  console.error(...);
}
neoncube2 commented 2 months ago

Removed this code from the original example, since it doesn't seem necessary to repro:

 headers: {
  Authorization: 'Bearer ' + API_TOKE
}
neoncube2 commented 2 months ago

ECONNABORTED errors also seem to be uncatchable and cause the process to exit. Stack trace:

node:internal/deps/undici/undici:13185
      Error.captureStackTrace(err);
            ^

TypeError: fetch failed
    at node:internal/deps/undici/undici:13185:13
    at async getOldCourse (file:///E:/memrise-community-courses/fix-column-types.mjs:27:30) {
  [cause]: Error: write ECONNABORTED
      at WriteWrap.onWriteComplete [as oncomplete] (node:internal/stream_base_commons:95:16)
      at handleWriteReq (node:internal/stream_base_commons:59:21)
      at writeGeneric (node:internal/stream_base_commons:150:15)
      at Socket._writeGeneric (node:net:953:11)
      at Socket._write (node:net:965:8)
      at writeOrBuffer (node:internal/streams/writable:570:12)
      at _write (node:internal/streams/writable:499:10)
      at Writable.write (node:internal/streams/writable:508:10)
      at writeBuffer (node:internal/deps/undici/undici:6346:20)
      at writeH1 (node:internal/deps/undici/undici:6248:9) {
    errno: -4079,
    code: 'ECONNABORTED',
    syscall: 'write'
  }
}
RedYetiDev commented 2 months ago

Once in every ~100 fetch() calls.

I'm unable to reproduce, can you try and make a reproduction (maybe using a loop?):

$ for i in {1..100}; do node --no-warnings test.mjs; done
[no output]
KhafraDev commented 2 months ago

Is it uncaught, or are you logging the error? Since it's wrapped in a 'fetch failed' error it was handled properly in undici.

neoncube2 commented 1 month ago

It appears to be uncaught. It kills the process whenever it happens.

I'm still able to reproduce this, but it appears to only happen under a narrow set of circumstances (perhaps when a PHP script times out, or when a MySQL call within a PHP script times out). I'm working to narrow this down to a more reproducible test case :)

neoncube2 commented 1 month ago

Interesting. It looks like this only happens when two async tasks are queued up at the same time. This triggers the error:

const getNewVocabularyTask = getUpdateVocabularyBody();
const getOldCourseTask = getOldCourse();

const newVocabulary = await getNewVocabularyTask;
const oldCourse = await getOldCourseTask;

But this doesn't:

const getOldCourseTask = getOldCourse();

const oldCourse = await getOldCourseTask;

Still trying to come up with a clean repro.