AWS SDK

AWS SDK

rev. 39ed07918eb1729475c85ddf93a83d0b1539e928

Files changed:

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-http-client/docs/design/connection-pool.md

@@ -24,24 +83,93 @@
   44     44   
released permit can become reachable. [Liveness](#liveness) defines the progress assumptions and the
   45     45   
cross-scope HTTP/2 condition in which no such resource becomes reachable.
   46     46   
   47     47   
### Local reuse is partition-local and bounded
   48     48   
   49     49   
A local reuse hit performs bounded work independent of partition count. It consults no origin-wide
   50     50   
coordination and reads no other partition's state. An unbounded origin constructs no admission, peer-index,
   51     51   
or cross-partition ordering machinery; cross-partition coordination begins only after local reuse misses on
   52     52   
a bounded origin with no free capacity.
   53     53   
          54  +
### Coordination cost is bounded
          55  +
          56  +
Pool locks are never nested. Admission selects from maintained origin or
          57  +
eligibility-group indexes rather than scanning partitions or connections, and
          58  +
each detached action performs bounded work before it either settles or produces
          59  +
one successor. Cross-partition H2 reuse moves generation identity, not transport
          60  +
ownership. Changes that claim to preserve local-path cost must show that they add
          61  +
no origin-wide coordination, partition-wide scan, or shared allocation to a
          62  +
steady local reuse hit.
          63  +
   54     64   
### Pool behavior and state are observable
   55     65   
   56     66   
The pool reports connection lifecycle events and per-partition statistics sufficient to diagnose
   57     67   
establishment, reuse, waiting, logical close, and physical teardown. Events identify the origin and owning
   58     68   
partition, and installed connections also identify the stable connection and negotiated protocol.
   59     69   
Statistics distinguish establishment, admitted H1 and H2 state, draining state, active H2 streams, waiters,
   60     70   
and physically live transports.
   61     71   
   62     72   
## Architecture
   63     73   
@@ -87,97 +257,298 @@
  107    117   
    |-- OriginAdmission(s3)
  108    118   
    |     `-- P0 and P1 cells
  109    119   
    `-- OriginAdmission(dynamodb)
  110    120   
          `-- P0 cell
  111    121   
```
  112    122   
  113    123   
Because the partition set is fixed and the origin set is not, partitions are the outer level: each partition
  114    124   
owns its own map from origin to cell, so the structure that grows is always inside one partition. A cell that
  115    125   
no request has asked for does not exist.
  116    126   
  117         -
An **`OriginAdmission`** holds what all partitions sharing a bounded origin must agree on: its connection
  118         -
budget, cross-cell demand order, and index of cells for that origin. It stores the shared `OriginKey` once and
  119         -
keys its internal cell, demand, and availability records by `PartitionId`; the origin component is invariant
  120         -
inside this authority. Nothing else spans partitions.
         127  +
An **`OriginAdmission`** holds what all partitions sharing a bounded origin must agree on: its capacity budget,
         128  +
demand schedule, and H1/H2 supply indexes. It stores the shared `OriginKey` once and keys its internal cell,
         129  +
demand, and supply records by `PartitionId`; the origin component is invariant inside this authority. Nothing
         130  +
else spans partitions.
  121    131   
  122    132   
#### Ownership and lifetime
  123    133   
  124    134   
```text
  125    135   
ConnectionPool
  126    136   
`-- PartitionRegistry
  127    137   
    |-- PartitionState by PartitionId
  128    138   
    |   `-- OriginCell by OriginKey       local waiters and protocol records
  129    139   
    `-- OriginAdmission by OriginKey      bounded-origin permits and ordering
  130    140   
  131    141   
Client
  132    142   
|-- ConnectionPool
  133    143   
`-- resolved PartitionState
  134    144   
```
  135    145   
  136    146   
| Type              | Created                                | Destroyed                                     | Shared across partitions |
  137    147   
| ----------------- | -------------------------------------- | --------------------------------------------- | ------------------------ |
  138    148   
| `ConnectionPool`  | by the builder                         | when the last pool, client, and request release it | —                        |
  139    149   
| `Partition`       | at construction, from the declared set | at pool drop                                  | no                       |
  140    150   
| `OriginCell`      | first request for (partition, origin)  | not while the origin is live                  | no                       |
  141    151   
| `OriginAdmission` | first request for a *bounded* origin   | not while the pool lives                      | yes                      |
  142    152   
| `Client`          | by the caller, freely                  | by the caller                                 | —                        |
  143    153   
  144    154   
`Client` is what a caller holds and what implements the smithy runtime's `HttpClient`. It pairs the pool with
  145    155   
one resolved partition, so a request never searches for its partition — the handle already names it.
  146    156   
         157  +
Each mutable authority has one lock domain:
         158  +
         159  +
| Authority | Owns |
         160  +
| --- | --- |
         161  +
| Admission registry | One admission authority for each bounded canonical origin. |
         162  +
| Partition origin map | Stable cell identity for one partition and canonical origin. |
         163  +
| Origin admission | Available capacity, canonical demand, indexed H1/H2 supply, and retained H1 matches. |
         164  +
| Origin cell | Local acquisition order, H1 sender state, H2 flights and generations, and supply revisions. |
         165  +
| Connection lifecycle | Dispatch eligibility, accepted-dispatch count, bounded capacity, and physical connection completion. |
         166  +
| H2 request claim | Upload and response completion for one prospective or accepted request. |
         167  +
| Partition maintenance | Idle deadlines, wake publication, and shutdown. |
         168  +
         169  +
No transition holds two pool locks at once. Values that cross lock domains own
         170  +
their rejection or drop fallback until the receiving authority commits them.
         171  +
  147    172   
An `OriginAdmission` exists only for a bounded origin. A local miss normally
  148    173   
establishes on the requesting partition. When no permit is free, admission may
  149    174   
use compatible peer protocol state or reclaim peer capacity for that demand.
  150    175   
An unbounded origin never needs cross-partition admission or peer indexes; its
  151    176   
cells are independent. Reuse scope controls which peer protocol state is
  152    177   
compatible, while reclaim may recover capacity across eligibility groups.
  153    178   
  154         -
Pool retention, request accounting, root-I/O ownership, and bounded capacity
         179  +
Pool retention, request accounting, physical connection ownership, and bounded capacity
  155    180   
have different lifetimes.
  156    181   
  157    182   
```text
  158    183   
pool lifetime
  159    184   
  160    185   
caller-held Client ------------------------------> ConnectionPool
  161    186   
request future, until response head/error -------> ConnectionPool
  162    187   
```
  163    188   
  164    189   
A `Client` and an in-flight request through response headers retain the pool.
  165    190   
Producing a response head or terminal error ends the request future's pool
  166    191   
hold. Protocol-specific guards own the remaining cleanup:
  167    192   
  168    193   
```text
  169    194   
post-header protocol lifetime
  170    195   
  171    196   
H1Exchange <------------------ response body or readiness task
  172    197   
PhysicalConnectionGuard <----- driver or upgraded root I/O
  173    198   
  174         -
H2 request lease
  175         -
  |-- receive endpoint <------ response body or upgrade bridge
  176         -
  `-- send endpoint <--------- accepted H2 request-body adapter
         199  +
H2 request claim
         200  +
  |-- response guard <--------- response body or upgrade bridge
         201  +
  `-- upload guard <------------ accepted H2 request-body adapter
  177    202   
```
  178    203   
  179    204   
An H1 exchange owns Hyper's exclusive HTTP/1 request handle (`SendRequest`) and
  180    205   
returns it only after a reusable message boundary. An
  181         -
H2 request lease releases only after both stream endpoints terminate. Root I/O
  182         -
may move from the driver into an upgrade while the same physical guard tracks
  183         -
pool ownership. Bounded capacity has a separate owner path:
         206  +
H2 request claim releases only after both request sides finish. Root I/O
         207  +
may move from the driver into an upgrade while the same
         208  +
`PhysicalConnectionGuard` tracks client ownership. Bounded capacity has a
         209  +
separate owner path:
  184    210   
  185    211   
```text
  186    212   
bounded connection capacity
  187    213   
  188    214   
OriginAdmission
  189    215   
  `-- issue --> EstablishmentPermit
  190    216   
                  +-- failure/drop ------------------------> OriginAdmission
  191    217   
                  `-- install --> ConnectionState owns CapacityLease
  192    218   
                                      `-- logical close ---> OriginAdmission
  193    219   
```
  194    220   
  195    221   
A bounded permit moves from admission to establishment and then to the
  196    222   
installed `ConnectionState`. Logical close returns it. Dispatch handles,
  197         -
`DispatchGuard`, and H2 request leases never own a connection permit.
         223  +
`DispatchGuard`, and H2 request claims never own a connection permit.
         224  +
         225  +
HTTP/1 and HTTP/2 share admission but not connection semantics:
         226  +
         227  +
| HTTP/1 | HTTP/2 |
         228  +
| --- | --- |
         229  +
| One request exclusively owns a sender. | Many requests may share one generation. |
         230  +
| Admission may retain an H1 match while a supplier-cell reservation crosses locks. | A route is detached identity-only work; admission retains no matching route lifecycle. |
         231  +
| Peer reuse transfers sender ownership to the requesting cell. | Peer reuse follows a route to the connection-owning generation. |
         232  +
| Borrow and reclaim serve the origin demand head; an incompatible head may deliberately block younger groups. | Route-ready eligibility groups rotate without consuming origin capacity. |
         233  +
| Reclaim closes a provisional H1 candidate or an idle H1 record. | Reclaim closes one exact idle generation. |
         234  +
| Reuse ends at one complete HTTP/1 exchange boundary. | An accepted request claim ends after both upload and response sides finish. |
         235  +
         236  +
These differences are protocol properties, not parallel abstractions waiting to
         237  +
be commonized. Shared code owns only the capacity, demand, and detached-action
         238  +
mechanics that have the same authority in both protocols.
  198    239   
  199    240   
#### Request path
  200    241   
  201    242   
A request resolves its partition-local cell and first attempts compatible
  202    243   
local selection. A miss registers one acquisition. Capacity and available
  203    244   
protocol state determine how that acquisition completes.
  204    245   
  205    246   
```text
  206    247   
Client(partition P, request URI)
  207    248   
`-- resolve OriginCell(P, origin)
  208    249   
    |
  209    250   
    |-- compatible local connection ------------------> dispatch
  210    251   
    |
  211    252   
    `-- local miss -> register one acquisition
  212    253   
        |
  213    254   
        |-- unbounded origin or free permit
  214    255   
        |   `-- establish on P owner runtime ----------> dispatch
  215    256   
        |
  216    257   
        `-- bounded origin at capacity
  217    258   
            |-- compatible peer connection ------------> dispatch
  218    259   
            |-- reclaimable peer capacity
  219    260   
            |   `-- close peer; transfer permit
  220    261   
            |       `-- establish on P owner runtime --> dispatch
  221    262   
            `-- otherwise park until state changes
  222    263   
  223    264   
selected protocol handle
  224    265   
`-- commit against logical close -> Hyper
  225    266   
    |-- request completes ------------> return or release protocol state
  226    267   
    |-- protocol upgrade ------------> caller owns upgraded lifecycle
  227         -
    `-- connection-terminal failure -> logical then physical close
         268  +
    `-- connection-terminal failure -> logical close, then physical connection ownership ends
  228    269   
```
  229    270   
  230    271   
[Local connection selection](#local-connection-selection) defines local selection.
  231    272   
[Connection establishment](#connection-establishment) defines establishment, placement, and protocol
  232    273   
convergence. [Bounded-capacity coordination](#bounded-capacity-coordination) defines parking, borrow, reclaim,
  233    274   
and resource delivery, with [Liveness](#liveness) stating when those paths guarantee progress.
  234    275   
[Dispatching and completing a request](#dispatching-and-completing-a-request) defines request preparation,
  235    276   
stale-reuse retry, and the transfer to response or upgrade ownership. The path ends in
  236    277   
[return or retirement](#connection-retirement-and-maintenance), where each terminal outcome either makes the
  237    278   
connection reusable or closes it.
@@ -513,554 +574,615 @@
  533    574   
```text
  534    575   
request on partition P for origin O
  535    576   
  |-- P already resolved by Client
  536    577   
  `-- P.origins[O]                     partition-local origin lookup
  537    578   
      `-- select compatible state      one OriginCell lock
  538    579   
```
  539    580   
  540    581   
That is the entire path for a reuse hit. It performs no origin-wide coordination, reads no other partition's
  541    582   
state, and touches no `OriginAdmission` or peer index. Its synchronization is
  542    583   
the requesting partition's own cell lock. A peer may acquire that lock to
  543         -
install or settle a bounded cross-cell reuse operation, so the lock can be
  544         -
contended, but the local hit does not consult origin-wide state. Its work is
         584  +
reserve or settle a bounded H1 match, so the lock can be contended, but the
         585  +
local hit does not consult origin-wide state. Its work is
  545    586   
independent of partition count, and traffic for another origin does not share
  546    587   
the cell.
  547    588   
  548    589   
A reused connection may be dead: the server can close an idle connection while it sits in the cell, and the
  549    590   
pool learns this only on dispatch. So "take a live idle connection" is provisional until the request is
  550    591   
accepted. Hyper's `try_send_request` returns the unsent request when the connection failed before accepting
  551    592   
it, and that returned request is the retry boundary — a reused connection that fails before acceptance is
  552    593   
transparently retried on a fresh one, invisibly to the caller. A request the connection had already accepted
  553    594   
is not retried here; whether to retry it is the caller's policy, because the pool cannot know the request was
  554    595   
not acted on. This distinguishes a *reused* connection, where a pre-acceptance failure is the expected
@@ -649,690 +1552,1610 @@
  669    710   
increments the free count rather than storing returned permits. The representation avoids an allocation
  670    711   
on capacity return while the permit and lease types preserve linear ownership.
  671    712   
  672    713   
The spawned connection driver is wrapped by a **driver lifecycle guard** armed only after the record and its
  673    714   
generation-specific close authority exist. The guard holds a non-retaining close handle, not the lease. If
  674    715   
the driver completes, the wrapper requests logical close with `ProtocolClosed` and the driver's source error.
  675    716   
If the wrapper is dropped before completion, including when its owning runtime shuts down, the guard's
  676    717   
`Drop` requests logical close with `OwnerRuntimeShutdown`. Either request races through the record's existing
  677    718   
idempotent close transition, so a prior pool drop, poison, reclaim, or protocol close wins without releasing
  678    719   
capacity twice. Because the handle does not retain the pool, driver tasks cannot form a lifetime cycle with
  679         -
the records they close. The root-I/O wrapper remains the separate authority for physical completion.
         720  +
the records they close. `PhysicalConnectionGuard` remains the separate authority for the end of
         721  +
client-owned physical connection I/O. Dropping it releases the client's transport handle; the operating
         722  +
system may continue TCP teardown.
  680    723   
  681    724   
This is capacity conservation on the create and driver paths: the permit has one
  682    725   
owner at every step, and cancellation either drops the establishing lease or logically closes the record
  683    726   
that received it.
  684    727   
  685    728   
#### Readiness
  686    729   
  687    730   
The connector is a Tower `Service<Uri>`, and the pool honors that contract: it drives the connector to ready
  688    731   
before calling it, and issues one call per readiness. The dispatch handle Hyper returns is a different thing.
  689    732   
Its readiness — whether the connection can accept another request — is an inherent
  690    733   
[`SendRequest::poll_ready`](https://github.com/hyperium/hyper/blob/v1.11.0/src/client/conn/http1.rs#L156),
  691    734   
not a Tower `Service`: Hyper has no Tower dependency, and the HTTP/2 form ignores its `Context` entirely
  692    735   
([`http2.rs:97`](https://github.com/hyperium/hyper/blob/v1.11.0/src/client/conn/http2.rs#L97)). Connection
  693    736   
readiness is thus a per-protocol dispatch question the pool answers against the connection's state, and does
  694    737   
not share machinery with the connector's Tower readiness. The two are related only by name.
  695    738   
  696    739   
#### HTTP/1 attempts and HTTP/2 flights
  697    740   
  698    741   
One HTTP/2 connection carries many concurrent request streams. The pool calls
  699    742   
one installed incarnation of that connection a *generation*. Replacements
  700    743   
receive new generation identities so delayed routes, GOAWAY handling, close
  701    744   
work, and request completion cannot affect a newer connection. An
  702         -
`H2Activation` is a reservation for a prospective stream on one exact
  703         -
generation; it becomes an accepted request lease only after Hyper accepts the
         745  +
`H2Activation` is authority for a prospective request on one exact
         746  +
generation; it becomes an accepted request claim only after Hyper accepts the
  704    747   
request.
  705    748   
  706    749   
The pool supports HTTP/1.1 and HTTP/2 and lets connector ALPN select the protocol. Each request has one of
  707    750   
three requirements. `H1Required` covers accepted request forms that require HTTP/1 wire semantics, including
  708    751   
HTTP/1.0, ordinary `CONNECT`, and Upgrade. `H1Compatible` may use either protocol. `H2Required` cannot dispatch
  709    752   
on HTTP/1. Request protocol is not part of the origin key.
  710    753   
  711    754   
The default rustls connector offers only `http/1.1` for `H1Required` and offers `h2, http/1.1` otherwise.
  712    755   
The s2n connector has a fixed HTTP ALPN offer. Connectors injected through the unstable test utility own
  713    756   
their protocol configuration and do not receive the pool's offer. Those paths may therefore negotiate H2
  714         -
for `H1Required`; the pool rejects that result as a protocol mismatch before Hyper establishment.
         757  +
for `H1Required`; the pool rejects that result as a non-retryable protocol mismatch before Hyper establishment.
  715    758   
`H2Required` does not make an H2-only offer. If the server
  716    759   
selects HTTP/1, the pool retains the useful H1 connection for compatible demand and returns an
  717    760   
unsupported-version error with that connection's metadata to the launching request.
  718    761   
  719    762   
The two protocols establish differently because they reuse differently. An HTTP/1 connection carries one
  720    763   
request at a time, so a cell that needs more concurrency needs more connections: each establishment is an
  721    764   
independent *attempt*, and several may run at once, each producing a connection for the request that launched
  722    765   
it. An HTTP/2 connection multiplexes, so one connection serves the whole cell; a second connection to an
  723    766   
origin the cell already serves is nearly pure waste. HTTP/2 establishment is therefore a *flight*,
  724    767   
coordinated so the cell keeps at most one accepting HTTP/2 generation — a request arriving during a flight
  725    768   
waits for it rather than starting its own, and the resulting generation is published to compatible waiters.
  726    769   
  727    770   
##### Post-ALPN convergence
  728    771   
  729    772   
The initial API imposes no pool-wide HTTP/1-only or HTTP/2-only policy. Connector ALPN resolves a transport's
  730    773   
protocol only after connect, so concurrent misses may each own a transport until then. Compatible attempts
  731    774   
remain independent when they negotiate H1 and converge on the cell's one flight only when they negotiate H2.
  732    775   
An H1-required attempt that nevertheless negotiates H2 terminates with a protocol-mismatch error.
  733    776   
  734    777   
The logical owner carried through this decision is an *establishment authority*: the transport and, on a
  735    778   
bounded origin, its capacity lease. The authority has exactly one owner even though the request waiting for
  736    779   
its result does not own it; cancellation of that request cannot silently drop the transport or permit. After
  737    780   
ALPN, the owner performs one cell-local select-or-join transition before starting the Hyper protocol handshake:
  738    781   
  739    782   
```text
  740    783   
automatic attempt owns transport + optional capacity lease + launching waiter
  741    784   
  |
  742    785   
  +-- ALPN = H1
  743    786   
  |     `-- run H1 handshake
  744    787   
  |           +-- success -> install H1 record; record takes capacity lease
  745    788   
  |           |     +-- launching waiter accepts H1 -> dispatch
  746    789   
  |           |     `-- waiter requires H2 -> retain H1 for compatible demand;
  747    790   
  |           |                              return the existing unsupported-version error
  748    791   
  |           `-- error -> close transport; return lease; fail launching waiter
  749    792   
  |
  750    793   
  `-- ALPN = H2
  751    794   
        +-- H1 required -> close transport; return lease; fail launching waiter
  752    795   
        |
  753    796   
        `-- H2 compatible
  754    797   
              `-- atomically inspect this cell's accepting generation and H2 flight
  755    798   
                    +-- accepting generation -> register waiter against generation
  756    799   
                    |                           close losing transport; return its lease
  757    800   
                    +-- flight exists --------> register waiter as participant
  758    801   
                    |                           close losing transport; return its lease
  759    802   
                    `-- neither exists -------> register flight; owner task drives it
  760    803   
                                                owner task retains transport + lease
  761    804   
                                                  |
  762    805   
                                                  +-- H2 handshake succeeds
  763    806   
                                                  |     -> install record; record takes lease
  764         -
                                                  |     -> publish generation; serve participants
         807  +
                                                  |     -> make generation visible; serve participants
  765    808   
                                                  `-- error
  766    809   
                                                        -> close transport; return lease
  767    810   
                                                        -> fail participants
  768    811   
```
  769    812   
  770    813   
The inspection and either registration or flight installation are one transition under the cell's
  771    814   
coordination. This is the linearization point: many automatic attempts may reach H2 ALPN, but at most one
  772    815   
becomes the flight. A losing authority has not started a Hyper driver, so its guard closes the negotiated
  773    816   
transport and returns its capacity lease; its launching waiter remains represented exactly once, as a
  774    817   
generation user or flight participant. The owner task completes the handshake, transfers capacity to the
  775    818   
connection record, and installs the generation before submitting the driver. A request activated during that
  776    819   
submission interval is retained by Hyper's dispatch channel until the driver is polled. Activation
  777    820   
revalidates the generation identity and accepting state; if either changed after registration, the waiter
  778    821   
returns to acquisition rather than dispatching through stale state.
  779    822   
  780         -
A waiter that joins a flight or generation retains its original waiter sequence. On publication, the
  781         -
[generation gate](#http2-publication) orders flight participants and already committed compatible local
  782         -
waiters together; joining an accepting generation follows that same activation path. Post-ALPN convergence
         823  +
A waiter that joins a flight or generation retains its original waiter sequence. When the generation becomes
         824  +
visible, the [generation gate](#http2-peer-routing) orders flight participants and already committed
         825  +
compatible local waiters together; joining an accepting generation follows that same activation path.
         826  +
Post-ALPN convergence
  783    827   
therefore cannot let a later attempt barge ahead of an older compatible waiter.
  784    828   
  785    829   
Request version changes only compatibility. When an automatic attempt launched by an HTTP/2-marked request
  786    830   
negotiates HTTP/1, the H1 connection remains useful and is handed to compatible local demand or the idle set.
  787    831   
The launching request receives the same unsupported-version classification and connection metadata as the
  788    832   
existing client; it neither dispatches on H1 nor loops establishing until ALPN happens to choose H2.
  789    833   
  790    834   
A flight record owns its identity and participant waiter identities. The
  791    835   
connection-partition owner task separately owns the transport and optional
  792    836   
capacity lease while it drives the handshake. Cancelling a participant removes
  793    837   
only that waiter; it does not cancel the owner task. If every participant
  794    838   
cancels, the task may still install an idle generation, which retains bounded
  795    839   
capacity until ordinary close or idle expiration. Task drop closes the
  796    840   
transport, returns capacity, and fails every participant still retained by the
  797    841   
exact flight. Completion from an old flight identity is stale and cannot clear
  798         -
or publish over a successor.
         842  +
or replace a successor.
  799    843   
  800    844   
The ownership transfer is therefore fixed at each boundary:
  801    845   
  802    846   
| State               | Transport owner           | Capacity owner                              | Waiting-request owner              |
  803    847   
| ------------------- | ------------------------- | ------------------------------------------- | ---------------------------------- |
  804    848   
| independent attempt | establishment authority   | its optional capacity lease                 | cell waiter entry                  |
  805    849   
| H1 installed        | H1 connection record      | record's capacity lease                     | checked-out H1 guard or cell queue |
  806    850   
| H2 flight driver    | connection owner task     | owner task until record installation        | flight participant entry           |
  807    851   
| H2 joiner           | existing flight or record | existing owner; own capacity lease returned | participant or activation          |
  808         -
| H2 published        | H2 connection record      | record's capacity lease                     | H2 request lease after activation  |
         852  +
| H2 installed        | H2 connection generation  | generation's capacity lease                 | H2 request claim after activation  |
  809    853   
| failure or drop     | cleanup guard             | guard until admission return                | terminal result or re-acquisition  |
  810    854   
  811    855   
This is the mechanism behind the miss policy from
  812    856   
[Local connection selection](#local-connection-selection): a partition that misses locally establishes its
  813    857   
own connection. It reaches for a peer's connection only when it cannot establish, which the next section
  814    858   
covers.
  815    859   
  816    860   
#### Obligations
  817    861   
  818    862   
* **Establishment placement** [safety] — the connector, transport setup, TLS, ALPN, and handshake for a
  819    863   
  connection are polled only on its placement runtime: the runtime bound by the anonymous partition's first
  820    864   
  establishment or the runtime named by an explicit partition.
  821    865   
* **Hyper task placement** [safety] — tasks Hyper spawns for a connection run on that connection's partition
  822    866   
  runtime.
  823    867   
* **Single permit owner** [safety] — at every point in establishment a bounded connection's permit has
  824    868   
  exactly one owner, and a failed or dropped establishment returns its permit to admission.
  825    869   
* **Driver termination closes the record** [safety] — normal driver completion and cancellation of the
  826    870   
  guarded driver task both request the record's exactly-once logical close without owning or retaining its
  827    871   
  capacity lease.
  828    872   
* **Connector readiness** [safety] — the connector is driven to ready before each call, and each readiness
  829    873   
  admits one call.
  830    874   
* **Single generation** [safety] — a cell keeps at most one accepting HTTP/2 generation; establishments that
  831    875   
  resolve HTTP/2 collapse to one, and the losing transports close and return their permits.
  832    876   
* **Atomic H2 convergence** [safety] — after automatic ALPN selects H2, one cell-local transition either
  833    877   
  activates an accepting generation, joins the one current flight, or installs the caller as that flight's
  834         -
  driver before any H2 handshake or publication begins.
         878  +
  driver before any H2 handshake or generation installation begins.
  835    879   
* **Losing-attempt cleanup** [safety] — an automatic H2 attempt that joins existing state closes its
  836    880   
  unhandshaken transport and returns its capacity lease while retaining its launching waiter exactly once.
  837    881   
* **Compatibility-preserving H1 result** [safety] — an H1 result is retained for compatible demand; an
  838    882   
  H2-required launching waiter receives the existing unsupported-version result with connection metadata and
  839    883   
  never dispatches on H1 or loops establishment for a different ALPN outcome.
  840    884   
* **Required-H1 negotiation** [safety] — an H1-required request never dispatches on H2. A connector that
  841    885   
  accepts per-attempt ALPN offers receives an HTTP/1-only offer for that request.
  842    886   
* **Flight cancellation** [safety] — participant cancellation removes only that participant; terminal flight
  843    887   
  drop closes its transport, returns its lease, and leaves no live waiter attached to the retired flight
  844    888   
  identity.
  845    889   
  846    890   
### Bounded-capacity coordination
  847    891   
  848    892   
A bounded origin at its limit cannot establish: admission has no permit to issue. The permit it needs may
  849    893   
still exist — held by a connection this waiter cannot use, or held in another partition's cell — but a
  850    894   
present permit is not a usable one. So two things have to happen. A waiter parks until a permit it can use
  851    895   
becomes available, and capacity that exists elsewhere moves to where the demand is. This section covers how a
  852    896   
cell signals demand, how the pool decides which capacity a waiter may take, the two operations that move
  853    897   
capacity, and how a freed resource is delivered to a parked waiter. All of it is bounded-mode only; an
  854    898   
unbounded origin never reaches here.
  855    899   
  856    900   
#### Demand
  857    901   
  858    902   
A cell's demand is one aggregate state, not a per-request count: a cell either wants another connection or it
  859    903   
does not. The state is active while any request waits and inactive when none do, so it is a single standing
  860    904   
ticket per cell rather than one ticket per waiting request.
  861    905   
  862    906   
The waiting requests queue behind the ticket in arrival order. An arriving permit goes to the request at the
  863    907   
head, which stops waiting; the next becomes the head. The ticket is how returning connections and other
  864    908   
cells find a cell with unmet demand; the queue is how that cell chooses whom to serve first.
  865    909   
  866    910   
The ticket carries one thing beyond its presence: the head request's protocol requirement. Admission must know
  867         -
whether an HTTP/1 return, an HTTP/2 publication, or either can satisfy the head before it reserves a resource.
         911  +
whether an HTTP/1 return, an HTTP/2 route, or either can satisfy the head before it reserves a resource.
  868    912   
This is the minimum the signal must distinguish; finer matching remains a dispatch-time question.
  869    913   
  870    914   
The logical snapshot published to admission is:
  871    915   
  872    916   
```rust
  873    917   
enum ProtocolRequirement {
  874    918   
    H1Required,
  875    919   
    H1Compatible,
  876    920   
    H2Required,
  877    921   
}
  878    922   
  879    923   
enum DemandState {
  880    924   
    Active {
  881    925   
        head: ProtocolRequirement,
  882    926   
        eligibility_group: EligibilityGroup,
  883    927   
    },
  884    928   
    Inactive,
  885    929   
}
  886    930   
  887    931   
struct DemandSnapshot {
  888    932   
    id: DemandId,
  889    933   
    version: SnapshotVersion,
  890    934   
    state: DemandState,
  891    935   
}
  892    936   
```
  893    937   
  894    938   
A `DemandId` names one generation for the cell's current queue head and may receive at most one terminal
  895    939   
acquisition outcome. Its protocol requirement is stable. Serving or cancelling that head retires the
  896    940   
generation; if useful demand remains, the cell creates a successor ID for the new head. `SnapshotVersion` orders
  897    941   
complete replacements for the same ID, so a delayed active publication cannot overwrite retirement. An
  898    942   
inactive snapshot retires the demand. When it must queue, each active demand joins the applicable scheduling
  899    943   
orders at their tails; checked identity allocation does not reuse a demand ID after wraparound.
  900    944   
  901    945   
#### Bounded miss
  902    946   
  903    947   
A bounded miss registers local demand before it asks admission to act. This ordering makes a waiter visible
  904         -
before a permit, return, or publication can target it. An immediately available permit still follows the same
         948  +
before a permit, return, or route can target it. An immediately available permit still follows the same
  905    949   
delivery and acknowledgement path as a later one; there is no separate pre-registration fast path whose races
  906    950   
would need a second proof.
  907    951   
  908    952   
```text
  909    953   
local selection misses on cell C
  910    954   
  |
  911    955   
  +-- register waiter and current DemandSnapshot R under C's lock
  912    956   
  `-- publish complete snapshot R to OriginAdmission
  913    957   
        |
  914    958   
        +-- free permit
  915         -
        |     `-- create capacity delivery fence for R
         959  +
        |     `-- assign capacity to R
  916    960   
        |
  917    961   
        `-- no free permit
  918    962   
              `-- queue R in its origin order and applicable eligibility-group views
  919    963   
                    |
  920    964   
                    +-- compatible local H1/H2 appears
  921    965   
                    |     `-- satisfy locally; retire or replace R
  922    966   
                    |
  923    967   
                    +-- eligible peer H2 appears
  924         -
                    |     `-- publish generation reference to C
         968  +
                    |     `-- install generation route in C
  925    969   
                    |
  926    970   
                    +-- peer H1 is available
  927    971   
                    |     `-- borrow or reclaim for the oldest origin demand
  928    972   
                    |
  929    973   
                    `-- permit is released or reclaimed
  930         -
                          `-- create capacity delivery fence for R
         974  +
                          `-- assign capacity to R
  931    975   
  932    976   
capacity delivery reaches C
  933    977   
  |
  934    978   
  +-- R is stale, cancelled, or already satisfied -> refunnel permit
  935    979   
  `-- R is live
  936    980   
        +-- final local probe now succeeds -> use local resource; refunnel permit
  937    981   
        `-- still needs a connection ------> move lease to establishing task
  938    982   
  939    983   
borrow delivery reaches C
  940    984   
  |
  941    985   
  +-- R or its reserved waiter is stale -> return H1 to connection-owning cell
  942    986   
  `-- still compatible -----------------> move checked-out H1 guard to waiter
  943    987   
  944    988   
terminal outcome for R
  945    989   
  +-- no useful demand remains -> ticket becomes idle
  946    990   
  `-- useful demand remains ---> publish successor generation at applicable tails
  947    991   
```
  948    992   
  949    993   
The final local probe is part of accepting capacity, under the cell lock. It prevents a permit delivered
  950         -
concurrently with local return or H2 publication from causing an unnecessary new connection. Local progress,
         994  +
concurrently with local return or H2 route installation from causing an unnecessary new connection. Local progress,
  951    995   
waiter cancellation, and host delivery therefore race through generation and snapshot-version validation rather than
  952    996   
recall a payload already extracted under another lock.
  953    997   
  954    998   
#### Eligibility and capacity
  955    999   
  956   1000   
Two independent questions decide how a waiter is served, and they apply to different actions. *Eligibility*
  957   1001   
asks whether this partition may dispatch through a particular connection, which
  958   1002   
`ConnectionReuseScope` decides: under the default scope, partitions sharing a network
  959   1003   
interface are eligible for each other's connections and partitions on different interfaces are not.
  960   1004   
*Capacity* asks whether the origin may open another connection — one budget of `N` for the whole origin,
  961   1005   
regardless of how many partitions or interfaces exist.
  962   1006   
  963   1007   
```rust
  964   1008   
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
  965   1009   
#[non_exhaustive]
  966   1010   
pub enum ConnectionReuseScope {
  967   1011   
    Partition,
  968   1012   
    #[default]
  969   1013   
    NetworkInterface,
  970   1014   
    Pool,
  971   1015   
}
  972   1016   
```
  973   1017   
  974   1018   
`Partition` permits no cross-cell dispatch. `NetworkInterface` groups partitions by the exact configured
  975   1019   
interface, with all unbound partitions in one group. `Pool` permits reuse across every partition. Scope
  976   1020   
controls only dispatch eligibility: it does not constrain reclaim, which closes a connection and transfers
  977   1021   
capacity rather than moving I/O authority.
  978   1022   
  979   1023   
An **eligibility group** is the exact set of partitions whose reuse policies
  980   1024   
allow them to share a connection. Each partition belongs to one such group for
  981   1025   
an origin, derived from the configured scope above.
  982   1026   
  983   1027   
Dispatching through a connection that already exists consumes no capacity; that connection already holds one
  984   1028   
of the admitted permits. So an eligible reusable connection serves the waiter whether or not a permit is
  985   1029   
free — a pool at its limit still dispatches freely through all `N` of its connections. Capacity is consulted
  986   1030   
only when no eligible connection can serve the waiter and the cell must open one. The two questions do not
  987   1031   
read each other's state, and only that second case reaches admission: eligibility decides reuse, capacity
  988   1032   
decides establishment. Keeping them separate is what lets the two operations below relieve a shortage of one
  989   1033   
without disturbing the other.
  990   1034   
  991   1035   
#### Borrow and reclaim
  992   1036   
  993   1037   
A bounded miss may use another cell's HTTP/1 connection in two ways.
  994   1038   
  995   1039   
**Borrow** moves the exclusive Hyper request handle to the requesting cell for
  996   1040   
one dispatch. The connection record, driver, socket, runtime, and interface stay
  997   1041   
with the connection-owning cell. Borrow is therefore limited to cells in the
  998   1042   
same reuse eligibility group.
  999   1043   
 1000   1044   
**Reclaim** logically closes a reusable HTTP/1 connection and returns its
 1001   1045   
capacity lease to admission. The requesting cell can then establish on its own
 1002   1046   
partition. Reclaim moves no dispatch or I/O authority and is not limited by the
 1003   1047   
reuse scope.
 1004   1048   
 1005         -
Admission retains one origin-wide FIFO and one FIFO per eligibility group over
 1006         -
cells that own an HTTP/1 connection that is idle or may return. A cell appears
 1007         -
at most once in each applicable view. It is removed from both views while a
 1008         -
reuse operation is nonterminal or while a usable local fairness turn is owed,
 1009         -
and is reinserted from its next complete availability report.
        1049  +
`H1SupplyIndex` retains one origin-wide FIFO and one FIFO per eligibility
        1050  +
group over cells that own an HTTP/1 connection that is idle or may return.
        1051  +
A cell appears at most once in each applicable view. It is removed from both
        1052  +
views while an H1 match is nonterminal or while a usable local fairness turn
        1053  +
is owed, and is reinserted from its next complete supply revision.
 1010   1054   
 1011         -
Every availability report carries a monotonic revision assigned under the
 1012         -
connection-owning cell lock. Admission ignores an equal or older revision, so
 1013         -
reports crossing the unlocked cell-to-admission boundary cannot hide newer
 1014         -
state. An advertisement is a scheduling hint, not a connection handle or
        1055  +
Every `SupplyRevision<H1SupplyStatus>` carries a monotonic revision assigned
        1056  +
under the connection-owning cell lock. Admission ignores an equal or older
        1057  +
revision, so values crossing the unlocked cell-to-admission boundary cannot
        1058  +
hide newer state. The status is a policy input, not a connection handle or
 1015   1059   
capacity owner.
 1016   1060   
 1017         -
Borrow and reclaim share one cross-cell reuse protocol:
        1061  +
Borrow and reclaim share one retained H1 match lifecycle:
 1018   1062   
 1019   1063   
```rust
 1020         -
enum ReuseMode {
 1021         -
    Borrow,
 1022         -
    Reclaim,
        1064  +
enum H1MatchKind {
        1065  +
    BorrowSender,
        1066  +
    ReclaimCapacity,
 1023   1067   
}
 1024   1068   
 1025         -
enum ReusePhase {
 1026         -
    Installing,
 1027         -
    Installed,
        1069  +
enum H1MatchState {
        1070  +
    Reserving,
        1071  +
    WaitingForSender,
 1028   1072   
    Resolving,
 1029   1073   
    Cancelling,
 1030   1074   
}
 1031   1075   
 1032         -
struct ReuseOperation {
 1033         -
    id: ReuseId,
 1034         -
    connection_partition: PartitionId,
 1035         -
    requesting_partition: PartitionId,
        1076  +
struct H1Match {
        1077  +
    supplier: PartitionId,
        1078  +
    requester: PartitionId,
 1036   1079   
    demand: DemandId,
 1037         -
    mode: ReuseMode,
 1038         -
    phase: ReusePhase,
        1080  +
    kind: H1MatchKind,
        1081  +
    state: H1MatchState,
 1039   1082   
    cancelled: bool,
 1040   1083   
}
 1041   1084   
 1042         -
enum H1ReuseReservationState {
        1085  +
enum H1ReservationState {
 1043   1086   
    Available,
 1044         -
    Installed(ReuseId),
 1045         -
    Resolving(ReuseId),
        1087  +
    Installed(H1MatchId),
        1088  +
    Resolving(H1MatchId),
 1046   1089   
}
 1047   1090   
 1048         -
struct H1ReuseReservation {
 1049         -
    state: H1ReuseReservationState,
        1091  +
struct H1Reservation {
        1092  +
    state: H1ReservationState,
 1050   1093   
    local_turn_owed: bool,
 1051   1094   
}
        1095  +
        1096  +
struct H1CellState {
        1097  +
    records: HashMap<ConnectionId, H1Record>,
        1098  +
    idle_order: VecDeque<ConnectionId>,
        1099  +
    peer_reservation: H1Reservation,
        1100  +
}
 1052   1101   
```
 1053   1102   
 1054         -
Admission owns `ReuseOperation`; the connection-owning cell owns
 1055         -
`H1ReuseReservation`. Each connection-owning and requesting cell participates
 1056         -
in at most one nonterminal operation at a time. The operation mode is fixed at
 1057         -
selection. A reclaim selected from the origin-wide order cannot become a borrow
 1058         -
because its cells may belong to different eligibility groups. A borrow could
 1059         -
become a reclaim without violating eligibility, but keeping both modes fixed
 1060         -
avoids adding a second terminal path after installation; a rejected operation
        1103  +
Admission owns `H1Match`; the supplier cell owns `H1CellState` and its
        1104  +
`H1Reservation`. Each supplier and requesting cell participates in at most one
        1105  +
nonterminal match at a time. The match kind is fixed at selection. A reclaim
        1106  +
selected from the origin-wide order cannot become a borrow because its cells
        1107  +
may belong to different eligibility groups. A borrow could become a reclaim
        1108  +
without violating eligibility, but keeping both kinds fixed avoids adding a
        1109  +
second terminal path after reservation; a rejected match
 1061   1110   
returns to admission for a fresh selection.
 1062   1111   
 1063         -
An operation installs and resolves without nesting the admission,
 1064         -
connection-owning-cell, or requesting-cell locks:
        1112  +
A match reserves and resolves without nesting the admission, supplier-cell, or
        1113  +
requesting-cell locks:
 1065   1114   
 1066   1115   
```text
 1067   1116   
OriginAdmission owns queued demand R
 1068   1117   
  |
 1069         -
  `-- select peer connection-owning cell C
 1070         -
        `-- create ReuseOperation K in Installing
 1071         -
              `-- install K under C's lock
        1118  +
  `-- H1SupplyIndex selects peer supplier cell C
        1119  +
        `-- retain H1Match K in Reserving
        1120  +
              `-- reserve K under C's lock
 1072   1121   
        |
 1073   1122   
        +-- C owes a usable local turn
 1074   1123   
        |     `-- reject K; R stays queued
 1075   1124   
        |
 1076   1125   
        +-- C has an older local H1 candidate
 1077   1126   
        |     `-- reject K; R stays queued
 1078   1127   
        |
 1079   1128   
        +-- idle H1 available
 1080         -
        |     `-- C reservation -> Resolving(K); candidate guard owns H1
        1129  +
        |     `-- C reservation -> Resolving(K); H1Candidate owns sender
 1081   1130   
        |
 1082   1131   
        +-- active or returning H1 exists
 1083   1132   
        |     `-- C reservation -> Installed(K); reserve next reusable return
 1084   1133   
        |
 1085   1134   
        `-- no H1 can return
 1086   1135   
              `-- reject K; R stays queued
 1087   1136   
 1088   1137   
Installed(K) + reusable return at C
 1089         -
  `-- C reservation -> Resolving(K); candidate guard owns H1
        1138  +
  `-- C reservation -> Resolving(K); H1Candidate owns sender
 1090   1139   
 1091   1140   
candidate reaches OriginAdmission
 1092   1141   
  |
 1093   1142   
  +-- K or R is stale, cancelled, or already satisfied
 1094         -
  |     `-- candidate guard returns H1 through C's ordinary return path
        1143  +
  |     `-- H1Candidate returns sender through C's ordinary return path
 1095   1144   
  |
 1096         -
  +-- K is Borrow
 1097         -
  |     `-- fence R as Delivering(R, D)
        1145  +
  +-- K is BorrowSender
        1146  +
  |     `-- create DemandAssignment D for R
 1098   1147   
  |           `-- commit C's candidate before reserving requesting-cell waiter
 1099   1148   
  |                 +-- accepted -> waiter owns H1 selection
 1100         -
  |                 `-- rejected -> return H1 to C, then close D
        1149  +
  |                 `-- refused -> return H1 to C, then retry D
 1101   1150   
  |
 1102         -
  `-- K is Reclaim
        1151  +
  `-- K is ReclaimCapacity
 1103   1152   
        `-- revalidate under C's lock and attempt logical close
 1104   1153   
              `-- released permit enters ordinary capacity delivery
 1105   1154   
 1106         -
terminal C report
 1107         -
  `-- remove K; refresh C's availability; schedule next action
        1155  +
terminal H1SupplyOutcome
        1156  +
  `-- remove K; apply C's latest supply revision; schedule next action
 1108   1157   
```
 1109   1158   
 1110   1159   
A provisional candidate revalidates the connection generation, logical-close
 1111   1160   
state, idle policy, and matching cell reservation before it becomes an
 1112   1161   
`H1Selection` or is reclaimed. A failed revalidation returns the request handle
 1113   1162   
through ordinary owning-cell policy. If another close wins the reclaim race,
 1114   1163   
the released permit still follows its normal exactly-once admission path.
 1115   1164   
 1116         -
An installed reservation intercepts a reusable return under the
 1117         -
connection-owning cell lock before the handle can become locally idle. Local
        1165  +
An installed reservation intercepts a reusable return under the supplier-cell
        1166  +
lock before the handle can become locally idle. Local
 1118   1167   
H1-compatible demand that arrives after installation may therefore be
 1119   1168   
overtaken once. An irreversible borrow or successful reclaim records one local
 1120   1169   
fairness turn when compatible local demand exists. The next local H1 service
 1121   1170   
consumes that turn; if compatible demand disappears first, the turn clears.
 1122   1171   
An H2-required local head cannot consume the turn and does not block a reuse
 1123         -
operation that can make progress.
        1172  +
match that can make progress.
 1124   1173   
 1125         -
Cancellation marks an installing or resolving operation stale. An installed
 1126         -
reservation crosses back to the connection-owning cell and is cleared. A
        1174  +
Cancellation marks a reserving or resolving match stale. An installed
        1175  +
reservation crosses back to the supplier cell and is cleared. A
 1127   1176   
candidate already outside the cell lock returns through ordinary owning-cell
 1128   1177   
policy. Cancellation after irreversible transfer does not revoke an earned
 1129   1178   
fairness turn.
 1130   1179   
 1131         -
Every cross-lock action owns a typed fallback. Dropping an install or
 1132         -
cancellation action clears the cell reservation and completes the admission
 1133         -
operation. Dropping a candidate returns its request handle before the
 1134         -
connection-owning cell is advertised again. Dropping a capacity delivery
        1180  +
Every cross-lock action owns a typed fallback. Dropping an
        1181  +
`H1ReservationAction` or `H1CancellationAction` clears the cell reservation and
        1182  +
settles the admission match. Dropping an `H1Candidate` returns its sender before
        1183  +
the supplier cell becomes selectable again. Dropping a `DeliveryGuard`
 1135   1184   
returns the permit to admission. Fallbacks run no connector, protocol, wake,
 1136   1185   
or listener code while a pool lock is held.
 1137   1186   
 1138   1187   
A fallback invoked from `Drop` may synchronously acquire a bounded sequence of pool locks to publish its
 1139   1188   
terminal state, but it holds at most one pool lock at a time. Each lock transition produces the next typed
 1140   1189   
action only after releasing the previous lock, and each action retains an idempotent fallback until the next
 1141   1190   
domain owns the terminal state. The chain performs no await, retry loop, connector or protocol work, or work
 1142   1191   
proportional to waiter or partition count, and schedules wakes or callbacks only after releasing its lock.
 1143   1192   
 1144   1193   
Pool coordination uses a crate-level synchronization facade so production and Loom tests compile the same
 1145   1194   
lock-bearing code. Production lock wrappers retain access to guarded state after standard-library poisoning so
 1146         -
poisoning alone cannot prevent a later `Drop` fallback from returning a permit or closing a delivery fence.
        1195  +
poisoning alone cannot prevent a later `Drop` fallback from returning a permit or settling a demand assignment.
 1147   1196   
This does not make an interrupted state transition valid: code under a pool lock still preserves its
 1148   1197   
invariants without relying on poison recovery. Test builds also assert that a thread holds at most one pool
 1149   1198   
lock, turning the no-nesting rule into an executable check across the ordinary suite.
 1150   1199   
 1151   1200   
Both stay within one origin, and neither moves a driver, so the I/O-placement guarantee from
 1152   1201   
[Connection placement follows declared topology](#connection-placement-follows-declared-topology)
 1153   1202   
holds under both. This is why `max_connections_per_host` below the partition count is valid rather than an
 1154   1203   
error: a partition with no permit of its own may dispatch through an eligible
 1155   1204   
peer HTTP/1 sender or receive capacity through reclaim. The
 1156   1205   
default `NetworkInterface` scope uses both; `Partition` and `Pool` are the same machinery with a narrower or
 1157   1206   
wider eligibility group.
 1158   1207   
 1159   1208   
#### Ordering across cells
 1160   1209   
 1161   1210   
Each cell orders its own requests. Across cells, admission keeps one
 1162   1211   
origin-wide demand FIFO and separate origin and eligibility-group views over
 1163   1212   
cells with HTTP/1 connections:
 1164   1213   
 1165   1214   
```text
 1166   1215   
OriginAdmission(O)
 1167   1216   
  demand order:
 1168   1217   
    oldest -> C2/R8(H1) -> C0/R3(H2) -> C3/R5(H2) -> C1/R9(H1)
 1169   1218   
 1170         -
  available H1 connection-owning cells:
        1219  +
  selectable H1 supplier cells:
 1171   1220   
    origin view:       C0 -> C2 -> C3
 1172   1221   
    group eth0 view:   C0 -> C3
 1173   1222   
    group eth1 view:   C2
 1174   1223   
```
 1175   1224   
 1176   1225   
The demand order contains the current head generation from every requesting
 1177         -
cell waiting for origin capacity. An availability view contains a
 1178         -
connection-owning cell at most once while it has an H1 record that may return
 1179         -
or be reclaimed, has no nonterminal reuse operation, and owes no usable local
 1180         -
turn or older local H1 candidate. Removing a cell repairs both views
 1181         -
immediately, so grant work does not drain stale availability tickets.
        1226  +
cell waiting for origin capacity. An H1 supply view contains a supplier cell at
        1227  +
most once while it has an H1 record that may return or be reclaimed, has no
        1228  +
nonterminal match, and owes no usable local turn or older local H1-compatible
        1229  +
waiter. Removing a cell repairs both views immediately, so admission does not
        1230  +
drain stale supply entries.
 1182   1231   
 1183   1232   
HTTP/1 selection begins with the oldest origin demand:
 1184   1233   
 1185   1234   
```text
 1186   1235   
oldest origin demand R from requesting cell Q
 1187   1236   
  |
 1188   1237   
  +-- R accepts H1 and an eligible peer cell C exists
 1189         -
  |     `-- install Borrow reuse(C, Q, R)
        1238  +
  |     `-- retain BorrowSender match(C, Q, R)
 1190   1239   
  |
 1191   1240   
  +-- another peer H1 cell C exists
 1192         -
  |     `-- install Reclaim reuse(C, Q, R)
        1241  +
  |     `-- retain ReclaimCapacity match(C, Q, R)
 1193   1242   
  |
 1194   1243   
  `-- no peer connection
 1195         -
        `-- wait for capacity, local service, or a later availability report
        1244  +
        `-- wait for capacity, local service, or a later supply revision
 1196   1245   
```
 1197   1246   
 1198         -
The connection selector skips the requesting cell. Same-cell idle selection
 1199         -
and return are resolved under that cell's lock and do not create a cross-cell
 1200         -
operation. Borrow takes the oldest eligible peer; reclaim takes the oldest
        1247  +
The supply selector skips the requesting cell. Same-cell idle selection and
        1248  +
return are resolved under that cell's lock and do not create a cross-cell
        1249  +
match. Borrow takes the oldest eligible peer; reclaim takes the oldest
 1201   1250   
origin-wide peer.
 1202   1251   
 1203   1252   
The origin demand head is no younger than any eligibility-group demand head.
 1204   1253   
Selecting that origin head first therefore preserves eligible H1 ordering
 1205   1254   
without merging two demand heads. Eligibility changes only whether the
 1206   1255   
selected peer is borrowed or reclaimed.
 1207   1256   
 1208   1257   
The oldest origin demand therefore receives first use of every peer H1. If it
 1209   1258   
can borrow the selected connection, the warm connection remains open.
 1210   1259   
Otherwise reclaim closes it and returns capacity for the same oldest demand.
 1211   1260   
A younger demand in the connection's eligibility group cannot bypass the
 1212   1261   
older origin demand. One owning-cell fairness turn may follow an irreversible
 1213   1262   
transfer, bounding local overtaking without allowing peer traffic to consume
 1214   1263   
every return.
 1215   1264   
 1216         -
HTTP/2 peer publication uses eligibility-group demand because publication is
        1265  +
HTTP/2 peer routing uses eligibility-group demand because a route is
 1217   1266   
non-destructive and cannot satisfy an ineligible target. Those all-protocol
 1218   1267   
group views do not change HTTP/1's origin-head rule. Every cell choice remains
 1219   1268   
a stored-head operation, so the work to
 1220   1269   
grant one resource is independent of the number of cells and partitions.
 1221   1270   
 1222   1271   
#### Delivery
 1223   1272   
 1224         -
A released permit or provisional H1 can serve only one waiter. It must cross from admission to a cell without
 1225         -
being lost, copied, or left attached to a cancelled demand generation. A published H2 generation is different:
 1226         -
the connection record retains its permit and many compatible requests may take request leases from it. The
 1227         -
logical states keep these two cases separate:
        1273  +
A released permit or provisional H1 can serve only one waiter. It must cross
        1274  +
from admission to a cell without being lost, copied, or left attached to a
        1275  +
cancelled demand generation. `DemandAssignment` names the exact requester and
        1276  +
demand while that resource crosses lock domains:
 1228   1277   
 1229   1278   
```rust
 1230         -
enum DemandResidence {
 1231         -
    Idle,
        1279  +
struct DemandAssignment {
        1280  +
    id: DemandAssignmentId,
        1281  +
    requester: PartitionId,
        1282  +
    demand: DemandId,
        1283  +
}
        1284  +
        1285  +
enum DemandScheduleState {
        1286  +
    Unscheduled,
 1232   1287   
    Queued {
 1233   1288   
        demand: DemandId,
 1234         -
        links: OrderLinks,
        1289  +
        links: DemandLinks,
 1235   1290   
    },
 1236         -
    Delivering {
 1237         -
        demand: DemandId,
 1238         -
        delivery: DeliveryId,
 1239         -
        links: OrderLinks,
        1291  +
    PendingAssignment {
        1292  +
        assignment: DemandAssignment,
        1293  +
        version: SnapshotVersion,
        1294  +
        selected_order: DemandOrder,
        1295  +
        links: DemandLinks,
 1240   1296   
    },
 1241   1297   
}
 1242   1298   
 1243         -
enum AcquisitionPayload {
 1244         -
    Capacity(Permit),
        1299  +
enum DeliveryPayload {
        1300  +
    Capacity(CapacityPermit),
 1245   1301   
    BorrowedH1 {
 1246         -
        reuse_id: ReuseId,
 1247         -
        connection_partition: PartitionId,
 1248         -
        candidate: ReuseCandidate,
        1302  +
        match_id: H1MatchId,
        1303  +
        supplier: PartitionId,
        1304  +
        candidate: H1Candidate,
 1249   1305   
    },
 1250   1306   
}
 1251   1307   
 1252         -
enum MaterializedPayload {
 1253         -
    Capacity(EstablishmentPermit),
 1254         -
    BorrowedH1 {
 1255         -
        reuse_id: ReuseId,
 1256         -
        connection_partition: PartitionId,
 1257         -
        selection: H1Selection,
        1308  +
enum DeliveryState {
        1309  +
    Pending(DeliveryPayload),
        1310  +
    Ready {
        1311  +
        step: AcquisitionStep,
        1312  +
        settlement: DeliverySettlementKind,
 1258   1313   
    },
        1314  +
    Disarmed,
 1259   1315   
}
 1260   1316   
 1261         -
enum DeliveryKind {
 1262         -
    Capacity,
 1263         -
    BorrowedH1 {
 1264         -
        reuse_id: ReuseId,
 1265         -
        connection_partition: PartitionId,
 1266         -
    },
        1317  +
struct DeliveryGuard {
        1318  +
    admission: Arc<OriginAdmission>,
        1319  +
    assignment: DemandAssignment,
        1320  +
    state: DeliveryState,
 1267   1321   
}
 1268   1322   
 1269         -
enum DeliveryAckResult {
 1270         -
    Accepted { successor: Option<DemandSnapshot> },
 1271         -
    RetrySameResidence,
 1272         -
    Rejected { successor: Option<DemandSnapshot> },
        1323  +
struct DeliverySettlement {
        1324  +
    admission: Arc<OriginAdmission>,
        1325  +
    assignment: DemandAssignment,
        1326  +
    successor: Option<DemandSnapshot>,
        1327  +
    kind: Option<DeliverySettlementKind>,
 1273   1328   
}
 1274   1329   
 1275         -
enum DeliveryGuardState {
 1276         -
    Undelivered {
 1277         -
        payload: AcquisitionPayload,
 1278         -
        on_drop: DeliveryAckResult,
 1279         -
    },
 1280         -
    Materialized {
 1281         -
        payload: MaterializedPayload,
 1282         -
        on_drop: DeliveryAckResult,
        1330  +
enum DeliverySettlementKind {
        1331  +
    Capacity,
        1332  +
    BorrowedH1 {
        1333  +
        connection_id: ConnectionId,
        1334  +
        match_id: H1MatchId,
        1335  +
        supplier: PartitionId,
 1283   1336   
    },
 1284         -
    Disarmed,
 1285   1337   
}
 1286   1338   
 1287         -
struct DeliveryAck {
 1288         -
    delivery: DeliveryId,
 1289         -
    requesting_partition: PartitionId,
 1290         -
    successor: Option<DemandSnapshot>,
 1291         -
    kind: DeliveryKind,
        1339  +
enum DemandAssignmentOutcome {
        1340  +
    Accepted { successor: Option<DemandSnapshot> },
        1341  +
    RetrySamePosition,
        1342  +
    Refused { successor: Option<DemandSnapshot> },
 1292   1343   
}
 1293   1344   
```
 1294   1345   
 1295         -
`Queued` and `Delivering` retain the same links in origin order.
 1296         -
`Delivering` fences that position until the requesting cell acknowledges the
 1297         -
delivery, so a younger demand cannot pass a payload between lock domains. A
 1298         -
reuse operation does not add another demand residence: demand remains `Queued`
 1299         -
while reservation installation resolves and becomes `Delivering` only when a
 1300         -
borrowed H1 or permit is ready to cross.
        1346  +
`Queued` and `PendingAssignment` retain the same origin and eligibility-group
        1347  +
links. A pending assignment excludes another resource from selecting that
        1348  +
demand without moving it to the back of either order. An H1 match does not
        1349  +
assign demand while supplier reservation crosses locks. Only a resolved borrow,
        1350  +
or an available permit, creates an assignment.
 1301   1351   
 1302   1352   
One `DeliveryGuard` carries either capacity or a borrowed H1. It materializes
 1303   1353   
every fallible connection-owning-cell transition before reserving the
 1304   1354   
requesting waiter. Capacity becomes an `EstablishmentPermit`; a borrowed
 1305   1355   
candidate revalidates its owning-cell reservation and becomes an
 1306   1356   
`H1Selection`. If candidate commit fails, the guard returns the handle and
 1307         -
closes or retries the admission fence without changing requesting-cell state.
        1357  +
retries the assignment without changing requesting-cell state.
 1308   1358   
 1309   1359   
An owned one-to-one delivery follows this sequence:
 1310   1360   
 1311   1361   
```text
 1312   1362   
OriginAdmission lock
 1313   1363   
  Queued(R)
 1314         -
    -> Delivering(R, D)
 1315         -
    -> extract DeliveryGuard::Undelivered(payload, retry R)
        1364  +
    -> PendingAssignment(D)
        1365  +
    -> extract DeliveryGuard::Pending(payload)
 1316   1366   
unlock OriginAdmission
 1317   1367   
  |
 1318         -
  +-- materialize payload
 1319         -
  |     +-- failure -> refunnel payload; finish D; requesting cell unchanged
 1320         -
  |     `-- success -> DeliveryGuard::Materialized
        1368  +
  +-- resolve payload
        1369  +
  |     +-- failure -> refunnel payload; retry D; requesting cell unchanged
        1370  +
  |     `-- success -> DeliveryGuard::Ready
 1321   1371   
  |
 1322   1372   
  `-- lock requesting cell
 1323   1373   
        +-- R and its oldest compatible waiter are live -> reserve waiter
 1324   1374   
        `-- stale, cancelled, satisfied, or incompatible -> reject guard
 1325   1375   
      unlock requesting cell
 1326         -
        `-- convert payload into acquisition event + DeliveryAck
        1376  +
        `-- convert payload into AcquisitionStep + DeliverySettlement
 1327   1377   
              `-- lock requesting cell
 1328         -
                    +-- accepted -> waiter owns event; acknowledge D
 1329         -
                    `-- cancelled -> return event; refunnel and reject D
        1378  +
                    +-- accepted -> waiter owns step; settle D as accepted
        1379  +
                    `-- cancelled -> return step; settle D as refused
 1330   1380   
```
 1331   1381   
 1332   1382   
The admission, connection-owning-cell, and requesting-cell locks are never
 1333   1383   
nested. Between them, the delivery guard is the only payload owner. After
 1334         -
requesting-cell installation, `DeliveryAck` owns the admission fence and the
 1335         -
requesting waiter owns the establishment permit or H1 selection.
 1336         -
 1337         -
The guard makes every drop point terminal. Dropping `Undelivered` returns its raw payload before updating the
 1338         -
fence. Dropping `Materialized` drops the establishment permit or returns the selected H1 to its
 1339         -
connection-owning cell, then updates the fence. Dropping `DeliveryAck` completes its stored acknowledgement
 1340         -
after requesting-cell state has become authoritative. Normal execution performs the same transitions
 1341         -
explicitly and disarms each fallback.
 1342         -
 1343         -
`Accepted` consumes the generation and either idles the ticket or installs its successor at the applicable
 1344         -
tails.
 1345         -
`RetrySameResidence` is used only when the same generation remains useful but this payload or publication cannot
 1346         -
serve it; it preserves the ticket's position. `Rejected` closes the old residence after the requesting cell has
 1347         -
refunnelled any owned payload and carries the complete current successor, if one. A complete newer demand snapshot
 1348         -
may retire or replace a residence before its action reaches the requesting cell; local generation validation
 1349         -
then rejects the late action without resurrecting old demand.
 1350         -
 1351         -
#### HTTP/2 publication
 1352         -
 1353         -
H2 publication carries no `AcquisitionPayload`. The connection-owning record continues to own the capacity
 1354         -
lease, while a `(connection-owning cell, generation identity)` notice says that compatible requests may take
 1355         -
H2 request leases. Publishing a new local generation first installs the record and accepting generation under
        1384  +
requesting-cell installation, `DeliverySettlement` owns assignment settlement
        1385  +
and any supplier-cell completion still owed; the requesting waiter owns the
        1386  +
establishment permit or H1 selection.
        1387  +
        1388  +
The guard makes every drop point terminal. Dropping `Pending` returns its raw
        1389  +
payload before retrying the assignment. Dropping `Ready` drops the
        1390  +
establishment permit or returns the selected H1 to its owning cell, then retries
        1391  +
the assignment. Once the requesting cell owns the step, dropping
        1392  +
`DeliverySettlement` records acceptance because cell state is authoritative.
        1393  +
Explicit refusal returns the step before settling the assignment as refused.
        1394  +
        1395  +
`Accepted` consumes the generation and either unschedules it or installs its
        1396  +
successor at the applicable tails. `RetrySamePosition` preserves the same
        1397  +
generation and both order positions. `Refused` ends the old generation after
        1398  +
the requesting cell has refunnelled any owned payload and carries the complete
        1399  +
current successor, if one. A complete newer demand snapshot may retire or
        1400  +
replace an assignment before its action reaches the requesting cell; local
        1401  +
generation validation then rejects the late action without resurrecting old
        1402  +
demand.
        1403  +
        1404  +
#### HTTP/2 peer routing
        1405  +
        1406  +
An H2 route carries no sender or capacity payload. The connection-owning generation continues to own the capacity
        1407  +
lease. The route names a connection-owning cell and generation from which compatible requests may take
        1408  +
H2 request claims. Installing a new local generation first installs the accepting generation under
 1356   1409   
the connection-owning cell lock, then makes that identity visible to compatible local waiters. They are woken
 1357         -
and admitted in bounded local turns; publication does not scan or synchronously wake an unbounded queue.
        1410  +
and admitted in bounded local turns; route installation does not scan or synchronously wake an unbounded queue.
 1358   1411   
 1359         -
Publication also installs a local fairness gate:
        1412  +
Generation visibility also installs a local fairness gate:
 1360   1413   
 1361   1414   
```rust
 1362         -
enum GenerationGate {
        1415  +
enum H2ActivationGate {
 1363   1416   
    Closed,
 1364   1417   
    Prioritizing {
 1365   1418   
        generation: H2GenerationId,
 1366   1419   
        cutoff: WaiterId,
 1367         -
        activating: Option<WaiterId>,
        1420  +
        active_turn: Option<WaiterId>,
 1368   1421   
    },
 1369   1422   
    Open { generation: H2GenerationId },
 1370   1423   
}
 1371   1424   
```
 1372   1425   
 1373   1426   
`Closed` names no usable generation. The cutoff is the newest compatible
 1374   1427   
waiter committed when a generation becomes visible. While the gate is
 1375   1428   
`Prioritizing`, those waiters receive activation oldest first, and
 1376         -
`activating` retains the one priority turn crossing to Hyper acceptance or
        1429  +
`active_turn` retains the one priority turn crossing to Hyper acceptance or
 1377   1430   
cancellation. When no waiter at or before the cutoff remains, the gate becomes
 1378   1431   
`Open`. Open generations issue concurrent prospective activations; Hyper owns
 1379   1432   
stream credit and flow control. Generation invalidation closes the gate and
 1380   1433   
returns unserved transferred waiters to acquisition. Each successful
 1381         -
activation creates its own H2 request lease.
        1434  +
activation creates its own H2 request claim.
 1382   1435   
 1383   1436   
A requesting cell retains peer-route crossing state separately:
 1384   1437   
 1385   1438   
```rust
 1386   1439   
struct PeerH2Route {
 1387   1440   
    route: H2Route,
 1388         -
    gate: GenerationGate,
 1389         -
    crossing: Option<WaiterId>,
        1441  +
    activation_gate: H2ActivationGate,
        1442  +
    crossing_waiter: Option<WaiterId>,
 1390   1443   
}
 1391   1444   
```
 1392   1445   
 1393   1446   
Queued peer-route activations cross the connection-cell lock one at a time so a failed exact-generation
 1394   1447   
reservation can restore the requesting cell's oldest turn. Once the route gate is open, direct arrivals
 1395         -
reserve independent prospective leases concurrently; `crossing` does not serialize them.
 1396         -
 1397         -
The peer-cell index holds one group-scoped advertisement for each accepting H2 generation. Record and
 1398         -
generation installation precede advertisement; transition out of accepting removes it. Either a new
 1399         -
advertisement or new group demand schedules a bounded publication turn from stored connection and demand heads.
 1400         -
The advertisement carries only connection-owning cell and generation identity, not a dispatch handle or
 1401         -
capacity lease. The publication guard revalidates it at the connection-owning and requesting cells. A stale
 1402         -
advertisement is removed or updated before the next turn, so peer H2 discovery does not scan cells.
 1403         -
 1404         -
Under bounded pressure, an accepting generation may also be announced to the
 1405         -
head of its eligibility-group all-protocol view. Admission fences that demand
 1406         -
generation in `Delivering` with a `DeliveryId`; the publication action carries
 1407         -
only connection-owning cell and generation identities plus an acknowledgement
        1448  +
reserve independent prospective claims concurrently; `crossing_waiter` does not serialize them.
        1449  +
        1450  +
`H2SupplyIndex` holds one group-scoped record for each cell whose current supply status names an accepting
        1451  +
generation. Generation installation precedes the supply revision that makes it visible; transition out of
        1452  +
accepting removes it from route selection. Either a changed supply revision or new group demand schedules a
        1453  +
bounded route turn from stored supplier and demand heads. The supply record carries only connection-owning
        1454  +
cell and generation identity, not a dispatch handle or capacity lease. The route guard revalidates it at the
        1455  +
connection-owning and requesting cells. A stale record is removed or updated before the next turn, so peer H2
        1456  +
discovery does not scan cells.
        1457  +
        1458  +
Under bounded pressure, an accepting generation may also be offered to the
        1459  +
head of its eligibility-group all-protocol view. Admission creates a
        1460  +
`DemandAssignment` for that exact generation; the route action carries only the
        1461  +
assignment, connection-owning cell, and generation identities plus a settlement
 1408   1462   
fallback, never the record's capacity lease.
 1409   1463   
The requesting cell revalidates the generation identity, accepting state, demand
 1410   1464   
generation, and reuse scope.
 1411   1465   
Acceptance makes the generation visible to compatible waiters there; rejection discards the stale notice
 1412   1466   
while the generation and
 1413   1467   
permit remain at the connection-owning cell. Acceptance acknowledges after requesting-cell visibility and the
 1414   1468   
named head's activation opportunity are committed, not after every local waiter has activated. Remaining local
 1415   1469   
waiters proceed through the generation gate in bounded turns and no longer advertise a connection need while
 1416         -
that generation remains usable. Later group tickets are handled by subsequent bounded publication turns, so
        1470  +
that generation remains usable. Later group tickets are handled by subsequent bounded route turns, so
 1417   1471   
one-to-many visibility does not turn one host action into work proportional to partition count.
 1418   1472   
 1419         -
An H1-required head cannot use an advertised HTTP/2 generation. If bounded origin capacity is exhausted,
 1420         -
admission may instead reserve the oldest advertised idle generation for reclaim. The connection cell
 1421         -
revalidates the exact generation, accepting residence, and zero prospective or accepted request counts before
        1473  +
An H1-required head cannot use an HTTP/2 supply record. If bounded origin capacity is exhausted,
        1474  +
admission may instead reserve the oldest indexed idle generation for reclaim. The connection cell
        1475  +
revalidates the exact generation, accepting residence, and zero prospective or active request counts before
 1422   1476   
closing it. Logical close returns its capacity to origin admission, which can then grant establishment to the
 1423         -
H1-required head. A generation with request work is not reclaimed. Its transition to idle publishes a new
 1424         -
advertisement and makes reclaim eligible.
        1477  +
H1-required head. A generation with request work is not reclaimed. Its transition to idle submits a new
        1478  +
supply revision and makes reclaim eligible.
 1425   1479   
 1426   1480   
Admission attempts this reclaim only when the transport factory guarantees that
 1427   1481   
an H1-required establishment uses HTTP/1. Without that guarantee, admission
 1428   1482   
could close a healthy H2 connection, negotiate H2 again, reject the result, and
 1429   1483   
repeat without satisfying the waiting request. The default rustls and cleartext
 1430   1484   
connectors provide the guarantee. The fixed-ALPN s2n connector and connectors
 1431   1485   
injected through the unstable test utility do not, so H1-required demand
 1432   1486   
remains queued behind their healthy H2 capacity until an ordinary close
 1433   1487   
releases a permit.
 1434   1488   
 1435         -
For bounded origins, a generation publishes only its zero-to-one and one-to-zero request-work transitions.
 1436         -
Intermediate multiplexed request counts remain cell-local. Unbounded origins have no admission publication.
        1489  +
For bounded origins, a cell submits H2 supply only when generation identity, idle state, or availability
        1490  +
changes. Intermediate multiplexed request counts remain cell-local. Unbounded origins have no admission route
        1491  +
selection.
 1437   1492   
 1438         -
Dropping a pending publication guard submits its `on_drop` acknowledgement so the fence retries or closes;
 1439         -
there is no single-owner payload to refunnel. Committing publication stores the requesting-cell
        1493  +
Dropping a pending route guard submits its fallback so the assignment retries or settles;
        1494  +
there is no single-owner payload to refunnel. Committing route installation stores the requesting-cell
 1440   1495   
acknowledgement, which is submitted before the guard disarms.
 1441   1496   
 1442         -
This separates publication from single delivery: a permit or H1 has one owner and one requesting cell, while
        1497  +
This separates route installation from single delivery: a permit or H1 has one owner and one requesting cell, while
 1443   1498   
an H2 generation remains owned by its connection cell and may be announced repeatedly. The transitions must
 1444   1499   
be model checked; their invariants are stated in [Correctness invariants](#correctness-invariants), with the
 1445   1500   
checks specified in [Appendix B](#appendix-b-validation).
 1446   1501   
 1447   1502   
#### Obligations
 1448   1503   
 1449   1504   
* **Bounded demand** [safety] — a cell carries at most one active demand generation regardless of how many
 1450   1505   
  requests wait, so demand accumulates no deficit and one residence receives at most one terminal outcome.
 1451   1506   
* **Snapshot ordering** [safety] — admission retains the newest complete demand version and rejects an action
 1452   1507   
  for a retired generation, so out-of-order publication cannot resurrect cancelled or satisfied demand.
 1453   1508   
* **Eligibility and capacity independence** [safety] — the capacity decision and the eligibility decision do
 1454   1509   
  not read each other's state.
 1455   1510   
* **Placement under transfer** [safety] — neither borrow nor reclaim moves a connection's driver or I/O off
 1456   1511   
  its owning partition.
 1457   1512   
* **Reclaim scope independence** [safety] — reclaim moves a permit without dispatching across a partition
 1458   1513   
  boundary, so it is not constrained by the reuse scope.
 1459   1514   
* **Protocol-compatible reclaim** [safety] — admission reclaims idle H2 capacity for H1-required demand only
 1460   1515   
  when the transport factory guarantees an HTTP/1 establishment result.
 1461         -
* **H2 work publication** [safety, optimization] — bounded cells publish when a generation crosses between
 1462         -
  zero and nonzero request work; intermediate stream counts require no admission update.
 1463         -
* **Single delivery** [safety] — one delivery identity owns at most one permit or provisional H1, commits it
 1464         -
  to at most one requesting waiter, and retains its scheduling fence until requesting-cell acknowledgement.
        1516  +
* **H2 supply revision** [safety, optimization] — bounded cells submit a new supply revision only when
        1517  +
  generation identity, idle state, or availability changes; intermediate stream counts require no admission
        1518  +
  update.
        1519  +
* **Single delivery** [safety] — one demand assignment carries at most one permit or provisional H1, commits it
        1520  +
  to at most one requesting waiter, and remains pending until requesting-cell settlement.
 1465   1521   
* **Refunnelling** [safety] — rejection, supersession, cancellation, task drop, or panic returns every
 1466   1522   
  undelivered permit to admission and every undelivered H1 to its connection-owning cell exactly once.
 1467         -
* **Publication ownership** [safety] — H2 publication carries generation identity, never the connection's
 1468         -
  capacity lease; request activation takes a request lease while the connection record remains the capacity owner.
 1469         -
* **Publication priority** [liveness] — publication closes the generation gate before visibility and offers
 1470         -
  activation to waiters committed at publication before newer arrivals, in bounded oldest-first turns.
        1523  +
* **Route ownership** [safety] — an H2 route carries generation identity, never the connection's
        1524  +
  capacity lease; request activation takes a request claim while the connection generation remains the capacity owner.
        1525  +
* **Generation visibility priority** [liveness] — making a local generation or peer route visible closes the
        1526  +
  generation gate and serves previously committed waiters in bounded oldest-first turns before later direct
        1527  +
  arrivals.
 1471   1528   
* **Cross-cell order** [safety] — H1 borrow and reclaim both serve the current origin head, preferring an
 1472         -
  eligible peer connection for borrow and otherwise reclaiming an origin peer. Peer H2 publication uses its
        1529  +
  eligible peer connection for borrow and otherwise reclaiming an origin peer. Peer H2 routing uses its
 1473   1530   
  all-protocol eligibility-group head. Same-cell H1 service remains cell-local.
 1474         -
* **Reuse operation completion** [safety] — one reuse operation reserves at most one connection-owning cell and
 1475         -
  one requesting cell; it remains authoritative until owning-cell completion and any borrowed delivery record
 1476         -
  acknowledges its terminal state.
 1477         -
* **Return interception** [liveness] — an installed reuse reservation intercepts the next reusable H1 before it
        1531  +
* **H1 match completion** [safety] — one H1 match reserves at most one supplier cell and one requesting cell; it
        1532  +
  remains authoritative until supplier-cell completion and any borrowed demand assignment settles its terminal
        1533  +
  state.
        1534  +
* **Return interception** [liveness] — an installed H1 reservation intercepts the next reusable H1 before it
 1478   1535   
  becomes idle, so a connection cycling continuously between active and reusable cannot strand requesting
 1479   1536   
  demand.
 1480   1537   
* **Owning-cell fairness turn** [liveness] — one irreversible cross-cell transfer creates one local turn when
 1481   1538   
  compatible local demand exists; the turn clears only when that demand is served or disappears.
 1482         -
* **Acknowledged progress** [liveness] — every extracted delivery or reuse action either acknowledges a
 1483         -
  terminal transition or executes its typed fallback, so a scheduling fence cannot remain pending solely
 1484         -
  because the executing future was dropped.
        1539  +
* **Acknowledged progress** [liveness] — every extracted delivery or detached action either settles a terminal
        1540  +
  transition or executes its typed fallback, so a demand assignment cannot remain pending solely because the
        1541  +
  executing future was dropped.
 1485   1542   
* **Cross-lock isolation** [safety] — admission and cell locks are never nested, and no pool lock is held
 1486   1543   
  across an await or while running connector, protocol, wake, or listener code. A synchronous fallback may
 1487   1544   
  visit a bounded sequence of lock domains but holds at most one pool lock at a time; each transition retains
 1488   1545   
  an idempotent fallback, and wakes and callbacks remain deferred until after unlock.
 1489         -
* **Bounded peer discovery** [optimization] — reuse and publication work select connection state from stored
 1490         -
  origin or group heads and validate one cell rather than scanning cells or connections. H1 availability is
 1491         -
  linked once and repaired eagerly; a cell publishes again only when its complete advertised or blocked state
 1492         -
  changes, and admission ignores reports older than its accepted availability revision.
        1546  +
* **Bounded peer discovery** [optimization] — reuse and route work select connection state from stored
        1547  +
  origin or group heads and validate one cell rather than scanning cells or connections. H1 supply is linked
        1548  +
  once and repaired eagerly; a cell submits again only when its complete returnable or peer-blocked status
        1549  +
  changes, and admission ignores older supply revisions.
 1493   1550   
 1494   1551   
### Liveness
 1495   1552   
 1496   1553   
A cell's queue orders its requests, and same-cell H1 service is resolved under
 1497   1554   
that cell's lock. Across cells, origin admission orders capacity demand. H1
 1498   1555   
reuse serves the current origin head: it borrows the oldest eligible peer
 1499   1556   
connection when available and otherwise reclaims the oldest origin peer.
 1500   1557   
 1501         -
HTTP/2 publication adds eligibility-group demand views because a generation can be announced only where it is
 1502         -
reusable and publication does not consume origin capacity. A terminal outcome sends any successor generation to
        1558  +
HTTP/2 peer routing adds eligibility-group demand views because a route can be installed only where the
        1559  +
generation is reusable, and routing does not consume origin capacity. A terminal outcome sends any successor
        1560  +
generation to
 1503   1561   
the applicable tails. An owning-cell fairness turn permits one local overtake after an irreversible
 1504         -
cross-cell transfer, but repeated reuse operations cannot keep that cell or an older peer from progressing.
        1562  +
cross-cell transfer, but repeated H1 matches cannot keep that cell or an older peer from progressing.
 1505   1563   
 1506         -
Within a cell, the generation gate offers a newly published H2 generation to already committed compatible
 1507         -
waiters before newer arrivals. Scheduling is work-conserving among eligible waiters: if a resource a waiter
 1508         -
could use is free, some eligible waiter is served rather than the resource sitting idle. Each choice is a
 1509         -
dequeue or stored-head comparison, so the work to grant one resource does not grow with the number of waiters
 1510         -
or partitions.
        1564  +
Within a cell, the generation gate offers a newly visible H2 generation to already committed compatible
        1565  +
waiters in bounded oldest-first turns before later direct arrivals. Scheduling is work-conserving among
        1566  +
eligible waiters: if a resource a waiter could use is free, some eligible waiter is served rather than the
        1567  +
resource sitting idle. Each choice is a dequeue or stored-head comparison, so the work to grant one resource
        1568  +
does not grow with the number of waiters or partitions.
 1511   1569   
 1512   1570   
Progress requires that a permit become reachable: an eligible connection returns reusable, an
 1513   1571   
HTTP/1 connection becomes reclaimable, or a permit is released. It is not promised while every permit for the
 1514   1572   
origin is held indefinitely by active HTTP/2 work that the waiter is not eligible to use. The pool does not
 1515   1573   
forcibly drain a live HTTP/2 connection to free such a permit; doing so would abort in-flight requests to
 1516   1574   
serve a waiter. A waiter in this state parks until eligibility or capacity changes.
 1517   1575   
 1518   1576   
#### Obligations
 1519   1577   
 1520   1578   
* **Bounded overtaking** [liveness] — a committed cell is not passed indefinitely by later arrivals; permits
 1521   1579   
  and H1 reuse use the origin-wide order, with eligible borrow preferred over reclaim for that head; peer H2
 1522         -
  publication uses the all-protocol group view; one owning-cell fairness turn may create only the documented
        1580  +
  routing uses the all-protocol group view; one owning-cell fairness turn may create only the documented
 1523   1581   
  bounded overtake.
 1524   1582   
* **Work-conserving service** [liveness] — while an eligible waiter and a resource it may use both exist,
 1525   1583   
  some eligible waiter is served.
 1526   1584   
* **Bounded grant work** [optimization] — the work to grant one resource does not grow with the waiter or
 1527   1585   
  partition count.
 1528   1586   
 1529   1587   
### Dispatching and completing a request
 1530   1588   
 1531   1589   
Acquisition ends with one request and one selected dispatch authority. Dispatch turns those into either a
 1532   1590   
terminal error or a response whose body, or upgrade path, owns the request's remaining protocol lifecycle.
@@ -1564,1622 +1975,2042 @@
 1584   1642   
             +-- Hyper returns original request -> UnsentReturned
 1585   1643   
             +-- Hyper accepts request ---------> WaitingForHeaders
 1586   1644   
                                                    +-- error -> TerminalError
 1587   1645   
                                                    `-- head  -> BodyGuardTransferred
 1588   1646   
```
 1589   1647   
 1590   1648   
`try_send_request` is still allowed to reject the envelope. That result is different from a pool-side stale
 1591   1649   
selection: only Hyper can certify that the original request remains unsent, and only a reused connection turns
 1592   1650   
that certification into transparent retry. A fresh connection returning the request is a terminal error.
 1593   1651   
 1594         -
For HTTP/2, activation of a request lease includes the generation and stream-capacity checks needed before
        1652  +
For HTTP/2, activation of a request claim includes the generation checks needed before
 1595   1653   
calling its sender. The same general boundary holds: pool state commits one dispatch before Hyper accepts the
 1596   1654   
envelope, and Hyper's returned-message behavior remains the only replay authority.
 1597   1655   
 1598   1656   
Once `try_send_request` accepts the request envelope, Hyper owns the request and its body. That point
 1599   1657   
discharges any H2 generation-gate opportunity; selecting or cloning a sender is not enough. The request future
 1600         -
continues to own the Hyper response future, the H1 checked-out guard or H2 receive endpoint, and a strong pool
 1601         -
reference while it waits for response headers; an accepted H2 request-body adapter owns the matching send
 1602         -
endpoint.
        1658  +
continues to own the Hyper response future, the H1 exchange or H2 response guard, and a strong pool
        1659  +
reference while it waits for response headers; an accepted H2 request-body adapter owns the matching upload
        1660  +
guard.
 1603   1661   
 1604         -
An accepted H2 stream has two terminal endpoints. A request-body adapter owns the send endpoint while Hyper
 1605         -
may still poll an upload; the request future owns the receive endpoint until it transfers that endpoint to the
 1606         -
response body or upgrade bridge. The request lease returns to the generation only after both endpoints are
 1607         -
terminal. A response arriving before a streaming upload finishes therefore cannot make the stream idle. Before
 1608         -
acceptance, the request-body endpoint is inert. If Hyper returns the request unsent, the pool disarms that
 1609         -
endpoint and can rearm the same adapter for a later selection without retaining the rejected generation.
        1662  +
An accepted H2 request has two independently owned sides. A request-body adapter owns the upload guard while
        1663  +
Hyper may still poll an upload; the request future owns the response guard until it transfers that guard to the
        1664  +
response body or upgrade bridge. The request claim returns to the generation only after both sides finish. A
        1665  +
response arriving before a streaming upload finishes therefore cannot make the request idle. Before acceptance,
        1666  +
the request-body guard is inert. If Hyper returns the request unsent, the pool disarms that guard and can rearm
        1667  +
the same adapter for a later selection without retaining the rejected generation.
 1610   1668   
 1611   1669   
#### Retry, timeout, and errors
 1612   1670   
 1613   1671   
There is one authority for transparent dispatch retry: Hyper must return the original, unsent request from
 1614   1672   
`try_send_request`, and the selected connection must have been reused. The pool restores the absolute URI,
 1615         -
disarms any unaccepted H2 body endpoint, retires or invalidates the stale selected connection, and sends that
        1673  +
disarms any unaccepted H2 upload guard, retires or invalidates the stale selected connection, and sends that
 1616   1674   
same request through acquisition again. An unsent failure from a fresh connection is terminal and still
 1617         -
retires a sender Hyper reported unable to accept the request; its inert body endpoint and selected guard cannot
        1675  +
retires a sender Hyper reported unable to accept the request; its inert upload guard and selected authority cannot
 1618   1676   
survive the error. The pool does not clone a request or replay one Hyper accepted.
 1619   1677   
 1620         -
`AcquisitionResult::Reacquire` is the internal transition for a waiter whose selected generation became stale
 1621         -
before Hyper observed the request. It carries no request copy and is not an SDK retry attempt: the original
 1622         -
request remains owned by the pool future and re-enters acquisition. A request returned unsent by Hyper uses the
        1678  +
`AcquisitionOutcome::RetryAcquisition` is the internal transition for an unserved waiter whose flight or generation
        1679  +
closes before it receives dispatch authority. It carries no request copy and is not an SDK retry attempt: the
        1680  +
original request remains owned by the pool future and re-enters acquisition. A request returned unsent by Hyper uses the
 1623   1681   
same loop only under the reused-connection rule above. Negotiated-protocol mismatch and a fresh Hyper error
 1624   1682   
without the original envelope are terminal.
 1625   1683   
 1626   1684   
After dispatch receives an H2 activation, the request may select at most two replacement H2 generations.
 1627         -
A third pre-acceptance rejection returns its retained connection error. This bound is independent of caller
 1628         -
timeout and cancellation and prevents repeated GOAWAY or close races from creating an unbounded establishment
 1629         -
loop.
        1685  +
A third pre-acceptance rejection returns its retained connection error. This bound applies only after an H2
        1686  +
target reaches dispatch. It is independent of caller timeout and cancellation and prevents stale selected
        1687  +
targets from retaining one request in the dispatch loop indefinitely.
 1630   1688   
 1631   1689   
With Hyper 1.11, the returned-request path after `try_send_request` is reachable for H1; H2 dispatch errors
 1632   1690   
after call do not carry the original request and are terminal. An H2 readiness failure observed before call is
 1633   1691   
different: the request is still locally owned and may return to acquisition without being replayed.
 1634   1692   
 1635   1693   
Every error after Hyper accepts the envelope is terminal for this dispatch. H1 conservatively retires because
 1636         -
the request may have reached the wire. H2 releases or resets the affected stream and drives its send and
 1637         -
receive endpoints to terminal state; a stream-local reset does not by itself invalidate the accepting
        1694  +
the request may have reached the wire. H2 releases or resets the affected stream and finishes its upload and
        1695  +
response guards; a stream-local reset does not by itself invalidate the accepting
 1638   1696   
generation. GOAWAY, a closed dispatcher, a connection error, or other connection-fatal evidence does
 1639   1697   
invalidate that generation. The resulting `ConnectorError` preserves the current source chain and
 1640   1698   
classification, including timeout, user, I/O, incomplete-message transient,
 1641   1699   
GOAWAY, and `REFUSED_STREAM` behavior.
 1642   1700   
 1643   1701   
The read timeout keeps its current scope: `PoolConnector::call` starts it around the client request operation,
 1644   1702   
including acquisition, establishment when needed, dispatch, and the wait for response headers, and ends it
 1645   1703   
when headers arrive. It does not time body reads or an upgraded stream. A connect timeout covers only the
 1646   1704   
connector operation for one establishment attempt; a participant waiting on another request's HTTP/2 flight
 1647   1705   
has no connector operation to time. When either timeout wins, dropping the operation follows the cancellation
 1648   1706   
rule for its current dispatch stage; timeout classification does not bypass lifecycle cleanup.
 1649   1707   
 1650   1708   
#### Response and cancellation ownership
 1651   1709   
 1652   1710   
Before returning response headers, the request future installs the response lifecycle guard and response
 1653   1711   
metadata. That transfer is atomic from the caller's perspective: after a successful call, the returned body
 1654   1712   
or upgrade path owns cleanup; before it, the request future does. A panic or cancellation cannot land between
 1655   1713   
the two with no owner.
 1656   1714   
 1657   1715   
| Stage               | Request                   | Dispatch handle                               | Connection / stream guard                                              | Response body            | Retry authority        |
 1658   1716   
| ------------------- | ------------------------- | --------------------------------------------- | ---------------------------------------------------------------------- | ------------------------ | ---------------------- |
 1659   1717   
| Acquiring           | Request future            | None                                          | Waiter or delivery fallback                                            | None                     | None                   |
 1660         -
| Prepared / selected | Request future            | Selected sender                               | H1 checked-out guard or prospective H2 lease                           | None                     | None                   |
 1661         -
| Unsent returned     | Request future regains it | Stale sender retires                          | Guard resolves; H2 endpoint is inert                                   | None                     | Reused connection only |
 1662         -
| Accepted / headers  | Hyper                     | H1 request future; H2 local handle releasable | H1 request future; H2 send and receive endpoints                       | None                     | None                   |
 1663         -
| Headers delivered   | Consumed                  | H1 body guard; no H2 local handle             | Body or upgrade owns H1 or H2 receive; request adapter may own H2 send | Caller owns guarded body | None                   |
 1664         -
| Terminal            | None                      | H1 owning cell or retired; H2 generation      | H1 returned or closing; H2 lease released                              | Completed or dropped     | None                   |
        1718  +
| Prepared / selected | Request future            | Selected sender                               | H1 selection or prospective H2 activation                              | None                     | None                   |
        1719  +
| Unsent returned     | Request future regains it | Stale sender retires                          | Selection resolves; H2 upload guard is inert                           | None                     | Reused connection only |
        1720  +
| Accepted / headers  | Hyper                     | H1 request future; H2 local sender clone      | H1 request future; H2 upload and response guards                       | None                     | None                   |
        1721  +
| Headers delivered   | Consumed                  | H1 exchange; no H2 local sender clone         | Body or upgrade owns H1 exchange or H2 response; request adapter may own H2 upload | Caller owns guarded body | None                   |
        1722  +
| Terminal            | None                      | H1 owning cell or retired; H2 generation      | H1 returned or closing; H2 claim released                              | Completed or dropped     | None                   |
 1665   1723   
 1666   1724   
The open connection record owns its capacity lease throughout this table. Dispatch never moves the permit into
 1667         -
the request, sender, body, or request lease; only logical close returns it to admission.
        1725  +
the request, sender, body, or request claim; only logical close returns it to admission.
 1668   1726   
 1669   1727   
Dropping during acquisition uses the waiter, delivery, and refunnelling rules already defined. Dropping after
 1670   1728   
selection but before call returns a still-usable H1 through its connection-owning cell's ordinary return path
 1671         -
or releases the H2 request lease. Dropping after Hyper accepts but before headers closes H1 through Hyper's supported
 1672         -
cancellation path; on H2 it resets only the stream with `CANCEL`, terminates the receive endpoint, and lets
 1673         -
the request-body adapter terminate the send endpoint before releasing the request lease. Dropping
        1729  +
or cancels the prospective H2 request claim. Dropping after Hyper accepts but before headers closes H1 through
        1730  +
Hyper's supported cancellation path; on H2 it resets only the stream with `CANCEL`, finishes the response guard,
        1731  +
and lets the request-body adapter finish the upload guard before releasing the request claim. Dropping
 1674   1732   
after headers follows the body rules in [Returning a connection](#returning-a-connection).
 1675   1733   
 1676   1734   
Poisoning is monotonic and generation-specific. Invoking captured metadata's poison callback immediately
 1677   1735   
removes the named H1 record or H2 generation from new dispatch, but does not abort an accepted request merely
 1678   1736   
to accelerate replacement. An active H1 begins logical close immediately and tears down after its exchange
 1679         -
reaches a terminal boundary. H2 stops accepting new leases while existing streams drain. A concurrent driver
        1737  +
reaches a terminal boundary. H2 stops accepting new claims while existing streams drain. A concurrent driver
 1680   1738   
error, GOAWAY, idle timeout, reclaim, or repeated poison races through the same exactly-once logical-close
 1681   1739   
transition.
 1682   1740   
 1683   1741   
#### Upgrades
 1684   1742   
 1685   1743   
An upgrade changes which object owns protocol completion. H1 drivers run with upgrade support. A `101`
 1686   1744   
response or successful HTTP/1 `CONNECT` logically closes the checked-out record before the response is
 1687   1745   
exposed: its sender cannot return to the pool, and bounded capacity is released immediately. There is no
 1688   1746   
separate upgrade-pending pool residence.
 1689   1747   
 1690   1748   
Hyper's upgrade-capable driver owns the subsequent transport transfer. It moves the wrapped transport and any
 1691         -
bytes read past the HTTP message into the `Upgraded` object. The caller then owns that I/O; dropping it signals
 1692         -
physical completion through the transport wrapper. If `OnUpgrade` is dropped or the transfer fails, Hyper
 1693         -
closes the transport instead.
        1749  +
bytes read past the HTTP message into the `Upgraded` object. The caller then owns that I/O; dropping it completes
        1750  +
the client's physical connection ownership through the transport wrapper. If `OnUpgrade` is dropped or the
        1751  +
transfer fails, Hyper closes the transport instead.
 1694   1752   
 1695   1753   
Hyper may complete its HTTP/1 driver in the same poll that delivers the upgrading response head. The driver
 1696   1754   
guard can therefore record `ProtocolClosed` before the request task observes the response. Once the response
 1697   1755   
path confirms `101` or successful `CONNECT`, it refines that close reason to `Upgraded`; the refinement changes
 1698   1756   
no ownership and cannot release bounded capacity again. In either poll order the H1 record was already
 1699   1757   
logically closed and can never return as an HTTP connection.
 1700   1758   
 1701   1759   
For H2 extended `CONNECT`, the physical H2 connection remains pooled but that stream is no longer represented
 1702         -
by an ordinary response body. An upgrade lifecycle bridge takes the response's receive endpoint and retains it
        1760  +
by an ordinary response body. An upgrade lifecycle bridge takes the response guard and retains it
 1703   1761   
with the upgraded stream until both upgraded directions are terminal. The owner-partition executor may attach
 1704   1762   
the bridge to Hyper's `UpgradedSendStreamTask`, but task completion alone is sufficient
 1705   1763   
only when it proves the receive half is also done; otherwise the Hyper integration needs a narrow full-stream
 1706         -
completion hook. The original request-body send endpoint must also be terminal before the lease releases.
 1707         -
Releasing the lease resets or completes that stream only. Other streams and future requests
        1764  +
completion hook. The original request-body upload guard must also finish before the claim releases.
        1765  +
Releasing the claim resets or completes that stream only. Other streams and future requests
 1708   1766   
may continue on the same accepting generation. Ordinary response-body completion must not release the
 1709         -
transferred lease early.
        1767  +
transferred claim early.
 1710   1768   
 1711   1769   
#### Obligations
 1712   1770   
 1713   1771   
* **Same-instance dispatch** [safety] — one selected H1 sender commits against logical close and calls
 1714   1772   
  `try_send_request` directly, without publishing or transferring an intermediate dispatch state.
 1715   1773   
* **Wire-form restoration** [safety] — temporary request-target rewriting never replaces the retained absolute
 1716   1774   
  URI used for retry, diagnostics, or errors.
 1717   1775   
* **Certified retry** [safety] — transparent retry uses only the original request Hyper returned unsent from a
 1718   1776   
  reused connection; an accepted request and a fresh-connection failure are never replayed.
 1719   1777   
* **Continuous response ownership** [safety] — the request future owns cleanup through response headers or
 1720   1778   
  terminal error, then transfers it to the response body or upgrade bridge before exposing the response.
 1721   1779   
* **Stage-local cancellation** [safety] — cancellation returns or retires H1 according to whether Hyper
 1722         -
  accepted it, and releases or resets only the selected H2 request lease.
        1780  +
  accepted it, and releases or resets only the selected H2 request claim.
 1723   1781   
* **Compatibility surface** [safety] — request validation, target form, proxy authentication, metadata,
 1724   1782   
  timeout scope, source chain, and error classification preserve the current client behavior.
 1725   1783   
* **Upgrade transfer** [safety] — an H1 upgrade transfers root I/O and cannot return to the HTTP pool; an H2
 1726         -
  extended `CONNECT` transfers its request lease to an upgrade bridge until both stream directions terminate.
 1727         -
* **Full-stream lease** [safety] — an accepted H2 request releases its request lease only after both its
 1728         -
  request-send and response-receive endpoints are terminal.
        1784  +
  extended `CONNECT` transfers its response guard to an upgrade bridge until both stream directions terminate.
        1785  +
* **Two-sided request claim** [safety] — an accepted H2 request releases its claim only after both its
        1786  +
  upload and response guards finish.
 1729   1787   
* **Poisoned generation** [safety] — poisoning prevents every later dispatch on the named record or generation
 1730   1788   
  without aborting already accepted work solely for replacement.
 1731   1789   
 1732   1790   
### Connection retirement and maintenance
 1733   1791   
 1734   1792   
After an accepted request reaches a terminal protocol boundary, its connection either returns to service or
 1735   1793   
leaves the pool. This section defines that decision and the two-step close that retires a connection.
 1736   1794   
 1737   1795   
#### Returning a connection
 1738   1796   
 1739         -
Response headers do not make a connection reusable. The guarded body owns H1 lifecycle or the H2 receive
 1740         -
endpoint until the response reaches end-of-stream, fails, or is dropped.
        1797  +
Response headers do not make a connection reusable. The guarded body owns H1 lifecycle or the H2 response
        1798  +
guard until the response reaches end-of-stream, fails, or is dropped.
 1741   1799   
 1742   1800   
For H1, end-of-stream begins return processing; the checked-out sender returns to its owning cell only after Hyper
 1743   1801   
also reports it ready for another request. Dropping an incomplete body is not itself evidence of reusability.
 1744   1802   
Hyper may synchronously consume an already-buffered remainder and prove the message boundary; if it does, the
 1745   1803   
same ready check may return the connection. If the remainder is unavailable, cancellation, body error, or
 1746   1804   
protocol state cannot prove the boundary, the connection logically closes. The pool does not parse or drain
 1747   1805   
HTTP independently of Hyper.
 1748   1806   
 1749   1807   
The response path polls H1 readiness once. If readiness is pending after the response reaches a reusable
 1750   1808   
protocol boundary, it transfers an `H1Exchange` into a readiness task spawned through the connection's
 1751   1809   
owner-partition `DriverSpawner`. The response body does not retain responsibility for polling
 1752   1810   
that sender, and `Drop` never waits. The task enters owning-cell return only after Hyper proves both the message
 1753   1811   
boundary and readiness for another request. Closed, poisoned, upgraded, or owner-runtime-shutdown outcomes
 1754   1812   
logically close the record; dropping the task owns the same connection-close fallback.
 1755   1813   
 1756         -
For H2, body end-of-stream or a stream-local error terminates the receive endpoint. Dropping an incomplete
 1757         -
body does the same and asks Hyper to send `RST_STREAM(CANCEL)`; the lease releases after the
 1758         -
request-body send endpoint also terminates. Neither outcome retires an otherwise healthy generation. GOAWAY,
        1814  +
For H2, body end-of-stream or a stream-local error finishes the response guard. Dropping an incomplete
        1815  +
body does the same and asks Hyper to send `RST_STREAM(CANCEL)`; the request claim releases after the
        1816  +
upload guard also finishes. Neither outcome retires an otherwise healthy generation. GOAWAY,
 1759   1817   
connection failure, or explicit poisoning may independently have moved the generation to draining, in which
 1760         -
case the last lease completes drain instead of returning it to accepting
        1818  +
case the last request claim completes drain instead of returning it to accepting
 1761   1819   
state. An H2 extended `CONNECT` follows its upgrade lifecycle bridge rather than the ordinary body terminal.
 1762   1820   
 1763   1821   
Every H1 return revalidates the record's generation, poison state, and idle
 1764   1822   
policy under the connection-owning cell lock. An unbounded origin serves
 1765   1823   
compatible local demand or installs the connection as idle directly because it
 1766   1824   
has no admission state or cross-cell reuse. For a bounded origin, the same
 1767         -
transition also checks its installed reuse reservation. An installed
        1825  +
transition also checks its installed peer reservation. An installed
 1768   1826   
reservation extracts the sender into a provisional candidate for borrow or
 1769   1827   
reclaim. Without a reservation, the owning cell first serves compatible local
 1770   1828   
demand and otherwise installs the sender as idle. This complete decision is
 1771   1829   
cell-local; a returning sender does not synchronously consult admission.
 1772   1830   
 1773         -
After the cell transition, a bounded connection-owning cell publishes an
 1774         -
availability change only when its complete advertised or blocked state changed.
 1775         -
Demand-driven admission may then install a future peer reuse operation, but it
 1776         -
cannot interpose between the just-completed local return decision and its sender ownership. `Reserved` is
 1777         -
counted as active rather than idle because no request may select it. Every transition revalidates retirement
        1831  +
After the cell transition, a bounded connection-owning cell submits an H1 supply
        1832  +
revision only when `has_returnable_connection` or `peer_use_blocked` changes.
        1833  +
Demand-driven admission may then retain a future H1 match, but it cannot
        1834  +
interpose between the just-completed local return decision and its sender
        1835  +
ownership. `ReservedForPeer` is counted as active rather than idle because no
        1836  +
request may select it. Every transition revalidates retirement
 1778   1837   
state, so a body that finishes concurrently with poison, reclaim, driver failure, or pool shutdown cannot
 1779         -
republish a connection after retirement.
        1838  +
make a connection selectable after retirement.
 1780   1839   
 1781         -
#### Two-phase close
        1840  +
#### Logical close and physical connection ownership
 1782   1841   
 1783         -
A connection that leaves the pool does so in two steps. At **logical
 1784         -
close** the connection stops accepting new work and releases its permit; at **physical close** the socket is
 1785         -
gone. The permit returns to admission at the first step, not the second, so a replacement can be admitted
 1786         -
while the old transport is still finishing its teardown.
        1842  +
A connection that leaves the pool has two independently tracked transitions. At
        1843  +
**logical close** it stops accepting new work and releases its permit. At
        1844  +
**physical connection completion** the client releases its transport handle,
        1845  +
whether because a driver dropped the root I/O or an upgraded protocol finished
        1846  +
with it. This does not assert that the peer, kernel, or TCP teardown has
        1847  +
completed. The permit returns to admission at logical close, so a replacement
        1848  +
can be admitted while the old transport is still draining or tearing down.
 1787   1849   
 1788   1850   
Releasing capacity at logical close is what keeps a slow teardown from stalling the pool. A connection's
 1789   1851   
socket does not close instantly: TLS sends `close_notify`, TCP exchanges FIN, and the OS may linger the
 1790   1852   
socket after that. Were the permit held until the socket was gone, a connection ending would block a waiter
 1791   1853   
for the length of a teardown the pool does not control. Instead the permit is released the moment the
 1792   1854   
connection stops taking work, and the driver finishes the teardown on its owning partition afterward.
 1793   1855   
 1794   1856   
The complete connection lifecycle is:
 1795   1857   
 1796   1858   
```text
 1797   1859   
establishing (attempt or flight owns capacity lease)
 1798   1860   
  |
 1799   1861   
  +-- failure/cancel -------------------------------> permit refunnelled; no connection
 1800   1862   
  |
 1801   1863   
  `-- handshake -> record owns capacity lease; guarded driver task armed
 1802   1864   
        |
 1803   1865   
        +-- H1 open/idle <---- successful return ---- H1 checked out
 1804   1866   
        |       |                                      |
 1805   1867   
        |       |                                      +-- response body complete
 1806   1868   
        |       |                                      |     `-- Hyper ready -> return
 1807   1869   
        |       |                                      +-- incomplete body dropped/error
 1808   1870   
        |       |                                      |     +-- Hyper proves boundary -> return
 1809   1871   
        |       |                                      |     `-- otherwise -> logical close
 1810   1872   
        |       |                                      `-- upgrade -> logical close + transfer I/O
 1811   1873   
        |       |
 1812   1874   
        |       `-- idle/reclaim/poison/driver close --------> logical close
 1813   1875   
        |
 1814   1876   
        `-- H2 accepting generation
 1815   1877   
                |
 1816         -
                +-- take request lease -> request-send + response-receive endpoints
 1817         -
                |                         `-- both terminal -> release request lease
        1878  +
                +-- accept request claim -> upload guard + response guard
        1879  +
                |                           `-- both finish -> release request claim
 1818   1880   
                |
 1819   1881   
                `-- GOAWAY/poison/driver close -------------> logical close
 1820   1882   
 1821   1883   
logical close (once: remove reuse eligibility + release capacity lease)
 1822   1884   
  |
 1823   1885   
  +-- no accepted work ------------------------------> transport teardown
 1824   1886   
  +-- H1 accepted exchange --------------------------> finish or cancel, then teardown
 1825         -
  +-- H2 request leases remain ----------------------> drain to zero, then teardown
        1887  +
  +-- H2 request claims remain ----------------------> drain to zero, then teardown
 1826   1888   
  `-- H1 upgraded I/O transferred ------------------> caller owns wrapped transport
 1827   1889   
                                                        |
 1828   1890   
transport root is dropped <-----------------------------+
 1829         -
  `-- physical completion
        1891  +
  `-- physical connection ownership ends
 1830   1892   
```
 1831   1893   
 1832         -
The consequence is that live sockets can outnumber admitted connections: a replacement admitted at logical
 1833         -
close coexists with a victim not yet physically closed. `max_connections_per_host` bounds admitted
 1834         -
connections; **no finite general bound on live sockets follows from it.** How long a socket lingers after
 1835         -
logical close depends on peer and OS behavior, and a busy origin can keep admitting replacements while earlier
 1836         -
sockets are still draining, so the count of physically-live sockets for one origin has no bound expressible in
 1837         -
`N` alone. A caller who needs a file-descriptor ceiling sets it at the OS, not through this option. Accepted
 1838         -
H2 streams that outlive their connection's logical close are draining, not admitted: they hold no permit and
 1839         -
accept no new requests, and they too close on their own schedule.
        1894  +
The consequence is that live sockets can outnumber admitted connections: a
        1895  +
replacement admitted at logical close can coexist with a victim whose transport
        1896  +
or kernel socket has not ended. `max_connections_per_host` bounds admitted
        1897  +
connections; **no finite general bound on live sockets follows from it.** How
        1898  +
long a socket lingers after logical close depends on peer and OS behavior, and a
        1899  +
busy origin can keep admitting replacements while earlier sockets are still
        1900  +
draining, so the count of live sockets for one origin has no bound expressible
        1901  +
in `N` alone. A caller who needs a file-descriptor ceiling sets it at the OS,
        1902  +
not through this option. Accepted H2 streams that outlive their connection's
        1903  +
logical close are draining, not admitted: they hold no permit and accept no new
        1904  +
requests, and they too close on their own schedule.
 1840   1905   
 1841   1906   
#### Why a connection retires
 1842   1907   
 1843   1908   
The reason recorded by the logical-close transition is part of the observation surface:
 1844   1909   
 1845   1910   
```rust
 1846   1911   
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
 1847   1912   
#[non_exhaustive]
 1848   1913   
pub enum CloseReason {
 1849   1914   
    IdleTimeout,
 1850   1915   
    Poisoned,
 1851   1916   
    ProtocolClosed,
 1852   1917   
    IncompleteH1Exchange,
 1853   1918   
    Upgraded,
 1854   1919   
    Reclaimed,
 1855   1920   
    PoolDropped,
 1856   1921   
    OwnerRuntimeShutdown,
 1857   1922   
}
 1858   1923   
```
 1859   1924   
 1860   1925   
A connection retires for one of a few reasons:
 1861   1926   
 1862   1927   
* **Idle timeout** — an H1 deadline starts when its sender becomes reusable in the idle set and is absent while
 1863   1928   
  the record is selected, active, or resolving return. A new H1 therefore begins aging only after its first
 1864   1929   
  idle installation. An H2 deadline starts when a generation becomes accepting, including a fresh generation
 1865         -
  that has not dispatched, and resets whenever a request lease commits to dispatch. Active streams do not
        1930  +
  that has not dispatched, and resets whenever a request claim commits to dispatch. Active streams do not
 1866   1931   
  suspend that deadline. H2 expiration moves the generation out of accepting state and begins logical close;
 1867         -
  accepted leases continue draining and retain the physical transport. A connection with idle timeout disabled
        1932  +
  accepted claims continue draining and retain the physical transport. A connection with idle timeout disabled
 1868   1933   
  is kept.
 1869   1934   
 1870   1935   
  Closing an otherwise-quiescent connection cannot wait for the next request, so each partition runs a
 1871   1936   
  maintenance task on its own runtime. Every task uses the pool's builder-injected `TimeSource` and
 1872   1937   
  `AsyncSleep`, so tests drive idle age with a fake clock. Because that time source is `SystemTime`, which can
 1873   1938   
  step backward or forward, idle age is measured against the scheduled sleep deadline the task already holds,
 1874   1939   
  not by subtracting two `SystemTime` readings. The deadline gives a monotonic floor: a clock jump changes when
 1875   1940   
  the task wakes but not whether a connection idle since a completed deadline has expired.
 1876   1941   
 1877   1942   
  The scheduler records the deadline represented by its current sleep. A newly idle connection wakes it only
 1878   1943   
  for an earlier deadline; ordinary checkout does not force a partition scan. An atomic start gate submits at
 1879   1944   
  most one task per partition. The task retains only weak cell registrations between scans, drops each strong
 1880   1945   
  scan snapshot before waiting, and exits on explicit partition shutdown even when no cell or deadline remains.
 1881   1946   
  At the start of a scan, it atomically retires the deadline that triggered the scan while capturing the
 1882   1947   
  scheduler revision. A connection returned to idle during the unlocked scan therefore advances the revision
 1883   1948   
  and forces a retry rather than being hidden behind an already elapsed deadline. Shutdown and earlier-deadline
 1884   1949   
  publication detach the waker under the scheduler lock and wake it after unlock. If the submitted maintenance
 1885   1950   
  future is dropped before normal completion, its task guard reopens the start gate so a later request can
 1886   1951   
  submit maintenance again.
 1887   1952   
 1888   1953   
* **Poisoning** — an explicit poison signal through captured connection metadata removes the named record or
 1889   1954   
  generation from future dispatch. Accepted work may finish, but the connection does not return to accepting
 1890   1955   
  or idle state.
 1891   1956   
* **Protocol close** — peer close, HTTP/2 `GOAWAY`, driver or dispatcher completion, and transport- or
 1892   1957   
  protocol-level connection failure retire the affected connection. Streams the peer still accepts may
 1893   1958   
  finish; later requests use a replacement. An H2 stream-local reset is not connection-fatal.
 1894   1959   
* **Incomplete H1 exchange** — cancellation, response-body drop, or an exchange error retires H1 when no
 1895   1960   
  independent connection-fatal signal has already selected `ProtocolClosed`, unless Hyper proves that it
 1896   1961   
  recovered the complete message boundary.
 1897   1962   
* **Upgrade** — an H1 connection leaves HTTP pool ownership when its transport transfers to the upgraded
 1898   1963   
  protocol. H2 extended `CONNECT` follows the request stream's upgrade bridge and does not retire the physical
 1899   1964   
  H2 connection.
 1900   1965   
* **Reclaim** — a bounded origin at its limit closes a connection to move its permit to a waiting cell, as
 1901   1966   
  [Bounded-capacity coordination](#bounded-capacity-coordination) describes. Reclaim never interrupts an in-flight
 1902   1967   
  request: it closes a connection that is idle now, or reserves one as it returns from its current request and
 1903   1968   
  closes it before it serves another. It does not abort active work to free a permit.
 1904   1969   
* **Pool or owner-runtime shutdown** — pool drop logically closes every remaining record. If an owning runtime
 1905   1970   
  drops a connection's guarded driver task, the driver lifecycle guard requests logical close with
 1906   1971   
  `OwnerRuntimeShutdown`; dropping the driver also closes its root transport unless an H1 upgrade already
 1907   1972   
  transferred that transport. Per-stream and upgrade tasks use their request-lifecycle cleanup and do not by
 1908   1973   
  themselves close a healthy H2 connection. Outstanding request and body guards run their normal terminal
 1909   1974   
  cleanup, and every close request races through the same exactly-once transition.
 1910   1975   
 1911   1976   
The first trigger to begin logical close removes reuse eligibility, releases capacity, and records the close
 1912   1977   
reason. Later triggers observe that terminal transition and cannot release capacity or report close again. The
 1913   1978   
one reason-only refinement is an H1 upgrade: Hyper can complete the protocol driver in the same poll that
 1914   1979   
delivers the upgrade response, so a request path that later confirms the upgrade may change
 1915   1980   
`ProtocolClosed` to `Upgraded` without repeating close, capacity release, or the close callback. The
 1916   1981   
refinement emits a structured diagnostic record carrying both reasons so tracing reflects the final
 1917   1982   
classification even when driver completion won the close race.
 1918   1983   
 1919   1984   
`Poisoned` is reserved for an explicit poison signal. `ProtocolClosed` is final when it reflects independently
 1920   1985   
observed connection-level termination; only later confirmation that the same H1 exchange upgraded may refine
 1921   1986   
it. The close event carries the source error when one exists. Other concurrent signals still race through
 1922   1987   
first-trigger-wins, but one initiating signal does not match both categories. Every reason ends at the same
 1923         -
physical completion, so capacity and lifecycle accounting do not depend on what won the race.
        1988  +
physical connection ownership transition, so capacity and lifecycle accounting do not depend on what won the race.
 1924   1989   
 1925   1990   
#### Obligations
 1926   1991   
 1927   1992   
* **Capacity on logical close** [safety] — a connection releases its permit at logical close, before physical
 1928         -
  close, and releases it exactly once.
        1993  +
  connection ownership ends, and releases it exactly once.
 1929   1994   
* **No dispatch after logical close** [safety] — dispatch commit and logical close race through mutually
 1930   1995   
  exclusive per-record or per-generation gates; a close that wins leaves the request locally owned, while a
 1931   1996   
  commit that wins is recorded as an in-flight dispatch and calls Hyper immediately without holding a pool
 1932   1997   
  lock.
 1933   1998   
* **Poison on retire** [safety] — a connection retired as unsafe is not returned to the idle set.
 1934   1999   
* **Reclaim spares active work** [safety] — reclaim closes a connection only when it is idle, whether idle
 1935   2000   
  now or on return from its current request; it never interrupts an in-flight request.
 1936   2001   
* **H1 boundary return** [safety] — H1 returns only after response end-of-stream or a Hyper-proven drain and a
 1937   2002   
  successful sender-ready check; pending readiness is owned by an owner-partition task, and dropping a body
 1938   2003   
  alone never returns the connection.
 1939   2004   
* **H2 stream isolation** [safety] — completion, error, or cancellation releases or resets one H2 request
 1940         -
  lease, after both endpoints terminate, without retiring a healthy accepting generation.
        2005  +
  claim, after both sides finish, without retiring a healthy accepting generation.
 1941   2006   
* **Return revalidation** [safety] — an H1 return checks generation and retirement state under its
 1942   2007   
  connection-owning cell before becoming visible; a sender awaiting admission remains owned by that cell and
 1943         -
  non-dispatchable in `Reserved`, so a late completion cannot reverse logical close or bypass return ordering.
 1944         -
* **Physical completion tracking** [safety] — root-I/O drop, not logical close or driver-future completion
 1945         -
  alone, terminates the physical connection lifetime, including after H1 upgrade.
        2008  +
  non-dispatchable in `ReservedForPeer`, so a late completion cannot reverse logical close or bypass return
        2009  +
  ordering.
        2010  +
* **Physical connection completion** [safety] — dropping the wrapped root I/O, not logical close or
        2011  +
  driver-future completion alone, releases the client's physical connection handle, including after H1
        2012  +
  upgrade. The operating system may continue TCP teardown afterward.
 1946   2013   
 1947   2014   
### Telemetry
 1948   2015   
 1949   2016   
An operator diagnosing a connection problem — establishment happening more often than expected, connections
 1950   2017   
closing early, reuse not occurring — cannot see it in request outcomes, which look the same whether a request
 1951   2018   
reused a warm connection or opened a fresh one. The pool reports what request outcomes hide, in two forms.
 1952   2019   
 1953   2020   
#### Events and statistics
 1954   2021   
 1955   2022   
Lifecycle **events** report transitions; statistics report current gauges. Their public types live here
@@ -2040,2107 +2148,2215 @@
 2060   2127   
```
 2061   2128   
 2062   2129   
Creation is the one event-order barrier: its synchronous callback returns before the connection becomes
 2063   2130   
request-visible. A callback panic instead unwinds the establishment task and guarded cleanup prevents the
 2064   2131   
pre-published record from becoming usable. After publication, callbacks from concurrent tasks are not
 2065   2132   
serialized. A reuse callback may still be running when another task begins a close callback, and no ordering
 2066   2133   
is promised between two concurrent reuses. Events causally emitted by one task are invoked in that task's
 2067   2134   
program order until a callback panics. This is the complete ordering contract; consumers needing a total order
 2068   2135   
add timestamps or sequencing in their listener.
 2069   2136   
 2070         -
`connection_reused` is emitted when an existing H1 guard or H2 request lease is committed to a request, before
        2137  +
`connection_reused` is emitted when an existing H1 guard or H2 activation is committed to a request, before
 2071   2138   
Hyper readiness. A stale selection may therefore be followed by `connection_closed` and a transparent retry;
 2072   2139   
the event reports the attempted reuse that operators need to diagnose. `connection_closed` marks logical
 2073   2140   
close, not physical teardown, and reports the reason that won that transition. If Hyper's H1 driver wins a
 2074   2141   
race with upgrade confirmation, the callback reports `ProtocolClosed` exactly once and the later one-way
 2075   2142   
diagnostic refinement records `ProtocolClosed -> Upgraded` without another callback. If the request path wins,
 2076   2143   
the callback reports `Upgraded` directly. Establishment failure is emitted after its capacity and waiter state
 2077   2144   
have been reconciled. All callbacks observe committed pool state.
 2078   2145   
 2079   2146   
The same `ConnectionInfo` remains available through the existing per-request connection-capture API. Events
 2080   2147   
extend observation; they do not replace metadata capture or its generation-specific poison callback.
 2081   2148   
 2082   2149   
Statistics use the same state boundaries:
 2083   2150   
 2084   2151   
```rust
 2085   2152   
#[derive(Clone, Copy, Debug, Default)]
 2086   2153   
#[non_exhaustive]
 2087   2154   
pub struct PartitionStats {
 2088   2155   
    pub establishing: usize,
 2089   2156   
    pub h1_idle: usize,
 2090   2157   
    pub h1_active: usize,
 2091   2158   
    pub h1_draining: usize,
 2092   2159   
    pub h2_accepting: usize,
 2093   2160   
    pub h2_draining: usize,
 2094   2161   
    pub h2_active_streams: usize,
 2095   2162   
    pub physically_live: usize,
 2096   2163   
    pub waiting_requests: usize,
 2097   2164   
}
 2098   2165   
 2099   2166   
pub struct OriginStats { /* sparse per-partition snapshot */ }
 2100   2167   
 2101   2168   
impl OriginStats {
 2102   2169   
    pub fn limit(&self) -> Option<usize>;
 2103   2170   
    pub fn admitted(&self) -> usize;
 2104   2171   
    pub fn get(&self, partition: PartitionId) -> Option<PartitionStats>;
 2105   2172   
    pub fn iter(&self) -> impl Iterator<Item = (PartitionId, PartitionStats)> + '_;
 2106   2173   
    pub fn is_empty(&self) -> bool;
 2107   2174   
}
 2108   2175   
 2109   2176   
impl ConnectionPool {
 2110   2177   
    pub fn stats(&self, origin: &OriginKey) -> OriginStats;
 2111   2178   
}
 2112   2179   
```
 2113   2180   
 2114   2181   
`establishing` starts when an attempt or flight is admitted and ends when it fails or installs a record.
 2115         -
`h1_idle` and `h1_active` partition logically open H1 records; checked-out and `Reserved` H1 records are
        2182  +
`h1_idle` and `h1_active` partition logically open H1 records; checked-out and `ReservedForPeer` H1 records are
 2116   2183   
active, while only dispatch-eligible records in the idle set are idle.
 2117         -
`h2_accepting` counts generations that may issue request leases, while `h2_active_streams` counts accepted
 2118         -
request leases across accepting and draining generations. Logical close moves a connection out of those
        2184  +
`h2_accepting` counts generations that may issue activations, while `h2_active_streams` counts accepted
        2185  +
request claims across accepting and draining generations. Logical close moves a connection out of those
 2119   2186   
admitted gauges and, while its transport remains, into `h1_draining` or `h2_draining`. An upgraded H1 remains
 2120   2187   
H1-draining until its wrapped root I/O drops. `physically_live` starts when the connector's returned transport
 2121   2188   
is wrapped for
 2122   2189   
lifecycle tracking and ends only at root-I/O drop, so it includes handshaking, admitted, draining, and
 2123   2190   
upgraded transports and may exceed the configured limit. `waiting_requests` counts requests registered in
 2124   2191   
acquisition that do not yet own a dispatch authority, including flight participants.
 2125   2192   
 2126   2193   
`OriginStats::admitted` derives the saturating sum of `establishing`, `h1_idle`, `h1_active`, and
 2127   2194   
`h2_accepting` from the captured partition rows; it is not another shared counter or admission authority.
 2128   2195   
Draining connections are excluded because they hold no permits. `limit` is the configured per-origin bound,
@@ -2175,2242 +2346,2416 @@
 2195   2262   
released, never copied. *Capacity* is the aggregate quantity permits account for, used in sums and bounds.
 2196   2263   
 2197   2264   
**Demand** — a cell's standing signal that it could use one more connection. One fixed ticket per cell,
 2198   2265   
not one per request, so demand cannot accumulate.
 2199   2266   
 2200   2267   
**Borrow** — moving an exclusive HTTP/1 request handle to a peer cell for dispatch while leaving the
 2201   2268   
connection record, driver, and socket with the connection-owning cell.
 2202   2269   
 2203   2270   
**Reclaim** — closing a connection so its permit can move to another cell. Transfers capacity, not I/O.
 2204   2271   
 2205         -
**Capacity lease**, **request lease**, and **handle** — a capacity lease is the exclusive hold on one permit
 2206         -
and moves from establishment to the connection record. An H2 request lease owns one prospective or accepted
 2207         -
stream's two-ended lifecycle but no permit. A dispatch handle can address a connection and owns neither kind of
 2208         -
capacity.
        2272  +
**Capacity lease**, **request claim**, and **handle** — a capacity lease is the exclusive hold on one permit
        2273  +
and moves from establishment to the connection record. An H2 request claim owns one prospective or accepted
        2274  +
request's two-sided lifecycle but no permit. A dispatch handle can address a connection and owns neither kind
        2275  +
of capacity.
 2209   2276   
 2210   2277   
**Retry authority** — proof that the same request may be dispatched again. Only Hyper returning the original
 2211   2278   
request unsent from a reused connection creates this authority; request clonability does not.
 2212   2279   
 2213         -
**Reacquire** — returning a still pool-owned request to acquisition after its selected protocol state becomes
 2214         -
stale before dispatch. It is an internal state transition, not an SDK retry or authority to clone an accepted
 2215         -
request.
        2280  +
**Retry acquisition** — returning a still pool-owned request to acquisition after its selected protocol state
        2281  +
becomes stale before dispatch. It is an internal state transition, not an SDK retry or authority to clone an
        2282  +
accepted request.
 2216   2283   
 2217         -
**Publish** and **deliver** — publication makes state visible to many unnamed readers; delivery hands one
 2218         -
value to one waiting party. `DemandResidence` tracks a ticket's residence and acknowledgement fence;
 2219         -
`DeliveryGuardState` owns a one-to-one payload while it crosses locks.
        2284  +
**Snapshot**, **revision**, and **delivery** — a demand snapshot or supply revision is a complete versioned
        2285  +
value submitted across lock domains. Delivery hands one owned permit or provisional H1 to one assigned demand.
        2286  +
`DemandScheduleState::PendingAssignment` excludes a second selection while `DeliveryGuard` owns the
        2287  +
one-to-one payload crossing toward the requesting cell.
 2220   2288   
 2221   2289   
**Attempt** and **flight** — an HTTP/1 establishment is an attempt, independent of other attempts; an
 2222   2290   
HTTP/2 establishment is a flight. Automatic attempts remain independent before ALPN, then atomically join or
 2223   2291   
install at most one post-ALPN H2 flight per cell. The flight record owns participant identities; the
 2224   2292   
connection-partition owner task owns handshake completion.
 2225   2293   
 2226         -
**Advertisement** and **route** — an advertisement is admission's report that one connection cell has an
 2227         -
accepting H2 generation. Publication installs a route in a requesting cell. The route names that exact
 2228         -
connection cell and generation but owns no sender, socket, driver, or capacity.
        2294  +
**Supply status** and **route** — supply status is a cell's versioned admission-facing projection of usable
        2295  +
connection state. Admission may install an H2 route in a requesting cell. The route names one exact
        2296  +
connection-owning cell and generation but owns no sender, socket, driver, or capacity.
 2229   2297   
 2230         -
**Logical close** — a connection stops accepting new work and releases its permit. **Physical close** —
 2231         -
the socket is gone. Physical close follows logical close by an unbounded interval, so live sockets can
 2232         -
outnumber admitted connections; `max_connections_per_host` bounds admitted connections, not file descriptors,
 2233         -
and no finite bound on live sockets follows from it.
        2298  +
**Logical close** — a connection stops accepting new work and releases its permit. **Physical connection
        2299  +
completion** — the client releases its transport handle. It may follow logical close by an unbounded interval
        2300  +
and does not assert that kernel socket teardown is complete, so live sockets can outnumber admitted connections;
        2301  +
`max_connections_per_host` bounds admitted connections, not file descriptors, and no finite bound on live
        2302  +
sockets follows from it.
 2234   2303   
 2235         -
**Draining generation** and **draining connection** — a draining H2 generation accepts no new request leases
        2304  +
**Draining generation** and **draining connection** — a draining H2 generation accepts no new request claims
 2236   2305   
while already accepted streams finish. A draining connection has logically closed and released its permit but
 2237   2306   
still has physically live root I/O, and is counted by `h1_draining` or `h2_draining`. An H2 record may be both
 2238   2307   
while its accepted streams and transport finish.
 2239   2308   
 2240   2309   
**Generation** — one uniquely identified incarnation of an installed HTTP/2
 2241   2310   
connection. It owns the authoritative request handle, accepting or draining
 2242   2311   
state, request counts, and connection capacity. A replacement connection has a
 2243   2312   
new generation identity so delayed work for the previous incarnation is
 2244   2313   
rejected.
 2245   2314   
 2246   2315   
**Demand generation** — one cell queue head's `DemandId`. A **snapshot version** orders complete publications
 2247   2316   
for that generation. Readers retain the newest publication and reject work for a retired generation.
 2248   2317   
 2249   2318   
**Obligation** — a duty a component owes, stated as one sentence an implementation either satisfies or does
 2250   2319   
not. `[safety]` obligations forbid a state; `[liveness]` obligations require an outcome; `[optimization]`
 2251   2320   
obligations bound a cost, and violating one is a regression rather than a defect.
 2252   2321   
 2253   2322   
## Correctness invariants
 2254   2323   
 2255   2324   
Each invariant states the property, what it rules out, and the obligations that enforce it. The obligations
 2256   2325   
themselves are defined in the Architecture sections; an invariant names them rather than restating them.
 2257   2326   
Optimization obligations are not invariants — violating one is a regression, not a defect — so they appear
 2258   2327   
here only where a cost bound is load-bearing for a correctness property.
 2259   2328   
 2260   2329   
**Capacity is conserved.** A bounded origin admits at most `max_connections_per_host` connections across all
 2261   2330   
partitions, and every permit has exactly one owner at all times. *Rules out:* admitting past the bound; a
 2262   2331   
leaked permit, which is capacity the pool can never reissue; a duplicated permit, which would let two
 2263   2332   
connections occupy one unit of capacity. *Enforced by:* Single permit owner, Driver termination closes the
 2264   2333   
record, and Capacity on logical close (one owner from admission through release, released exactly once),
 2265   2334   
Single delivery and Refunnelling (a permit crossing locks commits once or returns to admission), and
 2266   2335   
Losing-attempt cleanup and Flight cancellation (post-ALPN termination returns the establishment lease).
 2267   2336   
Capacity gates whether a new connection may be *established*; it does not gate dispatch on a connection
 2268   2337   
already admitted.
 2269   2338   
 2270   2339   
**Origins are total and canonical.** Every dispatched request maps to exactly one origin, and equivalent
 2271   2340   
spellings of one server map to one origin. *Rules out:* a request with no cell to resolve to; one server
 2272   2341   
splitting into two `OriginAdmission`s that each admit the full bound. *Enforced by:* Key totality,
 2273   2342   
Canonical key, and Version independence.
 2274   2343   
 2275   2344   
**Cell identity is stable.** A cell is never destroyed while its origin is reachable, and at most one cell
 2276   2345   
exists per (partition, origin). *Rules out:* a peer reference to a cell that has been freed or whose slot has
 2277   2346   
been reused; two cells racing into existence for one pair. *Enforced by:* Cell stability, Reference validity,
 2278   2347   
and Cell uniqueness.
 2279   2348   
 2280   2349   
**I/O stays on its owning partition.** A connection's socket, driver, and Hyper-spawned tasks run only on the
 2281   2350   
partition that established it, for the connection's life; reuse moves a dispatch handle and no I/O. *Rules
 2282   2351   
out:* a socket registered on one runtime and driven from another, which produces a cross-runtime wakeup on
 2283   2352   
every read and couples the connection's lifetime to a runtime that does not own it; bytes leaving an
 2284   2353   
interface the caller did not choose. *Enforced by:* Establishment placement, Driver placement, Hyper task
 2285   2354   
placement, Binding immutability, and Placement under transfer.
 2286   2355   
 2287   2356   
**No dispatch on an unusable connection.** No request is dispatched on a connection that has begun closing or
 2288   2357   
has been retired as unsafe. *Rules out:* use of a connection after logical close; drawing a connection a
 2289   2358   
prior error poisoned. *Enforced by:* No dispatch after logical close, Poison on retire, Reclaim spares active
 2290   2359   
work (reclaim closes a connection only while idle, never interrupting an in-flight request), Poisoned
 2291   2360   
generation, Return revalidation, and Same-instance dispatch.
 2292   2361   
 2293   2362   
**Dispatch and response ownership are continuous.** From selection through terminal response, exactly one
 2294   2363   
component owns the request and exactly one component owns the selected connection or stream cleanup. *Rules
 2295   2364   
out:* replaying a request that may have reached the wire; returning H1 while its response is still framed;
 2296         -
losing a checked-out connection or H2 request lease when a future is dropped; releasing an extended
        2365  +
losing a checked-out connection or H2 request claim when a future is dropped; releasing an extended
 2297   2366   
`CONNECT` lease when its empty response body completes. *Enforced by:* Same-instance dispatch, Certified
 2298   2367   
retry, Continuous response ownership, Stage-local cancellation, Upgrade transfer, H1 boundary return, H2
 2299   2368   
stream isolation, Full-stream lease, and Return revalidation.
 2300   2369   
 2301   2370   
**A one-to-one resource is delivered exactly once.** A provisional H1 or capacity lease has one owner until it
 2302   2371   
is committed to one eligible waiter or refunnelled. *Rules out:* a lost resource while an eligible waiter
 2303   2372   
sleeps; a double delivery where one resource serves two waiters; a cancelled waiter retaining capacity.
 2304   2373   
*Enforced by:* Bounded demand and Snapshot ordering identify the live generation; Single delivery retains its
 2305         -
fence through acknowledgement; Refunnelling and Acknowledged progress give every rejection and drop a terminal
        2374  +
assignment through settlement; Refunnelling and Acknowledged progress give every rejection and drop a terminal
 2306   2375   
path. An H2 generation is not a one-to-one resource: its connection record retains capacity while generation
 2307         -
identity is published to compatible local waiters and announced to eligible peer cells in bounded turns.
        2376  +
identity is visible to compatible local waiters and announced to eligible peer cells in bounded turns.
 2308   2377   
 2309   2378   
**No committed waiter starves.** An eligible committed waiter is served whenever a permit it may use becomes
 2310   2379   
reachable, and is not passed indefinitely by later arrivals. *Rules out:* unbounded overtaking; a resource
 2311   2380   
sitting idle while an eligible waiter waits; capacity stranded on a peer connection that returns reusable
 2312         -
without ever going observably idle; a newly published H2 generation serving newer local arrivals while older
        2381  +
without ever going observably idle; a newly visible H2 generation serving newer local arrivals while older
 2313   2382   
compatible waiters remain parked. *Enforced by:* Cross-cell order and Bounded overtaking (the oldest eligible
 2314   2383   
residence comes from a stored head), Return interception and Owning-cell fairness turn (a returning connection
 2315         -
reaches an older peer without starving its owning cell), Publication priority (the generation gate serves committed
 2316         -
local waiters before newer arrivals), and Work-conserving service, with Bounded grant work bounding the
        2384  +
reaches an older peer without starving its owning cell), Generation visibility priority (the generation gate
        2385  +
serves committed local waiters before newer arrivals), and Work-conserving service, with Bounded grant work
        2386  +
bounding the
 2317   2387   
coordination cost and Bounded peer discovery preventing peer searches from growing with partition count.
 2318   2388   
This holds only while progress is possible — it is not promised while every permit for the origin is held
 2319   2389   
indefinitely by active HTTP/2 work that the waiter is not eligible to use, a limit stated under
 2320   2390   
[Eligible requests make progress](#eligible-requests-make-progress).
 2321   2391   
 2322   2392   
**Observation cannot corrupt pool state.** Listener code runs outside pool locks and only after its triggering
 2323   2393   
transition is complete, so pool invariants do not depend on a listener succeeding. *Rules out:* a listener
 2324   2394   
observing or holding partially transitioned state; a panicking listener leaving committed state inconsistent;
 2325   2395   
a listener blocking coordination by retaining a pool lock. *Does not rule out:* a listener delaying or
 2326   2396   
ending the task that invokes it, or delaying work sequenced after its return. The creation callback is a
@@ -2348,2418 +2411,2481 @@
 2368   2438   
 2369   2439   
### Legacy builder shimming
 2370   2440   
 2371   2441   
The new pool and partitioned client can ship through additive APIs before they replace the existing
 2372   2442   
`Builder` and `ConnectorBuilder` internals. The later compatibility path should implement those legacy
 2373   2443   
builders as adapters onto this pool wherever their observable behavior can be represented faithfully, rather
 2374   2444   
than retaining a second connection-pool architecture behind deprecated entry points.
 2375   2445   
 2376   2446   
That shim requires a field-by-field audit of connector settings, idle defaults and nested-option semantics,
 2377   2447   
TCP and interface settings, proxy and TLS assembly, DNS overrides, runtime components, and custom connector
 2378         -
entry points. The implementation-neutral suites from smithy-rs PR #4767 are the acceptance baseline. A legacy
 2379         -
option that cannot be mapped exactly remains on the old path until an explicit compatibility decision is made;
 2380         -
the shim must not silently approximate it. Once the surface is covered, the hyper-util legacy pool can be
 2381         -
removed as an implementation dependency rather than kept alive solely by old builders.
        2448  +
entry points. The implementation-neutral client behavior suites are the acceptance baseline. A legacy option
        2449  +
that cannot be mapped exactly remains on the old path until an explicit compatibility decision is made; the
        2450  +
shim must not silently approximate it. Once the surface is covered, the hyper-util legacy pool can be removed
        2451  +
as an implementation dependency rather than kept alive solely by old builders.
 2382   2452   
 2383   2453   
---
 2384   2454   
 2385   2455   
## Appendix A: Public API and module structure
 2386   2456   
 2387   2457   
Appendix A assembles the callable construction surface. Types that define a mechanism remain with the
 2388   2458   
Architecture section that explains them: [partitions](#partitions), [origins](#origins-and-cells),
 2389   2459   
[reuse scope](#eligibility-and-capacity), [connection retirement](#why-a-connection-retires), and
 2390   2460   
[telemetry](#telemetry).
 2391   2461   
@@ -2512,2582 +2663,2701 @@
 2532   2602   
### Module structure
 2533   2603   
 2534   2604   
```text
 2535   2605   
aws-smithy-http-client/src/client/
 2536   2606   
  pool.rs              — ConnectionPool ownership and public re-exports
 2537   2607   
  pool/
 2538   2608   
    builder.rs         — Builder typestate, validation, connector assembly
 2539   2609   
    client.rs          — Client, PoolConnector, and ClientBuildError
 2540   2610   
    partition.rs       — partition declarations and runtime/interface placement
 2541   2611   
    origin.rs          — owned OriginKey, borrowed lookup, and canonicalization
 2542         -
    registry.rs        — PartitionRegistry, PartitionState, and stable cell publication
        2612  +
    registry.rs        — PartitionRegistry, PartitionState, and stable cell ownership
 2543   2613   
    cell.rs            — OriginCell and cell-level acquisition coordination
 2544   2614   
    cell/
 2545         -
      h1.rs            — HTTP/1 records, sender ownership, and reuse reservation
 2546         -
      h2.rs            — HTTP/2 flights, generations, routes, gates, and request leases
 2547         -
      waiters.rs       — local acquisition queue and delivery reservation
        2615  +
      h1.rs            — H1CellState, sender ownership, and peer reservation
        2616  +
      h2.rs            — HTTP/2 flights, generations, routes, and activation gates
        2617  +
      h2/
        2618  +
        request.rs     — HTTP/2 activation authority and two-sided request claims
        2619  +
      waiters.rs       — local acquisition queue and waiter resolution
 2548   2620   
    admission.rs       — bounded-origin capacity and unlocked action driving
 2549   2621   
    admission/
 2550         -
      demand.rs        — versioned demand order and delivery fences
        2622  +
      demand.rs        — versioned demand order and assignments
 2551   2623   
      order.rs         — checked intrusive order shared by admission indexes
 2552         -
      reuse.rs         — H1 availability order and cross-cell reuse operations
 2553         -
      delivery.rs      — capacity/H1 crossing guards and acknowledgements
 2554         -
      publication.rs   — HTTP/2 advertisements, peer publication, and fences
 2555         -
    establish.rs       — transport construction below protocol establishment
        2624  +
      h1.rs            — H1 supply indexes, retained matches, and detached actions
        2625  +
      delivery.rs      — capacity/H1 assignment handoff and settlement
        2626  +
      h2.rs            — H2 supply indexes, peer routing, and reclaim
        2627  +
    establish.rs       — negotiated-protocol routing and connection identity
 2556   2628   
    establish/
 2557   2629   
      h1.rs            — HTTP/1 handshake, installation, and driver
 2558   2630   
      h2.rs            — post-ALPN flight convergence, handshake, and driver
        2631  +
      transport.rs     — connector selection, placement, timeout, and ALPN inputs
 2559   2632   
    dispatch.rs        — protocol-neutral request routing
 2560   2633   
    dispatch/
 2561         -
      h1.rs            — HTTP/1 dispatch, reacquisition, and response ownership
        2634  +
      h1.rs            — HTTP/1 dispatch, non-acceptance, and response ownership
 2562   2635   
      h2.rs            — HTTP/2 dispatch and two-ended request completion
 2563   2636   
    maintenance.rs     — idle-deadline scheduling and partition task lifetime
 2564         -
    connection.rs      — records, leases, logical close, physical completion
        2637  +
    connection.rs      — connection identity, logical close, dispatch, and physical connection ownership
 2565   2638   
    events.rs          — listener and lifecycle event types
 2566   2639   
    stats.rs           — origin/partition snapshots and lifecycle gauges
 2567   2640   
aws-smithy-http-client/src/
 2568   2641   
  sync/
 2569   2642   
    mod.rs              — standard-library and Loom backend selection
 2570   2643   
    std.rs              — production synchronization facade
 2571   2644   
    loom.rs             — modeled synchronization facade
 2572   2645   
```
 2573   2646   
 2574   2647   
The inventory describes ownership boundaries, not implementation order. The `pool` module re-exports every
 2575   2648   
public type above; private modules may be split or combined without changing the architecture so long as the
 2576   2649   
lock, ownership, and hot-path boundaries remain intact.
 2577   2650   
 2578   2651   
The transport-connector contract below the pool is unchanged: it is a `Service<Uri>` yielding
 2579   2652   
`(IO, Connected)`. The pool composes transport connectors and does not replace that contract; the smithy
 2580   2653   
`HttpConnector` above the pool is the request-policy facade described earlier.
 2581   2654   
 2582   2655   
---
 2583   2656   
 2584   2657   
## Appendix B: Validation
 2585   2658   
 2586         -
Validation supplies implementation evidence for the contracts above; it does not redefine them. The
 2587         -
implementation-neutral connection harness and existing-client behavior suites in smithy-rs
 2588         -
[PR #4767](https://github.com/smithy-lang/smithy-rs/pull/4767) form the compatibility baseline. Pool-specific
 2589         -
tests preserved on `archive/conn-pool-4708` are an additional inventory, not an acceptance target: applicable
 2590         -
contracts may be retained or rewritten, implementation-specific assumptions may be obsolete, and the owned
 2591         -
state machine requires coverage that prototype tests did not contain.
 2592         -
 2593         -
The evidence levels have distinct jobs:
 2594         -
 2595         -
* **Unit, property, and bounded transition tests** cover construction, canonicalization, indexing,
 2596         -
  accounting, and explicit state-machine transitions. Where focused state-space enumeration is used, the test
 2597         -
  identifies its operation alphabet and bound; ordinary transition tests are not described as exhaustive.
 2598         -
* Focused **Loom kernels** compile the production synchronization-bearing code against Loom and exercise
 2599         -
  concurrent cell publication, permit and H1 delivery, H1 selection and return, borrowed-H1 materialization,
 2600         -
  reuse cancellation, logical close, maintenance publication or shutdown, HTTP/2 activation against close,
 2601         -
  two-ended request completion against generation close, route installation against requesting-cell
 2602         -
  cancellation, and peer publication against connection-cell close. They model these ownership boundaries
 2603         -
  rather than sockets or the complete network client.
 2604         -
* **Controlled-runtime tests** use injected time, sleep, connectors, and executors to force cancellation at
 2605         -
  ownership-distinct cancellation boundaries, submitted-future drop, idle deadlines, independent-runtime
 2606         -
  request movement, explicit placement checks, and connector or handshake failure.
 2607         -
* The **wire harness** verifies HTTP/1.1 and HTTP/2 behavior against scripted peers, including reuse,
 2608         -
  multiplexing, request-specific ALPN, GOAWAY before and after first-stream acceptance, stream reset,
 2609         -
  incomplete bodies, upgrades, poisoning, and transport close.
 2610         -
* **Differential tests** run the same implementation-neutral behavior contracts against the current
 2611         -
  Hyper-util-backed client and this pool. Any difference in request behavior, metadata, timeout scope, or error
 2612         -
  classification requires an explicit design decision rather than a rewritten oracle.
 2613         -
* **Benchmarks and stress tests** establish that the optimization and liveness contracts remain true at
 2614         -
  production concurrency and topology.
 2615         -
 2616         -
The required evidence maps to the architecture as follows:
 2617         -
 2618         -
| Mechanism                                                             | Primary evidence                                                                  | What it must establish                                                                                                                                                                                                                                         |
 2619         -
| --------------------------------------------------------------------- | --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
 2620         -
| Construction, topology, origin identity, and stable cells             | unit/property; allocation instrumentation; controlled runtime; Loom cell creation | invalid configurations fail; equivalent URIs share one origin; canonical request hits allocate no host storage; each pair has one stable cell; the anonymous partition binds one runtime but moves across its workers; explicit placement follows its contract |
 2621         -
| Smithy `HttpClient` boundary and operation policy                     | unit; controlled runtime; differential                                            | settings-specific facades share one pool and admission authority; request version does not split pool or admission identity; timeout scope, maintenance ownership, validation timing, and `hyper/1.x` metadata are preserved                                   |
 2622         -
| Local reuse, establishment, ALPN convergence, and generation identity | unit/property; bounded transitions; controlled runtime; wire; differential        | local hits avoid origin-wide coordination; connector readiness and placement hold; one H2 flight/generation wins; losing transports, leases, and waiters terminate exactly once                                                                                |
 2623         -
| Admission, demand generations, and origin/group ordering              | property; bounded transitions; Loom scheduling kernels; stress                    | the bound is never exceeded; stale demand snapshots and availability reports cannot resurrect obsolete state; each resource uses the correct scheduling scope; eligible committed demand has bounded overtaking                                                |
 2624         -
| Capacity delivery, H1 reuse operations, and owning-cell turns         | bounded transitions; Loom delivery/reuse kernels; controlled cancellation         | every permit and provisional H1 has one owner; candidate transfer revalidates owning-cell state; acknowledgement fences close; cancellation and task drop refunnel once; return interception cannot starve owning-cell demand                                  |
 2625         -
| H2 publication and request leases                                     | bounded transitions; Loom publication kernel; wire                                | publication moves no capacity; generation gates prioritize committed waiters; stale generations cannot dispatch; send and receive endpoints both terminate before lease release                                                                                |
 2626         -
| Dispatch, retry, bodies, upgrades, and metadata                       | controlled runtime; wire; differential                                            | one selected sender commits and calls Hyper without an intermediate published state; only Hyper-certified unsent reuse retries; cancellation has a stage-local owner; H1 framing and H2 stream isolation hold; metadata and error behavior are preserved       |
 2627         -
| Logical and physical close, maintenance, events, and statistics       | unit/property; Loom close/guard/maintenance kernels; time/runtime; wire           | driver completion and cancellation request logical close; permit release occurs once; root-I/O drop ends physical accounting; idle deadlines and shutdown clean up; callbacks see committed state and gauges converge to lifecycle state                       |
 2628         -
| Locality, liveness, topology scaling, and retained memory             | bounded transitions; repeated stress; benchmarks                                  | grant work is independent of partition count; no reachable resource remains idle behind demand; local reuse does not regress; topology scales without moving I/O; physical-socket and route-memory costs are measured                                          |
 2629         -
 2630         -
Correctness acceptance requires every applicable unit, bounded-transition, Loom, controlled-runtime, wire,
 2631         -
and differential suite to pass. A bounded state-space enumeration reports neither an invalid state nor a
 2632         -
terminal accounting error and identifies the operation set and bound it actually explored; an ordinary
 2633         -
bounded-transition test makes no exhaustiveness claim. Focused liveness tests must show that usable
 2634         -
capacity or a compatible connection enables progress under the scheduling conditions they construct.
 2635         -
Concurrency-sensitive suites run repeatedly in CI; a flaky failure is a correctness failure, not benchmark
 2636         -
noise.
        2659  +
Validation supplies evidence for the contracts above; it does not redefine
        2660  +
them.
        2661  +
        2662  +
| Concern | Required evidence |
        2663  +
| --- | --- |
        2664  +
| State and ownership | Focused transition tests and executable invariant checks. |
        2665  +
| Concurrency | Loom models using the production synchronization-bearing state machines. |
        2666  +
| Protocol behavior | Controlled-runtime and wire tests covering cancellation, close, reuse, multiplexing, upgrades, and failure. |
        2667  +
| Client compatibility | Implementation-neutral differential tests for request behavior, metadata, timeout scope, and error classification. |
        2668  +
| Performance and scaling | Allocation, contention, stress, and benchmark measurements under the relevant concurrency and partition topology. |
        2669  +
        2670  +
A correctness claim requires the applicable state, concurrency, protocol, and
        2671  +
compatibility evidence. A performance, bounded-work, or topology-scaling claim
        2672  +
requires measurements that exercise the claimed workload and bound. Focused
        2673  +
state-space enumeration identifies its operation alphabet and bound; an
        2674  +
ordinary transition test does not claim exhaustiveness.
 2637   2675   
 2638   2676   
---
 2639   2677   
 2640   2678   
## Appendix C: FAQ
 2641   2679   
 2642   2680   
### Why not build the pool from composable connector layers?
 2643   2681   
 2644   2682   
Hyper's ecosystem offers pooling as connector middleware — a cache layer, a connection-limit layer, a
 2645   2683   
negotiate layer, each a `Service` wrapping the one below. The pool owns the coordination layer because the
 2646   2684   
state it coordinates is not local to any one layer, and stacked layers give no layer the whole picture.

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-http-client/src/client/pool.rs

@@ -23,23 +264,281 @@
   43     43   
//! # State ownership
   44     44   
//!
   45     45   
//! ```text
   46     46   
//! ConnectionPool
   47     47   
//! `-- PoolInner
   48     48   
//!     |-- immutable policy and transport factory
   49     49   
//!     `-- PartitionRegistry
   50     50   
//!         |-- PartitionState per partition
   51     51   
//!         |   |-- runtime placement and idle maintenance
   52     52   
//!         |   `-- OriginCell per canonical origin
   53         -
//!         |       |-- acquisition queue
   54         -
//!         |       `-- protocol connection records
          53  +
//!         |       |-- acquisition queue and supply revisions
          54  +
//!         |       |-- H1 connection ownership
          55  +
//!         |       `-- H2 flights, generations, routes, and gates
   55     56   
//!         `-- OriginAdmission per bounded origin
   56         -
//!             |-- connection permits
   57         -
//!             |-- cross-cell demand order
   58         -
//!             `-- peer reuse indexes
          57  +
//!             |-- capacity budget
          58  +
//!             |-- demand schedule
          59  +
//!             |-- H1 supply index and retained matches
          60  +
//!             `-- H2 supply index and route/reclaim state
   59     61   
//! ```
   60     62   
//!
   61         -
//! One `OriginCell` lock owns local acquisition order and protocol residence.
          63  +
//! One `OriginCell` lock owns local acquisition order and protocol state.
   62     64   
//! For a bounded origin, `OriginAdmission` separately owns the origin-wide
   63         -
//! connection limit and cross-cell scheduling. An H2 request-lease lock owns
   64         -
//! its two endpoint bits. `ConnectionState` owns logical connection lifetime,
   65         -
//! and partition maintenance owns its scheduler state.
          65  +
//! connection limit and cross-cell matching. An H2 request-claim lock records
          66  +
//! independent upload and response completion. `ConnectionState` owns logical
          67  +
//! connection lifetime, and partition maintenance owns its timer state.
   66     68   
//!
   67         -
//! No two pool locks are held together. Delivery and publication guards carry
   68         -
//! payload or identity between cell and admission scopes. H2 lease completion
   69         -
//! detaches its dispatch guard before entering connection or cell state.
          69  +
//! No two pool locks are held together. Demand snapshots and supply revisions
          70  +
//! move cell state into admission. Assignments and detached guards carry one
          71  +
//! selected payload or route back toward a cell. H2 claim completion detaches
          72  +
//! its dispatch guard before entering connection or cell state.
   70     73   
//! Maintenance detaches cells and wakers before expiration or wake callbacks.
   71     74   
//!
   72     75   
//! # HTTP/1 request lifecycle
   73     76   
//!
   74     77   
//! Hyper represents an HTTP/1 connection with one exclusive
   75     78   
//! `SendRequest<SdkBody>` handle. This module calls that handle the sender. The
   76     79   
//! sender authorizes request dispatch but does not own the socket or protocol
   77     80   
//! driver.
   78     81   
//!
   79     82   
//! ```text
   80     83   
//! Client(partition, request)
   81     84   
//! `-- OriginCell(partition, origin)
   82     85   
//!     |-- local idle sender --------------------------> H1Selection
   83     86   
//!     `-- acquisition queue
   84     87   
//!         |-- returned local or eligible peer sender -> H1Selection
   85     88   
//!         |-- capacity permit -> establish HTTP/1 ---> H1Selection
   86     89   
//!         `-- reclaimed peer capacity -> establish ---> H1Selection
   87     90   
//!
   88     91   
//! H1Selection -- Hyper accepts request --> H1Exchange
   89     92   
//! H1Exchange
   90     93   
//!     |-- complete response + ready --> offer to owning OriginCell
   91     94   
//!     `-- failure, cancellation, or upgrade ----------> retire pool record
   92     95   
//! ```
   93     96   
//!
   94     97   
//! A local hit touches only the cell lock. On a miss, one queued acquisition
   95     98   
//! remains authoritative while a returned sender and establishment race to
   96     99   
//! satisfy it. Bounded origins may borrow an eligible peer sender or reclaim a
   97    100   
//! peer connection and transfer its permit; active connections are not
   98    101   
//! reclaimed.
   99    102   
//!
  100    103   
//! Dispatch commits against logical close before Hyper receives the request.
  101    104   
//! Once accepted, the response lifecycle retains the sender until Hyper proves
  102    105   
//! a complete reusable message boundary. Failure retires the connection, and
  103    106   
//! an upgrade closes the pool record before exposing upgraded root I/O.
  104    107   
//!
  105    108   
//! # HTTP/2 request lifecycle
  106    109   
//!
  107    110   
//! One HTTP/2 connection carries many concurrent request streams. The pool
  108    111   
//! calls one installed incarnation of that connection a generation. A
  109    112   
//! replacement connection receives a new generation identity so delayed
  110    113   
//! close, route, and completion work cannot affect it.
  111    114   
//!
  112    115   
//! The connection-owning cell retains the generation's authoritative Hyper
  113    116   
//! request handle and capacity. A requesting cell may retain only a route that
  114    117   
//! names the owning cell and one exact accepting generation. Each use
  115    118   
//! revalidates that route before cloning a transient request handle.
  116    119   
//!
  117    120   
//! ```text
  118    121   
//! Client(partition, request)
  119    122   
//! `-- OriginCell(partition, origin)
  120    123   
//!     |-- local accepting generation ----------------> H2Activation
  121    124   
//!     `-- acquisition queue
  122    125   
//!         |-- local flight result --------------------> H2Activation
  123    126   
//!         |-- eligible peer generation route --------> H2Activation
  124    127   
//!         `-- capacity permit -> connect + ALPN
  125    128   
//!             |-- HTTP/2 -> join or drive one flight -> H2Activation
  126    129   
//!             `-- HTTP/1 -> H1Selection or incompatible-version error
  127    130   
//!
  128         -
//! H2Activation -- Hyper accepts request --> accepted request lease
  129         -
//! accepted request lease
  130         -
//!     |-- request body ends or drops -----> send endpoint complete
  131         -
//!     `-- response body ends or drops ----> receive endpoint complete
  132         -
//! both endpoints complete ----------------> release generation request count
         131  +
//! H2Activation -- Hyper accepts request --> H2RequestClaim
         132  +
//! H2RequestClaim
         133  +
//!     |-- request body ends or drops -----> upload side complete
         134  +
//!     `-- response body ends or drops ----> response side complete
         135  +
//! both sides complete --------------------> release generation request count
  133    136   
//! ```
  134    137   
//!
  135    138   
//! `H2Activation` reserves pool accounting for a prospective stream on one
  136    139   
//! exact generation. It is not yet an HTTP/2 stream. Dropping it before Hyper
  137    140   
//! accepts the request returns its generation-gate turn and request count.
  138         -
//! Acceptance creates two independent endpoints because an upload and response
         141  +
//! Acceptance creates two independent completion sides because upload and response
  139    142   
//! can finish in either order. Logical close stops new activations and releases
  140    143   
//! bounded capacity; accepted streams retain the draining generation until
  141         -
//! both endpoints end. Hyper remains responsible for stream identifiers,
         144  +
//! both sides end. Hyper remains responsible for stream identifiers,
  142    145   
//! stream credit, and flow control.
  143    146   
//!
  144         -
//! Peer publication moves only route identity. The socket, protocol driver,
         147  +
//! A peer route moves only generation identity. The socket, protocol driver,
  145    148   
//! request handle, and capacity remain with the connection-owning partition.
  146    149   
//!
  147    150   
//! `ConnectionState` separates logical close, accepted-request accounting, and
  148         -
//! root-I/O ownership. Logical close rejects new dispatch and releases bounded
  149         -
//! capacity. `DispatchGuard` follows an accepted request, while
  150         -
//! `PhysicalConnectionGuard` follows root I/O until the pool no longer owns
  151         -
//! that transport; neither describes the operating system TCP state. All
  152         -
//! connection-owned work runs through the partition [`DriverSpawner`].
         151  +
//! physical connection ownership. Logical close rejects new dispatch and
         152  +
//! releases bounded capacity. `DispatchGuard` follows an accepted request,
         153  +
//! while `PhysicalConnectionGuard` follows root I/O until the client releases
         154  +
//! its transport handle. The operating system may continue TCP teardown
         155  +
//! afterward. All connection-owned work runs through the partition
         156  +
//! [`DriverSpawner`].
  153    157   
  154    158   
#![cfg_attr(
  155    159   
    smithy_http_client_loom,
  156    160   
    allow(
  157    161   
        dead_code,
  158    162   
        reason = "Loom builds replace runtime and transport paths with focused coordination models"
  159    163   
    )
  160    164   
)]
  161    165   
  162    166   
mod admission;
  163    167   
mod builder;
  164    168   
mod cell;
  165    169   
mod client;
  166    170   
mod connection;
  167    171   
mod dispatch;
  168    172   
mod establish;
  169    173   
mod maintenance;
  170    174   
mod origin;
  171    175   
mod partition;
  172    176   
mod registry;
  173    177   
  174    178   
pub use builder::{BuildError, Builder};
  175    179   
pub use client::{Client, ClientBuildError};
  176    180   
pub use connection::{CloseReason, ConnectionId};
  177    181   
pub use origin::{InvalidOrigin, OriginKey};
  178    182   
#[cfg(feature = "rt-tokio")]
  179    183   
pub use partition::TokioDriverSpawner;
  180    184   
pub use partition::{ConnectionReuseScope, DriverSpawner, Partition, PartitionId};
  181    185   
  182    186   
use crate::sync::Arc;
         187  +
use aws_smithy_runtime_api::client::result::ConnectorError;
         188  +
use aws_smithy_types::body::SdkBody;
  183    189   
use establish::TransportFactory;
  184         -
use registry::PartitionRegistry;
         190  +
use http_1x::{Request, Response};
         191  +
use registry::{PartitionRegistry, PartitionState};
  185    192   
use std::fmt;
  186    193   
use std::num::NonZeroUsize;
  187    194   
use std::sync::atomic::AtomicU64;
  188    195   
use std::sync::Arc as StdArc;
  189    196   
use std::time::Duration;
  190    197   
  191    198   
/// Shared connection topology and pooling policy.
  192    199   
///
  193    200   
/// Construct a pool with [`ConnectionPool::builder`], then create [`Client`]
  194    201   
/// handles for its anonymous or explicit partitions. The pool itself owns no
  195    202   
/// request placement; each client supplies that partition choice. Cloning a
  196    203   
/// pool or client shares all retained connections and admission state.
  197    204   
///
  198    205   
/// A pool built with explicit partitions does not also create an anonymous
  199    206   
/// partition. Dropping the final shared owner logically closes retained
  200    207   
/// connections and stops partition maintenance.
  201    208   
#[derive(Clone)]
  202    209   
pub struct ConnectionPool {
  203    210   
    /// Shared pool policy, topology, connector, and connection-ID allocator.
  204    211   
    inner: Arc<PoolInner>,
  205    212   
}
  206    213   
  207    214   
impl ConnectionPool {
  208    215   
    /// Returns a builder for a new connection pool.
  209    216   
    pub fn builder() -> Builder<super::TlsUnset> {
  210    217   
        Builder::default()
  211    218   
    }
         219  +
         220  +
    /// Routes one request from its selected partition through pool dispatch.
         221  +
    pub(in crate::client::pool) async fn send_request(
         222  +
        &self,
         223  +
        partition: Arc<PartitionState>,
         224  +
        request: Request<SdkBody>,
         225  +
        options: dispatch::RequestOptions,
         226  +
    ) -> Result<Response<SdkBody>, ConnectorError> {
         227  +
        dispatch::send(self, partition, request, options).await
         228  +
    }
  212    229   
}
  213    230   
  214    231   
impl fmt::Debug for ConnectionPool {
  215    232   
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  216    233   
        f.debug_struct("ConnectionPool")
  217    234   
            .field("partitions", &self.inner.registry)
  218    235   
            .field("idle_timeout", &self.inner.config.idle_timeout)
  219    236   
            .field(
  220    237   
                "max_connections_per_host",
  221    238   
                &self.inner.config.max_connections_per_host,
  222    239   
            )
  223    240   
            .field("reuse_scope", &self.inner.config.reuse_scope)
  224    241   
            .finish_non_exhaustive()
  225    242   
    }
  226    243   
}
  227    244   
  228    245   
/// Immutable pool policy retained with the partition registry.
  229    246   
#[derive(Clone, Debug)]
  230    247   
struct PoolConfig {
  231    248   
    /// Duration after which a reusable idle connection is retired.
  232    249   
    idle_timeout: Option<Duration>,
  233    250   
    /// Optional logical-connection bound shared by every partition for one origin.
  234    251   
    max_connections_per_host: Option<NonZeroUsize>,
  235    252   
    /// Partitions allowed to dispatch through one another's connections.
  236    253   
    reuse_scope: ConnectionReuseScope,
  237    254   
}
  238    255   
  239    256   
/// Shared implementation state behind [`ConnectionPool`].
  240    257   
struct PoolInner {
  241    258   
    /// Immutable settings shared by pool operations.
  242    259   
    config: PoolConfig,
  243         -
    /// Fixed partitions and lazily published per-origin state.
         260  +
    /// Fixed partitions and lazily created per-origin state.
  244    261   
    registry: PartitionRegistry,
  245    262   
    /// Type-erased construction of one partition-bound transport.
  246    263   
    transport: StdArc<dyn TransportFactory>,
  247    264   
    /// Monotonic identity source shared by every physical connection.
  248    265   
    next_connection_id: AtomicU64,
  249    266   
}
  250    267   
  251    268   
impl Drop for PoolInner {
  252    269   
    fn drop(&mut self) {
  253    270   
        self.registry.close_all(CloseReason::PoolDropped);

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-http-client/src/client/pool/admission.rs

@@ -1,1 +1217,1040 @@
    1      1   
/*
    2      2   
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
    3      3   
 * SPDX-License-Identifier: Apache-2.0
    4      4   
 */
    5      5   
    6         -
//! Capacity and demand shared by the cells of one bounded origin.
           6  +
//! Demand, connection supply, and capacity shared by one bounded origin.
    7      7   
//!
    8      8   
//! [`OriginAdmission`] stores the origin once and keys its partition cells,
    9         -
//! demand records, and availability indexes by [`PartitionId`]. Its lock is the
   10         -
//! sole authority for available capacity, origin-wide demand order, and reuse
   11         -
//! operations. Cell locks are never held with the admission lock.
           9  +
//! demand records, and supply indexes by [`PartitionId`]. Its lock is the sole
          10  +
//! authority for admission-resident capacity, origin-wide demand order, and
          11  +
//! retained HTTP/1 matching. Cell locks are never held with the admission lock.
   12     12   
//!
   13     13   
//! A bounded acquisition moves through the layer in this order:
   14     14   
//!
   15         -
//! 1. A cell publishes a complete [`DemandSnapshot`] after releasing its lock.
          15  +
//! 1. A cell submits a complete [`DemandSnapshot`] after releasing its lock.
   16     16   
//! 2. [`DemandSchedule`] replaces that partition snapshot and links active
   17     17   
//!    demand in the origin order and its eligibility-group order.
   18         -
//! 3. Capacity delivery and HTTP/1 reuse select from the origin head. HTTP/2
   19         -
//!    publication pairs a group head with an advertised peer generation.
   20         -
//! 4. A [`DeliveryGuard`] materializes its one capacity or HTTP/1 payload before
   21         -
//!    reserving the requesting waiter. Payload materialization failure
   22         -
//!    therefore leaves the waiter in its existing residence.
   23         -
//! 5. An [`H2PublicationGuard`] carries identities through the same unlocked
          18  +
//! 3. Capacity delivery and HTTP/1 matching select from the origin head.
          19  +
//!    HTTP/2 route matching pairs a group head with peer supply.
          20  +
//! 4. The selected demand becomes a [`DemandAssignment`] while a resource
          21  +
//!    crosses lock domains. Both scheduling positions remain attached, so
          22  +
//!    refusal can restore the demand without changing its order.
          23  +
//! 5. A [`DeliveryGuard`] resolves its capacity or HTTP/1 payload before
          24  +
//!    committing the exact assigned waiter.
          25  +
//! 6. An [`H2RouteGuard`] carries identities through the same unlocked
   24     26   
//!    handoff. It revalidates the connection-owning generation, then installs
   25     27   
//!    a route and activation opportunity in the requesting cell.
   26         -
//! 6. The requesting cell becomes authoritative before either guard submits its
   27         -
//!    admission acknowledgement. Rejection and drop execute the same terminal
   28         -
//!    acknowledgement paths.
          28  +
//! 7. The requesting cell becomes authoritative before either guard settles the
          29  +
//!    assignment. Refusal and drop execute the same fallback paths.
   29     30   
//!
   30     31   
//! Capacity and HTTP/1 handoffs own the payload they must return on failure.
   31         -
//! HTTP/2 publication moves no payload: the connection-owning cell retains its
   32         -
//! request handle, driver, socket, and capacity. No cell lock is held with
   33         -
//! the admission lock, and connection-owning and requesting cell locks are
   34         -
//! never held together.
          32  +
//! HTTP/2 route installation moves no payload: the connection-owning cell
          33  +
//! retains its request handle, driver, socket, and capacity. No cell lock is
          34  +
//! held with the admission lock, and connection-owning and requesting cell
          35  +
//! locks are never held together.
   35     36   
   36         -
use super::cell::OriginCell;
          37  +
use super::cell::{H1ReservationDecision, OriginCell};
   37     38   
use super::origin::OriginKey;
   38     39   
use super::partition::{EligibilityGroup, PartitionId};
          40  +
use super::registry::AdmissionPolicy;
   39     41   
use crate::sync::{Arc, Mutex, Weak};
   40     42   
use std::collections::HashMap;
   41     43   
use std::fmt;
   42     44   
use std::num::NonZeroUsize;
   43     45   
   44     46   
mod delivery;
   45     47   
mod demand;
          48  +
mod h1;
          49  +
mod h2;
   46     50   
mod order;
   47         -
mod publication;
   48         -
pub(in crate::client::pool) mod reuse;
   49     51   
   50         -
use self::demand::{DemandSchedule, PreparedCapacityDelivery};
   51         -
use self::order::{IntrusiveLinks, IntrusiveOrder};
   52         -
use self::publication::{
   53         -
    H2Publication, H2PublicationGuard, H2ReclaimAction, PreparedH2Publication, PreparedH2Reclaim,
          52  +
use self::demand::{
          53  +
    DemandAssignment, DemandAssignmentId, DemandAssignmentOutcome, DemandSchedule,
          54  +
    PreparedCapacityDelivery,
          55  +
};
          56  +
pub(in crate::client::pool) use self::demand::{
          57  +
    DemandId, DemandSnapshot, ProtocolRequirement, SnapshotVersion,
   54     58   
};
          59  +
use self::h1::{
          60  +
    H1CancellationAction, H1CapacityReclaim, H1ReservationAction, H1SupplierSettlement, H1Supply,
          61  +
    H1SupplyOutcome,
          62  +
};
          63  +
use self::h2::{H2CapacityReclaim, H2RouteGuard, H2Supply, PreparedH2Reclaim, PreparedH2Route};
          64  +
use self::order::{IntrusiveLinks, IntrusiveOrder};
   55     65   
pub(in crate::client::pool) use delivery::DeliveryGuard;
   56         -
pub(in crate::client::pool) use publication::H2AdvertisementSnapshot;
          66  +
pub(in crate::client::pool) use h1::{
          67  +
    H1Candidate, H1MatchId, H1SupplyStatus, PreparedH1Reservation,
          68  +
};
          69  +
pub(in crate::client::pool) use h2::H2SupplyStatus;
   57     70   
   58         -
/// Protocol capability required by the head waiter in a cell.
          71  +
/// Complete admission-facing protocol status at one cell-owned revision.
   59     72   
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
   60         -
pub(crate) enum ProtocolRequirement {
   61         -
    /// The waiter requires HTTP/1 wire semantics.
   62         -
    H1Required,
   63         -
    /// The waiter may dispatch over HTTP/1 or HTTP/2.
   64         -
    H1Compatible,
   65         -
    /// The waiter requires HTTP/2.
   66         -
    H2Required,
          73  +
pub(in crate::client::pool) struct SupplyRevision<T> {
          74  +
    /// Monotonic sequence number allocated under the cell lock.
          75  +
    pub(in crate::client::pool) revision: u64,
          76  +
    /// Complete status represented by this revision.
          77  +
    pub(in crate::client::pool) status: T,
   67     78   
}
   68     79   
   69         -
impl ProtocolRequirement {
   70         -
    /// Returns whether an HTTP/1 sender can satisfy this requirement.
   71         -
    pub(crate) fn accepts_h1(self) -> bool {
   72         -
        self != Self::H2Required
   73         -
    }
   74         -
   75         -
    /// Returns whether an HTTP/2 activation can satisfy this requirement.
   76         -
    pub(crate) fn accepts_h2(self) -> bool {
   77         -
        self != Self::H1Required
   78         -
    }
   79         -
}
   80         -
   81         -
/// Identity of one cell-local demand generation.
   82         -
///
   83         -
/// A generation begins when a waiter becomes the cell's bounded-demand head.
   84         -
/// The cell identity and this value together identify its publications.
   85         -
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
   86         -
pub(crate) struct DemandId(u64);
   87         -
   88         -
impl DemandId {
   89         -
    /// Constructs an identity allocated by one cell's monotonic demand counter.
   90         -
    pub(crate) const fn from_u64(value: u64) -> Self {
   91         -
        Self(value)
   92         -
    }
   93         -
}
   94         -
   95         -
/// Strict ordering of complete publications within one [`DemandId`].
   96         -
///
   97         -
/// Versions never wrap. Preserving strict order prevents a delayed publication
   98         -
/// from becoming current again after counter reuse (an ABA). A new FIFO head
   99         -
/// receives a new [`DemandId`] and starts again at [`SnapshotVersion::INITIAL`].
  100         -
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
  101         -
pub(crate) struct SnapshotVersion(u64);
  102         -
  103         -
impl SnapshotVersion {
  104         -
    /// Version assigned to the first snapshot for a demand generation.
  105         -
    pub(crate) const INITIAL: Self = Self(0);
  106         -
  107         -
    /// Advances the publication version for the same demand generation.
  108         -
    ///
  109         -
    /// # Panics
  110         -
    ///
  111         -
    /// Panics after `u64::MAX` replacements of one generation. Wrapping would
  112         -
    /// break stale-publication rejection; a new head waiter starts a
  113         -
    /// new generation and resets this counter.
  114         -
    pub(crate) fn next(self) -> Self {
  115         -
        Self(
  116         -
            self.0
  117         -
                .checked_add(1)
  118         -
                .expect("demand snapshot version exhausted"),
  119         -
        )
  120         -
    }
  121         -
}
  122         -
  123         -
/// Complete state published for one demand identity.
  124         -
#[derive(Clone, Debug, Eq, PartialEq)]
  125         -
pub(crate) enum DemandState {
  126         -
    /// The cell has a waiter that still needs a connection.
  127         -
    Active {
  128         -
        /// Protocol capability required by the cell's oldest waiter.
  129         -
        head: ProtocolRequirement,
  130         -
        /// Partitions whose connections may satisfy this demand.
  131         -
        eligibility_group: EligibilityGroup,
  132         -
    },
  133         -
    /// The demand has ended without a successor in this snapshot.
  134         -
    Inactive,
  135         -
}
  136         -
  137         -
/// Versioned replacement state for one cell's current demand generation.
  138         -
///
  139         -
/// A publication replaces the complete previous snapshot. This prevents a
  140         -
/// delayed active publication from reviving demand retired by a newer version.
  141         -
#[derive(Clone, Debug, Eq, PartialEq)]
  142         -
pub(crate) struct DemandSnapshot {
  143         -
    /// Cell-local identity of the head-waiter generation.
  144         -
    id: DemandId,
  145         -
    /// Ordering among publications for the same demand identity.
  146         -
    version: SnapshotVersion,
  147         -
    /// Complete current state of the demand generation.
  148         -
    state: DemandState,
  149         -
}
  150         -
  151         -
impl DemandSnapshot {
  152         -
    /// Describes the current head waiter for an active demand generation.
  153         -
    pub(crate) fn active(
  154         -
        id: DemandId,
  155         -
        version: SnapshotVersion,
  156         -
        head: ProtocolRequirement,
  157         -
        eligibility_group: EligibilityGroup,
  158         -
    ) -> Self {
  159         -
        Self {
  160         -
            id,
  161         -
            version,
  162         -
            state: DemandState::Active {
  163         -
                head,
  164         -
                eligibility_group,
  165         -
            },
  166         -
        }
  167         -
    }
  168         -
  169         -
    /// Returns whether this active demand can use an HTTP/2 generation.
  170         -
    pub(crate) fn accepts_h2(&self) -> bool {
  171         -
        matches!(
  172         -
            self.state,
  173         -
            DemandState::Active { head, .. } if head.accepts_h2()
  174         -
        )
  175         -
    }
  176         -
  177         -
    /// Retires a demand generation at the supplied publication version.
  178         -
    pub(crate) fn inactive(id: DemandId, version: SnapshotVersion) -> Self {
  179         -
        Self {
  180         -
            id,
  181         -
            version,
  182         -
            state: DemandState::Inactive,
  183         -
        }
  184         -
    }
  185         -
  186         -
    /// Returns the demand identity for cross-module transition tests.
  187         -
    #[cfg(test)]
  188         -
    pub(in crate::client::pool) fn id_for_test(&self) -> DemandId {
  189         -
        self.id
  190         -
    }
  191         -
  192         -
    /// Returns whether this snapshot still requests capacity.
  193         -
    pub(in crate::client::pool) fn is_active(&self) -> bool {
  194         -
        matches!(self.state, DemandState::Active { .. })
  195         -
    }
  196         -
  197         -
    /// Returns whether this snapshot may replace `current`.
  198         -
    ///
  199         -
    /// A newer generation supersedes every snapshot of an older generation;
  200         -
    /// publications within one generation are ordered by snapshot version.
  201         -
    fn is_newer_than(&self, current: &Self) -> bool {
  202         -
        self.id > current.id || (self.id == current.id && self.version > current.version)
          80  +
impl<T> SupplyRevision<T> {
          81  +
    pub(in crate::client::pool) fn new(revision: u64, status: T) -> Self {
          82  +
        Self { revision, status }
  203     83   
    }
  204     84   
}
  205     85   
  206     86   
/// One conserved unit of bounded-origin connection capacity.
  207     87   
///
  208     88   
/// This non-`Copy` value proves that admission removed one unit from its
  209     89   
/// available count. Identities are never reused so diagnostics remain
  210     90   
/// unambiguous.
  211         -
pub(super) struct Permit(u64);
          91  +
pub(super) struct CapacityPermit(u64);
  212     92   
  213         -
impl fmt::Debug for Permit {
          93  +
impl fmt::Debug for CapacityPermit {
  214     94   
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  215         -
        f.debug_tuple("Permit").field(&self.0).finish()
          95  +
        f.debug_tuple("CapacityPermit").field(&self.0).finish()
  216     96   
    }
  217     97   
}
  218     98   
  219         -
/// Stable identity of one admission-to-cell delivery.
  220         -
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
  221         -
pub(super) struct DeliveryId(u64);
  222         -
  223     99   
/// Shared admission authority for one bounded origin.
  224    100   
///
  225    101   
/// The state lock is the only authority for available capacity while it
  226    102   
/// resides in admission, and for demand order while no delivery is crossing
  227    103   
/// to a cell. Delivery work is detached before this lock is released.
  228         -
pub(crate) struct OriginAdmission {
         104  +
pub(in crate::client::pool) struct OriginAdmission {
  229    105   
    /// Canonical origin shared by every partition represented in `state`.
  230    106   
    origin: OriginKey,
  231    107   
    /// Whether admission may close idle H2 capacity for H1-required demand.
  232         -
    allow_h2_reclaim_for_h1: bool,
  233         -
    /// Capacity, demand, and delivery-fence state for this origin.
         108  +
    can_reclaim_h2_for_h1: bool,
         109  +
    /// Capacity, demand, and supply state for this origin.
  234    110   
    state: Mutex<AdmissionState>,
  235    111   
}
  236    112   
  237    113   
impl OriginAdmission {
  238         -
    /// Creates admission for at most `limit` logically open connections.
  239         -
    pub(crate) fn new(
  240         -
        origin: OriginKey,
  241         -
        limit: NonZeroUsize,
  242         -
        allow_h2_reclaim_for_h1: bool,
  243         -
    ) -> Arc<Self> {
         114  +
    /// Creates admission for one bounded origin policy.
         115  +
    pub(in crate::client::pool) fn new(origin: OriginKey, policy: AdmissionPolicy) -> Arc<Self> {
  244    116   
        Arc::new(Self {
  245    117   
            origin,
  246         -
            allow_h2_reclaim_for_h1,
  247         -
            state: Mutex::new(AdmissionState::new(limit)),
         118  +
            can_reclaim_h2_for_h1: policy.can_reclaim_h2_for_h1(),
         119  +
            state: Mutex::new(AdmissionState::new(policy.connection_limit())),
  248    120   
        })
  249    121   
    }
  250    122   
  251    123   
    #[cfg(test)]
  252    124   
    pub(super) fn for_test(limit: NonZeroUsize) -> Arc<Self> {
  253    125   
        Self::for_test_with_h2_reclaim(limit, true)
  254    126   
    }
  255    127   
  256    128   
    #[cfg(test)]
  257    129   
    pub(super) fn for_test_with_h2_reclaim(
  258    130   
        limit: NonZeroUsize,
  259         -
        allow_h2_reclaim_for_h1: bool,
         131  +
        can_reclaim_h2_for_h1: bool,
  260    132   
    ) -> Arc<Self> {
  261    133   
        Self::new(
  262    134   
            OriginKey::from_parts(http_1x::uri::Scheme::HTTPS, "example.com", None)
  263    135   
                .expect("test origin is valid"),
  264         -
            limit,
  265         -
            allow_h2_reclaim_for_h1,
         136  +
            AdmissionPolicy::new(limit, can_reclaim_h2_for_h1),
  266    137   
        )
  267    138   
    }
  268    139   
  269    140   
    /// Returns the canonical origin coordinated by this admission authority.
  270    141   
    fn origin(&self) -> &OriginKey {
  271    142   
        &self.origin
  272    143   
    }
  273    144   
  274    145   
    /// Registers or returns the unique retained cell for an identity.
  275         -
    pub(crate) fn register_cell(origin: &Arc<Self>, candidate: Arc<OriginCell>) -> Arc<OriginCell> {
         146  +
    pub(in crate::client::pool) fn register_cell(
         147  +
        origin: &Arc<Self>,
         148  +
        candidate: Arc<OriginCell>,
         149  +
    ) -> Arc<OriginCell> {
  276    150   
        let partition = candidate.id().partition();
  277    151   
        assert_eq!(
  278    152   
            origin.origin(),
  279    153   
            candidate.id().origin(),
  280    154   
            "cell origin did not match its admission authority"
  281    155   
        );
  282    156   
        let existing = {
  283    157   
            let mut state = origin.state.lock();
  284    158   
            match state.cells.get(&partition).and_then(Weak::upgrade) {
  285    159   
                Some(existing) => Some(existing),
  286    160   
                None => {
  287    161   
                    state.cells.insert(partition, Weak::from_arc(&candidate));
  288    162   
                    None
  289    163   
                }
  290    164   
            }
  291    165   
        };
  292    166   
        if let Some(existing) = existing {
  293    167   
            // Dropping the losing cell may return capacity to this admission
  294    168   
            // authority, so release the state lock first.
  295    169   
            drop(candidate);
  296    170   
            existing
  297    171   
        } else {
  298    172   
            candidate
  299    173   
        }
  300    174   
    }
  301    175   
  302         -
    /// Publishes a complete demand snapshot and drives any resulting delivery.
  303         -
    pub(crate) fn publish_demand(
  304         -
        origin: &Arc<Self>,
  305         -
        requesting_partition: PartitionId,
         176  +
    /// Submits a complete demand snapshot and runs resulting detached actions.
         177  +
    pub(in crate::client::pool) fn submit_demand_snapshot(
         178  +
        admission: &Arc<Self>,
         179  +
        requester: PartitionId,
  306    180   
        snapshot: DemandSnapshot,
  307    181   
    ) {
  308    182   
        let action = {
  309         -
            let mut state = origin.state.lock();
  310         -
            state.publish_demand(requesting_partition, snapshot);
  311         -
            Self::prepare_action(origin, &mut state)
         183  +
            let mut state = admission.state.lock();
         184  +
            state.apply_demand_snapshot(requester, snapshot);
         185  +
            Self::prepare_action(admission, &mut state)
  312    186   
        };
  313         -
        Self::drive(action);
         187  +
        Self::run_action_chain(action);
  314    188   
    }
  315    189   
  316    190   
    /// Selects at most one action while admission is locked.
  317    191   
    ///
  318    192   
    /// Cancellation cleanup runs first, followed by available capacity,
  319         -
    /// compatible HTTP/2 publication, HTTP/1 reuse, and idle-H2 reclaim. The
         193  +
    /// a compatible peer HTTP/2 route, HTTP/1 reuse, and idle-H2 reclaim. The
  320    194   
    /// returned action owns everything needed to run after releasing the
  321         -
    /// admission lock. Its completion prepares the next action, forming an
  322         -
    /// iterative pump without nesting admission and cell locks.
         195  +
    /// admission lock. Its completion may prepare a successor for the caller's
         196  +
    /// action chain. A guard dropped outside that chain may start another
         197  +
    /// chain, but admission and cell locks are never nested.
  323    198   
    fn prepare_action(origin: &Arc<Self>, state: &mut AdmissionState) -> Option<AdmissionAction> {
  324         -
        if let Some(cancellation) = state.h1.prepare_cancellation() {
  325         -
            return Some(AdmissionAction::H1(reuse::H1ReuseAction::cancel(
  326         -
                origin.clone(),
  327         -
                cancellation,
  328         -
            )));
         199  +
        if let Some(cancellation) = state.h1_supply.prepare_cancellation() {
         200  +
            return Some(AdmissionAction::CancelH1Reservation(
         201  +
                H1CancellationAction::new(origin.clone(), cancellation),
         202  +
            ));
  329    203   
        }
  330         -
        if let Some(pending) = state.schedule_one() {
  331         -
            return Some(AdmissionAction::Delivery(DeliveryGuard::capacity(
         204  +
        if let Some(prepared) = state.prepare_capacity_delivery() {
         205  +
            return Some(AdmissionAction::Deliver(DeliveryGuard::capacity(
  332    206   
                origin.clone(),
  333         -
                pending.delivery,
  334         -
                pending.requesting_partition,
  335         -
                pending.demand,
  336         -
                pending.permit,
         207  +
                prepared.assignment,
         208  +
                prepared.permit,
  337    209   
            )));
  338    210   
        }
  339         -
        if let Some(publication) = state.prepare_h2_publication() {
  340         -
            return Some(AdmissionAction::H2(H2PublicationGuard::new(
         211  +
        if let Some(route) = state.prepare_h2_route() {
         212  +
            return Some(AdmissionAction::AttachH2Route(H2RouteGuard::new(
  341    213   
                origin.clone(),
  342         -
                publication,
         214  +
                route,
  343    215   
            )));
  344    216   
        }
  345         -
        if let Some(reuse) = state.h1.prepare_reuse(&state.demand_schedule) {
  346         -
            return Some(AdmissionAction::H1(reuse::H1ReuseAction::install(
  347         -
                origin.clone(),
  348         -
                reuse,
  349         -
            )));
         217  +
        if let Some(reservation) = state.h1_supply.prepare_match(&state.demand) {
         218  +
            return Some(AdmissionAction::ReserveH1Supplier(
         219  +
                H1ReservationAction::new(origin.clone(), reservation),
         220  +
            ));
  350    221   
        }
  351         -
        if !origin.allow_h2_reclaim_for_h1 {
         222  +
        if !origin.can_reclaim_h2_for_h1 {
  352    223   
            return None;
  353    224   
        }
  354    225   
        state
  355         -
            .h2
  356         -
            .prepare_reclaim(&state.demand_schedule)
         226  +
            .h2_supply
         227  +
            .prepare_reclaim(&state.demand)
  357    228   
            .map(|reclaim| {
  358         -
                AdmissionAction::H2Reclaim(H2ReclaimAction::new(origin.clone(), reclaim))
         229  +
                AdmissionAction::ReclaimCapacity(CapacityReclaim::FromH2(H2CapacityReclaim::new(
         230  +
                    origin.clone(),
         231  +
                    reclaim,
         232  +
                )))
  359    233   
            })
  360    234   
    }
  361    235   
  362    236   
    /// Upgrades a registered requesting cell without holding the admission lock.
  363    237   
    fn cell(&self, id: &PartitionId) -> Option<Arc<OriginCell>> {
  364    238   
        let cell = {
  365    239   
            let state = self.state.lock();
  366    240   
            state.cells.get(id).cloned()
  367    241   
        };
  368    242   
        cell.and_then(|cell| cell.upgrade())
  369    243   
    }
  370    244   
  371         -
    /// Drives sequential unlocked actions until no completion schedules another.
  372         -
    pub(in crate::client::pool) fn drive(mut action: Option<AdmissionAction>) {
         245  +
    /// Runs one detached action chain until no completion prepares a successor.
         246  +
    pub(in crate::client::pool) fn run_action_chain(mut action: Option<AdmissionAction>) {
  373    247   
        while let Some(current) = action {
  374         -
            action = current.drive_once();
         248  +
            action = match current {
         249  +
                AdmissionAction::Deliver(delivery) => delivery.deliver(),
         250  +
                AdmissionAction::ReserveH1Supplier(reservation) => reservation.reserve_supplier(),
         251  +
                AdmissionAction::CancelH1Reservation(cancellation) => {
         252  +
                    cancellation.cancel_reservation()
         253  +
                }
         254  +
                AdmissionAction::SettleH1Supplier(settlement) => settlement.settle_supplier(),
         255  +
                AdmissionAction::AttachH2Route(route) => route.attach_route(),
         256  +
                AdmissionAction::ReclaimCapacity(reclaim) => reclaim.reclaim_capacity(),
         257  +
            };
  375    258   
        }
  376    259   
    }
  377    260   
  378         -
    /// Returns whether the named delivery still owns the requesting cell's demand fence.
         261  +
    /// Returns whether admission still recognizes this exact assignment.
  379    262   
    #[cfg(test)]
  380         -
    fn delivery_is_current(
  381         -
        &self,
  382         -
        delivery: DeliveryId,
  383         -
        requesting_partition: &PartitionId,
  384         -
        demand: DemandId,
  385         -
    ) -> bool {
  386         -
        self.state
  387         -
            .lock()
  388         -
            .delivery_is_current(delivery, requesting_partition, demand)
         263  +
    fn assignment_is_current(&self, assignment: &DemandAssignment) -> bool {
         264  +
        self.state.lock().demand.assignment_is_current(assignment)
  389    265   
    }
  390    266   
  391    267   
    /// Returns `permit` to admission and serves ordered demand when possible.
  392         -
    fn return_permit(origin: &Arc<Self>, permit: Permit) {
         268  +
    fn return_permit(origin: &Arc<Self>, permit: CapacityPermit) {
  393    269   
        let action = {
  394    270   
            let mut state = origin.state.lock();
  395    271   
            state.return_permit(permit);
  396    272   
            Self::prepare_action(origin, &mut state)
  397    273   
        };
  398         -
        Self::drive(action);
         274  +
        Self::run_action_chain(action);
  399    275   
    }
  400    276   
  401         -
    /// Closes a delivery fence and prepares at most one successor.
  402         -
    ///
  403         -
    /// `permit` is present when an undelivered payload is refunnelled. A
  404         -
    /// committed delivery leaves the permit in its installed
  405         -
    /// [`CapacityLease`] and acknowledges with `None`.
  406         -
    fn finish_delivery(
  407         -
        origin: &Arc<Self>,
  408         -
        delivery: DeliveryId,
  409         -
        requesting_partition: &PartitionId,
  410         -
        permit: Option<Permit>,
  411         -
        result: DeliveryAckResult,
         277  +
    /// Settles one demand assignment and returns an unused capacity permit.
         278  +
    fn settle_delivery(
         279  +
        admission: &Arc<Self>,
         280  +
        assignment: &DemandAssignment,
         281  +
        permit: Option<CapacityPermit>,
         282  +
        outcome: DemandAssignmentOutcome,
  412    283   
    ) -> Option<AdmissionAction> {
  413         -
        let mut state = origin.state.lock();
         284  +
        let mut state = admission.state.lock();
  414    285   
        if let Some(permit) = permit {
  415    286   
            state.return_permit(permit);
  416    287   
        }
  417         -
        state.finish_delivery(delivery, requesting_partition, result);
  418         -
        Self::prepare_action(origin, &mut state)
         288  +
        state.settle_assignment(assignment, outcome);
         289  +
        Self::prepare_action(admission, &mut state)
  419    290   
    }
  420    291   
  421         -
    /// Replaces one connection cell's complete HTTP/2 advertisement.
  422         -
    pub(in crate::client::pool) fn update_h2_advertisement(
  423         -
        origin: &Arc<Self>,
  424         -
        connection_partition: PartitionId,
  425         -
        group: EligibilityGroup,
  426         -
        snapshot: H2AdvertisementSnapshot,
         292  +
    /// Applies one cell's complete HTTP/1 supply revision.
         293  +
    pub(in crate::client::pool) fn apply_h1_supply_revision(
         294  +
        admission: &Arc<Self>,
         295  +
        supplier: PartitionId,
         296  +
        eligibility_group: EligibilityGroup,
         297  +
        revision: SupplyRevision<H1SupplyStatus>,
  427    298   
    ) {
  428         -
        let action = {
  429         -
            let mut state = origin.state.lock();
  430         -
            let AdmissionState {
  431         -
                h2,
  432         -
                demand_schedule,
  433         -
                ..
  434         -
            } = &mut *state;
  435         -
            h2.update(connection_partition, group, snapshot, demand_schedule);
  436         -
            Self::prepare_action(origin, &mut state)
  437         -
        };
  438         -
        Self::drive(action);
         299  +
        h1::apply_supply_revision(admission, supplier, eligibility_group, revision);
  439    300   
    }
  440    301   
  441         -
    /// Applies one H2 publication acknowledgement and prepares its successor.
  442         -
    fn finish_h2_publication(
  443         -
        origin: &Arc<Self>,
  444         -
        prepared: &PreparedH2Publication,
         302  +
    /// Rejects a returned sender whose retained match is no longer current.
         303  +
    pub(in crate::client::pool) fn reject_returned_h1_match(
         304  +
        admission: &Arc<Self>,
         305  +
        match_id: H1MatchId,
         306  +
        supplier: PartitionId,
         307  +
        revision: SupplyRevision<H1SupplyStatus>,
         308  +
    ) {
         309  +
        h1::reject_returned_match(admission, match_id, supplier, revision);
         310  +
    }
         311  +
         312  +
    /// Settles installation of one supplier-cell reservation.
         313  +
    pub(in crate::client::pool) fn settle_h1_reservation(
         314  +
        admission: &Arc<Self>,
         315  +
        match_id: H1MatchId,
         316  +
        supplier: PartitionId,
         317  +
        decision: H1ReservationDecision<H1Candidate>,
         318  +
    ) -> Option<AdmissionAction> {
         319  +
        h1::settle_reservation(admission, match_id, supplier, decision)
         320  +
    }
         321  +
         322  +
    /// Resolves a provisional sender against its retained match and demand.
         323  +
    pub(in crate::client::pool) fn resolve_h1_match(
         324  +
        admission: &Arc<Self>,
         325  +
        match_id: H1MatchId,
         326  +
        candidate: H1Candidate,
         327  +
    ) -> Option<AdmissionAction> {
         328  +
        h1::resolve_match(admission, match_id, candidate)
         329  +
    }
         330  +
         331  +
    /// Removes one retained H1 match and applies its supplier outcome.
         332  +
    fn settle_h1_match(
         333  +
        admission: &Arc<Self>,
         334  +
        match_id: H1MatchId,
         335  +
        outcome: H1SupplyOutcome,
         336  +
    ) -> Option<AdmissionAction> {
         337  +
        h1::settle_match(admission, match_id, outcome)
         338  +
    }
         339  +
         340  +
    /// Settles a borrowed-sender assignment and its retained H1 match.
         341  +
    fn settle_borrow_delivery(
         342  +
        admission: &Arc<Self>,
         343  +
        match_id: H1MatchId,
         344  +
        assignment: &DemandAssignment,
         345  +
        outcome: DemandAssignmentOutcome,
         346  +
        transferred_supplier: Option<PartitionId>,
         347  +
        refused_outcome: Option<H1SupplyOutcome>,
         348  +
    ) -> Option<AdmissionAction> {
         349  +
        h1::settle_borrow_delivery(
         350  +
            admission,
         351  +
            match_id,
         352  +
            assignment,
         353  +
            outcome,
         354  +
            transferred_supplier,
         355  +
            refused_outcome,
         356  +
        )
         357  +
    }
         358  +
         359  +
    /// Applies one cell's complete HTTP/2 supply revision.
         360  +
    pub(in crate::client::pool) fn apply_h2_supply_revision(
         361  +
        admission: &Arc<Self>,
         362  +
        supplier: PartitionId,
         363  +
        eligibility_group: EligibilityGroup,
         364  +
        revision: SupplyRevision<H2SupplyStatus>,
         365  +
    ) {
         366  +
        h2::apply_supply_revision(admission, supplier, eligibility_group, revision);
         367  +
    }
         368  +
         369  +
    /// Settles one H2 route assignment and repairs stale supply if needed.
         370  +
    fn settle_h2_route(
         371  +
        admission: &Arc<Self>,
         372  +
        prepared: &PreparedH2Route,
  445    373   
        stale_generation: Option<super::cell::h2::H2GenerationId>,
  446         -
        result: DeliveryAckResult,
         374  +
        outcome: DemandAssignmentOutcome,
  447    375   
    ) -> Option<AdmissionAction> {
  448         -
        let mut state = origin.state.lock();
  449         -
        if let Some(generation) = stale_generation {
  450         -
            let AdmissionState {
  451         -
                h2,
  452         -
                demand_schedule,
  453         -
                ..
  454         -
            } = &mut *state;
  455         -
            h2.remove_if_exact(&prepared.connection_partition, generation, demand_schedule);
  456         -
        }
  457         -
        state.finish_delivery(prepared.delivery, &prepared.requesting_partition, result);
  458         -
        Self::prepare_action(origin, &mut state)
         376  +
        h2::settle_route(admission, prepared, stale_generation, outcome)
  459    377   
    }
  460    378   
  461    379   
    /// Completes one exact idle-H2 reclaim crossing.
  462         -
    fn finish_h2_reclaim(
  463         -
        origin: &Arc<Self>,
         380  +
    fn settle_h2_reclaim(
         381  +
        admission: &Arc<Self>,
  464    382   
        prepared: &PreparedH2Reclaim,
  465         -
        snapshot: Option<H2AdvertisementSnapshot>,
         383  +
        revision: Option<SupplyRevision<H2SupplyStatus>>,
  466    384   
    ) -> Option<AdmissionAction> {
  467         -
        let mut state = origin.state.lock();
  468         -
        let AdmissionState {
  469         -
            h2,
  470         -
            demand_schedule,
  471         -
            ..
  472         -
        } = &mut *state;
  473         -
        h2.finish_reclaim(prepared, snapshot, demand_schedule);
  474         -
        Self::prepare_action(origin, &mut state)
         385  +
        h2::settle_reclaim(admission, prepared, revision)
  475    386   
    }
         387  +
  476    388   
    #[cfg(test)]
  477         -
    pub(super) fn publish_action_without_driving(
  478         -
        origin: &Arc<Self>,
  479         -
        requesting_partition: PartitionId,
         389  +
    pub(super) fn submit_action_without_running(
         390  +
        admission: &Arc<Self>,
         391  +
        requester: PartitionId,
  480    392   
        snapshot: DemandSnapshot,
  481    393   
    ) -> Option<AdmissionAction> {
  482         -
        let mut state = origin.state.lock();
  483         -
        state.publish_demand(requesting_partition, snapshot);
  484         -
        Self::prepare_action(origin, &mut state)
         394  +
        let mut state = admission.state.lock();
         395  +
        state.apply_demand_snapshot(requester, snapshot);
         396  +
        Self::prepare_action(admission, &mut state)
  485    397   
    }
  486    398   
  487    399   
    #[cfg(test)]
  488         -
    pub(super) fn publish_without_driving(
  489         -
        origin: &Arc<Self>,
  490         -
        requesting_partition: PartitionId,
         400  +
    pub(super) fn submit_without_running(
         401  +
        admission: &Arc<Self>,
         402  +
        requester: PartitionId,
  491    403   
        snapshot: DemandSnapshot,
  492    404   
    ) -> Option<DeliveryGuard> {
  493         -
        match Self::publish_action_without_driving(origin, requesting_partition, snapshot) {
  494         -
            Some(AdmissionAction::Delivery(delivery)) => Some(delivery),
  495         -
            Some(AdmissionAction::H1(_)) => {
  496         -
                panic!("capacity-only test unexpectedly prepared an HTTP/1 action")
  497         -
            }
  498         -
            Some(AdmissionAction::H2(_)) => {
  499         -
                panic!("capacity-only test unexpectedly prepared an HTTP/2 action")
  500         -
            }
  501         -
            Some(AdmissionAction::H2Reclaim(_)) => {
  502         -
                panic!("capacity-only test unexpectedly prepared an HTTP/2 reclaim action")
  503         -
            }
         405  +
        match Self::submit_action_without_running(admission, requester, snapshot) {
         406  +
            Some(AdmissionAction::Deliver(delivery)) => Some(delivery),
         407  +
            Some(_) => panic!("capacity-only test unexpectedly prepared another action"),
  504    408   
            None => None,
  505    409   
        }
  506    410   
    }
  507    411   
  508    412   
    #[cfg(test)]
  509         -
    fn counts(&self) -> AdmissionCounts {
         413  +
    fn probe(&self) -> AdmissionProbe {
  510    414   
        let state = self.state.lock();
  511         -
        AdmissionCounts {
  512         -
            limit: state.limit,
         415  +
        AdmissionProbe {
         416  +
            limit: state.capacity.limit,
  513    417   
            available: state.available_capacity(),
  514         -
            ordered: state.demand_schedule.len(),
  515         -
            queued: state.demand_schedule.queued_len(),
  516         -
            delivering: state.demand_schedule.delivering_len(),
         418  +
            ordered: state.demand.len(),
         419  +
            queued: state.demand.queued_len(),
         420  +
            assigned: state.demand.pending_assignment_count(),
  517    421   
        }
  518    422   
    }
  519    423   
  520    424   
    #[cfg(test)]
  521    425   
    pub(super) fn lease_for_test(origin: &Arc<Self>) -> CapacityLease {
  522    426   
        let permit = origin
  523    427   
            .state
  524    428   
            .lock()
  525    429   
            .take_permit()
  526    430   
            .expect("test origin had no available capacity");
  527    431   
        CapacityLease::new(origin.clone(), permit)
  528    432   
    }
  529    433   
  530    434   
    #[cfg(test)]
  531    435   
    pub(super) fn available_capacity_for_test(&self) -> usize {
  532    436   
        self.state.lock().available_capacity()
  533    437   
    }
  534    438   
  535    439   
    #[cfg(test)]
  536    440   
    pub(super) fn ordered_demand_count_for_test(&self) -> usize {
  537         -
        self.state.lock().demand_schedule.len()
         441  +
        self.state.lock().demand.len()
  538    442   
    }
  539    443   
  540    444   
    #[cfg(all(test, smithy_http_client_loom))]
  541    445   
    pub(super) fn clear_modeled_cells_for_test(&self) {
  542    446   
        // Loom has no modeled Weak, so its synchronization facade retains
  543    447   
        // cells strongly. Explicit teardown prevents that model-only
  544    448   
        // substitution from appearing as an Arc leak.
  545    449   
        self.state.lock().cells.clear();
  546    450   
    }
  547    451   
}
  548    452   
  549    453   
/// One detached step prepared while holding the bounded-origin lock.
  550    454   
pub(super) enum AdmissionAction {
  551    455   
    /// One capacity or borrowed-H1 payload handed to a requesting cell.
  552         -
    Delivery(DeliveryGuard),
  553         -
    /// HTTP/1 availability, reservation, or borrowed-sender work.
  554         -
    H1(reuse::H1ReuseAction),
  555         -
    /// HTTP/2 generation visibility handed to one requesting cell.
  556         -
    H2(H2PublicationGuard),
  557         -
    /// Exact idle HTTP/2 generation reserved at its connection cell.
  558         -
    H2Reclaim(H2ReclaimAction),
         456  +
    Deliver(DeliveryGuard),
         457  +
    /// Reserve the selected supplier at its owning HTTP/1 cell.
         458  +
    ReserveH1Supplier(H1ReservationAction),
         459  +
    /// Cancel a retained HTTP/1 supplier reservation.
         460  +
    CancelH1Reservation(H1CancellationAction),
         461  +
    /// Settle sender ownership at the original HTTP/1 supplier cell.
         462  +
    SettleH1Supplier(H1SupplierSettlement),
         463  +
    /// HTTP/2 generation route handed to one requesting cell.
         464  +
    AttachH2Route(H2RouteGuard),
         465  +
    /// Close selected connection supply outside admission to recover capacity.
         466  +
    ReclaimCapacity(CapacityReclaim),
  559    467   
}
  560    468   
  561    469   
impl AdmissionAction {
  562         -
    /// Executes one detached action and returns the next prepared step.
  563         -
    fn drive_once(self) -> Option<Self> {
         470  +
    /// Advances one crossing without recursively driving its successor.
         471  +
    #[cfg(all(test, smithy_http_client_loom))]
         472  +
    pub(super) fn run_once_for_test(self) -> Option<Self> {
  564    473   
        match self {
  565         -
            Self::Delivery(delivery) => delivery.deliver_once(),
  566         -
            Self::H1(action) => action.drive_once(),
  567         -
            Self::H2(publication) => publication.publish_once(),
  568         -
            Self::H2Reclaim(reclaim) => reclaim.drive_once(),
         474  +
            Self::Deliver(delivery) => delivery.deliver(),
         475  +
            Self::ReserveH1Supplier(reservation) => reservation.reserve_supplier(),
         476  +
            Self::CancelH1Reservation(cancellation) => cancellation.cancel_reservation(),
         477  +
            Self::SettleH1Supplier(settlement) => settlement.settle_supplier(),
         478  +
            Self::AttachH2Route(route) => route.attach_route(),
         479  +
            Self::ReclaimCapacity(reclaim) => reclaim.reclaim_capacity(),
  569    480   
        }
  570    481   
    }
         482  +
}
  571    483   
  572         -
    /// Advances one crossing without recursively driving its successor.
  573         -
    #[cfg(all(test, smithy_http_client_loom))]
  574         -
    pub(super) fn drive_once_for_test(self) -> Option<Self> {
  575         -
        self.drive_once()
         484  +
/// Protocol-specific detached reclaim with one shared admission action.
         485  +
pub(super) enum CapacityReclaim {
         486  +
    FromH1(H1CapacityReclaim),
         487  +
    FromH2(H2CapacityReclaim),
         488  +
}
         489  +
         490  +
impl CapacityReclaim {
         491  +
    fn reclaim_capacity(self) -> Option<AdmissionAction> {
         492  +
        match self {
         493  +
            Self::FromH1(reclaim) => reclaim.reclaim_capacity(),
         494  +
            Self::FromH2(reclaim) => reclaim.reclaim_capacity(),
         495  +
        }
  576    496   
    }
  577    497   
}
  578    498   
  579    499   
impl fmt::Debug for OriginAdmission {
  580    500   
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  581    501   
        f.debug_struct("OriginAdmission")
  582    502   
            .field("state", &self.state)
  583    503   
            .finish()
  584    504   
    }
  585    505   
}
  586    506   
  587    507   
/// One admitted connection slot for a bounded origin.
  588    508   
///
  589    509   
/// While this lease exists, one place in the origin's configured connection
  590    510   
/// limit is occupied. The lease moves through capacity delivery, waiter
  591    511   
/// resolution, and establishment into the installed
  592    512   
/// [`super::connection::ConnectionState`]. Logical close drops the installed
  593    513   
/// lease and makes that place available again.
  594    514   
///
  595    515   
/// Dropping a lease may synchronously deliver capacity to another waiter, so
  596    516   
/// callers must move it out of protected state before drop.
  597         -
pub(crate) struct CapacityLease {
         517  +
pub(in crate::client::pool) struct CapacityLease {
  598    518   
    /// Admission state to which this slot returns when the lease ends.
  599         -
    origin: Arc<OriginAdmission>,
         519  +
    admission: Arc<OriginAdmission>,
  600    520   
    /// Permit returned to admission when this lease ends.
  601         -
    permit: Option<Permit>,
         521  +
    permit: Option<CapacityPermit>,
  602    522   
}
  603    523   
  604    524   
impl CapacityLease {
  605    525   
    /// Takes ownership of a permit removed from admission's available set.
  606         -
    fn new(origin: Arc<OriginAdmission>, permit: Permit) -> Self {
         526  +
    fn new(admission: Arc<OriginAdmission>, permit: CapacityPermit) -> Self {
  607    527   
        Self {
  608         -
            origin,
         528  +
            admission,
  609    529   
            permit: Some(permit),
  610    530   
        }
  611    531   
    }
  612    532   
}
  613    533   
  614    534   
impl fmt::Debug for CapacityLease {
  615    535   
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  616    536   
        f.debug_struct("CapacityLease")
  617    537   
            .field("permit", &self.permit)
  618    538   
            .finish_non_exhaustive()
  619    539   
    }
  620    540   
}
  621    541   
  622    542   
impl Drop for CapacityLease {
  623    543   
    fn drop(&mut self) {
  624    544   
        if let Some(permit) = self.permit.take() {
  625         -
            OriginAdmission::return_permit(&self.origin, permit);
         545  +
            OriginAdmission::return_permit(&self.admission, permit);
  626    546   
        }
  627    547   
    }
  628    548   
}
  629    549   
  630         -
/// Admission's acknowledgement of a requesting cell-side delivery attempt.
  631         -
#[derive(Clone, Debug, Eq, PartialEq)]
  632         -
enum DeliveryAckResult {
  633         -
    /// The requesting cell accepted the payload and optionally published its successor demand.
  634         -
    Accepted { successor: Option<DemandSnapshot> },
  635         -
    /// The same demand remains live at its existing order position.
  636         -
    RetrySameResidence,
  637         -
    /// The old demand ended and may have a successor.
  638         -
    Rejected { successor: Option<DemandSnapshot> },
  639         -
}
  640         -
  641         -
/// Mutable bounded-origin state protected by one admission lock.
         550  +
/// Capacity not currently owned by a delivery, attempt, or connection.
  642    551   
#[derive(Debug)]
  643         -
struct AdmissionState {
  644         -
    /// Requesting cells, held weakly to avoid an ownership cycle.
  645         -
    cells: HashMap<PartitionId, Weak<OriginCell>>,
  646         -
    /// Permits not owned by a delivery, attempt, or connection.
         552  +
struct CapacityBudget {
         553  +
    /// Configured connection bound used to check capacity conservation.
         554  +
    limit: usize,
         555  +
    /// Permits available for a new establishment attempt.
  647    556   
    available: usize,
  648    557   
    /// Next never-reused permit identity.
  649    558   
    next_permit_id: u64,
  650         -
    /// Cross-cell demand records and their origin-wide scheduling order.
  651         -
    demand_schedule: DemandSchedule,
  652         -
    /// HTTP/1 availability reports and cross-cell reuse operations.
  653         -
    h1: reuse::H1Reuse,
  654         -
    /// HTTP/2 advertisements and eligibility-group publication turns.
  655         -
    h2: H2Publication,
  656         -
    /// Next never-reused delivery identity.
  657         -
    next_delivery: u64,
  658         -
    /// Configured connection bound used to check capacity conservation.
  659         -
    limit: usize,
  660    559   
}
  661    560   
  662         -
impl AdmissionState {
  663         -
    /// Creates a ledger with all configured capacity available.
         561  +
impl CapacityBudget {
  664    562   
    fn new(limit: NonZeroUsize) -> Self {
  665    563   
        let limit = limit.get();
  666    564   
        Self {
  667         -
            cells: HashMap::new(),
         565  +
            limit,
  668    566   
            available: limit,
  669    567   
            next_permit_id: 0,
  670         -
            demand_schedule: DemandSchedule::default(),
  671         -
            h1: reuse::H1Reuse::default(),
  672         -
            h2: H2Publication::default(),
  673         -
            next_delivery: 0,
  674         -
            limit,
  675    568   
        }
  676    569   
    }
  677    570   
  678         -
    /// Removes one available permit from admission.
  679         -
    fn take_permit(&mut self) -> Option<Permit> {
         571  +
    fn take_permit(&mut self) -> Option<CapacityPermit> {
  680    572   
        if self.available == 0 {
  681    573   
            return None;
  682    574   
        }
  683    575   
        let id = self.next_permit_id;
  684         -
        let next_permit_id = id.checked_add(1).expect("permit identity exhausted");
         576  +
        self.next_permit_id = id.checked_add(1).expect("permit identity exhausted");
  685    577   
        self.available -= 1;
  686         -
        self.next_permit_id = next_permit_id;
  687         -
        Some(Permit(id))
         578  +
        Some(CapacityPermit(id))
  688    579   
    }
  689    580   
  690         -
    /// Consumes a returned permit and restores one available slot.
  691         -
    fn return_permit(&mut self, _permit: Permit) {
         581  +
    fn return_permit(&mut self, _permit: CapacityPermit) {
  692    582   
        let available = self
  693    583   
            .available
  694    584   
            .checked_add(1)
  695    585   
            .expect("available capacity count overflowed");
  696    586   
        assert!(
  697    587   
            available <= self.limit,
  698    588   
            "available capacity exceeded the configured limit"
  699    589   
        );
  700    590   
        self.available = available;
  701    591   
    }
         592  +
}
         593  +
         594  +
/// Mutable bounded-origin state protected by one admission lock.
         595  +
#[derive(Debug)]
         596  +
struct AdmissionState {
         597  +
    /// Requesting cells, held weakly to avoid an ownership cycle.
         598  +
    cells: HashMap<PartitionId, Weak<OriginCell>>,
         599  +
    /// Conserved bounded-origin connection capacity.
         600  +
    capacity: CapacityBudget,
         601  +
    /// Canonical cross-cell demand and scheduling orders.
         602  +
    demand: DemandSchedule,
         603  +
    /// Admission's indexed view of HTTP/1 connection supply.
         604  +
    h1_supply: H1Supply,
         605  +
    /// Admission's indexed view of HTTP/2 connection supply.
         606  +
    h2_supply: H2Supply,
         607  +
    /// Next never-reused resource-to-demand assignment identity.
         608  +
    next_assignment_id: u64,
         609  +
}
         610  +
         611  +
impl AdmissionState {
         612  +
    /// Creates admission with all configured capacity available.
         613  +
    fn new(limit: NonZeroUsize) -> Self {
         614  +
        Self {
         615  +
            cells: HashMap::new(),
         616  +
            capacity: CapacityBudget::new(limit),
         617  +
            demand: DemandSchedule::default(),
         618  +
            h1_supply: H1Supply::default(),
         619  +
            h2_supply: H2Supply::default(),
         620  +
            next_assignment_id: 0,
         621  +
        }
         622  +
    }
         623  +
         624  +
    /// Removes one available permit from admission.
         625  +
    fn take_permit(&mut self) -> Option<CapacityPermit> {
         626  +
        self.capacity.take_permit()
         627  +
    }
         628  +
         629  +
    /// Consumes a returned permit and restores one available slot.
         630  +
    fn return_permit(&mut self, permit: CapacityPermit) {
         631  +
        self.capacity.return_permit(permit);
         632  +
    }
  702    633   
  703    634   
    #[cfg(test)]
  704    635   
    fn available_capacity(&self) -> usize {
  705         -
        self.available
         636  +
        self.capacity.available
  706    637   
    }
  707    638   
  708         -
    /// Applies one complete cell publication to cross-cell scheduling.
  709         -
    fn publish_demand(&mut self, requesting_partition: PartitionId, snapshot: DemandSnapshot) {
  710         -
        let old_group = self.demand_schedule.group_for(&requesting_partition);
  711         -
        self.demand_schedule.publish(requesting_partition, snapshot);
  712         -
        self.reconcile_demand_indexes(&requesting_partition, old_group);
         639  +
    /// Applies one complete cell snapshot to cross-cell demand scheduling.
         640  +
    fn apply_demand_snapshot(&mut self, requester: PartitionId, snapshot: DemandSnapshot) {
         641  +
        let old_group = self.demand.group_for(&requester);
         642  +
        self.demand.apply_snapshot(requester, snapshot);
         643  +
        self.reconcile_demand_indexes(&requester, old_group);
  713    644   
    }
  714    645   
  715    646   
    /// Refreshes protocol indexes derived from the canonical demand schedule.
  716    647   
    ///
  717    648   
    /// HTTP/1 indexes depend on the requesting partition's origin position.
  718         -
    /// HTTP/2 publication depends on both the previous and current eligibility
         649  +
    /// HTTP/2 route scheduling depends on both previous and current eligibility
  719    650   
    /// groups, so a group change must repair each view. The derived indexes do
  720    651   
    /// not own demand or ordering.
  721    652   
    fn reconcile_demand_indexes(
  722    653   
        &mut self,
  723    654   
        requesting_partition: &PartitionId,
  724    655   
        old_group: Option<EligibilityGroup>,
  725    656   
    ) {
  726         -
        self.h1
  727         -
            .reconcile_requesting_cell(requesting_partition, &self.demand_schedule);
         657  +
        self.h1_supply
         658  +
            .reconcile_requester(requesting_partition, &self.demand);
  728    659   
        if let Some(old_group) = old_group {
  729         -
            self.h2.reconcile_group(&old_group, &self.demand_schedule);
         660  +
            self.h2_supply.reconcile_group(&old_group, &self.demand);
  730    661   
        }
  731         -
        if let Some(group) = self.demand_schedule.group_for(requesting_partition) {
  732         -
            self.h2.reconcile_group(&group, &self.demand_schedule);
         662  +
        if let Some(group) = self.demand.group_for(requesting_partition) {
         663  +
            self.h2_supply.reconcile_group(&group, &self.demand);
  733    664   
        }
  734    665   
    }
  735    666   
  736    667   
    /// Pairs the oldest deliverable demand with one available permit.
  737         -
    fn schedule_one(&mut self) -> Option<PreparedCapacityDelivery> {
  738         -
        if !self.demand_schedule.head_is_queued() {
         668  +
    fn prepare_capacity_delivery(&mut self) -> Option<PreparedCapacityDelivery> {
         669  +
        if !self.demand.head_is_queued() {
  739    670   
            return None;
  740    671   
        }
  741    672   
  742    673   
        let permit = self.take_permit()?;
  743         -
        let delivery = self.take_delivery_id();
         674  +
        let assignment_id = self.take_assignment_id();
  744    675   
        let old_group = self
  745         -
            .demand_schedule
         676  +
            .demand
  746    677   
            .queued_head()
  747         -
            .and_then(|head| self.demand_schedule.group_for(&head.requesting_partition));
  748         -
        let scheduled = self
  749         -
            .demand_schedule
  750         -
            .reserve_origin_head(delivery)
         678  +
            .and_then(|head| self.demand.group_for(&head.requester));
         679  +
        let assignment = self
         680  +
            .demand
         681  +
            .prepare_origin_assignment(assignment_id)
  751    682   
            .expect("queued demand head disappeared");
  752         -
        self.reconcile_demand_indexes(&scheduled.requesting_partition, old_group);
  753         -
        Some(PreparedCapacityDelivery {
  754         -
            permit,
  755         -
            delivery,
  756         -
            requesting_partition: scheduled.requesting_partition,
  757         -
            demand: scheduled.demand,
  758         -
        })
  759         -
    }
  760         -
  761         -
    /// Delegates delivery-fence revalidation to the demand schedule.
  762         -
    #[cfg(test)]
  763         -
    fn delivery_is_current(
  764         -
        &self,
  765         -
        delivery: DeliveryId,
  766         -
        requesting_partition: &PartitionId,
  767         -
        demand: DemandId,
  768         -
    ) -> bool {
  769         -
        self.demand_schedule
  770         -
            .delivery_is_current(delivery, requesting_partition, demand)
         683  +
        self.reconcile_demand_indexes(&assignment.requester, old_group);
         684  +
        Some(PreparedCapacityDelivery { permit, assignment })
  771    685   
    }
  772    686   
  773         -
    /// Applies a requesting cell acknowledgement to the demand schedule.
  774         -
    fn finish_delivery(
         687  +
    /// Applies one detached assignment outcome to canonical demand.
         688  +
    fn settle_assignment(
  775    689   
        &mut self,
  776         -
        delivery: DeliveryId,
  777         -
        requesting_partition: &PartitionId,
  778         -
        result: DeliveryAckResult,
         690  +
        assignment: &DemandAssignment,
         691  +
        outcome: DemandAssignmentOutcome,
  779    692   
    ) {
  780         -
        let old_group = self.demand_schedule.group_for(requesting_partition);
  781         -
        self.demand_schedule
  782         -
            .finish_delivery(delivery, requesting_partition, result);
  783         -
        self.reconcile_demand_indexes(requesting_partition, old_group);
         693  +
        let old_group = self.demand.group_for(&assignment.requester);
         694  +
        self.demand.settle_assignment(assignment, outcome);
         695  +
        self.reconcile_demand_indexes(&assignment.requester, old_group);
  784    696   
    }
  785    697   
  786         -
    /// Reserves one eligibility-group head for identity-only H2 publication.
  787         -
    fn prepare_h2_publication(&mut self) -> Option<PreparedH2Publication> {
  788         -
        if !self.h2.has_ready_group() {
         698  +
    /// Reserves one eligibility-group head for an identity-only H2 route.
         699  +
    fn prepare_h2_route(&mut self) -> Option<PreparedH2Route> {
         700  +
        if !self.h2_supply.has_route_ready_group() {
  789    701   
            return None;
  790    702   
        }
  791         -
        let delivery = self.take_delivery_id();
  792         -
        self.h2.prepare(&mut self.demand_schedule, delivery)
         703  +
        let assignment_id = self.take_assignment_id();
         704  +
        self.h2_supply
         705  +
            .prepare_route(&mut self.demand, assignment_id)
  793    706   
    }
  794    707   
  795         -
    /// Allocates a delivery-fence identity that is never reused by this origin.
  796         -
    fn take_delivery_id(&mut self) -> DeliveryId {
  797         -
        let value = self.next_delivery;
  798         -
        self.next_delivery = value.checked_add(1).expect("delivery identity exhausted");
  799         -
        DeliveryId(value)
         708  +
    /// Allocates an assignment identity that is never reused by this origin.
         709  +
    fn take_assignment_id(&mut self) -> DemandAssignmentId {
         710  +
        let value = self.next_assignment_id;
         711  +
        self.next_assignment_id = value
         712  +
            .checked_add(1)
         713  +
            .expect("demand assignment identity exhausted");
         714  +
        DemandAssignmentId(value)
  800    715   
    }
  801    716   
}
  802    717   
  803    718   
/// Test snapshot of the bounded-origin capacity and demand ledger.
  804    719   
#[cfg(test)]
  805    720   
#[derive(Debug, Eq, PartialEq)]
  806         -
struct AdmissionCounts {
         721  +
struct AdmissionProbe {
  807    722   
    /// Configured connection capacity.
  808    723   
    limit: usize,
  809    724   
    /// Permits not currently owned by a connection or delivery.
  810    725   
    available: usize,
  811         -
    /// Demands linked in origin order, including a delivery fence.
         726  +
    /// Demands linked in origin order, including a pending assignment.
  812    727   
    ordered: usize,
  813    728   
    /// Demands eligible to start a delivery.
  814    729   
    queued: usize,
  815         -
    /// Demands currently fenced by a crossing delivery.
  816         -
    delivering: usize,
         730  +
    /// Demands currently owned by a detached assignment.
         731  +
    assigned: usize,
  817    732   
}
  818    733   
  819    734   
#[cfg(all(test, not(smithy_http_client_loom)))]
  820    735   
mod tests {
  821    736   
    use super::*;
  822    737   
    use crate::client::pool::partition::PartitionId;
  823    738   
  824    739   
    fn cell(origin: &Arc<OriginAdmission>, partition: usize) -> Arc<OriginCell> {
  825    740   
        let cell = Arc::new(OriginCell::new(
  826    741   
            PartitionId::from_index(partition),
  827    742   
            OriginKey::from_parts(http_1x::uri::Scheme::HTTPS, "example.com", None).unwrap(),
  828    743   
            EligibilityGroup::Pool,
  829    744   
            Some(origin.clone()),
  830    745   
            None,
  831    746   
        ));
  832    747   
        OriginAdmission::register_cell(origin, cell)
  833    748   
    }
  834    749   
  835    750   
    fn demand(id: u64) -> DemandSnapshot {
  836    751   
        DemandSnapshot::active(
  837    752   
            DemandId::from_u64(id),
  838    753   
            SnapshotVersion::INITIAL,
  839    754   
            ProtocolRequirement::H1Compatible,
  840    755   
            EligibilityGroup::Pool,
  841    756   
        )
  842    757   
    }
  843    758   
  844    759   
    #[test]
  845    760   
    #[should_panic(expected = "cell origin did not match its admission authority")]
  846    761   
    fn registration_rejects_a_cell_from_another_origin() {
  847    762   
        let admission = OriginAdmission::for_test(NonZeroUsize::new(1).unwrap());
  848    763   
        let candidate = Arc::new(OriginCell::new(
  849    764   
            PartitionId::from_index(1),
  850    765   
            OriginKey::from_parts(http_1x::uri::Scheme::HTTPS, "other.example.com", None).unwrap(),
  851    766   
            EligibilityGroup::Pool,
  852    767   
            Some(admission.clone()),
  853    768   
            None,
  854    769   
        ));
  855    770   
  856    771   
        OriginAdmission::register_cell(&admission, candidate);
  857    772   
    }
  858    773   
  859    774   
    #[test]
  860    775   
    fn permits_are_linear_and_never_reused() {
  861    776   
        let mut state = AdmissionState::new(NonZeroUsize::new(2).unwrap());
  862    777   
  863         -
        assert_eq!(2, state.available);
         778  +
        assert_eq!(2, state.available_capacity());
  864    779   
        let first = state.take_permit().unwrap();
  865    780   
        let first_id = first.0;
  866    781   
        let second = state.take_permit().unwrap();
  867         -
        assert_eq!(0, state.available);
         782  +
        assert_eq!(0, state.available_capacity());
  868    783   
        assert!(state.take_permit().is_none());
  869    784   
  870    785   
        state.return_permit(first);
  871    786   
        let third = state.take_permit().unwrap();
  872    787   
        assert_ne!(first_id, third.0);
  873    788   
        state.return_permit(second);
  874    789   
        state.return_permit(third);
  875         -
        assert_eq!(2, state.available);
         790  +
        assert_eq!(2, state.available_capacity());
  876    791   
    }
  877    792   
  878    793   
    #[test]
  879    794   
    fn equal_or_older_snapshots_do_not_replace_current_demand() {
  880    795   
        let mut state = AdmissionState::new(NonZeroUsize::new(1).unwrap());
  881    796   
        let requesting_partition = PartitionId::from_index(1);
  882    797   
        let current = demand(2);
  883         -
        state.publish_demand(requesting_partition, current.clone());
  884         -
        state.publish_demand(
         798  +
        state.apply_demand_snapshot(requesting_partition, current.clone());
         799  +
        state.apply_demand_snapshot(
  885    800   
            requesting_partition,
  886    801   
            DemandSnapshot::inactive(DemandId::from_u64(2), SnapshotVersion::INITIAL),
  887    802   
        );
  888         -
        state.publish_demand(requesting_partition, demand(1));
         803  +
        state.apply_demand_snapshot(requesting_partition, demand(1));
  889    804   
  890         -
        assert_eq!(1, state.demand_schedule.len());
         805  +
        assert_eq!(1, state.demand.len());
  891    806   
        assert_eq!(
  892    807   
            Some(&current),
  893         -
            state.demand_schedule.latest_for_test(&requesting_partition)
         808  +
            state.demand.latest_for_test(&requesting_partition)
  894    809   
        );
  895    810   
    }
  896    811   
  897    812   
    #[test]
  898    813   
    fn cancellation_churn_does_not_retain_order_entries() {
  899    814   
        let mut state = AdmissionState::new(NonZeroUsize::new(1).unwrap());
  900    815   
        let held = state.take_permit().unwrap();
  901    816   
        let requesting_partition = PartitionId::from_index(1);
  902    817   
  903    818   
        for id in 0..2_000 {
  904    819   
            let id = DemandId::from_u64(id);
  905         -
            state.publish_demand(
         820  +
            state.apply_demand_snapshot(
  906    821   
                requesting_partition,
  907    822   
                DemandSnapshot::active(
  908    823   
                    id,
  909    824   
                    SnapshotVersion::INITIAL,
  910    825   
                    ProtocolRequirement::H1Compatible,
  911    826   
                    EligibilityGroup::Pool,
  912    827   
                ),
  913    828   
            );
  914         -
            state.publish_demand(
         829  +
            state.apply_demand_snapshot(
  915    830   
                requesting_partition,
  916    831   
                DemandSnapshot::inactive(id, SnapshotVersion::INITIAL.next()),
  917    832   
            );
  918    833   
        }
  919    834   
  920         -
        assert_eq!(0, state.demand_schedule.len());
         835  +
        assert_eq!(0, state.demand.len());
  921    836   
        state.return_permit(held);
  922    837   
    }
  923    838   
  924    839   
    #[test]
  925    840   
    fn removing_middle_and_tail_demands_repairs_order() {
  926    841   
        let mut state = AdmissionState::new(NonZeroUsize::new(1).unwrap());
  927    842   
        let held = state.take_permit().unwrap();
  928    843   
        let targets: Vec<_> = (1..=5).map(PartitionId::from_index).collect();
  929    844   
  930    845   
        for (index, requesting_partition) in targets[..4].iter().enumerate() {
  931         -
            state.publish_demand(*requesting_partition, demand(index as u64 + 1));
         846  +
            state.apply_demand_snapshot(*requesting_partition, demand(index as u64 + 1));
  932    847   
        }
  933         -
        state.publish_demand(
         848  +
        state.apply_demand_snapshot(
  934    849   
            targets[1],
  935    850   
            DemandSnapshot::inactive(DemandId::from_u64(2), SnapshotVersion::INITIAL.next()),
  936    851   
        );
  937         -
        state.publish_demand(
         852  +
        state.apply_demand_snapshot(
  938    853   
            targets[3],
  939    854   
            DemandSnapshot::inactive(DemandId::from_u64(4), SnapshotVersion::INITIAL.next()),
  940    855   
        );
  941         -
        state.publish_demand(targets[4], demand(5));
  942         -
        assert_eq!(3, state.demand_schedule.len());
         856  +
        state.apply_demand_snapshot(targets[4], demand(5));
         857  +
        assert_eq!(3, state.demand.len());
  943    858   
  944    859   
        state.return_permit(held);
  945    860   
        for expected in [&targets[0], &targets[2], &targets[4]] {
  946         -
            let pending = state.schedule_one().unwrap();
  947         -
            assert_eq!(expected, &pending.requesting_partition);
  948         -
            state.finish_delivery(
  949         -
                pending.delivery,
  950         -
                &pending.requesting_partition,
  951         -
                DeliveryAckResult::Accepted { successor: None },
         861  +
            let pending = state.prepare_capacity_delivery().unwrap();
         862  +
            assert_eq!(expected, &pending.assignment.requester);
         863  +
            state.settle_assignment(
         864  +
                &pending.assignment,
         865  +
                DemandAssignmentOutcome::Accepted { successor: None },
  952    866   
            );
  953    867   
            state.return_permit(pending.permit);
  954    868   
        }
  955         -
        assert_eq!(0, state.demand_schedule.len());
         869  +
        assert_eq!(0, state.demand.len());
  956    870   
    }
  957    871   
  958    872   
    #[test]
  959    873   
    fn new_demand_moves_queued_cell_to_the_tail() {
  960    874   
        let mut state = AdmissionState::new(NonZeroUsize::new(1).unwrap());
  961    875   
        let held = state.take_permit().unwrap();
  962    876   
        let first = PartitionId::from_index(1);
  963    877   
        let second = PartitionId::from_index(2);
  964         -
        state.publish_demand(first, demand(1));
  965         -
        state.publish_demand(second, demand(2));
  966         -
        state.publish_demand(first, demand(3));
         878  +
        state.apply_demand_snapshot(first, demand(1));
         879  +
        state.apply_demand_snapshot(second, demand(2));
         880  +
        state.apply_demand_snapshot(first, demand(3));
  967    881   
        state.return_permit(held);
  968    882   
  969         -
        assert_eq!(second, state.schedule_one().unwrap().requesting_partition);
  970         -
    }
  971         -
  972         -
    #[test]
  973         -
    fn delivery_currency_includes_the_demand_id() {
  974         -
        let origin = OriginAdmission::for_test(NonZeroUsize::new(1).unwrap());
  975         -
        let requesting_partition = cell(&origin, 1);
  976         -
        let delivery = OriginAdmission::publish_without_driving(
  977         -
            &origin,
  978         -
            requesting_partition.id().partition(),
  979         -
            demand(1),
  980         -
        )
  981         -
        .unwrap();
  982         -
        assert!(delivery.is_current());
  983         -
  984         -
        origin
  985         -
            .state
  986         -
            .lock()
  987         -
            .publish_demand(requesting_partition.id().partition(), demand(2));
  988         -
        assert!(!delivery.is_current());
  989         -
        delivery.reject(None);
  990         -
    }
  991         -
  992         -
    #[test]
  993         -
    fn stale_successor_cannot_leave_active_demand_idle() {
  994         -
        let origin = OriginAdmission::for_test(NonZeroUsize::new(1).unwrap());
  995         -
        let requesting_partition = cell(&origin, 1);
  996         -
        let delivery = OriginAdmission::publish_without_driving(
  997         -
            &origin,
  998         -
            requesting_partition.id().partition(),
  999         -
            demand(1),
 1000         -
        )
 1001         -
        .unwrap();
 1002         -
        origin.state.lock().publish_demand(
 1003         -
            requesting_partition.id().partition(),
 1004         -
            DemandSnapshot::active(
 1005         -
                DemandId::from_u64(1),
 1006         -
                SnapshotVersion::INITIAL.next(),
 1007         -
                ProtocolRequirement::H1Compatible,
 1008         -
                EligibilityGroup::Pool,
 1009         -
            ),
         883  +
        assert_eq!(
         884  +
            second,
         885  +
            state
         886  +
                .prepare_capacity_delivery()
         887  +
                .unwrap()
         888  +
                .assignment
         889  +
                .requester
 1010    890   
        );
 1011         -
        delivery.reject(Some(demand(1)));
 1012         -
 1013         -
        assert_eq!(1, origin.counts().available);
 1014         -
        assert_eq!(0, origin.counts().ordered);
 1015         -
    }
 1016         -
 1017         -
    #[test]
 1018         -
    fn dropped_delivery_refunnels_capacity_and_preserves_order() {
 1019         -
        let origin = OriginAdmission::for_test(NonZeroUsize::new(1).unwrap());
 1020         -
        let first = cell(&origin, 1);
 1021         -
        let second = cell(&origin, 2);
 1022         -
        let (first_waiter, first_demand) =
 1023         -
            first.register_waiter_without_publish(ProtocolRequirement::H1Compatible);
 1024         -
        let (second_waiter, second_demand) =
 1025         -
            second.register_waiter_without_publish(ProtocolRequirement::H1Compatible);
 1026         -
        let delivery =
 1027         -
            OriginAdmission::publish_without_driving(&origin, first.id().partition(), first_demand)
 1028         -
                .unwrap();
 1029         -
        {
 1030         -
            let mut state = origin.state.lock();
 1031         -
            state.publish_demand(second.id().partition(), second_demand);
 1032         -
        }
 1033         -
        drop(delivery);
 1034         -
 1035         -
        let first_lease = OriginCell::take_ready_lease(&first, first_waiter)
 1036         -
            .expect("dropped delivery did not retry the original head");
 1037         -
        assert!(OriginCell::take_ready_lease(&second, second_waiter).is_none());
 1038         -
        assert_eq!(1, origin.counts().ordered);
 1039         -
 1040         -
        drop(first_lease);
 1041         -
        let second_lease = OriginCell::take_ready_lease(&second, second_waiter)
 1042         -
            .expect("younger demand did not run after the original head");
 1043         -
        drop(second_lease);
 1044    891   
    }
 1045    892   
 1046    893   
    #[test]
 1047    894   
    fn losing_registered_cell_returns_capacity_after_admission_unlocks() {
 1048    895   
        let origin = OriginAdmission::for_test(NonZeroUsize::new(1).unwrap());
 1049    896   
        let retained = cell(&origin, 1);
 1050    897   
        let candidate = Arc::new(OriginCell::new(
 1051    898   
            retained.id().partition(),
 1052    899   
            retained.id().origin().clone(),
 1053    900   
            EligibilityGroup::Pool,
 1054    901   
            Some(origin.clone()),
 1055    902   
            None,
 1056    903   
        ));
 1057    904   
        assert_eq!(retained.id(), candidate.id());
 1058    905   
 1059    906   
        let (_waiter, snapshot) =
 1060    907   
            candidate.register_waiter_without_publish(ProtocolRequirement::H1Compatible);
 1061    908   
        let mut delivery =
 1062         -
            OriginAdmission::publish_without_driving(&origin, candidate.id().partition(), snapshot)
         909  +
            OriginAdmission::submit_without_running(&origin, candidate.id().partition(), snapshot)
 1063    910   
                .expect("candidate demand did not reserve capacity");
 1064         -
        assert!(delivery.materialize_for_test());
         911  +
        assert!(delivery.resolve_payload_for_test());
 1065    912   
        assert!(OriginCell::receive_delivery(&candidate, delivery).is_none());
 1066    913   
        assert_eq!(0, origin.available_capacity_for_test());
 1067    914   
 1068    915   
        let winner = OriginAdmission::register_cell(&origin, candidate);
 1069    916   
        assert!(Arc::ptr_eq(&retained, &winner));
 1070    917   
        assert_eq!(
 1071    918   
            1,
 1072    919   
            origin.available_capacity_for_test(),
 1073    920   
            "losing candidate did not return capacity after registration"
 1074    921   
        );
 1075    922   
    }
 1076    923   
 1077         -
    #[test]
 1078         -
    fn expired_requesting_cell_refunnels_capacity() {
 1079         -
        let origin = OriginAdmission::for_test(NonZeroUsize::new(1).unwrap());
 1080         -
        let requesting_partition = cell(&origin, 1);
 1081         -
        let requesting_cell_id = requesting_partition.id().partition();
 1082         -
        let (_waiter, snapshot) =
 1083         -
            requesting_partition.register_waiter_without_publish(ProtocolRequirement::H1Compatible);
 1084         -
        let delivery =
 1085         -
            OriginAdmission::publish_without_driving(&origin, requesting_cell_id, snapshot)
 1086         -
                .unwrap();
 1087         -
 1088         -
        drop(requesting_partition);
 1089         -
        assert!(origin.cell(&requesting_cell_id).is_none());
 1090         -
        OriginAdmission::drive(Some(AdmissionAction::Delivery(delivery)));
 1091         -
 1092         -
        let counts = origin.counts();
 1093         -
        assert_eq!(1, counts.available);
 1094         -
        assert_eq!(0, counts.delivering);
 1095         -
        assert_eq!(0, counts.ordered);
 1096         -
    }
 1097         -
 1098    924   
    #[test]
 1099    925   
    fn separate_origins_conserve_capacity_independently() {
 1100    926   
        let first = OriginAdmission::for_test(NonZeroUsize::new(1).unwrap());
 1101    927   
        let second = OriginAdmission::for_test(NonZeroUsize::new(1).unwrap());
 1102    928   
        let first_cell = cell(&first, 1);
 1103    929   
        let second_cell = cell(&second, 1);
 1104    930   
 1105         -
        let first_delivery = OriginAdmission::publish_without_driving(
 1106         -
            &first,
 1107         -
            first_cell.id().partition(),
 1108         -
            demand(1),
 1109         -
        )
 1110         -
        .unwrap();
 1111         -
        let second_delivery = OriginAdmission::publish_without_driving(
         931  +
        let first_delivery =
         932  +
            OriginAdmission::submit_without_running(&first, first_cell.id().partition(), demand(1))
         933  +
                .unwrap();
         934  +
        let second_delivery = OriginAdmission::submit_without_running(
 1112    935   
            &second,
 1113    936   
            second_cell.id().partition(),
 1114    937   
            demand(1),
 1115    938   
        )
 1116    939   
        .unwrap();
 1117    940   
        assert_eq!(0, first.available_capacity_for_test());
 1118    941   
        assert_eq!(0, second.available_capacity_for_test());
 1119    942   
 1120    943   
        drop(first_delivery);
 1121    944   
        assert_eq!(1, first.available_capacity_for_test());
 1122    945   
        assert_eq!(0, second.available_capacity_for_test());
 1123    946   
 1124    947   
        drop(second_delivery);
 1125    948   
        assert_eq!(1, second.available_capacity_for_test());
 1126    949   
    }
 1127    950   
}
 1128    951   
 1129    952   
#[cfg(all(test, smithy_http_client_loom))]
 1130    953   
mod loom_tests {
 1131    954   
    use super::*;
 1132    955   
    use crate::client::pool::partition::PartitionId;
 1133    956   
 1134    957   
    fn id() -> PartitionId {
 1135    958   
        PartitionId::from_index(1)
 1136    959   
    }
 1137    960   
 1138    961   
    fn demand() -> DemandSnapshot {
 1139    962   
        DemandSnapshot::active(
 1140    963   
            DemandId::from_u64(1),
 1141    964   
            SnapshotVersion::INITIAL,
 1142    965   
            ProtocolRequirement::H1Compatible,
 1143    966   
            EligibilityGroup::Pool,
 1144    967   
        )
 1145    968   
    }
 1146    969   
 1147    970   
    #[test]
 1148         -
    fn release_and_demand_publication_conserve_one_permit() {
         971  +
    fn release_and_demand_submission_conserve_one_permit() {
 1149    972   
        loom::model(|| {
 1150    973   
            let origin = OriginAdmission::for_test(NonZeroUsize::new(1).unwrap());
 1151    974   
            let permit = origin.state.lock().take_permit().unwrap();
 1152    975   
            let lease = CapacityLease::new(origin.clone(), permit);
 1153    976   
 1154    977   
            let release = loom::thread::spawn(move || drop(lease));
 1155    978   
            let publish_origin = origin.clone();
 1156    979   
            let publish = loom::thread::spawn(move || {
 1157    980   
                let delivery =
 1158         -
                    OriginAdmission::publish_without_driving(&publish_origin, id(), demand());
         981  +
                    OriginAdmission::submit_without_running(&publish_origin, id(), demand());
 1159    982   
                drop(delivery);
 1160    983   
            });
 1161    984   
            release.join().unwrap();
 1162    985   
            publish.join().unwrap();
 1163    986   
 1164         -
            assert_eq!(1, origin.counts().available);
 1165         -
            assert!(origin.counts().ordered <= 1);
         987  +
            assert_eq!(1, origin.probe().available);
         988  +
            assert!(origin.probe().ordered <= 1);
 1166    989   
        });
 1167    990   
    }
 1168    991   
 1169    992   
    #[test]
 1170         -
    fn cancellation_preserves_an_outstanding_delivery_fence() {
         993  +
    fn cancellation_preserves_an_outstanding_demand_assignment() {
 1171    994   
        loom::model(|| {
 1172    995   
            use loom::sync::atomic::{AtomicBool, Ordering};
 1173    996   
 1174    997   
            let origin = OriginAdmission::for_test(NonZeroUsize::new(2).unwrap());
 1175    998   
            let requesting_partition = id();
 1176    999   
            let delivery =
 1177         -
                OriginAdmission::publish_without_driving(&origin, requesting_partition, demand())
        1000  +
                OriginAdmission::submit_without_running(&origin, requesting_partition, demand())
 1178   1001   
                    .unwrap();
 1179   1002   
            let release = Arc::new(AtomicBool::new(false));
 1180   1003   
 1181   1004   
            let delivery_release = release.clone();
 1182   1005   
            let dropped = loom::thread::spawn(move || {
 1183   1006   
                while !delivery_release.load(Ordering::Acquire) {
 1184   1007   
                    loom::thread::yield_now();
 1185   1008   
                }
 1186   1009   
                drop(delivery);
 1187   1010   
            });
 1188   1011   
 1189         -
            origin.state.lock().publish_demand(
        1012  +
            origin.state.lock().apply_demand_snapshot(
 1190   1013   
                requesting_partition,
 1191   1014   
                DemandSnapshot::inactive(DemandId::from_u64(1), SnapshotVersion::INITIAL.next()),
 1192   1015   
            );
 1193         -
            let duplicate = OriginAdmission::publish_without_driving(
        1016  +
            let duplicate = OriginAdmission::submit_without_running(
 1194   1017   
                &origin,
 1195   1018   
                requesting_partition,
 1196   1019   
                DemandSnapshot::active(
 1197   1020   
                    DemandId::from_u64(2),
 1198   1021   
                    SnapshotVersion::INITIAL,
 1199   1022   
                    ProtocolRequirement::H1Compatible,
 1200   1023   
                    EligibilityGroup::Pool,
 1201   1024   
                ),
 1202   1025   
            );
 1203   1026   
 1204   1027   
            release.store(true, Ordering::Release);
 1205   1028   
            dropped.join().unwrap();
 1206   1029   
            assert!(
 1207   1030   
                duplicate.is_none(),
 1208         -
                "outstanding delivery fence admitted a second delivery"
        1031  +
                "outstanding demand assignment admitted a second delivery"
 1209   1032   
            );
 1210   1033   
 1211         -
            let counts = origin.counts();
 1212         -
            assert_eq!(2, counts.available);
 1213         -
            assert_eq!(0, counts.delivering);
 1214         -
            assert_eq!(0, counts.ordered);
        1034  +
            let probe = origin.probe();
        1035  +
            assert_eq!(2, probe.available);
        1036  +
            assert_eq!(0, probe.assigned);
        1037  +
            assert_eq!(0, probe.ordered);
 1215   1038   
        });
 1216   1039   
    }
 1217   1040   
}