-
Notifications
You must be signed in to change notification settings - Fork 29.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Network request fails after event loop is blocked for a few seconds (regression in v20) #54293
Comments
// repro.js
const http = require("node:http");
const makePostRequest = () =>
new Promise((resolve, reject) => {
const onResponse = (res) => {
let data = "";
res.on("data", (chunk) => {
data += chunk;
});
res.on("end", () => resolve(data));
};
const req = http.request(
"http://127.0.0.1:3000/",
{ method: "POST" },
onResponse,
);
req.on("error", (e) => reject(e));
req.end();
});
const makeRequests = async () => {
await makePostRequest();
console.log("First request worked");
const timestamp = Date.now();
// Block the event loop for 10 seconds
while (Date.now() < timestamp + 10000);
// Enable the following line and it will work:
// await new Promise((resolve) => setTimeout(resolve, 10));
await makePostRequest();
console.log("It crashes before it gets here");
};
const server = http.createServer((req, res) => {
console.log("Request received");
res.statusCode = 200;
res.setHeader("Content-Type", "text/plain");
res.end("Hello world!\n");
});
server.listen(3000, "127.0.0.1", () => {
console.log("Server up");
makeRequests();
}); $ node repro.js
Server up
Request received
First request worked
node:internal/process/promises:391
triggerUncaughtException(err, true /* fromPromise */);
^
Error: read ECONNRESET
at TCP.onStreamRead (node:internal/stream_base_commons:218:20) {
errno: -104,
code: 'ECONNRESET',
syscall: 'read'
}
Node.js v22.6.0 |
Reproducible in |
This is caused by If you reduce the while loop to less than 5 seconds, or add a header Since the event loop was blocked and the socket destroy actions were scheduled in the next tick, your next request would be assigned to the socket to be destroyed. IMHO, the best fix is probably about how to detect this at the request stage |
Thank you so much for the information, @jazelly! I fixed the issue in my application by disabling Agree that your suggested approach sounds good:
|
Not suggesting Overall, I think this is an limitation in
|
Jason (@jazelly) and myself have spent some time to dig into this issue further more. The reason why the second request failed is because the socket is no longer kept alive after 5 seconds and server tries to destroy the socket (this is asynchronous) however client side does not know the socket is being destroyed and still tries to send an request by reusing the same socket. What happens next is that the request was assigned to the old socket by http agent at client side while the server is about to close the socket. The server then closed the socket and the request failed. You can find the similar issue here, hope it helps and please let us know if you have any other questions. |
Thanks for digging further into it, @jakecastelli and @jazelly! Let me summarize:
Damn, that sucks. It means there's little we can do except for retrying. Thanks for investigating and thanks for sharing! I appreciate the time you put into this. 🙌 |
I found this PR that will fix this issue. I verified it in my local env. |
@jazelly That's awesome, thanks! Do you know the right process to bring attention back to the PR? It seems stale. |
Collaborators with write permissions can help run CI for the PR. I don't have the permission but I believe once the CI is passed it will be landed soon. |
Thanks, @jazelly, for getting the PR to the attention of the right people. :) |
Fixes: nodejs#54649 Refs: nodejs#54293 Co-authored-by: Zanette Arrigo <[email protected]>
Fixes: nodejs#52649 Refs: nodejs#54293 Co-authored-by: Zanette Arrigo <[email protected]>
Fixes: nodejs#52649 Refs: nodejs#54293 Co-authored-by: Zanette Arrigo <[email protected]>
Fixes: nodejs#52649 Refs: nodejs#54293 Co-authored-by: Arrigo Zanette <[email protected]>
Fixes: #52649 Refs: #54293 Co-authored-by: Arrigo Zanette <[email protected]> PR-URL: #54863 Reviewed-By: James M Snell <[email protected]> Reviewed-By: LiviaMedeiros <[email protected]> Reviewed-By: Jake Yuesong Li <[email protected]> Reviewed-By: Ethan Arrowood <[email protected]>
Fixes: #52649 Refs: #54293 Co-authored-by: Arrigo Zanette <[email protected]> PR-URL: #54863 Reviewed-By: James M Snell <[email protected]> Reviewed-By: LiviaMedeiros <[email protected]> Reviewed-By: Jake Yuesong Li <[email protected]> Reviewed-By: Ethan Arrowood <[email protected]>
Fixes: #52649 Refs: #54293 Co-authored-by: Arrigo Zanette <[email protected]> PR-URL: #54863 Reviewed-By: James M Snell <[email protected]> Reviewed-By: LiviaMedeiros <[email protected]> Reviewed-By: Jake Yuesong Li <[email protected]> Reviewed-By: Ethan Arrowood <[email protected]>
Fixes: #52649 Refs: #54293 Co-authored-by: Arrigo Zanette <[email protected]> PR-URL: #54863 Reviewed-By: James M Snell <[email protected]> Reviewed-By: LiviaMedeiros <[email protected]> Reviewed-By: Jake Yuesong Li <[email protected]> Reviewed-By: Ethan Arrowood <[email protected]>
Fixes: #52649 Refs: #54293 Co-authored-by: Arrigo Zanette <[email protected]> PR-URL: #54863 Reviewed-By: James M Snell <[email protected]> Reviewed-By: LiviaMedeiros <[email protected]> Reviewed-By: Jake Yuesong Li <[email protected]> Reviewed-By: Ethan Arrowood <[email protected]>
Fixes: #52649 Refs: #54293 Co-authored-by: Arrigo Zanette <[email protected]> PR-URL: #54863 Reviewed-By: James M Snell <[email protected]> Reviewed-By: LiviaMedeiros <[email protected]> Reviewed-By: Jake Yuesong Li <[email protected]> Reviewed-By: Ethan Arrowood <[email protected]>
Fixes: #52649 Refs: #54293 Co-authored-by: Arrigo Zanette <[email protected]> PR-URL: #54863 Reviewed-By: James M Snell <[email protected]> Reviewed-By: LiviaMedeiros <[email protected]> Reviewed-By: Jake Yuesong Li <[email protected]> Reviewed-By: Ethan Arrowood <[email protected]>
Fixes: #52649 Refs: #54293 Co-authored-by: Arrigo Zanette <[email protected]> PR-URL: #54863 Reviewed-By: James M Snell <[email protected]> Reviewed-By: LiviaMedeiros <[email protected]> Reviewed-By: Jake Yuesong Li <[email protected]> Reviewed-By: Ethan Arrowood <[email protected]>
Fixes: nodejs#52649 Refs: nodejs#54293 Co-authored-by: Arrigo Zanette <[email protected]> PR-URL: nodejs#54863 Reviewed-By: James M Snell <[email protected]> Reviewed-By: LiviaMedeiros <[email protected]> Reviewed-By: Jake Yuesong Li <[email protected]> Reviewed-By: Ethan Arrowood <[email protected]>
Fixes: nodejs#52649 Refs: nodejs#54293 Co-authored-by: Arrigo Zanette <[email protected]> PR-URL: nodejs#54863 Reviewed-By: James M Snell <[email protected]> Reviewed-By: LiviaMedeiros <[email protected]> Reviewed-By: Jake Yuesong Li <[email protected]> Reviewed-By: Ethan Arrowood <[email protected]>
Version
v20.16.0
Platform
Subsystem
http
What steps will reproduce the bug?
http.request()
Code example:
How often does it reproduce? Is there a required condition?
Reproduces always.
What is the expected behavior? Why is that the expected behavior?
The network request works.
What do you see instead?
ECONNRESET
.Log from running the code above:
Additional information
This is a regression. The code works on Node.js 18. It fails on Node.js 20.
If we wait for 10ms before running the network request, it works.
The text was updated successfully, but these errors were encountered: