AWS SDK

AWS SDK

rev. 1ed3d5371c342b375a8e2197cd2254b0dc08a51c

Files changed:

tmp-codegen-diff/aws-sdk/sdk/aws-smithy-http-client/additional-ci

@@ -1,1 +23,26 @@
    1      1   
#!/bin/bash
    2      2   
#
    3      3   
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
    4      4   
# SPDX-License-Identifier: Apache-2.0
    5      5   
#
    6      6   
    7      7   
# This script contains additional CI checks to run for this specific package
    8      8   
    9      9   
set -e
   10     10   
          11  +
echo "### Checking standalone and pairwise feature combinations"
          12  +
cargo hack check --feature-powerset --depth 2 --exclude-all-features
          13  +
   11     14   
echo "### Testing with the wire harness, Rustls/AWS-LC, and s2n-tls"
   12     15   
cargo test --features wire-mock,rustls-aws-lc,s2n-tls
   13     16   
   14     17   
echo "### Checking HTTP proxy tests without a TLS provider"
   15     18   
cargo test --no-default-features --features default-client --test proxy_tests --no-run
   16     19   
   17     20   
echo "### Testing unstable custom rustls crypto provider"
   18     21   
# Enabling ring because the tests use it as the "custom" provider, and it transitively enables __rustls
   19     22   
RUSTFLAGS="--cfg aws_sdk_unstable" cargo test --features rustls-ring,rt-tokio
   20     23   
   21     24   
echo "### Running connection pool Loom models"
   22         -
RUSTFLAGS="--cfg smithy_http_client_loom" \
          25  +
RUSTFLAGS="${RUSTFLAGS:-} --cfg smithy_http_client_loom -D warnings" \
   23     26   
    cargo test --lib --features default-client,rt-tokio

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

@@ -1040,1040 +1280,1307 @@
 1060   1060   
 1061   1061   
Borrow and reclaim share one retained H1 match lifecycle:
 1062   1062   
 1063   1063   
```rust
 1064   1064   
enum H1MatchKind {
 1065   1065   
    BorrowSender,
 1066   1066   
    ReclaimCapacity,
 1067   1067   
}
 1068   1068   
 1069   1069   
enum H1MatchState {
        1070  +
    ProbingIdle,
 1070   1071   
    Reserving,
 1071   1072   
    WaitingForSender,
 1072   1073   
    Resolving,
 1073   1074   
    Cancelling,
 1074   1075   
}
 1075   1076   
 1076   1077   
struct H1Match {
 1077   1078   
    supplier: PartitionId,
 1078   1079   
    requester: PartitionId,
 1079   1080   
    demand: DemandId,
 1080   1081   
    kind: H1MatchKind,
 1081   1082   
    state: H1MatchState,
 1082   1083   
    cancelled: bool,
 1083   1084   
}
 1084   1085   
 1085   1086   
enum H1ReservationState {
 1086   1087   
    Available,
 1087   1088   
    Installed(H1MatchId),
 1088   1089   
    Resolving(H1MatchId),
 1089   1090   
}
 1090   1091   
 1091   1092   
struct H1Reservation {
 1092   1093   
    state: H1ReservationState,
 1093   1094   
    local_turn_owed: bool,
 1094   1095   
}
 1095   1096   
 1096   1097   
struct H1CellState {
 1097   1098   
    records: HashMap<ConnectionId, H1Record>,
 1098   1099   
    idle_order: VecDeque<ConnectionId>,
 1099   1100   
    peer_reservation: H1Reservation,
 1100   1101   
}
 1101   1102   
```
 1102   1103   
 1103   1104   
Admission owns `H1Match`; the supplier cell owns `H1CellState` and its
 1104   1105   
`H1Reservation`. Each supplier and requesting cell participates in at most one
 1105   1106   
nonterminal match at a time. The match kind is fixed at selection. A reclaim
 1106   1107   
selected from the origin-wide order cannot become a borrow because its cells
 1107   1108   
may belong to different eligibility groups. A borrow could become a reclaim
 1108   1109   
without violating eligibility, but keeping both kinds fixed avoids adding a
 1109   1110   
second terminal path after reservation; a rejected match
 1110   1111   
returns to admission for a fresh selection.
 1111   1112   
 1112   1113   
A match reserves and resolves without nesting the admission, supplier-cell, or
 1113   1114   
requesting-cell locks:
 1114   1115   
 1115   1116   
```text
 1116   1117   
OriginAdmission owns queued demand R
 1117   1118   
  |
 1118   1119   
  `-- H1SupplyIndex selects peer supplier cell C
 1119         -
        `-- retain H1Match K in Reserving
 1120         -
              `-- reserve K under C's lock
        1120  +
        +-- one eligible supplier -> retain H1Match K in Reserving
 1121   1121   
        |
 1122         -
        +-- C owes a usable local turn
 1123         -
        |     `-- reject K; R stays queued
 1124         -
        |
 1125         -
        +-- C has an older local H1 candidate
 1126         -
        |     `-- reject K; R stays queued
 1127         -
        |
 1128         -
        +-- idle H1 available
 1129         -
        |     `-- C reservation -> Resolving(K); H1Candidate owns sender
 1130         -
        |
 1131         -
        +-- active or returning H1 exists
 1132         -
        |     `-- C reservation -> Installed(K); reserve next reusable return
 1133         -
        |
 1134         -
        `-- no H1 can return
 1135         -
              `-- reject K; R stays queued
        1122  +
        `-- several eligible suppliers -> retain K in ProbingIdle
        1123  +
              `-- check C for an immediately idle sender under C's lock
        1124  +
        1125  +
ProbingIdle(K)
        1126  +
  |
        1127  +
  +-- idle H1 available
        1128  +
  |     `-- C reservation -> Resolving(K); H1Candidate owns sender
        1129  +
  |
        1130  +
  +-- C is busy or unavailable and probe bound remains
        1131  +
  |     `-- select a distinct eligible supplier and repeat
        1132  +
  |
        1133  +
  `-- final supplier check
        1134  +
        `-- choose one exact supplier; K -> Reserving
        1135  +
              `-- reserve K under that supplier's lock
        1136  +
        1137  +
Reserving(K)
        1138  +
  |
        1139  +
  +-- supplier owes a usable local turn
        1140  +
  |     `-- reject K; R stays queued
        1141  +
  |
        1142  +
  +-- supplier has an older local H1 candidate
        1143  +
  |     `-- reject K; R stays queued
        1144  +
  |
        1145  +
  +-- idle H1 available
        1146  +
  |     `-- supplier reservation -> Resolving(K); H1Candidate owns sender
        1147  +
  |
        1148  +
  +-- active or returning H1 exists
        1149  +
  |     `-- supplier reservation -> Installed(K); reserve next reusable return
        1150  +
  |
        1151  +
  `-- no H1 can return
        1152  +
        `-- reject K; R stays queued
 1136   1153   
 1137   1154   
Installed(K) + reusable return at C
 1138   1155   
  `-- C reservation -> Resolving(K); H1Candidate owns sender
 1139   1156   
 1140   1157   
candidate reaches OriginAdmission
 1141   1158   
  |
 1142   1159   
  +-- K or R is stale, cancelled, or already satisfied
 1143   1160   
  |     `-- H1Candidate returns sender through C's ordinary return path
 1144   1161   
  |
 1145   1162   
  +-- K is BorrowSender
 1146   1163   
  |     `-- create DemandAssignment D for R
 1147   1164   
  |           `-- commit C's candidate before reserving requesting-cell waiter
 1148   1165   
  |                 +-- accepted -> waiter owns H1 selection
 1149   1166   
  |                 `-- refused -> return H1 to C, then retry D
 1150   1167   
  |
 1151   1168   
  `-- K is ReclaimCapacity
 1152   1169   
        `-- revalidate under C's lock and attempt logical close
 1153   1170   
              `-- released permit enters ordinary capacity delivery
 1154   1171   
 1155   1172   
terminal H1SupplyOutcome
 1156   1173   
  `-- remove K; apply C's latest supply revision; schedule next action
 1157   1174   
```
 1158   1175   
 1159   1176   
A provisional candidate revalidates the connection generation, logical-close
 1160   1177   
state, idle policy, and matching cell reservation before it becomes an
 1161   1178   
`H1Selection` or is reclaimed. A failed revalidation returns the request handle
 1162   1179   
through ordinary owning-cell policy. If another close wins the reclaim race,
 1163   1180   
the released permit still follows its normal exactly-once admission path.
 1164   1181   
 1165   1182   
An installed reservation intercepts a reusable return under the supplier-cell
 1166   1183   
lock before the handle can become locally idle. Local
 1167   1184   
H1-compatible demand that arrives after installation may therefore be
 1168   1185   
overtaken once. An irreversible borrow or successful reclaim records one local
 1169   1186   
fairness turn when compatible local demand exists. The next local H1 service
 1170   1187   
consumes that turn; if compatible demand disappears first, the turn clears.
 1171   1188   
An H2-required local head cannot consume the turn and does not block a reuse
 1172   1189   
match that can make progress.
 1173   1190   
 1174   1191   
Cancellation marks a reserving or resolving match stale. An installed
 1175   1192   
reservation crosses back to the supplier cell and is cleared. A
 1176   1193   
candidate already outside the cell lock returns through ordinary owning-cell
 1177   1194   
policy. Cancellation after irreversible transfer does not revoke an earned
 1178   1195   
fairness turn.
 1179   1196   
 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`
 1184         -
returns the permit to admission. Fallbacks run no connector, protocol, wake,
 1185         -
or listener code while a pool lock is held.
        1197  +
Every cross-lock action owns a typed fallback. Dropping an `H1IdleProbeAction`
        1198  +
returns the supplier to its index and advances or settles the match. Dropping
        1199  +
an `H1ReservationAction` or `H1CancellationAction` clears the cell reservation
        1200  +
and settles the admission match. Dropping an `H1Candidate` returns its sender
        1201  +
before the supplier cell becomes selectable again. Dropping a `DeliveryGuard`
        1202  +
returns the permit to admission. Fallbacks run no connector, protocol, wake, or
        1203  +
listener code while a pool lock is held.
 1186   1204   
 1187   1205   
A fallback invoked from `Drop` may synchronously acquire a bounded sequence of pool locks to publish its
 1188   1206   
terminal state, but it holds at most one pool lock at a time. Each lock transition produces the next typed
 1189   1207   
action only after releasing the previous lock, and each action retains an idempotent fallback until the next
 1190   1208   
domain owns the terminal state. The chain performs no await, retry loop, connector or protocol work, or work
 1191   1209   
proportional to waiter or partition count, and schedules wakes or callbacks only after releasing its lock.
 1192   1210   
 1193   1211   
Pool coordination uses a crate-level synchronization facade so production and Loom tests compile the same
 1194   1212   
lock-bearing code. Production lock wrappers retain access to guarded state after standard-library poisoning so
 1195   1213   
poisoning alone cannot prevent a later `Drop` fallback from returning a permit or settling a demand assignment.
 1196   1214   
This does not make an interrupted state transition valid: code under a pool lock still preserves its
 1197   1215   
invariants without relying on poison recovery. Test builds also assert that a thread holds at most one pool
 1198   1216   
lock, turning the no-nesting rule into an executable check across the ordinary suite.
 1199   1217   
 1200   1218   
Both stay within one origin, and neither moves a driver, so the I/O-placement guarantee from
 1201   1219   
