Skip to content
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

Slow http CONNECT requests will block other requests when using ProxyAgent #2001

Closed
andrewfecenko opened this issue Mar 10, 2023 · 1 comment · Fixed by #2003
Closed

Slow http CONNECT requests will block other requests when using ProxyAgent #2001

andrewfecenko opened this issue Mar 10, 2023 · 1 comment · Fixed by #2003
Labels
bug Something isn't working

Comments

@andrewfecenko
Copy link
Contributor

andrewfecenko commented Mar 10, 2023

Bug Description

When using ProxyAgent, a slow CONNECT request to the proxy will block the Client's queue (this[kClient] in proxy-agent.js) and can cause delays in issues any requests with that ProxyAgent.

Reproducible By

We found that the easiest way to reproduce this with a test was to add a delay for the first connect request to the proxy via proxy.authenticate:

test('delayed proxy-agent request blocks queue', async (t) => {
  t.plan(2)
  const server = await buildServer()
  const proxy = await buildProxy()

  const serverUrl = `http://localhost:${server.address().port}`
  const proxyUrl = `http://localhost:${proxy.address().port}`
  const proxyAgent = new ProxyAgent({auth: Buffer.from('user:pass').toString('base64'), uri: proxyUrl});
  let firstConnect = true;
  await new Promise(async (resolve) => {
    let connectCount = 0
    console.log('running test');
    proxy.authenticate = function (req, res) {
      if (++connectCount === 2) {
        console.log('allowing connect');
        firstConnect = false;
        res(null, true)
      } else {
        console.log('delaying connect');
        setTimeout(() => {
          console.log('delayed connect');
          t.equal(firstConnect, true);
          res(null, true);
        }, 1000);
      }
    }

    server.on('request', (req, res) => {
      res.end()
    })

    request(`${serverUrl}`, { dispatcher: proxyAgent })
      .then((resp) => {
        resp.body.on('error', () => {}).destroy()
      })
      .catch(() => {})
    request(`${serverUrl}`, { dispatcher: proxyAgent })
      .then((resp) => {
        resp.body.on('error', () => {}).destroy()
        resolve();
      })
      .catch(() => {})
  })
  console.log('done');
  t.pass()
  server.close()
  proxy.close()
  proxyAgent.close()
})

Expected Behavior

A single request issued with a ProxyAgent should not block other requests also using that ProxyAgent.

Environment

Ran the above test using node v19.4.0 on macOS Ventura 13.2.1

Additional context

We discovered this issue after investigating delays in requests that use undici's request function with a ProxyAgent passed in as the dispatcher. We were instantiating a single ProxyAgent and using it in all requests, but we have since made a change to instantiate a new ProxyAgent for every single request call. This appears to have fixed the issue where a single request blocks other successive requests.

I believe one potential solution is to allow the consumers of ProxyAgent to choose whether they'd like this[kClient] to be instantiated with an instance of Client or an instance of Pool. If I change proxy-agent to use a Pool instead the above test no longer passes (the first connect request does not block the successive ones).

@andrewfecenko andrewfecenko added the bug Something isn't working label Mar 10, 2023
andrewfecenko pushed a commit to andrewfecenko/undici that referenced this issue Mar 11, 2023
The default use of a Client means that HTTP CONNECT requests to the
Proxy will block successive requests. Giving consumers the option to
use a Pool ensures that these requests will not block.

fixes: nodejs#2001
@andrewfecenko
Copy link
Contributor Author

As seen above, I've created a pull request with what I think are the necessary changes. Would love any and all thoughts

andrewfecenko pushed a commit to andrewfecenko/undici that referenced this issue Mar 11, 2023
The default use of a Client means that HTTP CONNECT requests to the
Proxy will block successive requests. Giving consumers the option to
supply a factory allows them use a Pool which ensures that these
requests will not block.

fixes: nodejs#2001
andrewfecenko pushed a commit to andrewfecenko/undici that referenced this issue Mar 11, 2023
The default use of a Client means that HTTP CONNECT requests to the
Proxy will block successive requests. Giving consumers the option to
supply a factory allows them use a Pool which ensures that these
requests will not block.

fixes: nodejs#2001
mcollina pushed a commit that referenced this issue Mar 13, 2023
* Add clientFactory option to ProxyAgent

The default use of a Client means that HTTP CONNECT requests to the
Proxy will block successive requests. Giving consumers the option to
supply a factory allows them use a Pool which ensures that these
requests will not block.

fixes: #2001

* fixup! Use pool by default and change clientFactory return type

* Add clientFactory to ProxyAgent docs

---------

Co-authored-by: Andrew Fecenko <afecenko@atlassian.com>
metcoder95 pushed a commit to metcoder95/undici that referenced this issue Jul 21, 2023
* Add clientFactory option to ProxyAgent

The default use of a Client means that HTTP CONNECT requests to the
Proxy will block successive requests. Giving consumers the option to
supply a factory allows them use a Pool which ensures that these
requests will not block.

fixes: nodejs#2001

* fixup! Use pool by default and change clientFactory return type

* Add clientFactory to ProxyAgent docs

---------

Co-authored-by: Andrew Fecenko <afecenko@atlassian.com>
crysmags pushed a commit to crysmags/undici that referenced this issue Feb 27, 2024
* Add clientFactory option to ProxyAgent

The default use of a Client means that HTTP CONNECT requests to the
Proxy will block successive requests. Giving consumers the option to
supply a factory allows them use a Pool which ensures that these
requests will not block.

fixes: nodejs#2001

* fixup! Use pool by default and change clientFactory return type

* Add clientFactory to ProxyAgent docs

---------

Co-authored-by: Andrew Fecenko <afecenko@atlassian.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant