Skip to content

WIP: Max total connections support for BalancedPool #4192

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

JoshMock
Copy link

@JoshMock JoshMock commented May 2, 2025

This relates to...

#3268 (comment)

Rationale

Work in progress on being able to enforce maximum total connections across all origins in a BalancedPool and Agent.

Changes

Features

Exposes maxTotalConnections option on BalancedPool and Agent that ensures the total number of open connections across all origins does not exceed the configured maximum.

Bug Fixes

Breaking Changes and Deprecations

Status

return this[kClients].map(pool => pool[kConnected]).reduce((a, b) => a + b, 0)
}

[kQueueResume] () {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@metcoder95 You asked elsewhere why I overrode other queue resume code. For one, I couldn't see how other extensions of PoolBase or DispatcherBase were triggering a queue resume event; if you can point me to where/how that happens that would be fantastic.

But also, this implementation specifically optimizes for finding an available Pool with a matching origin.

Thanks for your patience, as this is my first time digging through the Undici codebase, so I'm learning the internals as I go.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But is there a specific reason to override it? Or why was required? (maybe I missed something before)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my initial test runs, without this force trigger, the queue was never resumed and all pending requests were never run.

const { onComplete } = handler
handler.onComplete = (...args) => {
setTimeout(this[kQueueResume](), 0)
if (onComplete) onComplete(...args)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mentioned this in #3268, but to put it in context: wrapping a handler's onComplete function appears to happen every time a request is attempted, so if a request is taken from the queue x times before finally being sent, this call to kQueueResume happens x times as well. Testing this locally I noted that it caused kQueueResume calls to increase exponentially over time, which is not at all ideal.

I tried adding a private symbol handler[kResumable] = true when it had been wrapped, to avoid re-wrapping, but it seems that handler is a brand new object every time kDispatch is called.

if (typeof factory !== 'function') {
throw new InvalidArgumentError('factory must be a function.')
}

if (maxTotalConnections != null && (!Number.isFinite(maxTotalConnections) || maxTotalConnections < 0)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For backwards compatibility, shouldn't be better to default it to Infinity?

return this[kClients].map(pool => pool[kConnected]).reduce((a, b) => a + b, 0)
}

[kQueueResume] () {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But is there a specific reason to override it? Or why was required? (maybe I missed something before)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants