Node.js 16.4. We're seeing this on an AWS Lambda running Amazon Linux 2, but I was also able to repro on WSL2 with Ubuntu 20.04.
The gist of the issue is, an EPIPE error happens that emits an error
event directly on the Request
object, bypassing any _beforeError
and other handling inside a Request
, and instead going straight to rejecting the promise.
Our repro path is a bit convoluted, but reliable:
got
promise gets rejected with an EPIPE, without a retry.got
actually attempts a retry, but the promise is already rejected, so it errors with an unrelated "The onCancel
handler was attached after the promise settled." error.)https://gist.github.com/heypiotr/f97b3c2069770988a5f4f3785d83f730
What it looks like when I repro it:
heypiotr at PC in ~/sandbox/got-epipe
$ node index.mjs
requesting 0 0
{
"args": {},
"data": "",
"files": {
"bar": "bar\n"
},
"form": {
"foo": "foo"
},
"headers": {
"Accept-Encoding": "gzip, deflate, br",
"Content-Length": "285",
"Content-Type": "multipart/form-data; boundary=form-data-boundary-41m9vp3q1waroamv",
"Host": "httpbin.org",
"User-Agent": "got (https://github.com/sindresorhus/got)",
"X-Amzn-Trace-Id": "Root=1-621c9052-6c84c7890e0a794b6e14c8d4"
},
"json": null,
"origin": "213.127.39.54",
"url": "https://httpbin.org/post"
}
sleeping
^Z
[1]+ Stopped node index.mjs
heypiotr at PC in ~/sandbox/got-epipe
$ fg
node index.mjs
requesting 0 1
Trace: rejecting Error: write EPIPE
at Request.onError (file:///home/heypiotr/sandbox/got-epipe/node_modules/got/dist/source/as-promise/index.js:103:13)
at Object.onceWrapper (node:events:640:26)
at Request.emit (node:events:520:28)
at emitErrorNT (node:internal/streams/destroy:157:8)
at errorOrDestroy (node:internal/streams/destroy:220:7)
at onwriteError (node:internal/streams/writable:422:3)
at onwrite (node:internal/streams/writable:457:7)
at Object.callback (file:///home/heypiotr/sandbox/got-epipe/node_modules/got/dist/source/core/index.js:1025:13)
at callback (node:internal/streams/writable:552:21)
at onwriteError (node:internal/streams/writable:415:3)
console.trace
I added before this line, you can see Node.js emits the error
event on the Request
object directly via errorOrDestroy
=> emitErrorNT
error Error: write EPIPE
at WriteWrap.onWriteComplete [as oncomplete] (node:internal/stream_base_commons:94:16)
at writevGeneric (node:internal/stream_base_commons:138:26)
at TLSSocket.Socket._writeGeneric (node:net:793:11)
at TLSSocket.Socket._writev (node:net:802:8)
at doWrite (node:internal/streams/writable:406:12)
at clearBuffer (node:internal/streams/writable:561:5)
at TLSSocket.Writable.uncork (node:internal/streams/writable:348:7)
at ClientRequest._flushOutput (node:_http_outgoing:970:10)
at ClientRequest._flush (node:_http_outgoing:939:22)
at onSocketNT (node:_http_client:813:9) {
errno: -32,
code: 'EPIPE',
syscall: 'write'
}
try {} catch {}
surrounding the got
invocationsleeping
Retrying request, url=https://httpbin.org/post, retryCount=1, error=RequestError: write EPIPE
file:///home/heypiotr/sandbox/got-epipe/node_modules/p-cancelable/index.js:48
throw new Error('The `onCancel` handler was attached after the promise settled.');
^
Error: The `onCancel` handler was attached after the promise settled.
at onCancel (file:///home/heypiotr/sandbox/got-epipe/node_modules/p-cancelable/index.js:48:12)
at makeRequest (file:///home/heypiotr/sandbox/got-epipe/node_modules/got/dist/source/as-promise/index.js:33:13)
at Request.<anonymous> (file:///home/heypiotr/sandbox/got-epipe/node_modules/got/dist/source/as-promise/index.js:119:17)
at Object.onceWrapper (node:events:640:26)
at Request.emit (node:events:520:28)
at file:///home/heypiotr/sandbox/got-epipe/node_modules/got/dist/source/core/index.js:388:26
I'd be happy to work on a PR for this, but I could use some pointers on how to attack it.
I think that ideally, the fix would be somewhere inside Request
, so that it applies to both the Promise and the Stream APIs.
Could it be as simple as Request
listening for its own error
events and passing them through _beforeError
?
Pay now to fund the work behind this issue.
Get updates on progress being made.
Maintainer is rewarded once the issue is completed.
You're funding impactful open source efforts
You want to contribute to this effort
You want to get funding like this too