[Connection placement follows declared topology](#connection-placement-follows-declared-topology)
 1202   1220   
holds under both. This is why `max_connections_per_host` below the partition count is valid rather than an
 1203   1221   
error: a partition with no permit of its own may dispatch through an eligible
 1204   1222   
peer HTTP/1 sender or receive capacity through reclaim. The
 1205   1223   
default `NetworkInterface` scope uses both; `Partition` and `Pool` are the same machinery with a narrower or
 1206   1224   
wider eligibility group.
 1207   1225   
 1208   1226   
#### Ordering across cells
 1209   1227   
 1210   1228   
Each cell orders its own requests. Across cells, admission keeps one
 1211   1229   
origin-wide demand FIFO and separate origin and eligibility-group views over
 1212   1230   
cells with HTTP/1 connections:
 1213   1231   
 1214   1232   
```text
 1215   1233   
OriginAdmission(O)
 1216   1234   
  demand order:
 1217   1235   
    oldest -> C2/R8(H1) -> C0/R3(H2) -> C3/R5(H2) -> C1/R9(H1)
 1218   1236   
 1219   1237   
  selectable H1 supplier cells:
 1220   1238   
    origin view:       C0 -> C2 -> C3
 1221   1239   
    group eth0 view:   C0 -> C3
 1222   1240   
    group eth1 view:   C2
 1223   1241   
```
 1224   1242   
 1225   1243   
The demand order contains the current head generation from every requesting
 1226   1244   
cell waiting for origin capacity. An H1 supply view contains a supplier cell at
 1227   1245   
most once while it has an H1 record that may return or be reclaimed, has no
 1228   1246   
nonterminal match, and owes no usable local turn or older local H1-compatible
 1229   1247   
waiter. Removing a cell repairs both views immediately, so admission does not
 1230   1248   
drain stale supply entries.
 1231   1249   
 1232   1250   
HTTP/1 selection begins with the oldest origin demand:
 1233   1251   
 1234   1252   
```text
 1235   1253   
oldest origin demand R from requesting cell Q
 1236   1254   
  |
 1237   1255   
  +-- R accepts H1 and an eligible peer cell C exists
 1238         -
  |     `-- retain BorrowSender match(C, Q, R)
        1256  +
  |     `-- cross at most MAX_H1_SUPPLIER_CHECKS distinct suppliers
        1257  +
  |           +-- idle sender found -> resolve BorrowSender match(C, Q, R)
        1258  +
  |           `-- final check -> reserve one exact eligible supplier
 1239   1259   
  |
 1240   1260   
  +-- another peer H1 cell C exists
 1241   1261   
  |     `-- retain ReclaimCapacity match(C, Q, R)
 1242   1262   
  |
 1243   1263   
  `-- no peer connection
 1244   1264   
        `-- wait for capacity, local service, or a later supply revision
 1245   1265   
```
 1246   1266   
 1247   1267   
The supply selector skips the requesting cell. Same-cell idle selection and
 1248   1268   
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
 1250         -
origin-wide peer.
        1269  +
match. Borrow crosses at most `MAX_H1_SUPPLIER_CHECKS` distinct eligible
        1270  +
supplier cells. With one supplier, admission reserves it directly. With
        1271  +
several suppliers, preliminary crossings take only an immediately idle sender;
        1272  +
the final crossing installs the ordinary exact reservation, which takes an
        1273  +
idle sender when present or intercepts that supplier's next reusable return.
        1274  +
If eligible supply disappears during the crossings, ordinary reclaim selection
        1275  +
resumes. The fixed-size probe set prevents a supplier that re-enters the FIFO
        1276  +
from being checked twice in one search. Reclaim takes the oldest origin-wide
        1277  +
peer.
 1251   1278   
 1252   1279   
The origin demand head is no younger than any eligibility-group demand head.
 1253   1280   
Selecting that origin head first therefore preserves eligible H1 ordering
 1254   1281   
without merging two demand heads. Eligibility changes only whether the
 1255   1282   
selected peer is borrowed or reclaimed.
 1256   1283   
 1257   1284   
The oldest origin demand therefore receives first use of every peer H1. If it
 1258   1285   
can borrow the selected connection, the warm connection remains open.
 1259   1286   
Otherwise reclaim closes it and returns capacity for the same oldest demand.
 1260   1287   
A younger demand in the connection's eligibility group cannot bypass the
@@ -1504,1531 +1563,1594 @@
 1524   1551   
  capacity lease; request activation takes a request claim while the connection generation remains the capacity owner.
 1525   1552   
* **Generation visibility priority** [liveness] — making a local generation or peer route visible closes the
 1526   1553   
  generation gate and serves previously committed waiters in bounded oldest-first turns before later direct
 1527   1554   
  arrivals.
 1528   1555   
* **Cross-cell order** [safety] — H1 borrow and reclaim both serve the current origin head, preferring an
 1529   1556   
  eligible peer connection for borrow and otherwise reclaiming an origin peer. Peer H2 routing uses its
 1530   1557   
  all-protocol eligibility-group head. Same-cell H1 service remains cell-local.
 1531   1558   
* **H1 match completion** [safety] — one H1 match reserves at most one supplier cell and one requesting cell; it
 1532   1559   
  remains authoritative until supplier-cell completion and any borrowed demand assignment settles its terminal
 1533   1560   
  state.
        1561  +
* **Bounded H1 probing** [optimization] — one H1 borrow match crosses at most
        1562  +
  `MAX_H1_SUPPLIER_CHECKS` distinct eligible supplier cells. Preliminary crossings take only an immediately
        1563  +
  idle sender; the final crossing installs one exact supplier reservation. The work does not grow with the
        1564  +
  number of partitions.
 1534   1565   
* **Return interception** [liveness] — an installed H1 reservation intercepts the next reusable H1 before it
 1535   1566   
  becomes idle, so a connection cycling continuously between active and reusable cannot strand requesting
 1536   1567   
  demand.
 1537   1568   
* **Owning-cell fairness turn** [liveness] — one irreversible cross-cell transfer creates one local turn when
 1538   1569   
  compatible local demand exists; the turn clears only when that demand is served or disappears.
 1539   1570   
* **Acknowledged progress** [liveness] — every extracted delivery or detached action either settles a terminal
 1540   1571   
  transition or executes its typed fallback, so a demand assignment cannot remain pending solely because the
 1541   1572   
  executing future was dropped.
 1542   1573   
* **Cross-lock isolation** [safety] — admission and cell locks are never nested, and no pool lock is held
 1543   1574   
  across an await or while running connector, protocol, wake, or listener code. A synchronous fallback may
@@ -1994,2025 +2088,2123 @@
 2014   2045   
  non-dispatchable in `ReservedForPeer`, so a late completion cannot reverse logical close or bypass return
 2015   2046   
  ordering.
 2016   2047   
* **Physical connection completion** [safety] — dropping the wrapped root I/O, not logical close or
 2017   2048   
  driver-future completion alone, releases the client's physical connection handle, including after H1
 2018   2049   
  upgrade. The operating system may continue TCP teardown afterward.
 2019   2050   
 2020   2051   
### Telemetry
 2021   2052   
 2022   2053   
Request results do not reveal whether a request reused a connection, opened a
 2023   2054   
new one, waited for capacity, or observed a connection closing. The pool exposes
 2024         -
two complementary observations:
        2055  +
three complementary observations:
 2025   2056   
        2057  +
- request-attempt telemetry reports dispatch and connection selection facts
        2058  +
  through an optional request extension;
 2026   2059   
- lifecycle events report completed transitions; and
 2027   2060   
- statistics report current origin or partition-origin state.
 2028   2061   
 2029         -
Neither surface participates in admission, reuse, reclaim, or dispatch.
        2062  +
None participates in admission, reuse, reclaim, or dispatch.
 2030   2063   
 2031   2064   
#### Lifecycle events
 2032   2065   
 2033   2066   
One listener receives a non-exhaustive event enum:
 2034   2067   
 2035   2068   
```rust
 2036   2069   
pub trait ConnectionEventListener: Send + Sync + 'static {
 2037   2070   
    fn on_event(&self, event: &ConnectionEvent<'_>);
 2038   2071   
}
 2039   2072   
 2040   2073   
#[non_exhaustive]
 2041   2074   
pub enum ConnectionEvent<'a> {
 2042   2075   
    EstablishmentFailed(ConnectionEstablishmentFailed<'a>),
 2043   2076   
    Opened(ConnectionOpened<'a>),
 2044   2077   
    LogicalClose(ConnectionLogicalClose<'a>),
 2045   2078   
    PhysicalClose(ConnectionPhysicalClose<'a>),
 2046   2079   
}
 2047   2080   
```
 2048   2081   
 2049   2082   
The pool assigns each establishment a `ConnectionEstablishmentId`. A retry
 2050   2083   
starts another establishment and receives another identity; earlier failed
 2051   2084   
attempts are not folded into a later successful event. Establishment events
 2052   2085   
carry the canonical origin, owner partition, stage, selected protocol and
 2053   2086   
remote address when known, and timing for transport and Hyper protocol
 2054   2087   
handshake work.
 2055   2088   
 2056   2089   
`Opened` is emitted after Hyper produces the protocol request handle and the
 2057   2090   
connection is installed as pool supply. It carries the establishment
 2058         -
observation and the installed connection's immutable `ConnectionInfo`:
        2091  +
observation and the installed connection's immutable `ConnectionInfo`.
        2092  +
Successful establishment measurements are frozen after protocol installation
        2093  +
and before the connection is published to waiting demand:
 2059   2094   
 2060   2095   
```text
 2061   2096   
ConnectionEstablishmentInfo
 2062   2097   
  establishment ID
 2063   2098   
  origin
 2064   2099   
  owner partition
 2065   2100   
 2066   2101   
ConnectionInfo
 2067   2102   
  connection ID
 2068   2103   
  origin
@@ -2192,2227 +2253,2290 @@
 2212   2247   
 2213   2248   
A listener runs synchronously on the request, establishment, maintenance, or
 2214   2249   
driver task that produces an event. It must not block on work that requires
 2215   2250   
progress from that same task.
 2216   2251   
 2217   2252   
Listener invocation occurs outside every pool lock. On unwind-capable builds,
 2218   2253   
the pool catches and logs listener panics after the authoritative transition;
 2219   2254   
the panic does not alter pool state or prevent required cleanup. Abort-on-panic
 2220   2255   
builds retain their normal process-abort semantics.
 2221   2256   
 2222         -
Installing no listener avoids event timing, establishment identity allocation,
 2223         -
listener cloning, and callback work. Diagnostic connection counts remain
        2257  +
Installing no listener avoids establishment identity allocation, listener
        2258  +
cloning, and callback work. Successful establishment timing remains available
        2259  +
as immutable connection metadata. Request-attempt timing is read only when the
        2260  +
request carries its capture extension. Diagnostic connection counts remain
 2224   2261   
available independently of event configuration.
 2225   2262   
 2226   2263   
#### Obligations
 2227   2264   
 2228   2265   
* **Transition ownership** [safety] — each event is emitted by the transition
 2229   2266   
  that owns its final state, after releasing pool locks.
 2230   2267   
* **Installed event order** [safety] — one connection reports `Opened`, then
 2231   2268   
  `LogicalClose`, then `PhysicalClose`, with each event at most once.
 2232   2269   
* **Establishment terminality** [safety] — each establishment ends as failed,
 2233   2270   
  opened, or internally superseded exactly once.

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

@@ -1,1 +195,229 @@
   11     11   
/// TLS connector(s)
   12     12   
pub mod tls;
   13     13   
   14     14   
pub(crate) mod connect;
   15     15   
   16     16   
use self::connect::ConnectPathInner;
   17     17   
use crate::cfg::cfg_tls;
   18     18   
use crate::tls::TlsContext;
   19     19   
use aws_smithy_async::future::timeout::TimedOutError;
   20     20   
use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep};
          21  +
use aws_smithy_async::time::SharedTimeSource;
   21     22   
use aws_smithy_runtime_api::box_error::BoxError;
   22     23   
use aws_smithy_runtime_api::client::connection::CaptureSmithyConnection;
   23     24   
use aws_smithy_runtime_api::client::connection::ConnectionMetadata;
   24     25   
use aws_smithy_runtime_api::client::connector_metadata::ConnectorMetadata;
   25     26   
use aws_smithy_runtime_api::client::dns::ResolveDnsError;
          27  +
use aws_smithy_runtime_api::client::http::telemetry::CaptureHttpAttemptTelemetry;
   26     28   
use aws_smithy_runtime_api::client::http::{
   27     29   
    HttpClient, HttpConnector, HttpConnectorFuture, HttpConnectorSettings, SharedHttpClient,
   28     30   
    SharedHttpConnector,
   29     31   
};
   30     32   
use aws_smithy_runtime_api::client::orchestrator::{HttpRequest, HttpResponse};
   31     33   
use aws_smithy_runtime_api::client::result::ConnectorError;
   32     34   
use aws_smithy_runtime_api::client::runtime_components::{
   33     35   
    RuntimeComponents, RuntimeComponentsBuilder,
   34     36   
};
   35     37   
use aws_smithy_runtime_api::shared::IntoShared;
   36     38   
use aws_smithy_types::body::SdkBody;
   37     39   
use aws_smithy_types::config_bag::ConfigBag;
   38     40   
use aws_smithy_types::error::display::DisplayErrorContext;
   39     41   
use aws_smithy_types::retry::ErrorKind;
   40     42   
use client::connect::Connection;
   41     43   
use h2::Reason;
   42     44   
use http_1x::{Extensions, Uri};
   43     45   
use hyper::rt::{Read, Write};
   44     46   
use hyper_util::client::legacy as client;
   45     47   
use hyper_util::client::legacy::connect::dns::GaiResolver;
   46     48   
use hyper_util::client::legacy::connect::{
   47     49   
    capture_connection, CaptureConnection, Connect, HttpConnector as HyperHttpConnector, HttpInfo,
   48     50   
};
   49     51   
use hyper_util::client::proxy::matcher::Matcher;
   50     52   
use hyper_util::rt::{TokioExecutor, TokioTimer};
   51     53   
use std::borrow::Cow;
   52     54   
use std::collections::HashMap;
   53     55   
use std::error::Error;
   54     56   
use std::fmt;
   55     57   
use std::sync::RwLock;
   56         -
use std::time::Duration;
          58  +
use std::time::{Duration, SystemTime};
          59  +
          60  +
/// Optional timing for one complete HTTP connector call.
          61  +
pub(crate) struct ConnectorCallTimer {
          62  +
    capture: CaptureHttpAttemptTelemetry,
          63  +
    time_source: SharedTimeSource,
          64  +
    started_at: SystemTime,
          65  +
}
          66  +
          67  +
impl ConnectorCallTimer {
          68  +
    /// Starts timing when the request asks the connector to capture telemetry.
          69  +
    pub(crate) fn start(request: &HttpRequest, time_source: &SharedTimeSource) -> Option<Self> {
          70  +
        let capture = request.extension::<CaptureHttpAttemptTelemetry>()?.clone();
          71  +
        let time_source = time_source.clone();
          72  +
        let started_at = time_source.now();
          73  +
        Some(Self {
          74  +
            capture,
          75  +
            time_source,
          76  +
            started_at,
          77  +
        })
          78  +
    }
          79  +
          80  +
    /// Records the interval when the connector call returns.
          81  +
    pub(crate) fn finish(self) {
          82  +
        let Ok(duration) = self.time_source.now().duration_since(self.started_at) else {
          83  +
            return;
          84  +
        };
          85  +
        self.capture.record_connector_call_duration(duration);
          86  +
    }
          87  +
}
   57     88   
   58     89   
/// Given `HttpConnectorSettings` and an `SharedAsyncSleep`, create a `SharedHttpConnector` from defaults depending on what cargo features are activated.
   59     90   
pub fn default_connector(
   60     91   
    settings: &HttpConnectorSettings,
   61     92   
    sleep: Option<SharedAsyncSleep>,
   62     93   
) -> Option<SharedHttpConnector> {
   63     94   
    #[cfg(feature = "rustls-aws-lc")]
   64     95   
    {
   65     96   
        tracing::trace!(settings = ?settings, sleep = ?sleep, "creating a new default connector");
   66     97   
        let mut conn_builder = Connector::builder().connector_settings(settings.clone());
   67     98   
   68     99   
        if let Some(sleep) = sleep {
   69    100   
            conn_builder = conn_builder.sleep_impl(sleep);
   70    101   
        }
   71    102   
   72    103   
        let conn = conn_builder
   73    104   
            .tls_provider(tls::Provider::Rustls(
   74    105   
                tls::rustls_provider::CryptoMode::AwsLc,
   75    106   
            ))
   76    107   
            .build();
   77    108   
        Some(SharedHttpConnector::new(conn))
   78    109   
    }
   79    110   
    #[cfg(not(feature = "rustls-aws-lc"))]
   80    111   
    {
   81    112   
        tracing::trace!(settings = ?settings, sleep = ?sleep, "no default connector available");
   82    113   
        None
   83    114   
    }
   84    115   
}
   85    116   
   86    117   
/// [`HttpConnector`] used to make HTTP requests.
   87    118   
///
   88    119   
/// This connector also implements socket connect and read timeouts.
   89    120   
///
   90    121   
/// This shouldn't be used directly in most cases.
   91    122   
/// See the docs on [`Builder`] for examples of how to customize the HTTP client.
   92    123   
#[derive(Debug)]
   93    124   
pub struct Connector {
   94    125   
    adapter: Box<dyn HttpConnector>,
   95    126   
}
   96    127   
   97    128   
impl Connector {
   98    129   
    /// Builder for an HTTP connector.
   99    130   
    pub fn builder() -> ConnectorBuilder {
  100    131   
        ConnectorBuilder::default()
  101    132   
    }
  102    133   
}
  103    134   
  104    135   
impl HttpConnector for Connector {
  105    136   
    fn call(&self, request: HttpRequest) -> HttpConnectorFuture {
  106    137   
        self.adapter.call(request)
  107    138   
    }
  108    139   
}
  109    140   
  110    141   
/// Builder for [`Connector`].
  111    142   
#[derive(Debug, Clone)]
  112    143   
pub struct ConnectorBuilder<Tls = TlsUnset> {
  113    144   
    connector_settings: Option<HttpConnectorSettings>,
  114    145   
    sleep_impl: Option<SharedAsyncSleep>,
         146  +
    time_source: SharedTimeSource,
  115    147   
    client_builder: Option<hyper_util::client::legacy::Builder>,
  116    148   
    pool_idle_timeout: Option<Option<Duration>>,
  117    149   
    pool_max_idle_per_host: Option<usize>,
  118    150   
    enable_tcp_nodelay: bool,
  119    151   
    interface: Option<String>,
  120    152   
    proxy_config: Option<proxy::ProxyConfig>,
  121    153   
    #[allow(unused)]
  122    154   
    tls: Tls,
  123    155   
}
  124    156   
  125    157   
impl<Tls: Default> Default for ConnectorBuilder<Tls> {
  126    158   
    fn default() -> Self {
  127    159   
        Self {
  128    160   
            connector_settings: None,
  129    161   
            sleep_impl: None,
         162  +
            time_source: SharedTimeSource::default(),
  130    163   
            client_builder: None,
  131    164   
            pool_idle_timeout: None,
  132    165   
            pool_max_idle_per_host: None,
  133    166   
            // Curated default: TCP_NODELAY on. Without it, Nagle's algorithm
  134    167   
            // can hold a small write while earlier data is unacknowledged. On
  135    168   
            // request shapes emitted as multiple sub-MSS writes, this can add
  136    169   
            // an ACK wait, often RTT plus delayed-ACK time. Opt out with
  137    170   
            // `enable_tcp_nodelay(false)`.
  138    171   
            enable_tcp_nodelay: true,
  139    172   
            interface: None,
  140    173   
            proxy_config: None,
  141    174   
            tls: Tls::default(),
  142    175   
        }
  143    176   
    }
  144    177   
}
  145    178   
  146    179   
/// Initial builder state, `TlsProvider` choice required
  147    180   
#[derive(Default, Debug, Clone)]
  148    181   
#[non_exhaustive]
  149    182   
pub struct TlsUnset {}
  150    183   
  151    184   
/// TLS implementation selected
  152    185   
#[derive(Debug, Clone)]
  153    186   
pub struct TlsProviderSelected {
  154    187   
    #[allow(unused)]
  155    188   
    provider: tls::Provider,
  156    189   
    #[allow(unused)]
  157    190   
    context: TlsContext,
  158    191   
}
  159    192   
  160    193   
impl ConnectorBuilder<TlsUnset> {
  161    194   
    /// Set the TLS implementation to use for this connector
  162    195   
    pub fn tls_provider(self, provider: tls::Provider) -> ConnectorBuilder<TlsProviderSelected> {
  163    196   
        ConnectorBuilder {
  164    197   
            connector_settings: self.connector_settings,
  165    198   
            sleep_impl: self.sleep_impl,
         199  +
            time_source: self.time_source,
  166    200   
            client_builder: self.client_builder,
  167    201   
            enable_tcp_nodelay: self.enable_tcp_nodelay,
  168    202   
            interface: self.interface,
  169    203   
            proxy_config: self.proxy_config,
  170    204   
            pool_idle_timeout: self.pool_idle_timeout,
  171    205   
            pool_max_idle_per_host: self.pool_max_idle_per_host,
  172    206   
            tls: TlsProviderSelected {
  173    207   
                provider,
  174    208   
                context: TlsContext::default(),
  175    209   
            },
@@ -229,263 +328,369 @@
  249    283   
  250    284   
        let proxy_matcher = self
  251    285   
            .proxy_config
  252    286   
            .as_ref()
  253    287   
            .map(|config| config.clone().into_hyper_util_matcher());
  254    288   
  255    289   
        Connector {
  256    290   
            adapter: Box::new(Adapter {
  257    291   
                client: read_timeout,
  258    292   
                proxy_matcher,
         293  +
                time_source: self.time_source,
  259    294   
            }),
  260    295   
        }
  261    296   
    }
  262    297   
  263    298   
    /// Get the base TCP connector by mapping our config to the underlying `HttpConnector` from hyper
  264    299   
    /// (which is a base TCP connector with no TLS or any wrapping)
  265    300   
    fn base_connector(&self) -> HyperHttpConnector {
  266    301   
        self.base_connector_with_resolver(GaiResolver::new())
  267    302   
    }
  268    303   
  269    304   
    /// Get the base TCP connector by mapping our config to the underlying `HttpConnector` from hyper
  270    305   
    /// using the given resolver `R`
  271    306   
    fn base_connector_with_resolver<R>(&self, resolver: R) -> HyperHttpConnector<R> {
  272    307   
        let mut conn = HyperHttpConnector::new_with_resolver(resolver);
  273    308   
        conn.set_nodelay(self.enable_tcp_nodelay);
  274    309   
        #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
  275    310   
        if let Some(interface) = &self.interface {
  276    311   
            conn.set_interface(interface);
  277    312   
        }
  278    313   
        conn
  279    314   
    }
  280    315   
  281    316   
    /// Set the async sleep implementation used for timeouts
  282    317   
    ///
  283    318   
    /// Calling this is only necessary for testing or to use something other than
  284    319   
    /// [`default_async_sleep`].
  285    320   
    pub fn sleep_impl(mut self, sleep_impl: impl AsyncSleep + 'static) -> Self {
  286    321   
        self.sleep_impl = Some(sleep_impl.into_shared());
  287    322   
        self
  288    323   
    }
  289    324   
  290    325   
    /// Set the async sleep implementation used for timeouts
  291    326   
    ///
  292    327   
    /// Calling this is only necessary for testing or to use something other than
  293    328   
    /// [`default_async_sleep`].
  294    329   
    pub fn set_sleep_impl(&mut self, sleep_impl: Option<SharedAsyncSleep>) -> &mut Self {
  295    330   
        self.sleep_impl = sleep_impl;
  296    331   
        self
  297    332   
    }
  298    333   
         334  +
    /// Copies the runtime-component clock used for request telemetry.
         335  +
    fn set_time_source(&mut self, time_source: SharedTimeSource) -> &mut Self {
         336  +
        self.time_source = time_source;
         337  +
        self
         338  +
    }
         339  +
  299    340   
    /// Configure the HTTP settings for the `HyperAdapter`
  300    341   
    pub fn connector_settings(mut self, connector_settings: HttpConnectorSettings) -> Self {
  301    342   
        self.connector_settings = Some(connector_settings);
  302    343   
        self
  303    344   
    }
  304    345   
  305    346   
    /// Configure the HTTP settings for the `HyperAdapter`
  306    347   
    pub fn set_connector_settings(
  307    348   
        &mut self,
  308    349   
        connector_settings: Option<HttpConnectorSettings>,
@@ -473,514 +532,574 @@
  493    534   
}
  494    535   
  495    536   
/// Adapter to use a Hyper 1.0-based Client as an `HttpConnector`
  496    537   
///
  497    538   
/// This adapter also enables TCP `CONNECT` and HTTP `READ` timeouts via [`Connector::builder`].
  498    539   
struct Adapter<C> {
  499    540   
    client: timeout::HttpReadTimeout<
  500    541   
        hyper_util::client::legacy::Client<timeout::ConnectTimeout<C>, SdkBody>,
  501    542   
    >,
  502    543   
    proxy_matcher: Option<Matcher>,
         544  +
    time_source: SharedTimeSource,
  503    545   
}
  504    546   
  505    547   
impl<C> fmt::Debug for Adapter<C> {
  506    548   
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  507    549   
        f.debug_struct("Adapter")
  508    550   
            .field("client", &"** hyper client **")
  509    551   
            .field("proxy_matcher", &self.proxy_matcher.is_some())
  510    552   
            .finish()
  511    553   
    }
  512    554   
}
@@ -570,612 +657,701 @@
  590    632   
impl<C> HttpConnector for Adapter<C>
  591    633   
where
  592    634   
    C: Clone + Send + Sync + 'static,
  593    635   
    C: tower::Service<Uri>,
  594    636   
    C::Response: Connection + Read + Write + Unpin + 'static,
  595    637   
    timeout::ConnectTimeout<C>: Connect,
  596    638   
    C::Future: Unpin + Send + 'static,
  597    639   
    C::Error: Into<BoxError>,
  598    640   
{
  599    641   
    fn call(&self, request: HttpRequest) -> HttpConnectorFuture {
         642  +
        let connector_call_timer = ConnectorCallTimer::start(&request, &self.time_source);
  600    643   
        let mut request = match request.try_into_http1x() {
  601    644   
            Ok(request) => request,
  602    645   
            Err(err) => {
  603    646   
                return HttpConnectorFuture::ready(Err(ConnectorError::user(err.into())));
  604    647   
            }
  605    648   
        };
  606    649   
  607    650   
        self.add_proxy_auth_header(&mut request);
  608    651   
  609    652   
        let capture_connection = capture_connection(&mut request);
  610    653   
        if let Some(capture_smithy_connection) =
  611    654   
            request.extensions().get::<CaptureSmithyConnection>()
  612    655   
        {
  613    656   
            capture_smithy_connection
  614    657   
                .set_connection_retriever(move || extract_smithy_connection(&capture_connection));
  615    658   
        }
  616    659   
        let mut client = self.client.clone();
  617    660   
        use tower::Service;
  618    661   
        let fut = client.call(request);
  619    662   
        HttpConnectorFuture::new(async move {
  620         -
            let response = fut
  621         -
                .await
  622         -
                .map_err(downcast_error)?
  623         -
                .map(SdkBody::from_body_1_x);
  624         -
            match HttpResponse::try_from(response) {
  625         -
                Ok(response) => Ok(response),
  626         -
                Err(err) => Err(ConnectorError::other(err.into(), None)),
         663  +
            let result = match fut.await {
         664  +
                Ok(response) => HttpResponse::try_from(response.map(SdkBody::from_body_1_x))
         665  +
                    .map_err(|err| ConnectorError::other(err.into(), None)),
         666  +
                Err(err) => Err(downcast_error(err)),
         667  +
            };
         668  +
            if let Some(timer) = connector_call_timer {
         669  +
                timer.finish();
  627    670   
            }
         671  +
            result
  628    672   
        })
  629    673   
    }
  630    674   
}
  631    675   
  632    676   
/// Downcast errors coming out of hyper into an appropriate `ConnectorError`
  633    677   
fn downcast_error(err: BoxError) -> ConnectorError {
  634    678   
    // is a `TimedOutError` (from aws_smithy_async::timeout) in the chain? if it is, this is a timeout
  635    679   
    if find_source::<TimedOutError>(err.as_ref()).is_some() {
  636    680   
        return ConnectorError::timeout(err);
  637    681   
    }
@@ -1113,1157 +1192,1256 @@
 1133   1177   
 1134   1178   
fn new_conn_builder(
 1135   1179   
    client_builder: hyper_util::client::legacy::Builder,
 1136   1180   
    settings: Option<&HttpConnectorSettings>,
 1137   1181   
    runtime_components: Option<&RuntimeComponents>,
 1138   1182   
) -> ConnectorBuilder {
 1139   1183   
    let mut builder = Connector::builder().hyper_builder(client_builder);
 1140   1184   
    builder.set_connector_settings(settings.cloned());
 1141   1185   
    if let Some(components) = runtime_components {
 1142   1186   
        builder.set_sleep_impl(components.sleep_impl());
        1187  +
        if let Some(time_source) = components.time_source() {
        1188  +
            builder.set_time_source(time_source);
        1189  +
        }
 1143   1190   
    }
 1144   1191   
    builder
 1145   1192   
}
 1146   1193   
 1147   1194   
#[cfg(test)]
 1148   1195   
mod test {
 1149   1196   
    use std::io::{Error, ErrorKind};
 1150   1197   
    use std::pin::Pin;
 1151   1198   
    use std::sync::atomic::{AtomicU32, Ordering};
 1152   1199   
    use std::sync::Arc;
 1153   1200   
    use std::task::{Context, Poll};
 1154   1201   
 1155   1202   
    use crate::client::timeout::test::NeverConnects;
 1156   1203   
    use aws_smithy_async::assert_elapsed;
 1157   1204   
    use aws_smithy_async::rt::sleep::TokioSleep;
        1205  +
    use aws_smithy_async::test_util::ManualTimeSource;
 1158   1206   
    use aws_smithy_async::time::SystemTimeSource;
 1159   1207   
    use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder;
 1160   1208   
    use http_1x::Uri;
 1161   1209   
    use hyper::rt::ReadBufCursor;
 1162   1210   
    use hyper_util::client::legacy::connect::Connected;
        1211  +
    use std::time::UNIX_EPOCH;
        1212  +
        1213  +
    #[test]
        1214  +
    fn connector_call_timer_ignores_a_backwards_clock() {
        1215  +
        let capture = CaptureHttpAttemptTelemetry::new();
        1216  +
        let time_source = SharedTimeSource::new(ManualTimeSource::new(SystemTime::UNIX_EPOCH));
        1217  +
        1218  +
        ConnectorCallTimer {
        1219  +
            capture: capture.clone(),
        1220  +
            time_source,
        1221  +
            started_at: SystemTime::UNIX_EPOCH + Duration::from_secs(1),
        1222  +
        }
        1223  +
        .finish();
        1224  +
        1225  +
        assert_eq!(None, capture.get().connector_call_duration());
        1226  +
    }
 1163   1227   
 1164   1228   
    use super::*;
 1165   1229   
 1166   1230   
    #[tokio::test]
 1167   1231   
    async fn connector_selection() {
 1168   1232   
        // Create a client that increments a count every time it creates a new Connector
 1169   1233   
        let creation_count = Arc::new(AtomicU32::new(0));
 1170   1234   
        let http_client = build_with_tcp_conn_fn(None, None, None, {
 1171   1235   
            let count = creation_count.clone();
 1172   1236   
            move || {
@@ -1194,1258 +1280,1354 @@
 1214   1278   
        for handle in handles {
 1215   1279   
            handle.await.unwrap();
 1216   1280   
        }
 1217   1281   
 1218   1282   
        // Verify only 4 connectors were created amidst the chaos
 1219   1283   
        assert_eq!(4, creation_count.load(Ordering::Relaxed));
 1220   1284   
    }
 1221   1285   
 1222   1286   
    #[tokio::test]
 1223   1287   
    async fn hyper_io_error() {
        1288  +
        let time = ManualTimeSource::new(UNIX_EPOCH);
 1224   1289   
        let connector = TestConnection {
 1225         -
            inner: HangupStream,
        1290  +
            inner: HangupStream { time: time.clone() },
 1226   1291   
        };
 1227         -
        let adapter = Connector::builder().wrap_connector(connector).adapter;
 1228         -
        let err = adapter
 1229         -
            .call(HttpRequest::get("https://socket-hangup.com").unwrap())
 1230         -
            .await
 1231         -
            .expect_err("socket hangup");
        1292  +
        let mut builder = Connector::builder();
        1293  +
        builder.set_time_source(SharedTimeSource::new(time));
        1294  +
        let adapter = builder.wrap_connector(connector).adapter;
        1295  +
        let capture = CaptureHttpAttemptTelemetry::new();
        1296  +
        let mut request = HttpRequest::get("https://socket-hangup.com").unwrap();
        1297  +
        request.add_extension(capture.clone());
        1298  +
        let err = adapter.call(request).await.expect_err("socket hangup");
 1232   1299   
        assert!(err.is_io(), "unexpected error type: {:?}", err);
        1300  +
        assert_eq!(
        1301  +
            capture.get().connector_call_duration(),
        1302  +
            Some(Duration::from_secs(7))
        1303  +
        );
 1233   1304   
    }
 1234   1305   
 1235   1306   
    // ---- machinery to make a Hyper connector that responds with an IO Error
 1236   1307   
    #[derive(Clone)]
 1237         -
    struct HangupStream;
        1308  +
    struct HangupStream {
        1309  +
        time: ManualTimeSource,
        1310  +
    }
 1238   1311   
 1239   1312   
    impl Connection for HangupStream {
 1240   1313   
        fn connected(&self) -> Connected {
 1241   1314   
            Connected::new()
 1242   1315   
        }
 1243   1316   
    }
 1244   1317   
 1245   1318   
    impl Read for HangupStream {
 1246   1319   
        fn poll_read(
 1247   1320   
            self: Pin<&mut Self>,
 1248   1321   
            _cx: &mut Context<'_>,
 1249   1322   
            _buf: ReadBufCursor<'_>,
 1250   1323   
        ) -> Poll<std::io::Result<()>> {
        1324  +
            self.get_mut().time.advance(Duration::from_secs(7));
 1251   1325   
            Poll::Ready(Err(Error::new(
 1252   1326   
                ErrorKind::ConnectionReset,
 1253   1327   
                "connection reset",
 1254   1328   
            )))
 1255   1329   
        }
 1256   1330   
    }
 1257   1331   
 1258   1332   
    impl Write for HangupStream {
 1259   1333   
        fn poll_write(
 1260   1334   
            self: Pin<&mut Self>,

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

@@ -7,7 +98,98 @@
   27     27   
//!    a route and activation opportunity in the requesting cell.
   28     28   
//! 7. The requesting cell becomes authoritative before either guard settles the
   29     29   
//!    assignment. Refusal and drop execute the same fallback paths.
   30     30   
//!
   31     31   
//! Capacity and HTTP/1 handoffs own the payload they must return on failure.
   32     32   
//! HTTP/2 route installation moves no payload: the connection-owning cell
   33     33   
//! retains its request handle, driver, socket, and capacity. No cell lock is
   34     34   
//! held with the admission lock, and connection-owning and requesting cell
   35     35   
//! locks are never held together.
   36     36   
   37         -
use super::cell::{H1ReservationDecision, OriginCell};
          37  +
use super::cell::{H1IdleProbeDecision, H1ReservationDecision, OriginCell};
   38     38   
use super::origin::OriginKey;
   39     39   
use super::partition::{EligibilityGroup, PartitionId};
   40     40   
use super::registry::AdmissionPolicy;
   41     41   
use super::stats::ConnectionCapacityStats;
   42     42   
use crate::sync::{Arc, Mutex, Weak};
   43     43   
use std::collections::HashMap;
   44     44   
use std::fmt;
   45     45   
use std::num::NonZeroUsize;
   46     46   
   47     47   
mod delivery;
   48     48   
mod demand;
   49     49   
mod h1;
   50     50   
mod h2;
   51     51   
mod order;
   52     52   
   53     53   
use self::demand::{
   54     54   
    DemandAssignment, DemandAssignmentId, DemandAssignmentOutcome, DemandSchedule,
   55     55   
    PreparedCapacityDelivery,
   56     56   
};
   57     57   
pub(in crate::client::pool) use self::demand::{
   58     58   
    DemandId, DemandSnapshot, ProtocolRequirement, SnapshotVersion,
   59     59   
};
   60     60   
use self::h1::{
   61         -
    H1CancellationAction, H1CapacityReclaim, H1ReservationAction, H1SupplierSettlement, H1Supply,
   62         -
    H1SupplyOutcome,
          61  +
    H1CancellationAction, H1CapacityReclaim, H1IdleProbeAction, H1ReservationAction,
          62  +
    H1SupplierSettlement, H1Supply, H1SupplyOutcome, PreparedH1Match,
   63     63   
};
   64     64   
use self::h2::{H2CapacityReclaim, H2RouteGuard, H2Supply, PreparedH2Reclaim, PreparedH2Route};
   65     65   
use self::order::{IntrusiveLinks, IntrusiveOrder};
   66     66   
pub(in crate::client::pool) use delivery::DeliveryGuard;
   67     67   
pub(in crate::client::pool) use h1::{
   68         -
    H1Candidate, H1MatchId, H1SupplyStatus, PreparedH1Reservation,
          68  +
    H1Candidate, H1MatchId, H1SupplyStatus, PreparedH1IdleProbe, PreparedH1Reservation,
   69     69   
};
   70     70   
pub(in crate::client::pool) use h2::H2SupplyStatus;
   71     71   
   72     72   
/// Complete admission-facing protocol status at one cell-owned revision.
   73     73   
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
   74     74   
pub(in crate::client::pool) struct SupplyRevision<T> {
   75     75   
    /// Monotonic sequence number allocated under the cell lock.
   76     76   
    pub(in crate::client::pool) revision: u64,
   77     77   
    /// Complete status represented by this revision.
   78     78   
    pub(in crate::client::pool) status: T,
@@ -197,197 +289,295 @@
  217    217   
                prepared.assignment,
  218    218   
                prepared.permit,
  219    219   
            )));
  220    220   
        }
  221    221   
        if let Some(route) = state.prepare_h2_route() {
  222    222   
            return Some(AdmissionAction::AttachH2Route(H2RouteGuard::new(
  223    223   
                origin.clone(),
  224    224   
                route,
  225    225   
            )));
  226    226   
        }
  227         -
        if let Some(reservation) = state.h1_supply.prepare_match(&state.demand) {
  228         -
            return Some(AdmissionAction::ReserveH1Supplier(
  229         -
                H1ReservationAction::new(origin.clone(), reservation),
  230         -
            ));
         227  +
        if let Some(prepared) = state.h1_supply.prepare_match(&state.demand) {
         228  +
            return Some(match prepared {
         229  +
                PreparedH1Match::ProbeIdle(probe) => {
         230  +
                    AdmissionAction::ProbeH1Supplier(H1IdleProbeAction::new(origin.clone(), probe))
         231  +
                }
         232  +
                PreparedH1Match::Reserve(reservation) => AdmissionAction::ReserveH1Supplier(
         233  +
                    H1ReservationAction::new(origin.clone(), reservation),
         234  +
                ),
         235  +
            });
  231    236   
        }
  232    237   
        if !origin.can_reclaim_h2_for_h1 {
  233    238   
            return None;
  234    239   
        }
  235    240   
        state
  236    241   
            .h2_supply
  237    242   
            .prepare_reclaim(&state.demand)
  238    243   
            .map(|reclaim| {
  239    244   
                AdmissionAction::ReclaimCapacity(CapacityReclaim::FromH2(H2CapacityReclaim::new(
  240    245   
                    origin.clone(),
  241    246   
                    reclaim,
  242    247   
                )))
  243    248   
            })
  244    249   
    }
  245    250   
  246    251   
    /// Upgrades a registered requesting cell without holding the admission lock.
  247    252   
    fn cell(&self, id: &PartitionId) -> Option<Arc<OriginCell>> {
  248    253   
        let cell = {
  249    254   
            let state = self.state.lock();
  250    255   
            state.cells.get(id).cloned()
  251    256   
        };
  252    257   
        cell.and_then(|cell| cell.upgrade())
  253    258   
    }
  254    259   
  255    260   
    /// Runs one detached action chain until no completion prepares a successor.
  256    261   
    pub(in crate::client::pool) fn run_action_chain(mut action: Option<AdmissionAction>) {
  257    262   
        while let Some(current) = action {
  258    263   
            action = match current {
  259    264   
                AdmissionAction::Deliver(delivery) => delivery.deliver(),
         265  +
                AdmissionAction::ProbeH1Supplier(probe) => probe.probe_supplier(),
  260    266   
                AdmissionAction::ReserveH1Supplier(reservation) => reservation.reserve_supplier(),
  261    267   
                AdmissionAction::CancelH1Reservation(cancellation) => {
  262    268   
                    cancellation.cancel_reservation()
  263    269   
                }
  264    270   
                AdmissionAction::SettleH1Supplier(settlement) => settlement.settle_supplier(),
  265    271   
                AdmissionAction::AttachH2Route(route) => route.attach_route(),
  266    272   
                AdmissionAction::ReclaimCapacity(reclaim) => reclaim.reclaim_capacity(),
  267    273   
            };
  268    274   
        }
  269    275   
    }
@@ -292,298 +351,367 @@
  312    318   
    /// Rejects a returned sender whose retained match is no longer current.
  313    319   
    pub(in crate::client::pool) fn reject_returned_h1_match(
  314    320   
        admission: &Arc<Self>,
  315    321   
        match_id: H1MatchId,
  316    322   
        supplier: PartitionId,
  317    323   
        revision: SupplyRevision<H1SupplyStatus>,
  318    324   
    ) {
  319    325   
        h1::reject_returned_match(admission, match_id, supplier, revision);
  320    326   
    }
  321    327   
         328  +
    /// Settles one supplier-cell idle probe against its retained H1 match.
         329  +
    pub(in crate::client::pool) fn settle_h1_idle_probe(
         330  +
        admission: &Arc<Self>,
         331  +
        match_id: H1MatchId,
         332  +
        supplier: PartitionId,
         333  +
        decision: H1IdleProbeDecision<H1Candidate>,
         334  +
    ) -> Option<AdmissionAction> {
         335  +
        h1::settle_idle_probe(admission, match_id, supplier, decision)
         336  +
    }
         337  +
  322    338   
    /// Settles installation of one supplier-cell reservation.
  323    339   
    pub(in crate::client::pool) fn settle_h1_reservation(
  324    340   
        admission: &Arc<Self>,
  325    341   
        match_id: H1MatchId,
  326    342   
        supplier: PartitionId,
  327    343   
        decision: H1ReservationDecision<H1Candidate>,
  328    344   
    ) -> Option<AdmissionAction> {
  329    345   
        h1::settle_reservation(admission, match_id, supplier, decision)
  330    346   
    }
  331    347   
@@ -437,453 +514,533 @@
  457    473   
        // cells strongly. Explicit teardown prevents that model-only
  458    474   
        // substitution from appearing as an Arc leak.
  459    475   
        self.state.lock().cells.clear();
  460    476   
    }
  461    477   
}
  462    478   
  463    479   
/// One detached step prepared while holding the bounded-origin lock.
  464    480   
pub(super) enum AdmissionAction {
  465    481   
    /// One capacity or borrowed-H1 payload handed to a requesting cell.
  466    482   
    Deliver(DeliveryGuard),
         483  +
    /// Probe one selected HTTP/1 supplier for an immediately idle sender.
         484  +
    ProbeH1Supplier(H1IdleProbeAction),
  467    485   
    /// Reserve the selected supplier at its owning HTTP/1 cell.
  468    486   
    ReserveH1Supplier(H1ReservationAction),
  469    487   
    /// Cancel a retained HTTP/1 supplier reservation.
  470    488   
    CancelH1Reservation(H1CancellationAction),
  471    489   
    /// Settle sender ownership at the original HTTP/1 supplier cell.
  472    490   
    SettleH1Supplier(H1SupplierSettlement),
  473    491   
    /// HTTP/2 generation route handed to one requesting cell.
  474    492   
    AttachH2Route(H2RouteGuard),
  475    493   
    /// Close selected connection supply outside admission to recover capacity.
  476    494   
    ReclaimCapacity(CapacityReclaim),
  477    495   
}
  478    496   
  479    497   
impl AdmissionAction {
  480    498   
    /// Advances one crossing without recursively driving its successor.
  481    499   
    #[cfg(all(test, smithy_http_client_loom))]
  482    500   
    pub(super) fn run_once_for_test(self) -> Option<Self> {
  483    501   
        match self {
  484    502   
            Self::Deliver(delivery) => delivery.deliver(),
         503  +
            Self::ProbeH1Supplier(probe) => probe.probe_supplier(),
  485    504   
            Self::ReserveH1Supplier(reservation) => reservation.reserve_supplier(),
  486    505   
            Self::CancelH1Reservation(cancellation) => cancellation.cancel_reservation(),
  487    506   
            Self::SettleH1Supplier(settlement) => settlement.settle_supplier(),
  488    507   
            Self::AttachH2Route(route) => route.attach_route(),
  489    508   
            Self::ReclaimCapacity(reclaim) => reclaim.reclaim_capacity(),
  490    509   
        }
  491    510   
    }
  492    511   
}
  493    512   
  494    513   
/// Protocol-specific detached reclaim with one shared admission action.
@@ -950,969 +1050,1080 @@
  970    989   
  971    990   
    fn demand() -> DemandSnapshot {
  972    991   
        DemandSnapshot::active(
  973    992   
            DemandId::from_u64(1),
  974    993   
            SnapshotVersion::INITIAL,
  975    994   
            ProtocolRequirement::H1Compatible,
  976    995   
            EligibilityGroup::Pool,
  977    996   
        )
  978    997   
    }
  979    998   
         999  +
    /// Races return of the only capacity permit with publication of one demand.
        1000  +
    ///
        1001  +
    /// The permit remains conserved and demand is either absent or represented
        1002  +
    /// by one schedulable record, never by an outstanding assignment.
  980   1003   
    #[test]
  981   1004   
    fn release_and_demand_submission_conserve_one_permit() {
  982   1005   
        loom::model(|| {
  983   1006   
            let origin = OriginAdmission::for_test(NonZeroUsize::new(1).unwrap());
  984   1007   
            let permit = origin.state.lock().take_permit().unwrap();
  985   1008   
            let lease = CapacityLease::new(origin.clone(), permit);
  986   1009   
  987   1010   
            let release = loom::thread::spawn(move || drop(lease));
  988   1011   
            let publish_origin = origin.clone();
  989   1012   
            let publish = loom::thread::spawn(move || {
  990   1013   
                let delivery =
  991   1014   
                    OriginAdmission::submit_without_running(&publish_origin, id(), demand());
  992   1015   
                drop(delivery);
  993   1016   
            });
  994   1017   
            release.join().unwrap();
  995   1018   
            publish.join().unwrap();
  996   1019   
  997         -
            assert_eq!(1, origin.probe().available);
  998         -
            assert!(origin.probe().ordered <= 1);
        1020  +
            let probe = origin.probe();
        1021  +
            assert_eq!(1, probe.limit);
        1022  +
            assert_eq!(1, probe.available);
        1023  +
            assert_eq!(0, probe.assigned);
        1024  +
            assert!(
        1025  +
                matches!((probe.ordered, probe.queued), (0, 0) | (1, 1)),
        1026  +
                "demand publication left duplicate or unschedulable demand: {probe:?}"
        1027  +
            );
  999   1028   
        });
 1000   1029   
    }
 1001   1030   
        1031  +
    /// Replaces cancelled demand while its previous assignment is still detached.
        1032  +
    ///
        1033  +
    /// Assignment settlement and replacement publication may enter in either
        1034  +
    /// order, but capacity and the requester's active assignment remain singular.
 1002   1035   
    #[test]
 1003   1036   
    fn cancellation_preserves_an_outstanding_demand_assignment() {
 1004   1037   
        loom::model(|| {
 1005         -
            use loom::sync::atomic::{AtomicBool, Ordering};
 1006         -
 1007   1038   
            let origin = OriginAdmission::for_test(NonZeroUsize::new(2).unwrap());
 1008   1039   
            let requesting_partition = id();
 1009   1040   
            let delivery =
 1010   1041   
                OriginAdmission::submit_without_running(&origin, requesting_partition, demand())
 1011   1042   
                    .unwrap();
 1012         -
            let release = Arc::new(AtomicBool::new(false));
 1013         -
 1014         -
            let delivery_release = release.clone();
 1015         -
            let dropped = loom::thread::spawn(move || {
 1016         -
                while !delivery_release.load(Ordering::Acquire) {
 1017         -
                    loom::thread::yield_now();
 1018         -
                }
 1019         -
                drop(delivery);
        1043  +
            let dropping = loom::thread::spawn(move || drop(delivery));
        1044  +
            let replacement_origin = origin.clone();
        1045  +
            let replacing = loom::thread::spawn(move || {
        1046  +
                replacement_origin.state.lock().apply_demand_snapshot(
        1047  +
                    requesting_partition,
        1048  +
                    DemandSnapshot::inactive(
        1049  +
                        DemandId::from_u64(1),
        1050  +
                        SnapshotVersion::INITIAL.next(),
        1051  +
                    ),
        1052  +
                );
        1053  +
                OriginAdmission::submit_without_running(
        1054  +
                    &replacement_origin,
        1055  +
                    requesting_partition,
        1056  +
                    DemandSnapshot::active(
        1057  +
                        DemandId::from_u64(2),
        1058  +
                        SnapshotVersion::INITIAL,
        1059  +
                        ProtocolRequirement::H1Compatible,
        1060  +
                        EligibilityGroup::Pool,
        1061  +
                    ),
        1062  +
                )
 1020   1063   
            });
 1021   1064   
 1022         -
            origin.state.lock().apply_demand_snapshot(
 1023         -
                requesting_partition,
 1024         -
                DemandSnapshot::inactive(DemandId::from_u64(1), SnapshotVersion::INITIAL.next()),
 1025         -
            );
 1026         -
            let duplicate = OriginAdmission::submit_without_running(
 1027         -
                &origin,
 1028         -
                requesting_partition,
 1029         -
                DemandSnapshot::active(
 1030         -
                    DemandId::from_u64(2),
 1031         -
                    SnapshotVersion::INITIAL,
 1032         -
                    ProtocolRequirement::H1Compatible,
 1033         -
                    EligibilityGroup::Pool,
 1034         -
                ),
 1035         -
            );
 1036         -
 1037         -
            release.store(true, Ordering::Release);
 1038         -
            dropped.join().unwrap();
 1039         -
            assert!(
 1040         -
                duplicate.is_none(),
 1041         -
                "outstanding demand assignment admitted a second delivery"
 1042         -
            );
        1065  +
            dropping.join().unwrap();
        1066  +
            if let Some(replacement) = replacing.join().unwrap() {
        1067  +
                assert!(
        1068  +
                    replacement.is_current(),
        1069  +
                    "replacement delivery did not own the current demand assignment"
        1070  +
                );
        1071  +
                drop(replacement);
        1072  +
            }
 1043   1073   
 1044   1074   
            let probe = origin.probe();
 1045   1075   
            assert_eq!(2, probe.available);
 1046   1076   
            assert_eq!(0, probe.assigned);
 1047   1077   
            assert_eq!(0, probe.ordered);
 1048   1078   
        });
 1049   1079   
    }
 1050   1080   
}

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

@@ -1,1 +78,129 @@
    9      9   
//! origin demand with one supplier. Compatible demand borrows the sender.
   10     10   
//! Incompatible demand closes the selected connection to recover capacity.
   11     11   
//!
   12     12   
//! Admission retains an [`H1Match`] while supplier reservation crosses locks or
   13     13   
//! waits for a sender return. The match owns identities and phase flags only.
   14     14   
//! Detached values own resources and their drop fallback.
   15     15   
//!
   16     16   
//! Admission advances each match through these phases:
   17     17   
//!
   18     18   
//! ```text
          19  +
//! ProbingIdle -- idle sender extracted ----------------------> Resolving
          20  +
//! ProbingIdle -- supplier busy and probes remain ------------> ProbingIdle
          21  +
//! ProbingIdle -- probes exhausted ---------------------------> Reserving
          22  +
//! ProbingIdle(cancelled) -- supplier reply ------------------> removed
   19     23   
//! Reserving -- reservation installed -----------------------> WaitingForSender
   20     24   
//! Reserving -- sender extracted ----------------------------> Resolving
   21     25   
//! Reserving(cancelled) -- reservation installed ------------> Cancelling
   22     26   
//! Reserving(cancelled) -- sender extracted -----------------> Resolving
   23     27   
//! Reserving -- reservation rejected or cell expired --------> removed
   24     28   
//! WaitingForSender -- sender return intercepted ------------> Resolving
   25     29   
//! WaitingForSender -- demand cancelled ---------------------> Cancelling
   26     30   
//! WaitingForSender -- terminal supplier outcome ------------> removed
   27     31   
//! Cancelling -- sender return intercepted ------------------> Resolving
   28     32   
//! Cancelling -- reservation cleared -------------------------> removed
   29     33   
//! Resolving -- repeated candidate handoff ------------------> Resolving
   30     34   
//! Resolving -- borrow or reclaim completes ------------------> removed
   31     35   
//! ```
   32     36   
//!
   33     37   
//! A sender extracted during installation takes the `Resolving` path even if
   34     38   
//! cancellation raced with the install acknowledgement. The extracted sender
   35     39   
//! must complete its terminal transition before cancellation can finish.
   36     40   
   37     41   
use super::{
   38     42   
    AdmissionAction, DeliveryGuard, DemandAssignmentOutcome, DemandId, DemandSchedule,
   39     43   
    IntrusiveLinks, IntrusiveOrder, OriginAdmission, SupplyRevision,
   40     44   
};
   41     45   
use crate::client::pool::cell::h1::{H1Selection, ProvisionalH1};
   42         -
use crate::client::pool::cell::{H1ReservationDecision, OriginCell};
          46  +
use crate::client::pool::cell::{H1IdleProbeDecision, H1ReservationDecision, OriginCell};
   43     47   
use crate::client::pool::partition::{EligibilityGroup, PartitionId};
   44     48   
use crate::sync::Arc;
   45     49   
use aws_smithy_runtime_api::client::connection::ConnectionId;
   46     50   
use std::collections::{HashMap, VecDeque};
   47     51   
use std::fmt;
   48     52   
          53  +
/// Maximum supplier cells crossed before waiting for one exact H1 return.
          54  +
///
          55  +
/// Preliminary checks take only an immediately idle sender. The final check
          56  +
/// uses the ordinary reservation, which takes an idle sender when present or
          57  +
/// intercepts that supplier's next reusable return.
          58  +
const MAX_H1_SUPPLIER_CHECKS: usize = 3;
          59  +
          60  +
/// Exact supplier cells already checked by one bounded idle search.
          61  +
///
          62  +
/// The fixed-size representation keeps the miss path allocation-free and
          63  +
/// prevents a supplier that re-enters the FIFO from being checked twice.
          64  +
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
          65  +
struct H1IdleProbeSet {
          66  +
    suppliers: [Option<PartitionId>; MAX_H1_SUPPLIER_CHECKS],
          67  +
    len: usize,
          68  +
}
          69  +
          70  +
impl H1IdleProbeSet {
          71  +
    /// Starts one probe sequence with its first selected supplier.
          72  +
    fn first(supplier: PartitionId) -> Self {
          73  +
        let mut suppliers = [None; MAX_H1_SUPPLIER_CHECKS];
          74  +
        suppliers[0] = Some(supplier);
          75  +
        Self { suppliers, len: 1 }
          76  +
    }
          77  +
          78  +
    /// Returns whether this sequence already checked `supplier`.
          79  +
    fn contains(&self, supplier: PartitionId) -> bool {
          80  +
        self.suppliers[..self.len].contains(&Some(supplier))
          81  +
    }
          82  +
          83  +
    /// Records one distinct supplier within the fixed probe bound.
          84  +
    fn record(&mut self, supplier: PartitionId) {
          85  +
        assert!(
          86  +
            self.len < MAX_H1_SUPPLIER_CHECKS,
          87  +
            "HTTP/1 idle probe bound exceeded"
          88  +
        );
          89  +
        debug_assert!(!self.contains(supplier));
          90  +
        self.suppliers[self.len] = Some(supplier);
          91  +
        self.len += 1;
          92  +
    }
          93  +
          94  +
    /// Returns whether another idle-only probe fits before final reservation.
          95  +
    fn may_probe_before_reservation(&self) -> bool {
          96  +
        self.len + 1 < MAX_H1_SUPPLIER_CHECKS
          97  +
    }
          98  +
}
          99  +
   49    100   
/// Identity of one retained HTTP/1 demand-to-supplier match.
   50    101   
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
   51    102   
pub(in crate::client::pool) struct H1MatchId(u64);
   52    103   
   53    104   
impl H1MatchId {
   54    105   
    /// Creates a deterministic identity for focused transition tests.
   55    106   
    #[cfg(test)]
   56    107   
    pub(in crate::client::pool) const fn for_test(value: u64) -> Self {
   57    108   
        Self(value)
   58    109   
    }
@@ -160,211 +290,431 @@
  180    231   
    /// Admission-side progress of the match.
  181    232   
    state: H1MatchState,
  182    233   
    /// Whether requesting cell demand became stale before connection-cell
  183    234   
    /// resolution.
  184    235   
    cancelled: bool,
  185    236   
}
  186    237   
  187    238   
/// Origin-side progress of one retained H1 match.
  188    239   
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
  189    240   
enum H1MatchState {
         241  +
    /// Admission is checking bounded supplier cells for an idle sender.
         242  +
    ProbingIdle {
         243  +
        /// Exact suppliers already checked by this search.
         244  +
        probed: H1IdleProbeSet,
         245  +
    },
  190    246   
    /// Admission selected a connection, but the cell has not reserved it.
  191    247   
    Reserving,
  192    248   
    /// The cell will intercept the connection's next reusable return.
  193    249   
    WaitingForSender,
  194    250   
    /// A provisional sender is resolving outside admission.
  195    251   
    Resolving,
  196    252   
    /// Request cancellation must clear the installed reservation.
  197    253   
    Cancelling,
  198    254   
}
  199    255   
  200    256   
/// Work required to reserve one supplier outside the admission lock.
  201    257   
#[derive(Debug)]
  202    258   
pub(in crate::client::pool) struct PreparedH1Reservation {
  203    259   
    /// Match whose supplier is being reserved.
  204    260   
    pub(in crate::client::pool) match_id: H1MatchId,
  205    261   
    /// Cell whose connection was selected while admission was locked.
  206    262   
    pub(in crate::client::pool) supplier: PartitionId,
  207    263   
}
  208    264   
         265  +
/// Work required to probe one supplier cell for an idle sender.
         266  +
pub(in crate::client::pool) struct PreparedH1IdleProbe {
         267  +
    /// Match whose supplier is being checked.
         268  +
    pub(in crate::client::pool) match_id: H1MatchId,
         269  +
    /// Supplier cell selected while admission was locked.
         270  +
    pub(in crate::client::pool) supplier: PartitionId,
         271  +
}
         272  +
         273  +
/// Next detached crossing prepared for one H1 demand/supplier match.
         274  +
pub(super) enum PreparedH1Match {
         275  +
    /// Probe one supplier for an immediately idle sender.
         276  +
    ProbeIdle(PreparedH1IdleProbe),
         277  +
    /// Reserve one supplier's idle sender or next reusable return.
         278  +
    Reserve(PreparedH1Reservation),
         279  +
}
         280  +
         281  +
#[cfg(test)]
         282  +
impl PreparedH1Match {
         283  +
    fn match_id(&self) -> H1MatchId {
         284  +
        match self {
         285  +
            Self::ProbeIdle(prepared) => prepared.match_id,
         286  +
            Self::Reserve(prepared) => prepared.match_id,
         287  +
        }
         288  +
    }
         289  +
         290  +
    fn supplier(&self) -> PartitionId {
         291  +
        match self {
         292  +
            Self::ProbeIdle(prepared) => prepared.supplier,
         293  +
            Self::Reserve(prepared) => prepared.supplier,
         294  +
        }
         295  +
    }
         296  +
}
         297  +
  209    298   
/// Work required to cancel a reservation outside the admission lock.
  210    299   
pub(super) struct PreparedH1Cancellation {
  211    300   
    /// Match whose supplier reservation must be cleared.
  212    301   
    match_id: H1MatchId,
  213    302   
    /// Cell that owns the reservation.
  214    303   
    supplier: PartitionId,
  215    304   
}
  216    305   
  217    306   
impl H1SupplyIndex {
  218    307   
    fn apply_revision(
  219    308   
        &mut self,
  220    309   
        supplier: PartitionId,
  221    310   
        eligibility_group: EligibilityGroup,
  222    311   
        revision: SupplyRevision<H1SupplyStatus>,
  223    312   
    ) {
  224    313   
        if self
  225    314   
            .records
  226    315   
            .get(&supplier)
  227    316   
            .is_some_and(|record| record.revision >= revision.revision)
  228    317   
        {
  229    318   
            return;
  230    319   
        }
  231    320   
        self.unlink_supplier(&supplier);
  232    321   
        let record = self
  233    322   
            .records
  234    323   
            .entry(supplier)
  235    324   
            .or_insert_with(|| H1SupplyRecord {
  236    325   
                eligibility_group: eligibility_group.clone(),
  237    326   
                revision: revision.revision,
  238    327   
                status: revision.status,
  239    328   
                reserved_by: None,
  240    329   
                index_state: H1SupplyIndexState::Unlinked,
  241    330   
            });
  242    331   
        record.eligibility_group = eligibility_group;
  243    332   
        record.revision = revision.revision;
  244    333   
        record.status = revision.status;
  245    334   
        self.link_supplier_if_selectable(&supplier);
  246    335   
    }
  247    336   
  248    337   
    fn select_borrow_supplier(
  249    338   
        &mut self,
  250    339   
        eligibility_group: &EligibilityGroup,
  251    340   
        requester: PartitionId,
  252    341   
    ) -> Option<PartitionId> {
  253         -
        let supplier = {
  254         -
            let order = self.suppliers_by_group.get(eligibility_group)?;
  255         -
            self.first_peer_supplier(order, requester, false)?
  256         -
        };
         342  +
        self.select_borrow_supplier_excluding(eligibility_group, requester, None)
         343  +
    }
         344  +
         345  +
    /// Returns the oldest eligible peer not already checked by this probe.
         346  +
    ///
         347  +
    /// Traversal is bounded by the probe limit plus the requesting cell, which
         348  +
    /// may occupy the same group order but cannot supply itself.
         349  +
    fn first_borrow_supplier_excluding(
         350  +
        &self,
         351  +
        eligibility_group: &EligibilityGroup,
         352  +
        requester: PartitionId,
         353  +
        probed: Option<&H1IdleProbeSet>,
         354  +
    ) -> Option<PartitionId> {
         355  +
        let mut supplier = self.suppliers_by_group.get(eligibility_group)?.head();
         356  +
        for _ in 0..=MAX_H1_SUPPLIER_CHECKS {
         357  +
            let current = supplier?;
         358  +
            if current != requester && !probed.is_some_and(|probed| probed.contains(current)) {
         359  +
                return Some(current);
         360  +
            }
         361  +
            supplier = self.next_group_supplier(current);
         362  +
        }
         363  +
        None
         364  +
    }
         365  +
         366  +
    /// Removes the oldest eligible peer not already checked by this probe.
         367  +
    fn select_borrow_supplier_excluding(
         368  +
        &mut self,
         369  +
        eligibility_group: &EligibilityGroup,
         370  +
        requester: PartitionId,
         371  +
        probed: Option<&H1IdleProbeSet>,
         372  +
    ) -> Option<PartitionId> {
         373  +
        let supplier =
         374  +
            self.first_borrow_supplier_excluding(eligibility_group, requester, probed)?;
  257    375   
        self.unlink_supplier(&supplier);
  258    376   
        Some(supplier)
  259    377   
    }
  260    378   
         379  +
    /// Returns whether another distinct peer can participate in this search.
         380  +
    fn has_borrow_supplier_excluding(
         381  +
        &self,
         382  +
        eligibility_group: &EligibilityGroup,
         383  +
        requester: PartitionId,
         384  +
        probed: Option<&H1IdleProbeSet>,
         385  +
    ) -> bool {
         386  +
        self.first_borrow_supplier_excluding(eligibility_group, requester, probed)
         387  +
            .is_some()
         388  +
    }
         389  +
         390  +
    /// Returns the next supplier in one eligibility-group order.
         391  +
    fn next_group_supplier(&self, supplier: PartitionId) -> Option<PartitionId> {
         392  +
        let record = self
         393  +
            .records
         394  +
            .get(&supplier)
         395  +
            .expect("ordered H1 supplier disappeared");
         396  +
        let H1SupplyIndexState::Linked { group, .. } = &record.index_state else {
         397  +
            unreachable!("ordered H1 supplier was unlinked");
         398  +
        };
         399  +
        group.next
         400  +
    }
         401  +
  261    402   
    fn select_peer_reclaim_supplier(&mut self, requester: PartitionId) -> Option<PartitionId> {
  262    403   
        let supplier = self.first_peer_supplier(&self.reclaim_order, requester, true)?;
  263    404   
        self.unlink_supplier(&supplier);
  264    405   
        Some(supplier)
  265    406   
    }
  266    407   
  267    408   
    fn select_oldest_reclaim_supplier(&mut self) -> Option<PartitionId> {
  268    409   
        let supplier = self.reclaim_order.head()?;
  269    410   
        self.unlink_supplier(&supplier);
  270    411   
        Some(supplier)
@@ -478,619 +656,939 @@
  498    639   
            return;
  499    640   
        }
  500    641   
        retained.cancelled = true;
  501    642   
        if retained.state == H1MatchState::WaitingForSender {
  502    643   
            retained.state = H1MatchState::Cancelling;
  503    644   
            self.cancellations.push_back(match_id);
  504    645   
        }
  505    646   
        self.assert_consistent();
  506    647   
    }
  507    648   
  508         -
    pub(super) fn prepare_match(
  509         -
        &mut self,
  510         -
        demand: &DemandSchedule,
  511         -
    ) -> Option<PreparedH1Reservation> {
         649  +
    pub(super) fn prepare_match(&mut self, demand: &DemandSchedule) -> Option<PreparedH1Match> {
  512    650   
        let queued = demand.queued_head()?;
  513    651   
        if self.by_requester.contains_key(&queued.requester) {
  514    652   
            return None;
  515    653   
        }
  516         -
        let (supplier, kind) = if queued.requirement.accepts_h1() {
  517         -
            match self
         654  +
         655  +
        if queued.requirement.accepts_h1() {
         656  +
            if let Some(supplier) = self
  518    657   
                .index
  519    658   
                .select_borrow_supplier(&queued.eligibility_group, queued.requester)
  520    659   
            {
  521         -
                Some(supplier) => (supplier, H1MatchKind::BorrowSender),
  522         -
                None => (
  523         -
                    self.index.select_peer_reclaim_supplier(queued.requester)?,
  524         -
                    H1MatchKind::ReclaimCapacity,
  525         -
                ),
         660  +
                let probe_idle = self.index.has_borrow_supplier_excluding(
         661  +
                    &queued.eligibility_group,
         662  +
                    queued.requester,
         663  +
                    None,
         664  +
                );
         665  +
                let state = if probe_idle {
         666  +
                    H1MatchState::ProbingIdle {
         667  +
                        probed: H1IdleProbeSet::first(supplier),
         668  +
                    }
         669  +
                } else {
         670  +
                    H1MatchState::Reserving
         671  +
                };
         672  +
                let match_id = self.insert_match(
         673  +
                    supplier,
         674  +
                    queued.requester,
         675  +
                    queued.demand,
         676  +
                    H1MatchKind::BorrowSender,
         677  +
                    state,
         678  +
                );
         679  +
                return Some(if probe_idle {
         680  +
                    PreparedH1Match::ProbeIdle(PreparedH1IdleProbe { match_id, supplier })
         681  +
                } else {
         682  +
                    PreparedH1Match::Reserve(PreparedH1Reservation { match_id, supplier })
         683  +
                });
  526    684   
            }
  527         -
        } else {
  528         -
            (
  529         -
                self.index.select_oldest_reclaim_supplier()?,
         685  +
            let supplier = self.index.select_peer_reclaim_supplier(queued.requester)?;
         686  +
            let match_id = self.insert_match(
         687  +
                supplier,
         688  +
                queued.requester,
         689  +
                queued.demand,
  530    690   
                H1MatchKind::ReclaimCapacity,
  531         -
            )
  532         -
        };
         691  +
                H1MatchState::Reserving,
         692  +
            );
         693  +
            return Some(PreparedH1Match::Reserve(PreparedH1Reservation {
         694  +
                match_id,
         695  +
                supplier,
         696  +
            }));
         697  +
        }
         698  +
         699  +
        let supplier = self.index.select_oldest_reclaim_supplier()?;
         700  +
        let match_id = self.insert_match(
         701  +
            supplier,
         702  +
            queued.requester,
         703  +
            queued.demand,
         704  +
            H1MatchKind::ReclaimCapacity,
         705  +
            H1MatchState::Reserving,
         706  +
        );
         707  +
        Some(PreparedH1Match::Reserve(PreparedH1Reservation {
         708  +
            match_id,
         709  +
            supplier,
         710  +
        }))
         711  +
    }
         712  +
         713  +
    /// Retains one demand-to-supplier match and removes its supplier from
         714  +
    /// selection until the match advances or terminates.
         715  +
    fn insert_match(
         716  +
        &mut self,
         717  +
        supplier: PartitionId,
         718  +
        requester: PartitionId,
         719  +
        demand: DemandId,
         720  +
        kind: H1MatchKind,
         721  +
        state: H1MatchState,
         722  +
    ) -> H1MatchId {
  533    723   
        let match_id = self.take_match_id();
  534         -
        let record = self
  535         -
            .index
  536         -
            .records
  537         -
            .get_mut(&supplier)
  538         -
            .expect("selected H1 supplier disappeared");
  539         -
        debug_assert!(record.reserved_by.is_none());
  540         -
        record.reserved_by = Some(match_id);
  541         -
        self.by_requester.insert(queued.requester, match_id);
         724  +
        self.reserve_supplier(supplier, match_id);
         725  +
        self.by_requester.insert(requester, match_id);
  542    726   
        self.matches.insert(
  543    727   
            match_id,
  544    728   
            H1Match {
  545    729   
                supplier,
  546         -
                requester: queued.requester,
  547         -
                demand: queued.demand,
         730  +
                requester,
         731  +
                demand,
  548    732   
                kind,
  549         -
                state: H1MatchState::Reserving,
         733  +
                state,
  550    734   
                cancelled: false,
  551    735   
            },
  552    736   
        );
  553    737   
        self.assert_consistent();
  554         -
        Some(PreparedH1Reservation { match_id, supplier })
         738  +
        match_id
         739  +
    }
         740  +
         741  +
    /// Attaches one retained match to its currently selected supplier.
         742  +
    fn reserve_supplier(&mut self, supplier: PartitionId, match_id: H1MatchId) {
         743  +
        let record = self
         744  +
            .index
         745  +
            .records
         746  +
            .get_mut(&supplier)
         747  +
            .expect("selected H1 supplier disappeared");
         748  +
        debug_assert!(record.reserved_by.is_none());
         749  +
        record.reserved_by = Some(match_id);
         750  +
    }
         751  +
         752  +
    /// Applies one probe miss and prepares the next probe or exact reservation.
         753  +
    ///
         754  +
    /// Only the supplier-cell probe reply path calls this method. The supplier
         755  +
    /// named by `outcome` is still reserved by `match_id` on entry.
         756  +
    fn prepare_next_idle_probe(
         757  +
        &mut self,
         758  +
        match_id: H1MatchId,
         759  +
        demand: &DemandSchedule,
         760  +
        outcome: H1SupplyOutcome,
         761  +
    ) -> Option<PreparedH1Match> {
         762  +
        let outcome_supplier = *outcome.supplier();
         763  +
        self.apply_h1_supply_outcome(outcome);
         764  +
        let retained = self.matches.get(&match_id).cloned()?;
         765  +
        debug_assert_eq!(retained.supplier, outcome_supplier);
         766  +
        if let Some(record) = self.index.records.get_mut(&outcome_supplier) {
         767  +
            if record.reserved_by == Some(match_id) {
         768  +
                record.reserved_by = None;
         769  +
            }
         770  +
        }
         771  +
        self.index.link_supplier_if_selectable(&outcome_supplier);
         772  +
         773  +
        let H1MatchState::ProbingIdle { mut probed } = retained.state else {
         774  +
            unreachable!("HTTP/1 idle probe settled outside its probing phase");
         775  +
        };
         776  +
        if retained.cancelled || !demand.is_current_queued(&retained.requester, retained.demand) {
         777  +
            self.remove_match(match_id);
         778  +
            self.assert_consistent();
         779  +
            return None;
         780  +
        }
         781  +
        let eligibility_group = demand
         782  +
            .group_for(&retained.requester)
         783  +
            .expect("queued H1 demand lost its eligibility group");
         784  +
        if let Some(supplier) = self.index.select_borrow_supplier_excluding(
         785  +
            &eligibility_group,
         786  +
            retained.requester,
         787  +
            Some(&probed),
         788  +
        ) {
         789  +
            let probe_idle = probed.may_probe_before_reservation()
         790  +
                && self.index.has_borrow_supplier_excluding(
         791  +
                    &eligibility_group,
         792  +
                    retained.requester,
         793  +
                    Some(&probed),
         794  +
                );
         795  +
            self.reserve_supplier(supplier, match_id);
         796  +
            let retained = self
         797  +
                .matches
         798  +
                .get_mut(&match_id)
         799  +
                .expect("probing H1 match disappeared");
         800  +
            retained.supplier = supplier;
         801  +
            if probe_idle {
         802  +
                probed.record(supplier);
         803  +
                retained.state = H1MatchState::ProbingIdle { probed };
         804  +
                self.assert_consistent();
         805  +
                return Some(PreparedH1Match::ProbeIdle(PreparedH1IdleProbe {
         806  +
                    match_id,
         807  +
                    supplier,
         808  +
                }));
         809  +
            }
         810  +
            retained.state = H1MatchState::Reserving;
         811  +
            self.assert_consistent();
         812  +
            return Some(PreparedH1Match::Reserve(PreparedH1Reservation {
         813  +
                match_id,
         814  +
                supplier,
         815  +
            }));
         816  +
        }
         817  +
         818  +
        self.remove_match(match_id);
         819  +
        self.assert_consistent();
         820  +
        None
         821  +
    }
         822  +
         823  +
    /// Advances one successful idle probe to candidate resolution.
         824  +
    fn settle_idle_probe_candidate(&mut self, match_id: H1MatchId) -> Option<H1Match> {
         825  +
        let retained = self.matches.get_mut(&match_id)?;
         826  +
        if !matches!(retained.state, H1MatchState::ProbingIdle { .. }) {
         827  +
            return None;
         828  +
        }
         829  +
        retained.state = H1MatchState::Resolving;
         830  +
        let retained = retained.clone();
         831  +
        self.assert_consistent();
         832  +
        Some(retained)
  555    833   
    }
  556    834   
  557    835   
    pub(super) fn prepare_cancellation(&mut self) -> Option<PreparedH1Cancellation> {
  558    836   
        while let Some(match_id) = self.cancellations.pop_front() {
  559    837   
            let Some(retained) = self.matches.get(&match_id) else {
  560    838   
                continue;
  561    839   
            };
  562    840   
            if retained.state != H1MatchState::Cancelling {
  563    841   
                continue;
  564    842   
            }
  565    843   
            return Some(PreparedH1Cancellation {
  566    844   
                match_id,
  567    845   
                supplier: retained.supplier,
  568    846   
            });
  569    847   
        }
  570    848   
        None
  571    849   
    }
  572    850   
  573    851   
    fn settle_reservation(&mut self, match_id: H1MatchId, resolved: bool) -> Option<H1Match> {
  574    852   
        let retained = self.matches.get_mut(&match_id)?;
  575    853   
        if retained.state != H1MatchState::Reserving {
  576    854   
            return None;
  577    855   
        }
  578    856   
        retained.state = if resolved {
  579    857   
            H1MatchState::Resolving
  580    858   
        } else if retained.cancelled {
  581    859   
            H1MatchState::Cancelling
  582    860   
        } else {
  583    861   
            H1MatchState::WaitingForSender
  584    862   
        };
  585    863   
        if retained.state == H1MatchState::Cancelling {
  586    864   
            self.cancellations.push_back(match_id);
  587    865   
        }
  588    866   
        let retained = retained.clone();
  589    867   
        self.assert_consistent();
  590    868   
        Some(retained)
  591    869   
    }
  592    870   
  593    871   
    fn begin_resolution(&mut self, match_id: H1MatchId) -> Option<H1Match> {
  594    872   
        let retained = self.matches.get_mut(&match_id)?;
  595    873   
        if !matches!(
  596    874   
            retained.state,
  597    875   
            H1MatchState::WaitingForSender | H1MatchState::Resolving | H1MatchState::Cancelling
  598    876   
        ) {
  599    877   
            return None;
  600    878   
        }
  601    879   
        retained.state = H1MatchState::Resolving;
  602    880   
        let retained = retained.clone();
  603    881   
        self.assert_consistent();
  604    882   
        Some(retained)
  605    883   
    }
  606    884   
  607    885   
    fn settle_match(&mut self, match_id: H1MatchId, outcome: H1SupplyOutcome) -> Option<H1Match> {
  608    886   
        let outcome_supplier = *outcome.supplier();
  609    887   
        self.apply_h1_supply_outcome(outcome);
  610         -
        let retained = self.matches.remove(&match_id);
         888  +
        let retained = self.remove_match(match_id);
  611    889   
        if let Some(retained) = retained.as_ref() {
  612    890   
            debug_assert_eq!(retained.supplier, outcome_supplier);
  613         -
            if self.by_requester.get(&retained.requester) == Some(&match_id) {
  614         -
                self.by_requester.remove(&retained.requester);
  615         -
            }
  616         -
            if let Some(record) = self.index.records.get_mut(&retained.supplier) {
  617         -
                if record.reserved_by == Some(match_id) {
  618         -
                    record.reserved_by = None;
  619         -
                }
  620         -
            }
  621    891   
        }
  622    892   
        self.index.link_supplier_if_selectable(&outcome_supplier);
  623    893   
        self.assert_consistent();
  624    894   
        retained
  625    895   
    }
  626    896   
         897  +
    fn remove_match(&mut self, match_id: H1MatchId) -> Option<H1Match> {
         898  +
        let retained = self.matches.remove(&match_id)?;
         899  +
        if self.by_requester.get(&retained.requester) == Some(&match_id) {
         900  +
            self.by_requester.remove(&retained.requester);
         901  +
        }
         902  +
        if let Some(record) = self.index.records.get_mut(&retained.supplier) {
         903  +
            if record.reserved_by == Some(match_id) {
         904  +
                record.reserved_by = None;
         905  +
            }
         906  +
        }
         907  +
        Some(retained)
         908  +
    }
         909  +
  627    910   
    fn apply_h1_supply_outcome(&mut self, outcome: H1SupplyOutcome) {
  628    911   
        match outcome {
  629    912   
            H1SupplyOutcome::SupplierLive { supplier, revision } => {
  630    913   
                let Some(eligibility_group) = self
  631    914   
                    .index
  632    915   
                    .records
  633    916   
                    .get(&supplier)
  634    917   
                    .map(|record| record.eligibility_group.clone())
  635    918   
                else {
  636    919   
                    return;
@@ -688,971 +747,1077 @@
  708    991   
                        .iter()
  709    992   
                        .skip(index + 1)
  710    993   
                        .any(|id| id == match_id),
  711    994   
                    "H1 cancellation was queued more than once"
  712    995   
                );
  713    996   
            }
  714    997   
        }
  715    998   
    }
  716    999   
}
  717   1000   
        1001  +
/// Detached probe of one supplier cell for an immediately idle H1 sender.
        1002  +
pub(in crate::client::pool) struct H1IdleProbeAction {
        1003  +
    /// Admission authority that owns the retained H1 match.
        1004  +
    admission: Arc<OriginAdmission>,
        1005  +
    /// Probe identity retained until the supplier reply is settled.
        1006  +
    prepared: Option<PreparedH1IdleProbe>,
        1007  +
}
        1008  +
        1009  +
impl H1IdleProbeAction {
        1010  +
    /// Creates one unlocked supplier-cell probe.
        1011  +
    pub(super) fn new(admission: Arc<OriginAdmission>, prepared: PreparedH1IdleProbe) -> Self {
        1012  +
        Self {
        1013  +
            admission,
        1014  +
            prepared: Some(prepared),
        1015  +
        }
        1016  +
    }
        1017  +
        1018  +
    /// Probes the selected supplier and returns the next admission action.
        1019  +
    pub(super) fn probe_supplier(mut self) -> Option<AdmissionAction> {
        1020  +
        let prepared = self
        1021  +
            .prepared
        1022  +
            .take()
        1023  +
            .expect("HTTP/1 idle probe consumed more than once");
        1024  +
        let Some(supplier) = self.admission.cell(&prepared.supplier) else {
        1025  +
            return settle_idle_probe_miss(
        1026  +
                &self.admission,
        1027  +
                prepared.match_id,
        1028  +
                H1SupplyOutcome::supplier_expired(prepared.supplier),
        1029  +
            );
        1030  +
        };
        1031  +
        OriginCell::try_take_idle_h1(&supplier, self.admission.clone(), prepared)
        1032  +
    }
        1033  +
}
        1034  +
        1035  +
impl Drop for H1IdleProbeAction {
        1036  +
    fn drop(&mut self) {
        1037  +
        let Some(prepared) = self.prepared.take() else {
        1038  +
            return;
        1039  +
        };
        1040  +
        let outcome = h1_supply_outcome(&self.admission, &prepared.supplier, |supplier| {
        1041  +
            supplier.current_h1_supply_revision()
        1042  +
        });
        1043  +
        let next = settle_idle_probe_miss(&self.admission, prepared.match_id, outcome);
        1044  +
        OriginAdmission::run_action_chain(next);
        1045  +
    }
        1046  +
}
        1047  +
  718   1048   
/// Reservation crossing from admission to one supplier cell.
  719   1049   
pub(in crate::client::pool) struct H1ReservationAction {
  720   1050   
    admission: Arc<OriginAdmission>,
  721   1051   
    prepared: Option<PreparedH1Reservation>,
  722   1052   
}
  723   1053   
  724   1054   
impl H1ReservationAction {
  725   1055   
    pub(super) fn new(admission: Arc<OriginAdmission>, prepared: PreparedH1Reservation) -> Self {
  726   1056   
        Self {
  727   1057   
            admission,
@@ -1024,1354 +1083,1465 @@
 1044   1374   
    revision: SupplyRevision<H1SupplyStatus>,
 1045   1375   
) {
 1046   1376   
    let action = settle_match(
 1047   1377   
        admission,
 1048   1378   
        match_id,
 1049   1379   
        H1SupplyOutcome::supplier_live(supplier, revision),
 1050   1380   
    );
 1051   1381   
    OriginAdmission::run_action_chain(action);
 1052   1382   
}
 1053   1383   
        1384  +
/// Settles one supplier-cell idle probe against its retained match.
        1385  +
pub(super) fn settle_idle_probe(
        1386  +
    admission: &Arc<OriginAdmission>,
        1387  +
    match_id: H1MatchId,
        1388  +
    supplier: PartitionId,
        1389  +
    decision: H1IdleProbeDecision<H1Candidate>,
        1390  +
) -> Option<AdmissionAction> {
        1391  +
    match decision {
        1392  +
        H1IdleProbeDecision::Unavailable(revision) => settle_idle_probe_miss(
        1393  +
            admission,
        1394  +
            match_id,
        1395  +
            H1SupplyOutcome::supplier_live(supplier, revision),
        1396  +
        ),
        1397  +
        H1IdleProbeDecision::Candidate(candidate) => {
        1398  +
            {
        1399  +
                let mut state = admission.state.lock();
        1400  +
                if state
        1401  +
                    .h1_supply
        1402  +
                    .settle_idle_probe_candidate(match_id)
        1403  +
                    .is_none()
        1404  +
                {
        1405  +
                    drop(state);
        1406  +
                    return reject_candidate(admission, match_id, candidate);
        1407  +
                }
        1408  +
            }
        1409  +
            resolve_match(admission, match_id, candidate)
        1410  +
        }
        1411  +
    }
        1412  +
}
        1413  +
        1414  +
/// Applies one missing idle sender and continues bounded supplier selection.
        1415  +
fn settle_idle_probe_miss(
        1416  +
    admission: &Arc<OriginAdmission>,
        1417  +
    match_id: H1MatchId,
        1418  +
    outcome: H1SupplyOutcome,
        1419  +
) -> Option<AdmissionAction> {
        1420  +
    let mut state = admission.state.lock();
        1421  +
    let super::AdmissionState {
        1422  +
        h1_supply, demand, ..
        1423  +
    } = &mut *state;
        1424  +
    let next = h1_supply.prepare_next_idle_probe(match_id, demand, outcome);
        1425  +
    match next {
        1426  +
        Some(PreparedH1Match::ProbeIdle(prepared)) => Some(AdmissionAction::ProbeH1Supplier(
        1427  +
            H1IdleProbeAction::new(admission.clone(), prepared),
        1428  +
        )),
        1429  +
        Some(PreparedH1Match::Reserve(prepared)) => Some(AdmissionAction::ReserveH1Supplier(
        1430  +
            H1ReservationAction::new(admission.clone(), prepared),
        1431  +
        )),
        1432  +
        None => OriginAdmission::prepare_action(admission, &mut state),
        1433  +
    }
        1434  +
}
        1435  +
 1054   1436   
pub(super) fn settle_reservation(
 1055   1437   
    admission: &Arc<OriginAdmission>,
 1056   1438   
    match_id: H1MatchId,
 1057   1439   
    supplier: PartitionId,
 1058   1440   
    decision: H1ReservationDecision<H1Candidate>,
 1059   1441   
) -> Option<AdmissionAction> {
 1060   1442   
    match decision {
 1061   1443   
        H1ReservationDecision::Rejected(revision) => settle_match(
 1062   1444   
            admission,
 1063   1445   
            match_id,
@@ -1245,1627 +1419,1917 @@
 1265   1647   
                group.clone(),
 1266   1648   
            ),
 1267   1649   
        );
 1268   1650   
        let mut supply = H1Supply::default();
 1269   1651   
        supply.apply_revision(requesting_partition, group, supply_revision(1, true, false));
 1270   1652   
 1271   1653   
        let prepared = supply
 1272   1654   
            .prepare_match(&schedule)
 1273   1655   
            .expect("local HTTP/1 capacity was not selected for reclaim");
 1274   1656   
 1275         -
        assert_eq!(requesting_partition, prepared.supplier);
        1657  +
        assert_eq!(requesting_partition, prepared.supplier());
 1276   1658   
        assert_eq!(
 1277   1659   
            H1MatchKind::ReclaimCapacity,
 1278         -
            supply.matches[&prepared.match_id].kind
        1660  +
            supply.matches[&prepared.match_id()].kind
 1279   1661   
        );
 1280   1662   
    }
 1281   1663   
 1282   1664   
    #[test]
 1283         -
    fn borrow_supplier_selection_skips_the_requesting_cell() {
        1665  +
    fn single_peer_supplier_is_reserved_without_an_idle_probe() {
 1284   1666   
        let requesting_partition = cell(1);
 1285   1667   
        let peer = cell(2);
 1286   1668   
        let group = EligibilityGroup::Pool;
 1287   1669   
        let mut supply = H1Supply::default();
 1288   1670   
        supply.apply_revision(
 1289   1671   
            requesting_partition,
 1290   1672   
            group.clone(),
 1291   1673   
            supply_revision(1, true, false),
 1292   1674   
        );
 1293   1675   
        supply.apply_revision(peer, group.clone(), supply_revision(1, true, false));
 1294   1676   
 1295   1677   
        let prepared = supply
 1296   1678   
            .prepare_match(&schedule(requesting_partition, group))
 1297   1679   
            .expect("peer connection cell was not selected");
 1298         -
        assert_eq!(peer, prepared.supplier);
 1299         -
        assert_ne!(requesting_partition, prepared.supplier);
        1680  +
        assert_eq!(peer, prepared.supplier());
        1681  +
        assert_ne!(requesting_partition, prepared.supplier());
        1682  +
        assert!(matches!(prepared, PreparedH1Match::Reserve(_)));
        1683  +
    }
        1684  +
        1685  +
    #[test]
        1686  +
    fn last_available_supplier_becomes_the_fallback_reservation() {
        1687  +
        let requester = cell(9);
        1688  +
        let group = EligibilityGroup::Pool;
        1689  +
        let schedule = schedule(requester, group.clone());
        1690  +
        let suppliers = [cell(1), cell(2)];
        1691  +
        let mut supply = H1Supply::default();
        1692  +
        for supplier in suppliers {
        1693  +
            supply.apply_revision(supplier, group.clone(), supply_revision(1, true, false));
        1694  +
        }
        1695  +
        1696  +
        let first = supply
        1697  +
            .prepare_match(&schedule)
        1698  +
            .expect("first idle probe was not prepared");
        1699  +
        let match_id = first.match_id();
        1700  +
        assert_eq!(suppliers[0], first.supplier());
        1701  +
        assert!(matches!(first, PreparedH1Match::ProbeIdle(_)));
        1702  +
        1703  +
        let fallback = supply
        1704  +
            .prepare_next_idle_probe(
        1705  +
                match_id,
        1706  +
                &schedule,
        1707  +
                H1SupplyOutcome::supplier_live(suppliers[0], supply_revision(2, true, false)),
        1708  +
            )
        1709  +
            .expect("last supplier was not retained as the fallback");
        1710  +
        assert_eq!(suppliers[1], fallback.supplier());
        1711  +
        assert!(matches!(fallback, PreparedH1Match::Reserve(_)));
        1712  +
    }
        1713  +
        1714  +
    #[test]
        1715  +
    fn idle_probing_checks_distinct_suppliers_through_the_bound() {
        1716  +
        let requester = cell(9);
        1717  +
        let group = EligibilityGroup::Pool;
        1718  +
        let schedule = schedule(requester, group.clone());
        1719  +
        let suppliers = [cell(1), cell(2), cell(3), cell(4)];
        1720  +
        let mut supply = H1Supply::default();
        1721  +
        supply.apply_revision(requester, group.clone(), supply_revision(1, true, false));
        1722  +
        for supplier in suppliers {
        1723  +
            supply.apply_revision(supplier, group.clone(), supply_revision(1, true, false));
        1724  +
        }
        1725  +
        1726  +
        let first = supply
        1727  +
            .prepare_match(&schedule)
        1728  +
            .expect("first idle probe was not prepared");
        1729  +
        let match_id = first.match_id();
        1730  +
        assert_eq!(suppliers[0], first.supplier());
        1731  +
        assert!(matches!(first, PreparedH1Match::ProbeIdle(_)));
        1732  +
        1733  +
        let second = supply
        1734  +
            .prepare_next_idle_probe(
        1735  +
                match_id,
        1736  +
                &schedule,
        1737  +
                H1SupplyOutcome::supplier_live(suppliers[0], supply_revision(2, true, false)),
        1738  +
            )
        1739  +
            .expect("second idle probe was not prepared");
        1740  +
        assert_eq!(suppliers[1], second.supplier());
        1741  +
        assert!(matches!(second, PreparedH1Match::ProbeIdle(_)));
        1742  +
        1743  +
        let fallback = supply
        1744  +
            .prepare_next_idle_probe(
        1745  +
                match_id,
        1746  +
                &schedule,
        1747  +
                H1SupplyOutcome::supplier_live(suppliers[1], supply_revision(3, true, false)),
        1748  +
            )
        1749  +
            .expect("bounded probes did not retain an exact fallback");
        1750  +
        assert_eq!(suppliers[2], fallback.supplier());
        1751  +
        assert!(matches!(fallback, PreparedH1Match::Reserve(_)));
        1752  +
        assert_eq!(H1MatchKind::BorrowSender, supply.matches[&match_id].kind);
        1753  +
        assert!(supply.index.records[&requester].index_state.is_linked());
        1754  +
        assert!(supply.index.records[&suppliers[3]].index_state.is_linked());
        1755  +
    }
        1756  +
        1757  +
    #[test]
        1758  +
    fn idle_probe_miss_removes_cancelled_match_without_reserving_a_return() {
        1759  +
        let suppliers = [cell(1), cell(2)];
        1760  +
        let requester = cell(3);
        1761  +
        let group = EligibilityGroup::Pool;
        1762  +
        let mut schedule = schedule(requester, group.clone());
        1763  +
        let mut supply = H1Supply::default();
        1764  +
        for supplier in suppliers {
        1765  +
            supply.apply_revision(supplier, group.clone(), supply_revision(1, true, false));
        1766  +
        }
        1767  +
        let prepared = supply
        1768  +
            .prepare_match(&schedule)
        1769  +
            .expect("idle probe was not prepared");
        1770  +
        let match_id = prepared.match_id();
        1771  +
        assert!(matches!(prepared, PreparedH1Match::ProbeIdle(_)));
        1772  +
        1773  +
        schedule.apply_snapshot(
        1774  +
            requester,
        1775  +
            DemandSnapshot::inactive(DemandId::from_u64(1), SnapshotVersion::INITIAL.next()),
        1776  +
        );
        1777  +
        supply.reconcile_requester(&requester, &schedule);
        1778  +
        assert!(supply
        1779  +
            .prepare_next_idle_probe(
        1780  +
                match_id,
        1781  +
                &schedule,
        1782  +
                H1SupplyOutcome::supplier_live(suppliers[0], supply_revision(2, true, false)),
        1783  +
            )
        1784  +
            .is_none());
        1785  +
        1786  +
        assert!(!supply.matches.contains_key(&match_id));
        1787  +
        assert!(!supply.by_requester.contains_key(&requester));
        1788  +
        for supplier in suppliers {
        1789  +
            assert_eq!(None, supply.index.records[&supplier].reserved_by);
        1790  +
            assert!(supply.index.records[&supplier].index_state.is_linked());
        1791  +
        }
 1300   1792   
    }
 1301   1793   
 1302   1794   
    #[test]
 1303   1795   
    fn resolving_match_discards_its_lazy_cancellation_entry() {
 1304         -
        let supplier = cell(1);
 1305         -
        let requester = cell(2);
        1796  +
        let suppliers = [cell(1), cell(2)];
        1797  +
        let requester = cell(3);
 1306   1798   
        let group = EligibilityGroup::Pool;
 1307   1799   
        let mut schedule = schedule(requester, group.clone());
 1308   1800   
        let mut supply = H1Supply::default();
 1309         -
        supply.apply_revision(supplier, group.clone(), supply_revision(1, true, false));
        1801  +
        for supplier in suppliers {
        1802  +
            supply.apply_revision(supplier, group.clone(), supply_revision(1, true, false));
        1803  +
        }
 1310   1804   
        let prepared = supply
 1311   1805   
            .prepare_match(&schedule)
 1312   1806   
            .expect("supplier cell did not produce a retained match");
 1313         -
        supply.settle_reservation(prepared.match_id, false);
        1807  +
        let match_id = prepared.match_id();
        1808  +
        assert!(matches!(prepared, PreparedH1Match::ProbeIdle(_)));
        1809  +
        let prepared = supply
        1810  +
            .prepare_next_idle_probe(
        1811  +
                match_id,
        1812  +
                &schedule,
        1813  +
                H1SupplyOutcome::supplier_live(suppliers[0], supply_revision(2, true, false)),
        1814  +
            )
        1815  +
            .expect("idle probe did not retain an exact reservation");
        1816  +
        assert!(matches!(prepared, PreparedH1Match::Reserve(_)));
        1817  +
        supply.settle_reservation(match_id, false);
 1314   1818   
 1315   1819   
        schedule.apply_snapshot(
 1316   1820   
            requester,
 1317   1821   
            DemandSnapshot::inactive(DemandId::from_u64(1), SnapshotVersion::INITIAL.next()),
 1318   1822   
        );
 1319   1823   
        supply.reconcile_requester(&requester, &schedule);
 1320         -
        assert_eq!(
 1321         -
            H1MatchState::Cancelling,
 1322         -
            supply.matches[&prepared.match_id].state
 1323         -
        );
        1824  +
        assert_eq!(H1MatchState::Cancelling, supply.matches[&match_id].state);
 1324   1825   
 1325         -
        supply.begin_resolution(prepared.match_id);
 1326         -
        assert_eq!(
 1327         -
            H1MatchState::Resolving,
 1328         -
            supply.matches[&prepared.match_id].state
 1329         -
        );
        1826  +
        supply.begin_resolution(match_id);
        1827  +
        assert_eq!(H1MatchState::Resolving, supply.matches[&match_id].state);
 1330   1828   
        assert!(
 1331   1829   
            supply.prepare_cancellation().is_none(),
 1332   1830   
            "resolving match produced stale cancellation work"
 1333   1831   
        );
 1334   1832   
        assert!(supply.cancellations.is_empty());
 1335   1833   
    }
 1336   1834   
 1337   1835   
    #[test]
 1338   1836   
    fn expired_supplier_cell_terminates_its_match_without_resubmission() {
 1339   1837   
        let connection_partition = cell(1);
 1340   1838   
        let requesting_partition = cell(2);
 1341   1839   
        let group = EligibilityGroup::Pool;
 1342   1840   
        let schedule = schedule(requesting_partition, group.clone());
 1343   1841   
        let mut supply = H1Supply::default();
 1344   1842   
        supply.apply_revision(connection_partition, group, supply_revision(1, true, false));
 1345   1843   
        let prepared = supply
 1346   1844   
            .prepare_match(&schedule)
 1347   1845   
            .expect("supplier cell did not produce a retained match");
 1348   1846   
 1349   1847   
        supply.settle_match(
 1350         -
            prepared.match_id,
        1848  +
            prepared.match_id(),
 1351   1849   
            H1SupplyOutcome::supplier_expired(connection_partition),
 1352   1850   
        );
 1353   1851   
 1354   1852   
        assert!(!supply.index.records.contains_key(&connection_partition));
 1355   1853   
        assert!(supply.prepare_match(&schedule).is_none());
 1356   1854   
        assert!(supply.matches.is_empty());
 1357   1855   
    }
 1358   1856   
 1359   1857   
    #[test]
 1360   1858   
    fn stale_supply_outcome_cannot_hide_newer_status() {
 1361   1859   
        let connection_partition = cell(1);
 1362   1860   
        let requesting_partition = cell(2);
 1363   1861   
        let group = EligibilityGroup::Pool;
 1364   1862   
        let schedule = schedule(requesting_partition, group.clone());
 1365   1863   
        let mut supply = H1Supply::default();
 1366   1864   
        supply.apply_revision(connection_partition, group, supply_revision(1, true, false));
 1367   1865   
        let prepared = supply
 1368   1866   
            .prepare_match(&schedule)
 1369   1867   
            .expect("supplier cell did not produce a retained match");
 1370   1868   
 1371   1869   
        supply.settle_match(
 1372         -
            prepared.match_id,
        1870  +
            prepared.match_id(),
 1373   1871   
            H1SupplyOutcome::supplier_live(connection_partition, supply_revision(3, true, false)),
 1374   1872   
        );
 1375   1873   
        supply.settle_match(
 1376         -
            prepared.match_id,
        1874  +
            prepared.match_id(),
 1377   1875   
            H1SupplyOutcome::supplier_live(connection_partition, supply_revision(2, false, false)),
 1378   1876   
        );
 1379   1877   
 1380   1878   
        let supplier = supply
 1381   1879   
            .index
 1382   1880   
            .records
 1383   1881   
            .get(&connection_partition)
 1384   1882   
            .expect("stale revision removed the supplier cell");
 1385   1883   
        assert!(supplier.status.has_returnable_connection);
 1386   1884   
        assert!(supplier.index_state.is_linked());
 1387   1885   
        assert_eq!(3, supplier.revision);
 1388   1886   
    }
 1389   1887   
 1390   1888   
    #[test]
 1391   1889   
    fn duplicate_supply_outcome_still_refreshes_status() {
 1392   1890   
        let connection_partition = cell(1);
 1393   1891   
        let requesting_partition = cell(2);
 1394   1892   
        let group = EligibilityGroup::Pool;
 1395   1893   
        let schedule = schedule(requesting_partition, group.clone());
 1396   1894   
        let mut supply = H1Supply::default();
 1397   1895   
        supply.apply_revision(connection_partition, group, supply_revision(1, true, false));
 1398   1896   
        let prepared = supply
 1399   1897   
            .prepare_match(&schedule)
 1400   1898   
            .expect("supplier cell did not produce a retained match");
 1401   1899   
 1402   1900   
        supply.settle_match(
 1403         -
            prepared.match_id,
        1901  +
            prepared.match_id(),
 1404   1902   
            H1SupplyOutcome::supplier_live(connection_partition, supply_revision(2, true, false)),
 1405   1903   
        );
 1406   1904   
        supply.settle_match(
 1407         -
            prepared.match_id,
        1905  +
            prepared.match_id(),
 1408   1906   
            H1SupplyOutcome::supplier_live(connection_partition, supply_revision(3, false, false)),
 1409   1907   
        );
 1410   1908   
 1411   1909   
        let supplier = supply
 1412   1910   
            .index
 1413   1911   
            .records
 1414   1912   
            .get(&connection_partition)
 1415   1913   
            .expect("live supply revision removed the supplier cell");
 1416   1914   
        assert!(!supplier.status.has_returnable_connection);
 1417   1915   
        assert!(!supplier.index_state.is_linked());

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

@@ -249,249 +308,333 @@
  269    269   
            );
  270    270   
            self.assert_consistent();
  271    271   
            return H1ReservationDecision::Installed;
  272    272   
        }
  273    273   
  274    274   
        let report = self.current_h1_supply_revision();
  275    275   
        self.assert_consistent();
  276    276   
        H1ReservationDecision::Rejected(report)
  277    277   
    }
  278    278   
         279  +
    /// Extracts an idle sender without reserving a future sender return.
         280  +
    fn try_take_idle_h1(&mut self, match_id: H1MatchId) -> H1IdleProbeDecision<OwnedH1Sender> {
         281  +
        let local_h1_demand = self.acquisitions.has_h1_compatible_waiter();
         282  +
        if self.h1.blocks_peer_selection(local_h1_demand)
         283  +
            || self.acquisitions.has_prior_h1_waiter()
         284  +
            || !self.h1.peer_reservation_available()
         285  +
        {
         286  +
            let revision = self.current_h1_supply_revision();
         287  +
            self.assert_consistent();
         288  +
            return H1IdleProbeDecision::Unavailable(revision);
         289  +
        }
         290  +
         291  +
        let Some(owner) = self.h1.take_idle_candidate() else {
         292  +
            let revision = self.current_h1_supply_revision();
         293  +
            self.assert_consistent();
         294  +
            return H1IdleProbeDecision::Unavailable(revision);
         295  +
        };
         296  +
        assert!(
         297  +
            self.h1.reserve_resolving(match_id),
         298  +
            "idle HTTP/1 connection could not reserve its peer match"
         299  +
        );
         300  +
        self.assert_consistent();
         301  +
        H1IdleProbeDecision::Candidate(owner)
         302  +
    }
         303  +
  279    304   
    /// Clears a peer reservation and returns the cell's complete H1 status.
  280    305   
    fn cancel_h1_reservation(&mut self, match_id: H1MatchId) -> SupplyRevision<H1SupplyStatus> {
  281    306   
        self.h1.release_peer_reservation(match_id);
  282    307   
        let local_h1_demand = self.acquisitions.has_h1_compatible_waiter();
  283    308   
        self.h1.clear_unused_turn(local_h1_demand);
  284    309   
        self.assert_consistent();
  285    310   
        self.current_h1_supply_revision()
  286    311   
    }
  287    312   
  288    313   
    /// Clears a reservation before its provisional sender follows ordinary return.
@@ -311,336 +370,413 @@
  331    356   
/// Supplier-cell decision for one HTTP/1 reservation attempt.
  332    357   
pub(in crate::client::pool) enum H1ReservationDecision<C> {
  333    358   
    /// A future reusable sender return will satisfy the retained match.
  334    359   
    Installed,
  335    360   
    /// An idle sender was extracted immediately.
  336    361   
    Candidate(C),
  337    362   
    /// The supplier cell could not reserve sender ownership for the match.
  338    363   
    Rejected(SupplyRevision<H1SupplyStatus>),
  339    364   
}
  340    365   
         366  +
/// Supplier-cell result of probing for an immediately idle HTTP/1 sender.
         367  +
pub(in crate::client::pool) enum H1IdleProbeDecision<C> {
         368  +
    /// An idle sender was extracted immediately.
         369  +
    Candidate(C),
         370  +
    /// The supplier remains live but has no sender available for immediate use.
         371  +
    Unavailable(SupplyRevision<H1SupplyStatus>),
         372  +
}
         373  +
         374  +
impl<C> H1IdleProbeDecision<C> {
         375  +
    /// Maps only the detached sender while preserving the supplier decision.
         376  +
    fn map_candidate<D>(self, map: impl FnOnce(C) -> D) -> H1IdleProbeDecision<D> {
         377  +
        match self {
         378  +
            Self::Candidate(candidate) => H1IdleProbeDecision::Candidate(map(candidate)),
         379  +
            Self::Unavailable(revision) => H1IdleProbeDecision::Unavailable(revision),
         380  +
        }
         381  +
    }
         382  +
}
         383  +
  341    384   
impl<C> H1ReservationDecision<C> {
  342    385   
    /// Maps only the detached sender while preserving the supplier decision.
  343    386   
    fn map_candidate<D>(self, map: impl FnOnce(C) -> D) -> H1ReservationDecision<D> {
  344    387   
        match self {
  345    388   
            Self::Installed => H1ReservationDecision::Installed,
  346    389   
            Self::Candidate(candidate) => H1ReservationDecision::Candidate(map(candidate)),
  347    390   
            Self::Rejected(revision) => H1ReservationDecision::Rejected(revision),
  348    391   
        }
  349    392   
    }
  350    393   
}
@@ -1128,1171 +1187,1277 @@
 1148   1191   
            .take_ready_h1(waiter)
 1149   1192   
            .expect("eligible peer did not receive the idle HTTP/1 sender");
 1150   1193   
 1151   1194   
        assert_eq!(11, borrowed.test_sender_id());
 1152   1195   
        assert_eq!(0, admission.available_capacity_for_test());
 1153   1196   
        assert_eq!((1, 0), connection_cell.h1_counts());
 1154   1197   
        drop(borrowed);
 1155   1198   
        assert_eq!((1, 1), connection_cell.h1_counts());
 1156   1199   
    }
 1157   1200   
        1201  +
    #[test]
        1202  +
    fn peer_idle_probe_skips_a_busy_supplier_for_an_idle_one() {
        1203  +
        let admission = OriginAdmission::for_test(NonZeroUsize::new(2).unwrap());
        1204  +
        let origin = OriginKey::from_parts(Scheme::HTTPS, "example.com", None).unwrap();
        1205  +
        let supplier = |index| {
        1206  +
            OriginAdmission::register_cell(
        1207  +
                &admission,
        1208  +
                Arc::new(OriginCell::new(
        1209  +
                    PartitionId::from_index(index),
        1210  +
                    origin.clone(),
        1211  +
                    EligibilityGroup::Pool,
        1212  +
                    Some(admission.clone()),
        1213  +
                    None,
        1214  +
                )),
        1215  +
            )
        1216  +
        };
        1217  +
        let busy_cell = supplier(1);
        1218  +
        let idle_cell = supplier(2);
        1219  +
        let requesting_cell = supplier(3);
        1220  +
        1221  +
        let busy_lease = OriginAdmission::lease_for_test(&admission);
        1222  +
        let (busy_connection, _busy_physical) = ConnectionState::bounded(
        1223  +
            ConnectionInfo::for_test(ConnectionId::new(1), busy_cell.id().partition()),
        1224  +
            busy_lease,
        1225  +
        );
        1226  +
        let busy = OriginCell::insert_selected_h1(&busy_cell, busy_connection, H1Sender::test(11));
        1227  +
        1228  +
        let idle_lease = OriginAdmission::lease_for_test(&admission);
        1229  +
        let (idle_connection, _idle_physical) = ConnectionState::bounded(
        1230  +
            ConnectionInfo::for_test(ConnectionId::new(2), idle_cell.id().partition()),
        1231  +
            idle_lease,
        1232  +
        );
        1233  +
        OriginCell::insert_idle_h1(&idle_cell, idle_connection, H1Sender::test(22));
        1234  +
        1235  +
        let waiter =
        1236  +
            OriginCell::register_waiter(&requesting_cell, ProtocolRequirement::H1Compatible);
        1237  +
        let borrowed = requesting_cell
        1238  +
            .take_ready_h1(waiter)
        1239  +
            .expect("bounded idle probing did not find the second supplier");
        1240  +
        1241  +
        assert_eq!(22, borrowed.test_sender_id());
        1242  +
        assert_eq!((1, 0), busy_cell.h1_counts());
        1243  +
        assert_eq!((1, 0), idle_cell.h1_counts());
        1244  +
        drop(borrowed);
        1245  +
        drop(busy);
        1246  +
    }
        1247  +
 1158   1248   
    #[test]
 1159   1249   
    fn ineligible_peer_reclaims_h1_capacity_without_moving_the_sender() {
 1160   1250   
        let (admission, connection_cell, requesting_cell) = bounded_peer_cells(
 1161   1251   
            1,
 1162   1252   
            EligibilityGroup::Partition(PartitionId::from_index(1)),
 1163   1253   
            EligibilityGroup::Partition(PartitionId::from_index(2)),
 1164   1254   
        );
 1165   1255   
        let lease = OriginAdmission::lease_for_test(&admission);
 1166   1256   
        let (connection, _physical) = ConnectionState::bounded(connection_info(1), lease);
 1167   1257   
        OriginCell::insert_idle_h1(&connection_cell, connection.clone(), H1Sender::test(11));
@@ -2103,2193 +2162,2282 @@
 2123   2213   
        drop(activation);
 2124   2214   
        assert_eq!(0, requesting_cell.probe().retained);
 2125   2215   
        assert!(OriginCell::close_h2(
 2126   2216   
            &connection_cell,
 2127   2217   
            generation,
 2128   2218   
            CloseReason::PoolDropped,
 2129   2219   
        ));
 2130   2220   
        assert_eq!(1, admission.available_capacity_for_test());
 2131   2221   
    }
 2132   2222   
        2223  +
    #[test]
        2224  +
    fn prepared_h2_route_executes_and_settles_assignment() {
        2225  +
        let (admission, connection_cell, requesting_cell) =
        2226  +
            bounded_peer_cells(1, EligibilityGroup::Pool, EligibilityGroup::Pool);
        2227  +
        let (generation, _connection, _physical) =
        2228  +
            install_bounded_h2(&admission, &connection_cell, 1);
        2229  +
        let (waiter, demand) =
        2230  +
            requesting_cell.register_waiter_without_publish(ProtocolRequirement::H2Required);
        2231  +
        let action = OriginAdmission::submit_action_without_running(
        2232  +
            &admission,
        2233  +
            requesting_cell.id().partition(),
        2234  +
            demand,
        2235  +
        )
        2236  +
        .expect("peer demand did not prepare an HTTP/2 route");
        2237  +
        2238  +
        OriginAdmission::run_action_chain(Some(action));
        2239  +
        let activation = take_ready_h2(&requesting_cell, waiter);
        2240  +
        assert_eq!(generation, activation.generation());
        2241  +
        drop(activation);
        2242  +
        2243  +
        assert_eq!(0, requesting_cell.probe().retained);
        2244  +
        assert_eq!(0, admission.ordered_demand_count_for_test());
        2245  +
        assert!(OriginCell::close_h2(
        2246  +
            &connection_cell,
        2247  +
            generation,
        2248  +
            CloseReason::PoolDropped,
        2249  +
        ));
        2250  +
        assert_eq!(1, admission.available_capacity_for_test());
        2251  +
    }
        2252  +
 2133   2253   
    #[test]
 2134   2254   
    fn peer_h2_route_respects_eligibility_group() {
 2135   2255   
        let groups = [
 2136   2256   
            (
 2137   2257   
                EligibilityGroup::Partition(PartitionId::from_index(1)),
 2138   2258   
                EligibilityGroup::Partition(PartitionId::from_index(2)),
 2139   2259   
            ),
 2140   2260   
            (
 2141   2261   
                EligibilityGroup::NetworkInterface(Some(Arc::from("eth0"))),
 2142   2262   
                EligibilityGroup::NetworkInterface(Some(Arc::from("eth1"))),
@@ -2341,2461 +2400,2521 @@
 2361   2481   
            &connection_cell,
 2362   2482   
            generation,
 2363   2483   
            CloseReason::PoolDropped,
 2364   2484   
        ));
 2365   2485   
    }
 2366   2486   
}
 2367   2487   
 2368   2488   
#[cfg(all(test, smithy_http_client_loom))]
 2369   2489   
mod loom_tests {
 2370   2490   
    use super::*;
        2491  +
    use crate::sync::{AtomicBool, AtomicUsize, Ordering};
 2371   2492   
    use http_1x::uri::Scheme;
 2372   2493   
    use std::num::NonZeroUsize;
 2373   2494   
 2374   2495   
    fn bounded_cell() -> (Arc<OriginAdmission>, Arc<OriginCell>) {
 2375   2496   
        bounded_cell_with_limit(1)
 2376   2497   
    }
 2377   2498   
 2378   2499   
    fn bounded_cell_with_limit(limit: usize) -> (Arc<OriginAdmission>, Arc<OriginCell>) {
 2379   2500   
        let admission = OriginAdmission::for_test(NonZeroUsize::new(limit).unwrap());
 2380   2501   
        let candidate = Arc::new(OriginCell::new(
@@ -2412,2533 +2980,3537 @@
 2432   2553   
            EligibilityGroup::Pool,
 2433   2554   
            None,
 2434   2555   
            None,
 2435   2556   
        ))
 2436   2557   
    }
 2437   2558   
 2438   2559   
    fn connection_info(id: u64) -> Arc<ConnectionInfo> {
 2439   2560   
        ConnectionInfo::for_test(ConnectionId::new(id), PartitionId::from_index(1))
 2440   2561   
    }
 2441   2562   
        2563  +
    /// Races local HTTP/1 selection with close.
        2564  +
    ///
        2565  +
    /// The sender must have one winner and no installed record may survive.
 2442   2566   
    #[test]
 2443   2567   
    fn h1_selection_linearizes_against_close() {
 2444   2568   
        loom::model(|| {
 2445   2569   
            let cell = unbounded_cell();
 2446   2570   
            let (connection, _physical) = ConnectionState::unbounded(connection_info(1));
 2447   2571   
            OriginCell::insert_idle_h1(&cell, connection.clone(), H1Sender::test(11));
 2448   2572   
            let close = H1CloseHandle::new(&cell, &connection);
 2449   2573   
 2450   2574   
            let select_cell = cell.clone();
 2451   2575   
            let select = loom::thread::spawn(move || OriginCell::select_h1(&select_cell));
 2452   2576   
            let close = loom::thread::spawn(move || close.close(CloseReason::Poisoned));
 2453   2577   
 2454   2578   
            let selected = select.join().unwrap();
 2455   2579   
            assert!(close.join().unwrap());
 2456   2580   
            drop(selected);
 2457   2581   
 2458   2582   
            assert_eq!((0, 0), cell.h1_counts());
 2459   2583   
            assert_eq!(Some(CloseReason::Poisoned), connection.probe().close_reason);
 2460   2584   
        });
 2461   2585   
    }
 2462   2586   
        2587  +
    /// Races a reusable HTTP/1 return with close.
        2588  +
    ///
        2589  +
    /// Close must prevent the returning sender from becoming selectable again.
 2463   2590   
    #[test]
 2464   2591   
    fn h1_return_linearizes_against_close() {
 2465   2592   
        loom::model(|| {
 2466   2593   
            let cell = unbounded_cell();
 2467   2594   
            let (connection, _physical) = ConnectionState::unbounded(connection_info(1));
 2468   2595   
            let selection =
 2469   2596   
                OriginCell::insert_selected_h1(&cell, connection.clone(), H1Sender::test(11));
 2470   2597   
            let exchange = selection.into_exchange();
 2471   2598   
            let close = H1CloseHandle::new(&cell, &connection);
 2472   2599   
 2473   2600   
            let returning = loom::thread::spawn(move || exchange.offer_for_reuse());
 2474   2601   
            let closing = loom::thread::spawn(move || close.close(CloseReason::Poisoned));
 2475   2602   
            returning.join().unwrap();
 2476   2603   
            assert!(closing.join().unwrap());
 2477   2604   
 2478   2605   
            assert_eq!((0, 0), cell.h1_counts());
 2479   2606   
            assert_eq!(Some(CloseReason::Poisoned), connection.probe().close_reason);
 2480   2607   
        });
 2481   2608   
    }
 2482   2609   
        2610  +
    /// Races local sender delivery with cancellation of its target waiter.
        2611  +
    ///
        2612  +
    /// Cancellation may win, but the exclusive sender must return exactly once.
 2483   2613   
    #[test]
 2484   2614   
    fn h1_delivery_and_waiter_cancellation_preserve_the_sender() {
 2485   2615   
        loom::model(|| {
 2486   2616   
            let cell = unbounded_cell();
 2487   2617   
            let (connection, _physical) = ConnectionState::unbounded(connection_info(1));
 2488   2618   
            let selection = OriginCell::insert_selected_h1(&cell, connection, H1Sender::test(11));
 2489   2619   
            let waiter = OriginCell::register_waiter(&cell, ProtocolRequirement::H1Compatible);
 2490   2620   
 2491   2621   
            let returning = loom::thread::spawn(move || drop(selection));
 2492   2622   
            let cancel_cell = cell.clone();
 2493   2623   
            let cancelling =
 2494   2624   
                loom::thread::spawn(move || OriginCell::cancel_waiter(&cancel_cell, waiter));
 2495   2625   
            returning.join().unwrap();
 2496   2626   
            assert!(cancelling.join().unwrap());
 2497   2627   
 2498   2628   
            assert_eq!((1, 1), cell.h1_counts());
 2499   2629   
            assert_eq!(0, cell.probe().retained);
 2500   2630   
            assert_eq!(
 2501   2631   
                11,
 2502   2632   
                OriginCell::select_h1(&cell)
 2503   2633   
                    .expect("sender was lost during return/cancel")
 2504   2634   
                    .test_sender_id()
 2505   2635   
            );
 2506   2636   
        });
 2507   2637   
    }
 2508   2638   
        2639  +
    /// Races an idle return with completion of an already-started establishment.
        2640  +
    ///
        2641  +
    /// Exactly one result satisfies the waiter and the losing sender remains reusable.
 2509   2642   
    #[test]
 2510   2643   
    fn returned_h1_and_establishment_race_for_one_waiter() {
 2511   2644   
        loom::model(|| {
 2512   2645   
            let cell = unbounded_cell();
 2513   2646   
            let waiter = OriginCell::register_waiter(&cell, ProtocolRequirement::H1Compatible);
 2514   2647   
            let AcquisitionStep::StartEstablishment(permit) = cell
 2515   2648   
                .take_ready_event(waiter)
 2516   2649   
                .expect("unbounded miss did not start establishment")
 2517   2650   
            else {
 2518   2651   
                panic!("unbounded miss completed before establishment started");
 2519   2652   
            };
 2520   2653   
            assert!(cell.start_establishment(waiter));
 2521   2654   
            drop(permit);
 2522   2655   
 2523   2656   
            let (returning_connection, _returning_physical) =
 2524   2657   
                ConnectionState::unbounded(connection_info(1));
 2525   2658   
            let returning =
 2526   2659   
                OriginCell::insert_selected_h1(&cell, returning_connection, H1Sender::test(11));
 2527   2660   
            let (fresh_connection, _fresh_physical) =
 2528   2661   
                ConnectionState::unbounded(connection_info(2));
 2529   2662   
            let fresh = OriginCell::insert_selected_h1(&cell, fresh_connection, H1Sender::test(22));
 2530   2663   
 2531   2664   
            let returning = loom::thread::spawn(move || drop(returning));
 2532   2665   
            let completion_cell = cell.clone();
 2533   2666   
            let completing = loom::thread::spawn(move || {
 2534   2667   
                completion_cell.complete_establishment(waiter, AcquisitionOutcome::H1(fresh));
 2535   2668   
            });
 2536   2669   
            returning.join().unwrap();
 2537   2670   
            completing.join().unwrap();
 2538   2671   
 2539   2672   
            assert_eq!((2, 1), cell.h1_counts());
 2540   2673   
            let winner = cell
 2541   2674   
                .take_ready_h1(waiter)
 2542   2675   
                .expect("both acquisition outcomes were lost");
 2543   2676   
            assert!(matches!(winner.test_sender_id(), 11 | 22));
 2544   2677   
            drop(winner);
 2545   2678   
            assert_eq!((2, 2), cell.h1_counts());
 2546   2679   
        });
 2547   2680   
    }
 2548   2681   
        2682  +
    /// Races first establishment commitment with a reusable HTTP/1 return.
        2683  +
    ///
        2684  +
    /// A return that wins before commitment must satisfy the waiter without being displaced.
 2549   2685   
    #[test]
 2550   2686   
    fn first_establishment_poll_races_returned_h1() {
 2551   2687   
        loom::model(|| {
 2552   2688   
            let cell = unbounded_cell();
 2553   2689   
            let waiter = OriginCell::register_waiter(&cell, ProtocolRequirement::H1Compatible);
 2554   2690   
            let AcquisitionStep::StartEstablishment(permit) = cell
 2555   2691   
                .take_ready_event(waiter)
 2556   2692   
                .expect("unbounded miss did not prepare establishment")
 2557   2693   
            else {
 2558   2694   
                panic!("unbounded miss completed before establishment was prepared");
 2559   2695   
            };
 2560   2696   
 2561   2697   
            let (returning_connection, _returning_physical) =
 2562   2698   
                ConnectionState::unbounded(connection_info(1));
 2563   2699   
            let returning =
 2564   2700   
                OriginCell::insert_selected_h1(&cell, returning_connection, H1Sender::test(11));
 2565   2701   
            let return_thread = loom::thread::spawn(move || drop(returning));
 2566   2702   
            let start_cell = cell.clone();
 2567   2703   
            let start_thread = loom::thread::spawn(move || start_cell.start_establishment(waiter));
 2568   2704   
 2569   2705   
            return_thread.join().unwrap();
 2570   2706   
            let started = start_thread.join().unwrap();
 2571   2707   
            drop(permit);
 2572   2708   
 2573   2709   
            if started {
 2574   2710   
                let (fresh_connection, _fresh_physical) =
 2575   2711   
                    ConnectionState::unbounded(connection_info(2));
 2576   2712   
                let fresh =
 2577   2713   
                    OriginCell::insert_selected_h1(&cell, fresh_connection, H1Sender::test(22));
 2578   2714   
                cell.complete_establishment(waiter, AcquisitionOutcome::H1(fresh));
 2579   2715   
            }
 2580   2716   
 2581   2717   
            let winner = cell
 2582   2718   
                .take_ready_h1(waiter)
 2583   2719   
                .expect("returned H1 was lost at the first-poll boundary");
 2584   2720   
            assert_eq!(11, winner.test_sender_id());
 2585   2721   
            drop(winner);
 2586   2722   
            assert_eq!(
 2587   2723   
                (usize::from(started) + 1, usize::from(started) + 1),
 2588   2724   
                cell.h1_counts()
 2589   2725   
            );
 2590   2726   
        });
 2591   2727   
    }
 2592   2728   
        2729  +
    /// Races local HTTP/1 return with bounded-capacity delivery.
        2730  +
    ///
        2731  +
    /// The sender wins the waiter while the unused capacity returns to admission.
 2593   2732   
    #[test]
 2594   2733   
    fn h1_return_and_capacity_delivery_complete_one_waiter() {
 2595   2734   
        loom::model(|| {
 2596   2735   
            let (admission, cell) = bounded_cell_with_limit(2);
 2597   2736   
            let installed_lease = OriginAdmission::lease_for_test(&admission);
 2598   2737   
            let (connection, _physical) =
 2599   2738   
                ConnectionState::bounded(connection_info(1), installed_lease);
 2600   2739   
            let returning = OriginCell::insert_selected_h1(&cell, connection, H1Sender::test(11));
 2601   2740   
            let (waiter, demand) =
 2602   2741   
                cell.register_waiter_without_publish(ProtocolRequirement::H1Compatible);
 2603         -
            let mut delivery =
        2742  +
            let delivery =
 2604   2743   
                OriginAdmission::submit_without_running(&admission, cell.id().partition(), demand)
 2605   2744   
                    .expect("published demand did not reserve capacity");
 2606         -
            assert!(
 2607         -
                delivery.resolve_payload_for_test(),
 2608         -
                "capacity delivery did not materialize"
 2609         -
            );
 2610   2745   
 2611         -
            let delivery_cell = cell.clone();
 2612   2746   
            let delivering = loom::thread::spawn(move || {
 2613         -
                drop(OriginCell::receive_delivery(&delivery_cell, delivery));
        2747  +
                OriginAdmission::run_action_chain(Some(AdmissionAction::Deliver(delivery)));
 2614   2748   
            });
 2615   2749   
            let returning = loom::thread::spawn(move || drop(returning));
 2616   2750   
            delivering.join().unwrap();
 2617   2751   
            returning.join().unwrap();
 2618   2752   
 2619   2753   
            assert_eq!(1, admission.available_capacity_for_test());
 2620   2754   
            let selection = cell
 2621   2755   
                .take_ready_h1(waiter)
 2622   2756   
                .expect("return and capacity delivery lost the HTTP/1 sender");
 2623   2757   
            assert_eq!(11, selection.test_sender_id());
 2624   2758   
            drop(selection);
 2625   2759   
            admission.clear_modeled_cells_for_test();
 2626   2760   
        });
 2627   2761   
    }
 2628   2762   
        2763  +
    /// Races a retained peer match with cancellation of its requesting waiter.
        2764  +
    ///
        2765  +
    /// Three preemptions cover either actor entering first and a complete
        2766  +
    /// reservation round trip across the admission and cell locks.
 2629   2767   
    #[test]
 2630   2768   
    fn peer_match_and_request_cancellation_preserve_the_sender() {
 2631   2769   
        let mut model = loom::model::Builder::new();
 2632         -
        model.preemption_bound = Some(2);
        2770  +
        model.preemption_bound.get_or_insert(3);
 2633   2771   
        model.check(|| {
 2634   2772   
            let (admission, connection_cell, requesting_cell) =
 2635   2773   
                bounded_peer_cells(EligibilityGroup::Pool, EligibilityGroup::Pool);
 2636   2774   
            let lease = OriginAdmission::lease_for_test(&admission);
 2637   2775   
            let (connection, _physical) = ConnectionState::bounded(connection_info(1), lease);
 2638   2776   
            let returning =
 2639   2777   
                OriginCell::insert_selected_h1(&connection_cell, connection, H1Sender::test(11));
 2640   2778   
            let waiter =
 2641   2779   
                OriginCell::register_waiter(&requesting_cell, ProtocolRequirement::H1Compatible);
 2642   2780   
 2643   2781   
            // Loom's default 4 KiB coroutine stack is too small for the full
 2644   2782   
            // sender-return and admission-settlement path modeled here.
 2645   2783   
            let returning = loom::thread::Builder::new()
 2646   2784   
                .stack_size(16 * 1024)
 2647   2785   
                .spawn(move || drop(returning))
 2648   2786   
                .unwrap();
 2649   2787   
            let cancel_requesting_cell = requesting_cell.clone();
 2650   2788   
            let cancelling = loom::thread::Builder::new()
 2651   2789   
                .stack_size(16 * 1024)
 2652   2790   
                .spawn(move || OriginCell::cancel_waiter(&cancel_requesting_cell, waiter))
 2653   2791   
                .unwrap();
 2654   2792   
            returning.join().unwrap();
 2655   2793   
            assert!(cancelling.join().unwrap());
 2656   2794   
 2657   2795   
            assert_eq!((1, 1), connection_cell.h1_counts());
 2658   2796   
            assert_eq!(0, requesting_cell.probe().retained);
 2659   2797   
            assert_eq!(0, admission.ordered_demand_count_for_test());
 2660   2798   
            admission.clear_modeled_cells_for_test();
 2661   2799   
        });
 2662   2800   
    }
 2663   2801   
        2802  +
    /// Races a busy idle-probe reply with cancellation of its demand.
        2803  +
    ///
        2804  +
    /// The supplier returns to its index, no fallback reservation survives,
        2805  +
    /// and the active sender remains owned by its connection cell.
        2806  +
    #[test]
        2807  +
    fn busy_idle_probe_and_cancellation_leave_no_retained_match() {
        2808  +
        let mut model = loom::model::Builder::new();
        2809  +
        model.preemption_bound.get_or_insert(3);
        2810  +
        model.check(|| {
        2811  +
            let admission = OriginAdmission::for_test(NonZeroUsize::new(2).unwrap());
        2812  +
            let origin = OriginKey::from_parts(Scheme::HTTPS, "example.com", None).unwrap();
        2813  +
            let first = OriginAdmission::register_cell(
        2814  +
                &admission,
        2815  +
                Arc::new(OriginCell::new(
        2816  +
                    PartitionId::from_index(1),
        2817  +
                    origin.clone(),
        2818  +
                    EligibilityGroup::Pool,
        2819  +
                    Some(admission.clone()),
        2820  +
                    None,
        2821  +
                )),
        2822  +
            );
        2823  +
            let second = OriginAdmission::register_cell(
        2824  +
                &admission,
        2825  +
                Arc::new(OriginCell::new(
        2826  +
                    PartitionId::from_index(2),
        2827  +
                    origin.clone(),
        2828  +
                    EligibilityGroup::Pool,
        2829  +
                    Some(admission.clone()),
        2830  +
                    None,
        2831  +
                )),
        2832  +
            );
        2833  +
            let requesting_cell = OriginAdmission::register_cell(
        2834  +
                &admission,
        2835  +
                Arc::new(OriginCell::new(
        2836  +
                    PartitionId::from_index(3),
        2837  +
                    origin,
        2838  +
                    EligibilityGroup::Pool,
        2839  +
                    Some(admission.clone()),
        2840  +
                    None,
        2841  +
                )),
        2842  +
            );
        2843  +
            let first_lease = OriginAdmission::lease_for_test(&admission);
        2844  +
            let (first_connection, _first_physical) =
        2845  +
                ConnectionState::bounded(connection_info(1), first_lease);
        2846  +
            let first_returning =
        2847  +
                OriginCell::insert_selected_h1(&first, first_connection, H1Sender::test(11));
        2848  +
            let second_lease = OriginAdmission::lease_for_test(&admission);
        2849  +
            let (second_connection, _second_physical) =
        2850  +
                ConnectionState::bounded(connection_info(2), second_lease);
        2851  +
            let second_returning =
        2852  +
                OriginCell::insert_selected_h1(&second, second_connection, H1Sender::test(22));
        2853  +
            let (waiter, demand) =
        2854  +
                requesting_cell.register_waiter_without_publish(ProtocolRequirement::H1Compatible);
        2855  +
            let action = OriginAdmission::submit_action_without_running(
        2856  +
                &admission,
        2857  +
                requesting_cell.id().partition(),
        2858  +
                demand,
        2859  +
            )
        2860  +
            .expect("peer demand did not prepare an idle probe");
        2861  +
        2862  +
            let probing = loom::thread::Builder::new()
        2863  +
                .stack_size(16 * 1024)
        2864  +
                .spawn(move || OriginAdmission::run_action_chain(Some(action)))
        2865  +
                .unwrap();
        2866  +
            let cancel_cell = requesting_cell.clone();
        2867  +
            let cancelling = loom::thread::Builder::new()
        2868  +
                .stack_size(16 * 1024)
        2869  +
                .spawn(move || OriginCell::cancel_waiter(&cancel_cell, waiter))
        2870  +
                .unwrap();
        2871  +
            probing.join().unwrap();
        2872  +
            assert!(cancelling.join().unwrap());
        2873  +
            drop(first_returning);
        2874  +
            drop(second_returning);
        2875  +
        2876  +
            assert_eq!(vec![11], first.h1_idle_sender_ids());
        2877  +
            assert_eq!(vec![22], second.h1_idle_sender_ids());
        2878  +
            assert!(first.state.lock().h1.peer_reservation_available());
        2879  +
            assert!(second.state.lock().h1.peer_reservation_available());
        2880  +
            assert_eq!(0, admission.ordered_demand_count_for_test());
        2881  +
            admission.clear_modeled_cells_for_test();
        2882  +
        });
        2883  +
    }
        2884  +
        2885  +
    /// Races cancellation with a later idle supplier winning bounded probing.
        2886  +
    ///
        2887  +
    /// Whether cancellation or candidate delivery wins, both sender identities
        2888  +
    /// return to their owning cells exactly once. Cancellation returns `true`
        2889  +
    /// when it removes either a waiting record or a ready, unclaimed selection;
        2890  +
    /// in the latter case it also owns cleanup of that selection.
        2891  +
    #[test]
        2892  +
    fn later_idle_probe_and_cancellation_preserve_each_sender() {
        2893  +
        let mut model = loom::model::Builder::new();
        2894  +
        model.preemption_bound.get_or_insert(3);
        2895  +
        model.check(|| {
        2896  +
            let admission = OriginAdmission::for_test(NonZeroUsize::new(2).unwrap());
        2897  +
            let origin = OriginKey::from_parts(Scheme::HTTPS, "example.com", None).unwrap();
        2898  +
            let first = OriginAdmission::register_cell(
        2899  +
                &admission,
        2900  +
                Arc::new(OriginCell::new(
        2901  +
                    PartitionId::from_index(1),
        2902  +
                    origin.clone(),
        2903  +
                    EligibilityGroup::Pool,
        2904  +
                    Some(admission.clone()),
        2905  +
                    None,
        2906  +
                )),
        2907  +
            );
        2908  +
            let second = OriginAdmission::register_cell(
        2909  +
                &admission,
        2910  +
                Arc::new(OriginCell::new(
        2911  +
                    PartitionId::from_index(2),
        2912  +
                    origin.clone(),
        2913  +
                    EligibilityGroup::Pool,
        2914  +
                    Some(admission.clone()),
        2915  +
                    None,
        2916  +
                )),
        2917  +
            );
        2918  +
            let requester = OriginAdmission::register_cell(
        2919  +
                &admission,
        2920  +
                Arc::new(OriginCell::new(
        2921  +
                    PartitionId::from_index(3),
        2922  +
                    origin,
        2923  +
                    EligibilityGroup::Pool,
        2924  +
                    Some(admission.clone()),
        2925  +
                    None,
        2926  +
                )),
        2927  +
            );
        2928  +
            let first_lease = OriginAdmission::lease_for_test(&admission);
        2929  +
            let (first_connection, _first_physical) =
        2930  +
                ConnectionState::bounded(connection_info(1), first_lease);
        2931  +
            let returning =
        2932  +
                OriginCell::insert_selected_h1(&first, first_connection, H1Sender::test(11));
        2933  +
            let second_lease = OriginAdmission::lease_for_test(&admission);
        2934  +
            let (second_connection, _second_physical) =
        2935  +
                ConnectionState::bounded(connection_info(2), second_lease);
        2936  +
            OriginCell::insert_idle_h1(&second, second_connection, H1Sender::test(22));
        2937  +
            let (waiter, demand) =
        2938  +
                requester.register_waiter_without_publish(ProtocolRequirement::H1Compatible);
        2939  +
            let action = OriginAdmission::submit_action_without_running(
        2940  +
                &admission,
        2941  +
                requester.id().partition(),
        2942  +
                demand,
        2943  +
            )
        2944  +
            .expect("peer demand did not prepare an idle probe");
        2945  +
        2946  +
            let probing = loom::thread::Builder::new()
        2947  +
                .stack_size(16 * 1024)
        2948  +
                .spawn(move || OriginAdmission::run_action_chain(Some(action)))
        2949  +
                .unwrap();
        2950  +
            let cancel_cell = requester.clone();
        2951  +
            let cancelling = loom::thread::Builder::new()
        2952  +
                .stack_size(16 * 1024)
        2953  +
                .spawn(move || OriginCell::cancel_waiter(&cancel_cell, waiter))
        2954  +
                .unwrap();
        2955  +
            probing.join().unwrap();
        2956  +
            assert!(cancelling.join().unwrap());
        2957  +
            drop(returning);
        2958  +
        2959  +
            let mut sender_ids = first.h1_idle_sender_ids();
        2960  +
            sender_ids.extend(second.h1_idle_sender_ids());
        2961  +
            sender_ids.extend(requester.h1_idle_sender_ids());
        2962  +
            sender_ids.sort_unstable();
        2963  +
            assert_eq!(vec![11, 22], sender_ids);
        2964  +
            assert!(first.state.lock().h1.peer_reservation_available());
        2965  +
            assert!(second.state.lock().h1.peer_reservation_available());
        2966  +
            assert_eq!(0, admission.ordered_demand_count_for_test());
        2967  +
            admission.clear_modeled_cells_for_test();
        2968  +
        });
        2969  +
    }
        2970  +
        2971  +
    /// Races cancellation with the second idle probe and exact fallback reservation.
        2972  +
    ///
        2973  +
    /// Three busy suppliers force two distinct idle-only probes followed by
        2974  +
    /// reservation of the third supplier. Cancellation begins after the second
        2975  +
    /// probe is selected, so every explored schedule crosses the full bounded
        2976  +
    /// probe path while racing its final supplier transition.
        2977  +
    #[test]
        2978  +
    fn second_idle_probe_and_cancellation_preserve_all_senders() {
        2979  +
        let mut model = loom::model::Builder::new();
        2980  +
        model.preemption_bound.get_or_insert(3);
        2981  +
        model.check(|| {
        2982  +
            let admission = OriginAdmission::for_test(NonZeroUsize::new(3).unwrap());
        2983  +
            let origin = OriginKey::from_parts(Scheme::HTTPS, "example.com", None).unwrap();
        2984  +
            let first = OriginAdmission::register_cell(
        2985  +
                &admission,
        2986  +
                Arc::new(OriginCell::new(
        2987  +
                    PartitionId::from_index(1),
        2988  +
                    origin.clone(),
        2989  +
                    EligibilityGroup::Pool,
        2990  +
                    Some(admission.clone()),
        2991  +
                    None,
        2992  +
                )),
        2993  +
            );
        2994  +
            let second = OriginAdmission::register_cell(
        2995  +
                &admission,
        2996  +
                Arc::new(OriginCell::new(
        2997  +
                    PartitionId::from_index(2),
        2998  +
                    origin.clone(),
        2999  +
                    EligibilityGroup::Pool,
        3000  +
                    Some(admission.clone()),
        3001  +
                    None,
        3002  +
                )),
        3003  +
            );
        3004  +
            let third = OriginAdmission::register_cell(
        3005  +
                &admission,
        3006  +
                Arc::new(OriginCell::new(
        3007  +
                    PartitionId::from_index(3),
        3008  +
                    origin.clone(),
        3009  +
                    EligibilityGroup::Pool,
        3010  +
                    Some(admission.clone()),
        3011  +
                    None,
        3012  +
                )),
        3013  +
            );
        3014  +
            let requester = OriginAdmission::register_cell(
        3015  +
                &admission,
        3016  +
                Arc::new(OriginCell::new(
        3017  +
                    PartitionId::from_index(4),
        3018  +
                    origin,
        3019  +
                    EligibilityGroup::Pool,
        3020  +
                    Some(admission.clone()),
        3021  +
                    None,
        3022  +
                )),
        3023  +
            );
        3024  +
        3025  +
            let first_lease = OriginAdmission::lease_for_test(&admission);
        3026  +
            let (first_connection, _first_physical) =
        3027  +
                ConnectionState::bounded(connection_info(1), first_lease);
        3028  +
            let first_returning =
        3029  +
                OriginCell::insert_selected_h1(&first, first_connection, H1Sender::test(11));
        3030  +
            let second_lease = OriginAdmission::lease_for_test(&admission);
        3031  +
            let (second_connection, _second_physical) =
        3032  +
                ConnectionState::bounded(connection_info(2), second_lease);
        3033  +
            let second_returning =
        3034  +
                OriginCell::insert_selected_h1(&second, second_connection, H1Sender::test(22));
        3035  +
            let third_lease = OriginAdmission::lease_for_test(&admission);
        3036  +
            let (third_connection, _third_physical) =
        3037  +
                ConnectionState::bounded(connection_info(3), third_lease);
        3038  +
            let third_returning =
        3039  +
                OriginCell::insert_selected_h1(&third, third_connection, H1Sender::test(33));
        3040  +
        3041  +
            let (waiter, demand) =
        3042  +
                requester.register_waiter_without_publish(ProtocolRequirement::H1Compatible);
        3043  +
            let first_probe = OriginAdmission::submit_action_without_running(
        3044  +
                &admission,
        3045  +
                requester.id().partition(),
        3046  +
                demand,
        3047  +
            )
        3048  +
            .expect("peer demand did not prepare its first idle probe");
        3049  +
            assert!(matches!(&first_probe, AdmissionAction::ProbeH1Supplier(_)));
        3050  +
        3051  +
            let probes = Arc::new(AtomicUsize::new(0));
        3052  +
            let second_probe_started = Arc::new(AtomicBool::new(false));
        3053  +
            let probing_count = probes.clone();
        3054  +
            let probing_started = second_probe_started.clone();
        3055  +
            let probing = loom::thread::Builder::new()
        3056  +
                .stack_size(16 * 1024)
        3057  +
                .spawn(move || {
        3058  +
                    probing_count.fetch_add(1, Ordering::SeqCst);
        3059  +
                    let second_probe = first_probe
        3060  +
                        .run_once_for_test()
        3061  +
                        .expect("first miss did not prepare a second idle probe");
        3062  +
                    assert!(matches!(&second_probe, AdmissionAction::ProbeH1Supplier(_)));
        3063  +
                    probing_count.fetch_add(1, Ordering::SeqCst);
        3064  +
                    probing_started.store(true, Ordering::SeqCst);
        3065  +
                    let fallback = second_probe.run_once_for_test();
        3066  +
                    OriginAdmission::run_action_chain(fallback);
        3067  +
                })
        3068  +
                .unwrap();
        3069  +
            let cancel_cell = requester.clone();
        3070  +
            let cancelling = loom::thread::Builder::new()
        3071  +
                .stack_size(16 * 1024)
        3072  +
                .spawn(move || {
        3073  +
                    while !second_probe_started.load(Ordering::SeqCst) {
        3074  +
                        loom::thread::yield_now();
        3075  +
                    }
        3076  +
                    OriginCell::cancel_waiter(&cancel_cell, waiter)
        3077  +
                })
        3078  +
                .unwrap();
        3079  +
            probing.join().unwrap();
        3080  +
            assert!(cancelling.join().unwrap());
        3081  +
            assert_eq!(2, probes.load(Ordering::SeqCst));
        3082  +
        3083  +
            drop(first_returning);
        3084  +
            drop(second_returning);
        3085  +
            drop(third_returning);
        3086  +
        3087  +
            let mut sender_ids = first.h1_idle_sender_ids();
        3088  +
            sender_ids.extend(second.h1_idle_sender_ids());
        3089  +
            sender_ids.extend(third.h1_idle_sender_ids());
        3090  +
            sender_ids.extend(requester.h1_idle_sender_ids());
        3091  +
            sender_ids.sort_unstable();
        3092  +
            assert_eq!(vec![11, 22, 33], sender_ids);
        3093  +
            assert!(first.state.lock().h1.peer_reservation_available());
        3094  +
            assert!(second.state.lock().h1.peer_reservation_available());
        3095  +
            assert!(third.state.lock().h1.peer_reservation_available());
        3096  +
            assert_eq!(0, admission.ordered_demand_count_for_test());
        3097  +
            assert_eq!(0, admission.available_capacity_for_test());
        3098  +
            admission.clear_modeled_cells_for_test();
        3099  +
        });
        3100  +
    }
        3101  +
        3102  +
    /// Races a probed supplier return with installation of the fallback reservation.
        3103  +
    ///
        3104  +
    /// The first supplier is busy when probed. Its sender returns after the
        3105  +
    /// miss while admission installs the exact reservation on the second
        3106  +
    /// supplier. The fallback must satisfy the waiter without losing,
        3107  +
    /// duplicating, or reserving the returning sender.
        3108  +
    #[test]
        3109  +
    fn supplier_return_between_probe_miss_and_fallback_preserves_both_senders() {
        3110  +
        let mut model = loom::model::Builder::new();
        3111  +
        model.preemption_bound.get_or_insert(3);
        3112  +
        model.check(|| {
        3113  +
            let admission = OriginAdmission::for_test(NonZeroUsize::new(2).unwrap());
        3114  +
            let origin = OriginKey::from_parts(Scheme::HTTPS, "example.com", None).unwrap();
        3115  +
            let first = OriginAdmission::register_cell(
        3116  +
                &admission,
        3117  +
                Arc::new(OriginCell::new(
        3118  +
                    PartitionId::from_index(1),
        3119  +
                    origin.clone(),
        3120  +
                    EligibilityGroup::Pool,
        3121  +
                    Some(admission.clone()),
        3122  +
                    None,
        3123  +
                )),
        3124  +
            );
        3125  +
            let second = OriginAdmission::register_cell(
        3126  +
                &admission,
        3127  +
                Arc::new(OriginCell::new(
        3128  +
                    PartitionId::from_index(2),
        3129  +
                    origin.clone(),
        3130  +
                    EligibilityGroup::Pool,
        3131  +
                    Some(admission.clone()),
        3132  +
                    None,
        3133  +
                )),
        3134  +
            );
        3135  +
            let requester = OriginAdmission::register_cell(
        3136  +
                &admission,
        3137  +
                Arc::new(OriginCell::new(
        3138  +
                    PartitionId::from_index(3),
        3139  +
                    origin,
        3140  +
                    EligibilityGroup::Pool,
        3141  +
                    Some(admission.clone()),
        3142  +
                    None,
        3143  +
                )),
        3144  +
            );
        3145  +
        3146  +
            let first_lease = OriginAdmission::lease_for_test(&admission);
        3147  +
            let (first_connection, _first_physical) =
        3148  +
                ConnectionState::bounded(connection_info(1), first_lease);
        3149  +
            let returning =
        3150  +
                OriginCell::insert_selected_h1(&first, first_connection, H1Sender::test(11));
        3151  +
        3152  +
            let second_lease = OriginAdmission::lease_for_test(&admission);
        3153  +
            let (second_connection, _second_physical) =
        3154  +
                ConnectionState::bounded(connection_info(2), second_lease);
        3155  +
            OriginCell::insert_idle_h1(&second, second_connection, H1Sender::test(22));
        3156  +
        3157  +
            let (waiter, demand) =
        3158  +
                requester.register_waiter_without_publish(ProtocolRequirement::H1Compatible);
        3159  +
            let probe = OriginAdmission::submit_action_without_running(
        3160  +
                &admission,
        3161  +
                requester.id().partition(),
        3162  +
                demand,
        3163  +
            )
        3164  +
            .expect("peer demand did not prepare an idle probe");
        3165  +
            let fallback = probe
        3166  +
                .run_once_for_test()
        3167  +
                .expect("busy probe did not prepare the fallback reservation");
        3168  +
        3169  +
            let reserving = loom::thread::Builder::new()
        3170  +
                .stack_size(16 * 1024)
        3171  +
                .spawn(move || OriginAdmission::run_action_chain(Some(fallback)))
        3172  +
                .unwrap();
        3173  +
            let returning = loom::thread::Builder::new()
        3174  +
                .stack_size(16 * 1024)
        3175  +
                .spawn(move || drop(returning))
        3176  +
                .unwrap();
        3177  +
            reserving.join().unwrap();
        3178  +
            returning.join().unwrap();
        3179  +
        3180  +
            let selected = requester
        3181  +
                .take_ready_h1(waiter)
        3182  +
                .expect("fallback reservation did not satisfy the waiter");
        3183  +
            assert_eq!(22, selected.test_sender_id());
        3184  +
            drop(selected);
        3185  +
        3186  +
            let mut sender_ids = first.h1_idle_sender_ids();
        3187  +
            sender_ids.extend(second.h1_idle_sender_ids());
        3188  +
            sender_ids.extend(requester.h1_idle_sender_ids());
        3189  +
            sender_ids.sort_unstable();
        3190  +
            assert_eq!(vec![11, 22], sender_ids);
        3191  +
            assert!(first.state.lock().h1.peer_reservation_available());
        3192  +
            assert!(second.state.lock().h1.peer_reservation_available());
        3193  +
            assert_eq!(0, admission.ordered_demand_count_for_test());
        3194  +
            admission.clear_modeled_cells_for_test();
        3195  +
        });
        3196  +
    }
        3197  +
        3198  +
    /// Races borrowed-sender delivery with a local sender return.
        3199  +
    ///
        3200  +
    /// Both exclusive sender identities must remain installed exactly once.
        3201  +
    /// Three preemptions cover either actor entering first and one crossing
        3202  +
    /// returning through both lock domains.
 2664   3203   
    #[test]
 2665   3204   
    fn borrowed_delivery_racing_a_local_return_preserves_both_senders() {
 2666   3205   
        let mut model = loom::model::Builder::new();
 2667         -
        model.preemption_bound = Some(2);
        3206  +
        model.preemption_bound.get_or_insert(3);
 2668   3207   
        model.check(|| {
 2669   3208   
            let (admission, connection_cell, requesting_cell) =
 2670   3209   
                bounded_peer_cells_with_limit(2, EligibilityGroup::Pool, EligibilityGroup::Pool);
 2671   3210   
            let owning_lease = OriginAdmission::lease_for_test(&admission);
 2672   3211   
            let (owned_connection, _owning_physical) =
 2673   3212   
                ConnectionState::bounded(connection_info(1), owning_lease);
 2674   3213   
            OriginCell::insert_idle_h1(&connection_cell, owned_connection, H1Sender::test(11));
 2675   3214   
            let requesting_lease = OriginAdmission::lease_for_test(&admission);
 2676   3215   
            let (requesting_connection, _requesting_physical) =
 2677   3216   
                ConnectionState::bounded(connection_info(2), requesting_lease);
 2678   3217   
            let local = OriginCell::insert_selected_h1(
 2679   3218   
                &requesting_cell,
 2680   3219   
                requesting_connection,
 2681   3220   
                H1Sender::test(22),
 2682   3221   
            );
 2683   3222   
            let (waiter, demand) =
 2684   3223   
                requesting_cell.register_waiter_without_publish(ProtocolRequirement::H1Compatible);
 2685   3224   
            let install = OriginAdmission::submit_action_without_running(
 2686   3225   
                &admission,
 2687   3226   
                requesting_cell.id().partition(),
 2688   3227   
                demand,
 2689   3228   
            )
 2690   3229   
            .expect("peer demand did not prepare an H1 match");
 2691         -
            let delivery = install
 2692         -
                .run_once_for_test()
 2693         -
                .expect("H1 supplier reservation did not prepare a delivery");
 2694         -
 2695   3230   
            let delivering = loom::thread::spawn(move || {
 2696         -
                OriginAdmission::run_action_chain(Some(delivery));
        3231  +
                OriginAdmission::run_action_chain(Some(install));
 2697   3232   
            });
 2698   3233   
            let returning = loom::thread::spawn(move || drop(local));
 2699   3234   
            delivering.join().unwrap();
 2700   3235   
            returning.join().unwrap();
 2701   3236   
 2702   3237   
            let selected = requesting_cell
 2703   3238   
                .take_ready_h1(waiter)
 2704   3239   
                .expect("delivery/return race did not satisfy the requesting cell waiter");
 2705   3240   
            drop(selected);
        3241  +
            let mut sender_ids = connection_cell.h1_idle_sender_ids();
        3242  +
            sender_ids.extend(requesting_cell.h1_idle_sender_ids());
        3243  +
            sender_ids.sort_unstable();
        3244  +
            assert_eq!(
        3245  +
                vec![11, 22],
        3246  +
                sender_ids,
        3247  +
                "delivery/return race duplicated or lost an HTTP/1 sender"
        3248  +
            );
 2706   3249   
            assert_eq!(
 2707   3250   
                2,
 2708   3251   
                connection_cell.h1_counts().0 + requesting_cell.h1_counts().0
 2709   3252   
            );
 2710   3253   
            assert_eq!(
 2711   3254   
                2,
 2712   3255   
                connection_cell.h1_counts().1 + requesting_cell.h1_counts().1
 2713   3256   
            );
 2714   3257   
            assert!(connection_cell.state.lock().h1.peer_reservation_available());
 2715   3258   
            admission.clear_modeled_cells_for_test();
 2716   3259   
        });
 2717   3260   
    }
 2718   3261   
        3262  +
    /// Races peer-sender materialization with close of the owning cell record.
        3263  +
    ///
        3264  +
    /// The request must receive the sender or establishment capacity, never
        3265  +
    /// become stranded. Three preemptions cover the delivery-close-delivery
        3266  +
    /// schedule across the two cell locks and admission lock.
 2719   3267   
    #[test]
 2720   3268   
    fn borrow_materialization_races_owning_cell_close_without_stranding_request() {
 2721   3269   
        let mut model = loom::model::Builder::new();
 2722         -
        model.preemption_bound = Some(2);
        3270  +
        model.preemption_bound.get_or_insert(3);
 2723   3271   
        model.check(|| {
 2724   3272   
            let (admission, connection_cell, requesting_cell) =
 2725   3273   
                bounded_peer_cells(EligibilityGroup::Pool, EligibilityGroup::Pool);
 2726   3274   
            let lease = OriginAdmission::lease_for_test(&admission);
 2727   3275   
            let (connection, _physical) = ConnectionState::bounded(connection_info(1), lease);
 2728   3276   
            OriginCell::insert_idle_h1(&connection_cell, connection.clone(), H1Sender::test(11));
 2729   3277   
            let close = H1CloseHandle::new(&connection_cell, &connection);
 2730   3278   
            let (waiter, demand) =
 2731   3279   
                requesting_cell.register_waiter_without_publish(ProtocolRequirement::H1Compatible);
 2732   3280   
            let install = OriginAdmission::submit_action_without_running(
 2733   3281   
                &admission,
 2734   3282   
                requesting_cell.id().partition(),
 2735   3283   
                demand,
 2736   3284   
            )
 2737   3285   
            .expect("peer demand did not prepare an H1 match");
 2738         -
            let delivery = install
 2739         -
                .run_once_for_test()
 2740         -
                .expect("H1 supplier reservation did not prepare a delivery");
 2741         -
 2742   3286   
            let delivering = loom::thread::spawn(move || {
 2743         -
                OriginAdmission::run_action_chain(Some(delivery));
        3287  +
                OriginAdmission::run_action_chain(Some(install));
 2744   3288   
            });
 2745   3289   
            let closing = loom::thread::spawn(move || close.close(CloseReason::Poisoned));
 2746   3290   
            delivering.join().unwrap();
 2747   3291   
            closing.join().unwrap();
 2748   3292   
 2749   3293   
            let event = requesting_cell
 2750   3294   
                .take_ready_event(waiter)
 2751   3295   
                .expect("borrow/close race stranded its requesting cell waiter");
 2752   3296   
            match event {
 2753   3297   
                AcquisitionStep::Resolved(AcquisitionOutcome::H1(selection)) => {
 2754   3298   
                    drop(selection);
 2755   3299   
                }
 2756   3300   
                AcquisitionStep::StartEstablishment(permit) => {
 2757   3301   
                    assert!(OriginCell::cancel_waiter(&requesting_cell, waiter));
 2758   3302   
                    drop(permit);
 2759   3303   
                }
 2760   3304   
                AcquisitionStep::Resolved(AcquisitionOutcome::Failed(error)) => {
 2761   3305   
                    panic!("unexpected establishment failure: {error}")
 2762   3306   
                }
 2763   3307   
                AcquisitionStep::Resolved(AcquisitionOutcome::RetryAcquisition) => {
 2764   3308   
                    panic!("HTTP/1 reuse model requested reacquisition")
 2765   3309   
                }
 2766   3310   
                AcquisitionStep::Resolved(AcquisitionOutcome::H2(_)) => {
 2767   3311   
                    panic!("HTTP/1 reuse model received an HTTP/2 activation")
 2768   3312   
                }
 2769   3313   
            }
 2770   3314   
            assert_eq!(0, requesting_cell.probe().retained);
 2771   3315   
            assert_eq!(1, admission.available_capacity_for_test());
 2772   3316   
            admission.clear_modeled_cells_for_test();
 2773   3317   
        });
 2774   3318   
    }
 2775   3319   
        3320  +
    /// Races HTTP/1 capacity reclaim with independent close of the same connection.
        3321  +
    ///
        3322  +
    /// Capacity must return exactly once. Three preemptions cover close and
        3323  +
    /// reclaim entering in either order and the resulting admission delivery.
 2776   3324   
    #[test]
 2777   3325   
    fn reclaim_and_connection_close_release_exactly_one_capacity_slot() {
 2778   3326   
        let mut model = loom::model::Builder::new();
 2779         -
        model.preemption_bound = Some(2);
        3327  +
        model.preemption_bound.get_or_insert(3);
 2780   3328   
        model.check(|| {
 2781   3329   
            let (admission, connection_cell, requesting_cell) = bounded_peer_cells(
 2782   3330   
                EligibilityGroup::Partition(PartitionId::from_index(1)),
 2783   3331   
                EligibilityGroup::Partition(PartitionId::from_index(2)),
 2784   3332   
            );
 2785   3333   
            let lease = OriginAdmission::lease_for_test(&admission);
 2786   3334   
            let (connection, _physical) = ConnectionState::bounded(connection_info(1), lease);
 2787   3335   
            let returning = OriginCell::insert_selected_h1(
 2788   3336   
                &connection_cell,
 2789   3337   
                connection.clone(),
 2790   3338   
                H1Sender::test(11),
 2791   3339   
            );
 2792   3340   
            let close = H1CloseHandle::new(&connection_cell, &connection);
 2793   3341   
            let waiter =
 2794   3342   
                OriginCell::register_waiter(&requesting_cell, ProtocolRequirement::H1Compatible);
 2795   3343   
            let local_waiter =
 2796   3344   
                OriginCell::register_waiter(&connection_cell, ProtocolRequirement::H1Compatible);
 2797   3345   
 2798   3346   
            // Loom's default 4 KiB coroutine stack is too small for the full
 2799   3347   
            // sender-return and connection-close paths modeled here.
 2800   3348   
            let returning = loom::thread::Builder::new()
 2801   3349   
                .name("returning H1 sender".into())
 2802   3350   
                .stack_size(16 * 1024)
 2803   3351   
                .spawn(move || drop(returning))
 2804   3352   
                .unwrap();
 2805   3353   
            let closing = loom::thread::Builder::new()
 2806   3354   
                .name("closing H1 connection".into())
 2807   3355   
                .stack_size(16 * 1024)
 2808   3356   
                .spawn(move || close.close(CloseReason::Poisoned))
 2809   3357   
                .unwrap();
 2810   3358   
            returning.join().unwrap();
 2811   3359   
            let close_won = closing.join().unwrap();
 2812   3360   
 2813   3361   
            assert_eq!(
 2814   3362   
                !close_won,
 2815   3363   
                connection_cell.state.lock().h1.local_turn_owed(),
 2816   3364   
                "only a completed reclaim may earn the connection-owning cell a local fairness turn"
 2817   3365   
            );
 2818   3366   
 2819   3367   
            let replacement = OriginCell::take_ready_lease(&requesting_cell, waiter)
 2820   3368   
                .expect("close/reclaim race did not deliver released capacity");
 2821   3369   
            assert_eq!((0, 0), connection_cell.h1_counts());
 2822   3370   
            assert_eq!(0, admission.available_capacity_for_test());
 2823   3371   
            assert!(OriginCell::cancel_waiter(&connection_cell, local_waiter));
 2824   3372   
            drop(replacement);
 2825   3373   
            assert_eq!(1, admission.available_capacity_for_test());
 2826   3374   
            admission.clear_modeled_cells_for_test();
 2827   3375   
        });
 2828   3376   
    }
 2829   3377   
        3378  +
    /// Races bounded-capacity delivery with cancellation of its target waiter.
        3379  +
    ///
        3380  +
    /// Refusal must refunnel the permit after the cell lock is released.
 2830   3381   
    #[test]
 2831   3382   
    fn delivery_and_cancellation_race_refunnels_capacity() {
 2832   3383   
        loom::model(|| {
 2833   3384   
            let (admission, cell) = bounded_cell();
 2834   3385   
            let (first, demand) =
 2835   3386   
                cell.register_waiter_without_publish(ProtocolRequirement::H1Compatible);
 2836         -
            let mut delivery =
        3387  +
            let delivery =
 2837   3388   
                OriginAdmission::submit_without_running(&admission, cell.id().partition(), demand)
 2838   3389   
                    .unwrap();
 2839         -
            assert!(
 2840         -
                delivery.resolve_payload_for_test(),
 2841         -
                "capacity delivery did not materialize"
 2842         -
            );
 2843   3390   
 2844         -
            let delivery_cell = cell.clone();
 2845   3391   
            let deliver = loom::thread::spawn(move || {
 2846         -
                drop(OriginCell::receive_delivery(&delivery_cell, delivery));
        3392  +
                OriginAdmission::run_action_chain(Some(AdmissionAction::Deliver(delivery)));
 2847   3393   
            });
 2848   3394   
            let cancel_cell = cell.clone();
 2849   3395   
            let cancel =
 2850   3396   
                loom::thread::spawn(move || OriginCell::cancel_waiter(&cancel_cell, first));
 2851   3397   
            deliver.join().unwrap();
 2852   3398   
            cancel.join().unwrap();
 2853   3399   
 2854   3400   
            assert_eq!(1, admission.available_capacity_for_test());
 2855   3401   
            admission.clear_modeled_cells_for_test();
 2856   3402   
        });
 2857   3403   
    }
 2858   3404   
        3405  +
    /// Races cell-local peer route attachment with waiter cancellation.
        3406  +
    ///
        3407  +
    /// This model isolates the requesting-cell transition. The production
        3408  +
    /// admission preparation and settlement path is exercised separately above.
 2859   3409   
    #[test]
 2860         -
    fn h2_route_installation_races_requesting_cell_cancellation() {
        3410  +
    fn h2_route_attachment_linearizes_against_requesting_cell_cancellation() {
 2861   3411   
        let mut model = loom::model::Builder::new();
 2862         -
        model.preemption_bound = Some(2);
        3412  +
        model.preemption_bound.get_or_insert(3);
 2863   3413   
        model.check(|| {
 2864   3414   
            let (admission, connection_cell, requesting_cell) =
 2865   3415   
                bounded_peer_cells(EligibilityGroup::Pool, EligibilityGroup::Pool);
 2866   3416   
            let lease = OriginAdmission::lease_for_test(&admission);
 2867   3417   
            let (connection, _physical) = ConnectionState::bounded(connection_info(1), lease);
 2868   3418   
            let generation = OriginCell::install_h2_for_test(&connection_cell, connection, 1, None);
 2869   3419   
            let (waiter, demand) =
 2870   3420   
                requesting_cell.register_waiter_without_publish(ProtocolRequirement::H2Required);
 2871   3421   
            let route = h2::H2Route::new(&connection_cell, generation);
 2872   3422   
 2873   3423   
            let install_cell = requesting_cell.clone();
 2874   3424   
            let installing = loom::thread::spawn(move || {
 2875   3425   
                OriginCell::attach_h2_route(
 2876   3426   
                    &install_cell,
 2877   3427   
                    route,
 2878   3428   
                    &EligibilityGroup::Pool,
 2879   3429   
                    demand.id_for_test(),
 2880   3430   
                )
 2881   3431   
            });
 2882   3432   
            let cancel_cell = requesting_cell.clone();
 2883   3433   
            let cancelling =
 2884   3434   
                loom::thread::spawn(move || OriginCell::cancel_waiter(&cancel_cell, waiter));
 2885   3435   
            let _installed = installing.join().unwrap();
 2886   3436   
            assert!(cancelling.join().unwrap());
 2887   3437   
 2888   3438   
            assert_eq!(0, requesting_cell.probe().retained);
 2889         -
            assert_eq!(0, admission.ordered_demand_count_for_test());
 2890   3439   
            assert_eq!(0, admission.available_capacity_for_test());
 2891   3440   
            assert!(OriginCell::close_h2(
 2892   3441   
                &connection_cell,
 2893   3442   
                generation,
 2894   3443   
                CloseReason::PoolDropped,
 2895   3444   
            ));
 2896   3445   
            assert_eq!(1, admission.available_capacity_for_test());
 2897   3446   
            admission.clear_modeled_cells_for_test();
 2898   3447   
        });
 2899   3448   
    }
 2900   3449   
        3450  +
    /// Races route publication, generation close, and peer route service.
        3451  +
    ///
        3452  +
    /// The three-preemption bound permits each actor to intervene before route
        3453  +
    /// settlement returns through admission to the requesting cell.
 2901   3454   
    #[test]
 2902   3455   
    fn h2_route_acknowledgement_races_generation_close_and_route_service() {
 2903   3456   
        let mut model = loom::model::Builder::new();
 2904         -
        model.preemption_bound = Some(3);
        3457  +
        model.preemption_bound.get_or_insert(3);
 2905   3458   
        model.check(|| {
 2906   3459   
            let (admission, connection_cell, requesting_cell) =
 2907   3460   
                bounded_peer_cells(EligibilityGroup::Pool, EligibilityGroup::Pool);
 2908   3461   
            let lease = OriginAdmission::lease_for_test(&admission);
 2909   3462   
            let (connection, _physical) = ConnectionState::bounded(connection_info(1), lease);
 2910   3463   
            let generation = OriginCell::install_h2_for_test(&connection_cell, connection, 1, None);
 2911   3464   
            let (waiter, demand) =
 2912   3465   
                requesting_cell.register_waiter_without_publish(ProtocolRequirement::H2Required);
 2913   3466   
            let action = OriginAdmission::submit_action_without_running(
 2914   3467   
                &admission,
 2915   3468   
                requesting_cell.id().partition(),
 2916   3469   
                demand,
 2917   3470   
            )
 2918   3471   
            .expect("peer demand did not prepare a route");
 2919   3472   
 2920   3473   
            let publishing = loom::thread::spawn(move || {
 2921   3474   
                OriginAdmission::run_action_chain(Some(action));
 2922   3475   
            });
 2923   3476   
            let closing_cell = connection_cell.clone();
 2924   3477   
            let closing = loom::thread::spawn(move || {
 2925   3478   
                OriginCell::close_h2(&closing_cell, generation, CloseReason::Poisoned)
 2926   3479   
            });
 2927   3480   
            let servicing_cell = requesting_cell.clone();
 2928   3481   
            let servicing = loom::thread::spawn(move || {
 2929   3482   
                OriginCell::offer_peer_h2(&servicing_cell);
 2930   3483   
            });
 2931   3484   
 2932   3485   
            publishing.join().unwrap();
 2933   3486   
            assert!(closing.join().unwrap());
 2934   3487   
            servicing.join().unwrap();
 2935   3488   
 2936   3489   
            assert!(
 2937   3490   
                OriginCell::cancel_waiter(&requesting_cell, waiter),
 2938   3491   
                "route acknowledgement lost the live demand"
 2939   3492   
            );
 2940   3493   
            assert_eq!(0, requesting_cell.probe().retained);
 2941   3494   
            assert_eq!(0, admission.ordered_demand_count_for_test());
 2942   3495   
            assert_eq!(1, admission.available_capacity_for_test());
 2943   3496   
            admission.clear_modeled_cells_for_test();
 2944   3497   
        });
 2945   3498   
    }
 2946   3499   
        3500  +
    /// Races peer route publication with close of the routed generation.
        3501  +
    ///
        3502  +
    /// Three preemptions cover either actor entering first and the stale route
        3503  +
    /// acknowledgement returning to admission.
 2947   3504   
    #[test]
 2948   3505   
    fn h2_route_close_race_preserves_capacity_ownership() {
 2949   3506   
        let mut model = loom::model::Builder::new();
 2950         -
        model.preemption_bound = Some(2);
        3507  +
        model.preemption_bound.get_or_insert(3);
 2951   3508   
        model.check(|| {
 2952   3509   
            let (admission, connection_cell, requesting_cell) =
 2953   3510   
                bounded_peer_cells(EligibilityGroup::Pool, EligibilityGroup::Pool);
 2954   3511   
            let lease = OriginAdmission::lease_for_test(&admission);
 2955   3512   
            let (connection, _physical) = ConnectionState::bounded(connection_info(1), lease);
 2956   3513   
            let generation = OriginCell::install_h2_for_test(&connection_cell, connection, 1, None);
 2957   3514   
            let (waiter, demand) =
 2958   3515   
                requesting_cell.register_waiter_without_publish(ProtocolRequirement::H2Required);
 2959   3516   
            let action = OriginAdmission::submit_action_without_running(
 2960   3517   
                &admission,