Skip to content

Maintain connection reservations incrementally in the pool#1076

Open
Kludex wants to merge 4 commits into
mainfrom
pool-incremental-reservations
Open

Maintain connection reservations incrementally in the pool#1076
Kludex wants to merge 4 commits into
mainfrom
pool-incremental-reservations

Conversation

@Kludex

@Kludex Kludex commented Jul 23, 2026

Copy link
Copy Markdown
Member

Summary

Stacked on #1075. Implements the follow-up suggested by @11kkw there: the last O(in-flight) cost per assignment pass was rebuilding request_connections from the full request list, which is quadratic in aggregate across a large burst.

Changes

  • Replace the per-pass {r.connection for r in self._requests} rebuild with a refcounted dict[connection, int] on the pool.
  • Update it at the mutation points: assignment, ConnectionNotAvailable requeue (now under the thread lock), cancellation, and response close.
  • Sync pool generated via unasync as usual.

Benchmarks

Unbounded concurrent requests against a local uvicorn server, best of 3:

n #1075 this PR
1,000 0.33s (3072 rps) 0.34s (2972 rps)
5,000 1.75s (2853 rps) 1.50s (3325 rps)
10,000 3.85s (2595 rps) 3.07s (3257 rps)

Throughput on #1075 decays as burst size grows (the residual quadratic term); with this change it stays flat, making total pool work effectively linear.

Verification

  • Full suite passes with 100% coverage; mypy/ruff/unasync clean.
  • HTTP/2 against hypercorn over TLS: 500-request cold burst and warm-idle burst multiplex on a single connection, unchanged.
  • Real services with http2=True unchanged; streaming, cancellation storms, keepalive expiry, PoolTimeout, and threaded sync Client all behave as before.

AI Disclaimer

This PR was developed with the assistance of either Claude or Codex. I've reviewed and verified the changes.

Review in cubic

Kludex added 4 commits July 23, 2026 09:18
Assignment does not change an HTTP/1.1 connection's state, so a newly
idle connection was handed to every queued request in one pass and
re-picked by later passes until the winner sent on it. Every loser woke
up, failed with ConnectionNotAvailable, re-entered the queue, and
triggered another full O(n) assignment pass - quadratic churn at high
queue depth.

Drop a connection from the availability snapshot once assigned, and
exclude idle connections already reserved by an in-flight request when
building the snapshot. 1000 concurrent requests against a local server
drop from 5.1s to 0.9s with the default pool, and from 100s to 1.1s
with max_connections=1. HTTP/2 multiplexing is unaffected: an active h2
connection is not idle, so it stays available to additional streams.
An idle HTTP/2 connection can serve further requests while reserved, so
treating it like HTTP/1.1 could leave a queued burst waiting for the
next pool event instead of multiplexing. Add can_multiplex() to the
connection interface (False by default, True for established HTTP/2)
and only apply the reserved-idle exclusion to connections that cannot
multiplex.
Each assignment pass walked every in-flight request even when the pool
was saturated, and re-probed reserved idle connections for expiry with
an is_readable socket check on every interleaved pass. Break out of the
assignment loop once no connection is available and no new one may be
created, and skip expiry checks and surplus-keepalive eviction for
connections reserved by an assigned request - they were health-checked
at assignment time, and evicting them would hand the winning request a
closed connection.

1000 unbounded concurrent requests against a local server now complete
in 0.41s versus 0.51s sequential, compared to 0.93s before this change
and 5.1s before #1075.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No issues found across 2 files

Re-trigger cubic

Base automatically changed from pool-single-assignment to main July 23, 2026 11:53
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.

1 participant