Sitelet https://github.com/nodejs/node/issues/50188
Skip to content

HTTP Server close is taking up to 5 seconds in node 18 #50188

Description

@DanielRamosAcosta

Version

v18.18.2

Platform

Darwin MacBook-Pro-de-Daniel.local 23.0.0 Darwin Kernel Version 23.0.0: Fri Sep 15 14:43:05 PDT 2023; root:xnu-10002.1.13~1/RELEASE_ARM64_T6020 arm64

Subsystem

http

What steps will reproduce the bug?

  1. Create an http server
  2. Start listening
  3. Take at least one request
  4. Try to close the server
  5. It takes exactly 5 seconds

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

I have only reproduced the bug in node 18, but is deterministic, it happens every time

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

In node 19 and 20 it's only taking 0.5 seconds. Thats a normal time that I would expect.

What do you see instead?

I see 5 seconds of delay in order to close

Additional information

I've reproduced the error in this repository with a bare minimum with Github Actions: DanielRamosAcosta/nodejs-close-server-bug

Here is the CI report:

This is affecting to a library I'm maintaining similar to supertest. If you see the CI there, Node 18 tests are taking very long due to the servers being closed.

Activity

  1. H4ad commented on Oct 14, 2023

    @H4ad
    Member

    Started on 18.18.1, the 18.18.0 does not have this weird delay.

    EDIT: it has the delay also on 18.18.0

  2. himself65 commented on Oct 15, 2023

    @himself65
    Member
    image

    Started on 18.18.1, the 18.18.0 does not have this weird delay.

    No, I think all 18 version has such issue

  3. himself65 commented on Oct 15, 2023

    @himself65
    Member

    Related PR: #48383

  4. himself65 commented on Oct 15, 2023

    @himself65
    Member

    The hot fix:

    import * as http from "node:http";
    import { promisify } from "node:util";
    
    const server = http.createServer(async (req, res) => {
        res.writeHead(200, { "Content-Type": "text/plain" });
        res.write("Hello world");
        res.end();
    });
    const listenPromisied = promisify(server.listen.bind(server));
    const closePromisied = promisify(server.close.bind(server));
    
    await listenPromisied(0, "127.0.0.1");
    const address = server.address();
    await fetch(`http://${address.address}:${address.port}`);
    console.time("server close");
    + server.closeAllConnections();
    await closePromisied();
    console.timeEnd("server close");
  5. added
    httpIssues and PRs related to the http subsystem.
    on Oct 15, 2023
  6. H4ad commented on Oct 15, 2023

    @H4ad
    Member

    Should we bisect or closeAllConnections is expected to be called?

  7. himself65 commented on Oct 15, 2023

    @himself65
    Member

    The latest behavior is to close all sockets, I think there are some backport issues on 18. Let me see if I can do the backport

  8. H4ad commented on Oct 15, 2023

    @H4ad
    Member

    The 19.0.0 looks like the first release without this delay.

    I will try bisect to see if I can find the commit that fixes the delay and then we can backport.

    The #48383 was released on 20.4.0, and this issue started on 18.0.0 (or even earlier, I didn't try rewrite the fetch call)

  9. H4ad commented on Oct 15, 2023

    @H4ad
    Member

    @himself65 bisect will not help in this case:

    The merge base 19064bec341185a8c15fc438cfcf0df8633a179e is bad.
    This means the bug has been fixed between 19064bec341185a8c15fc438cfcf0df8633a179e and [cc993fb2760d01457955f5b9ff787d559ed1c34e].
    

    From what I understand, 18.x and 19.x have different git histories, so there's no way to find the bad commit using bisect.
    At least, I don't know a way to do it, I'm open to suggestions.

  10. himself65 commented on Oct 15, 2023

    @himself65
    Member

    I tried nodejs 16.x. now I think there might have a regression on nodejs 16 -> 18

    import * as http from "node:http";
    import { promisify } from "node:util";
    import fetch from 'node-fetch'
    
    const server = http.createServer(async (req, res) => {
        res.writeHead(200, { "Content-Type": "text/plain" });
        res.write("Hello world");
        res.end();
    });
    const listenPromisied = promisify(server.listen.bind(server));
    const closePromisied = promisify(server.close.bind(server));
    
    await listenPromisied(0, "127.0.0.1");
    const address = server.address();
    await fetch(`http://${address.address}:${address.port}`);
    console.time("server close");
    await closePromisied();
    console.timeEnd("server close");
    ➜  nodejs git:(main) ✗ node index.mjs
    server close: 0.158ms
    ➜  nodejs git:(main) ✗ node -v
    v16.20.2
  11. H4ad commented on Oct 15, 2023

    @H4ad
    Member

    It's a regression from 17.x -> 18.x

    h4ad:node-copy-4/ (main✗) $ node test-close-2.mjs                                                                                                                                                                                                                    
    server close: 0.108ms
    h4ad:node-copy-4/ (main✗) $ node -v
    v17.9.1
    
  12. H4ad commented on Oct 15, 2023

    @H4ad
    Member

    Based on https://github.com/nodejs/node/pull/49091/files#diff-d692ac4524379ec6a1201165e8ff8d3267c8130e07014e8221ebf7e6f80c6641R1560-R1567, and this little change:

    const server = http.createServer({ keepAliveTimeout: 200 }, async (req, res) => {
    h4ad:node-copy-4/ (main✗) $ node test-close-2.mjs
    server close: 0.255ms
    h4ad:node-copy-4/ (main✗) $ node -v
    v18.18.1
    

    @himself65 I think you are right, the #48383 should fix the issue, I thought it landed on 18.18.0 but it did not, sorry for the confusion.

    Let me know if you will do the backport, if not, I can do that.

  13. H4ad commented on Oct 15, 2023

    @H4ad
    Member

    I built 18.18.0 with fix #48383, it solves the delay.

  14. himself65 commented on Oct 15, 2023

    @himself65
    Member

    Based on https://github.com/nodejs/node/pull/49091/files#diff-d692ac4524379ec6a1201165e8ff8d3267c8130e07014e8221ebf7e6f80c6641R1560-R1567, and this little change:

    const server = http.createServer({ keepAliveTimeout: 200 }, async (req, res) => {
    h4ad:node-copy-4/ (main✗) $ node test-close-2.mjs
    server close: 0.255ms
    h4ad:node-copy-4/ (main✗) $ node -v
    v18.18.1
    

    @himself65 I think you are right, the #48383 should fix the issue, I thought it landed on 18.18.0 but it did not, sorry for the confusion.

    Let me know if you will do the backport, if not, I can do that.

    I might don't have time on backport you can try that.

    I read some CIGTM code, i have no idea on this #48383 (comment)

  15. 2 remaining items

  16. himself65 commented on Oct 15, 2023

    @himself65
    Member

    Now I believe this is a kind of bug in undici

  17. added
    fetchIssues and PRs related to the Fetch API.
    and removed
    httpIssues and PRs related to the http subsystem.
    regressionIssues related to regressions.
    on Oct 15, 2023
  18. himself65 commented on Oct 15, 2023

    @himself65
    Member

    I think this is not a regression. From undici 4.4.1 (first version that has fetch), the delay is always there

  19. himself65 commented on Oct 16, 2023

    @himself65
    Member

    Hotfix

    res.writeHead(200, { 'Content-Type': 'text/plain', 'Connection': 'close' })
  20. himself65 commented on Oct 16, 2023

    @himself65
    Member

    Thanks to nodejs/undici#2348 (comment)

    The real reason is keep-alive is by default in somewhere

  21. added
    httpIssues and PRs related to the http subsystem.
    and removed
    fetchIssues and PRs related to the Fetch API.
    on Oct 16, 2023
  22. himself65 commented on Oct 16, 2023

    @himself65
    Member

    this.shouldKeepAlive = true;

    globalAgent: new Agent({ keepAlive: true, scheduling: 'lifo', timeout: 5000 }),

  23. benjamingr commented on Oct 17, 2023

    @benjamingr
    Member

    The code fetches and then does not read or dispose of the response. This is expected behavior I think? You only waited for headers, you might still read the body of the response.

  24. bjohansebas commented on May 24, 2025

    @bjohansebas
    Member

    Node 18 is already EOL, and it seems this doesn’t happen in higher major versions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    httpIssues and PRs related to the http subsystem.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions