6 6 | //! State local to one partition and origin.
|
7 7 | //!
|
8 8 | //! An [`OriginCell`] owns its acquisition order and protocol records. A
|
9 9 | //! bounded cell publishes aggregate demand to [`OriginAdmission`] after
|
10 10 | //! releasing its lock, and admission returns capacity through the same
|
11 11 | //! unlocked boundary.
|
12 12 | //!
|
13 13 | //! [`AcquisitionQueue`] owns every live acquisition attempt, including the
|
14 14 | //! bounded FIFO, delivery crossings, establishment launch, cancellation, and
|
15 15 | //! terminal results. Its module documents the complete transition model.
|
16 - | //! [`CellState`] couples that model to HTTP/1 sender residence so a local
|
17 - | //! return either satisfies one acquisition or becomes idle, never both.
|
16 + | //! [`CellState`] couples that model to HTTP/1 sender residence, HTTP/2
|
17 + | //! flight and generation residence, generation gates, and the cross-cell reuse
|
18 + | //! reservation. A local HTTP/1 return either satisfies one acquisition or
|
19 + | //! becomes idle, never both. An HTTP/2 activation becomes visible only with a
|
20 + | //! prospective generation lease and one recorded gate opportunity.
|
18 21 | //!
|
19 22 | //! Permits, senders, admission updates, and task wakes are detached from
|
20 23 | //! mutable state before their fallback or callback runs. This keeps drop and
|
21 24 | //! wake paths outside the cell lock.
|
22 25 |
|
23 26 | pub(super) mod h1;
|
27 + | pub(super) mod h2;
|
24 28 | mod waiters;
|
25 29 |
|
26 30 | #[cfg(test)]
|
27 31 | use self::h1::H1CloseHandle;
|
28 32 | #[cfg(all(test, not(smithy_http_client_loom)))]
|
29 33 | use self::h1::H1DriverGuard;
|
30 - | use self::h1::{H1Records, H1ReuseReservation, H1Selection, H1Sender, OwnedH1, ProvisionalH1};
|
34 + | #[cfg(test)]
|
35 + | use self::h1::H1Sender;
|
36 + | use self::h1::{H1Records, H1ReuseReservation, H1Selection, OwnedH1};
|
31 37 | #[cfg(test)]
|
32 38 | use self::waiters::CellSnapshot;
|
33 39 | pub(in crate::client::pool) use self::waiters::WaiterId;
|
34 40 | use self::waiters::{AcquisitionQueue, DeliveryReservation, ResultInstallError};
|
35 - | use super::admission::reuse::{
|
36 - | H1Availability, H1AvailabilitySnapshot, PreparedReuseInstall, ReuseCandidate, ReuseId,
|
37 - | ReuseInstallResult,
|
38 - | };
|
39 - | #[cfg(test)]
|
40 - | use super::admission::DemandSnapshot;
|
41 + | use super::admission::reuse::{H1Availability, H1AvailabilitySnapshot, ReuseId};
|
41 42 | use super::admission::{
|
42 - | AdmissionAction, CapacityLease, DeliveryGuard, OriginAdmission, ProtocolRequirement,
|
43 + | AdmissionAction, CapacityLease, DeliveryGuard, DemandSnapshot, H2AdvertisementSnapshot,
|
44 + | OriginAdmission, ProtocolRequirement,
|
43 45 | };
|
46 + | use super::connection::CloseReason;
|
44 47 | #[cfg(test)]
|
45 48 | use super::connection::ConnectionInfo;
|
46 - | use super::connection::{CloseReason, ConnectionState};
|
49 + | #[cfg(test)]
|
50 + | use super::connection::ConnectionState;
|
47 51 | use super::maintenance::PartitionMaintenance;
|
48 52 | use super::origin::OriginKey;
|
49 53 | use super::partition::{EligibilityGroup, PartitionId};
|
50 54 | use crate::sync::{Arc, Mutex};
|
55 + | #[cfg(test)]
|
51 56 | use aws_smithy_runtime_api::client::connection::ConnectionId;
|
52 57 | use aws_smithy_runtime_api::client::result::ConnectorError;
|
53 58 | use std::task::{Context, Poll};
|
54 59 | use std::time::SystemTime;
|
55 60 |
|
56 61 | /// Stable identity of an [`OriginCell`].
|
57 62 | #[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
58 63 | pub(crate) struct CellId {
|
59 64 | /// Partition that owns this cell's connection work.
|
60 65 | partition: PartitionId,
|
61 66 | /// Canonical origin whose requests share this cell.
|
62 67 | origin: OriginKey,
|
63 68 | }
|
64 69 |
|
65 70 | impl CellId {
|
66 71 | pub(super) fn new(partition: PartitionId, origin: OriginKey) -> Self {
|
67 72 | Self { partition, origin }
|
68 73 | }
|
69 74 |
|
70 75 | pub(crate) fn partition(&self) -> PartitionId {
|
71 76 | self.partition
|
72 77 | }
|
73 78 |
|
74 79 | pub(crate) fn origin(&self) -> &OriginKey {
|
75 80 | &self.origin
|
76 81 | }
|
77 82 | }
|
78 83 |
|
79 84 | /// Stable state shared by requests for one partition and canonical origin.
|
80 85 | pub(crate) struct OriginCell {
|
81 86 | /// Complete identity used by partition and admission indexes.
|
82 87 | id: CellId,
|
83 88 | /// Partitions whose connections may satisfy this cell's demand.
|
84 89 | eligibility_group: EligibilityGroup,
|
85 90 | /// Origin-wide authority present only when capacity is bounded.
|
86 91 | admission: Option<Arc<OriginAdmission>>,
|
87 92 | /// Partition-owned idle deadline scheduler.
|
88 93 | maintenance: Option<Arc<PartitionMaintenance>>,
|
89 94 | /// Waiter order and protocol records protected by one cell-local lock.
|
90 95 | state: Mutex<CellState>,
|
91 96 | }
|
92 97 |
|
93 98 | /// Mutable state protected by one partition-origin cell lock.
|
94 99 | ///
|
95 - | /// Waiter outcomes and HTTP/1 residence share this lock so a returning sender
|
96 - | /// either becomes visible to one waiter or enters idle storage, never both.
|
100 + | /// Waiter outcomes and protocol residence share this lock. HTTP/1 returns,
|
101 + | /// HTTP/2 activation gates, flight participants, and cancellation therefore
|
102 + | /// commit against one acquisition order.
|
97 103 | #[derive(Debug, Default)]
|
98 104 | struct CellState {
|
99 105 | /// Cell-local acquisition order and delivered results.
|
100 106 | waiters: AcquisitionQueue,
|
101 107 | /// Cell-owned HTTP/1 records and reusable sender order.
|
102 108 | h1: H1Records,
|
109 + | /// Cell-owned HTTP/2 flight and installed generations.
|
110 + | h2: h2::H2Records,
|
103 111 | /// One cross-cell reuse reservation and its local fairness debt.
|
104 112 | reuse: H1ReuseReservation,
|
105 113 | /// Last HTTP/1 availability published to bounded-origin admission.
|
106 114 | published_h1_availability: Option<H1Availability>,
|
107 115 | /// Revision used to reject availability reports that cross out of order.
|
108 116 | h1_availability_revision: u64,
|
117 + | /// Last complete H2 advertisement reported to origin admission.
|
118 + | published_h2_advertisement: Option<(h2::H2GenerationId, bool)>,
|
119 + | /// Revision used to reject H2 advertisements that cross out of order.
|
120 + | h2_advertisement_revision: u64,
|
109 121 | }
|
110 122 |
|
111 123 | impl CellState {
|
112 124 | /// Checks the coupled waiter, sender, and reuse-reservation state machines.
|
113 125 | fn assert_consistent(&self) {
|
126 + | #[cfg(any(debug_assertions, test))]
|
127 + | if std::thread::panicking() {
|
128 + | return;
|
129 + | }
|
114 130 | self.waiters.assert_consistent();
|
115 131 | self.h1.assert_consistent();
|
132 + | self.h2.assert_consistent();
|
133 + | #[cfg(any(debug_assertions, test))]
|
134 + | self.h2.assert_pending_waiters(&self.waiters);
|
116 135 | self.reuse
|
117 136 | .assert_consistent(self.h1.supports_installed_reuse());
|
118 137 | }
|
119 138 |
|
120 139 | /// Reports the HTTP/1 availability admission needs without exposing cell internals.
|
121 140 | fn h1_availability(&self) -> H1Availability {
|
122 141 | let local_h1_demand = self.waiters.can_accept_h1();
|
123 142 | H1Availability {
|
124 143 | advertised: self.h1.has_returnable(),
|
125 144 | blocked: !self.reuse.is_available()
|
126 145 | || self.reuse.blocks_peer_reuse(local_h1_demand)
|
127 146 | || self.waiters.has_prior_h1_candidate(),
|
128 147 | }
|
129 148 | }
|
130 149 |
|
131 150 | /// Returns an availability update only when admission's view must change.
|
132 151 | fn take_h1_availability_update(&mut self) -> Option<H1AvailabilitySnapshot> {
|
133 152 | let current = self.h1_availability();
|
134 153 | if self.published_h1_availability == Some(current) {
|
135 154 | return None;
|
136 155 | }
|
137 156 | Some(self.record_h1_availability(current))
|
138 157 | }
|
139 158 |
|
140 159 | /// Returns the current versioned availability for a reuse transition.
|
141 160 | fn report_h1_availability(&mut self) -> H1AvailabilitySnapshot {
|
142 161 | let current = self.h1_availability();
|
143 162 | if self.published_h1_availability == Some(current) {
|
144 163 | return H1AvailabilitySnapshot::new(self.h1_availability_revision, current);
|
145 164 | }
|
146 165 | self.record_h1_availability(current)
|
147 166 | }
|
148 167 |
|
149 168 | /// Advances the revision and records changed HTTP/1 availability.
|
150 169 | fn record_h1_availability(&mut self, availability: H1Availability) -> H1AvailabilitySnapshot {
|
151 170 | self.h1_availability_revision = self
|
152 171 | .h1_availability_revision
|
153 172 | .checked_add(1)
|
154 173 | .expect("HTTP/1 availability revision exhausted");
|
155 174 | self.published_h1_availability = Some(availability);
|
156 175 | H1AvailabilitySnapshot::new(self.h1_availability_revision, availability)
|
157 176 | }
|
158 177 |
|
178 + | /// Returns an H2 advertisement only when admission's view must change.
|
179 + | fn take_h2_advertisement_update(&mut self) -> Option<H2AdvertisementSnapshot> {
|
180 + | let current = self.h2_advertisement();
|
181 + | if self.published_h2_advertisement == current {
|
182 + | return None;
|
183 + | }
|
184 + | Some(self.record_h2_advertisement(current))
|
185 + | }
|
186 + |
|
187 + | /// Returns the current versioned H2 advertisement for a reclaim transition.
|
188 + | fn report_h2_advertisement(&mut self) -> H2AdvertisementSnapshot {
|
189 + | let current = self.h2_advertisement();
|
190 + | if self.published_h2_advertisement == current {
|
191 + | return Self::h2_advertisement_snapshot(self.h2_advertisement_revision, current);
|
192 + | }
|
193 + | self.record_h2_advertisement(current)
|
194 + | }
|
195 + |
|
196 + | /// Returns the exact publishable generation and its idle state.
|
197 + | fn h2_advertisement(&self) -> Option<(h2::H2GenerationId, bool)> {
|
198 + | self.h2
|
199 + | .publishable_generation()
|
200 + | .map(|generation| (generation, self.h2.is_idle(generation)))
|
201 + | }
|
202 + |
|
203 + | /// Advances the revision and records changed H2 availability.
|
204 + | fn record_h2_advertisement(
|
205 + | &mut self,
|
206 + | advertisement: Option<(h2::H2GenerationId, bool)>,
|
207 + | ) -> H2AdvertisementSnapshot {
|
208 + | self.h2_advertisement_revision = self
|
209 + | .h2_advertisement_revision
|
210 + | .checked_add(1)
|
211 + | .expect("HTTP/2 advertisement revision exhausted");
|
212 + | self.published_h2_advertisement = advertisement;
|
213 + | Self::h2_advertisement_snapshot(self.h2_advertisement_revision, advertisement)
|
214 + | }
|
215 + |
|
216 + | fn h2_advertisement_snapshot(
|
217 + | revision: u64,
|
218 + | advertisement: Option<(h2::H2GenerationId, bool)>,
|
219 + | ) -> H2AdvertisementSnapshot {
|
220 + | match advertisement {
|
221 + | Some((generation, true)) => H2AdvertisementSnapshot::idle(revision, generation),
|
222 + | Some((generation, false)) => H2AdvertisementSnapshot::accepting(revision, generation),
|
223 + | None => H2AdvertisementSnapshot::unavailable(revision),
|
224 + | }
|
225 + | }
|
226 + |
|
159 227 | /// Atomically decides whether a peer may reserve this cell's connection.
|
160 228 | fn install_reuse(&mut self, reuse_id: ReuseId) -> ReuseInstall {
|
161 229 | let local_h1_demand = self.waiters.can_accept_h1();
|
162 230 | if self.reuse.blocks_peer_reuse(local_h1_demand)
|
163 231 | || self.waiters.has_prior_h1_candidate()
|
164 232 | || !self.reuse.is_available()
|
165 233 | {
|
166 234 | let report = self.report_h1_availability();
|
167 235 | self.assert_consistent();
|
168 236 | return ReuseInstall::Rejected(report);
|
216 284 | fn finish_reuse(&mut self, reuse_id: ReuseId, transferred: bool) -> H1AvailabilitySnapshot {
|
217 285 | if transferred {
|
218 286 | let local_h1_demand = self.waiters.can_accept_h1();
|
219 287 | self.reuse.complete_transfer(reuse_id, local_h1_demand);
|
220 288 | } else {
|
221 289 | self.reuse.reject(reuse_id);
|
222 290 | }
|
223 291 | self.assert_consistent();
|
224 292 | self.report_h1_availability()
|
225 293 | }
|
294 + |
|
295 + | /// Removes active HTTP/2-compatible demand already served by visible H2 state.
|
296 + | fn publishable_demand_updates(
|
297 + | &self,
|
298 + | updates: [Option<DemandSnapshot>; 2],
|
299 + | ) -> [Option<DemandSnapshot>; 2] {
|
300 + | let h2_visible = self.h2.has_visible_h2();
|
301 + | updates.map(|snapshot| {
|
302 + | snapshot
|
303 + | .filter(|snapshot| !h2_visible || !snapshot.is_active() || !snapshot.accepts_h2())
|
304 + | })
|
305 + | }
|
226 306 | }
|
227 307 |
|
228 308 | /// Cell-local result of attempting to install one peer reuse reservation.
|
229 309 | enum ReuseInstall {
|
230 310 | /// The reservation will intercept a future sender return.
|
231 311 | Installed,
|
232 312 | /// An idle sender was detached for immediate resolution.
|
233 313 | Candidate(OwnedH1),
|
234 314 | /// The cell could not reserve a connection for the operation.
|
235 315 | Rejected(H1AvailabilitySnapshot),
|
236 316 | }
|
237 317 |
|
238 318 | /// A terminal result that may satisfy an acquisition waiter.
|
239 319 | #[derive(Debug)]
|
240 320 | pub(super) enum AcquisitionResult {
|
241 321 | /// An exclusive HTTP/1 sender selected from an installed cell-owned record.
|
242 322 | H1(H1Selection),
|
323 + | /// A prospective request lease against one HTTP/2 generation.
|
324 + | H2(h2::H2Activation),
|
243 325 | /// Establishment failed before producing a dispatchable connection.
|
244 326 | Failed(ConnectorError),
|
327 + | /// An HTTP/2 generation closed before serving a transferred attempt.
|
328 + | Reacquire,
|
245 329 | }
|
246 330 |
|
247 331 | /// One event observed while driving an acquisition attempt.
|
248 332 | #[derive(Debug)]
|
249 333 | pub(super) enum AcquisitionEvent {
|
250 334 | /// Capacity is available and one establishment attempt may start.
|
251 335 | Establish(EstablishmentPermit),
|
252 336 | /// A returned sender or establishment produced the terminal result.
|
253 337 | Complete(AcquisitionResult),
|
254 338 | }
|
255 339 |
|
256 340 | /// Optional bounded-origin capacity for one establishment attempt.
|
257 341 | ///
|
258 342 | /// Unbounded origins carry no lease. For bounded origins, dropping this value
|
259 343 | /// before connection installation returns the permit to admission.
|
260 344 | #[derive(Debug)]
|
261 345 | pub(super) struct EstablishmentPermit {
|
262 346 | /// Bounded-origin slot transferred into a connection on success.
|
263 347 | lease: Option<CapacityLease>,
|
264 348 | }
|
265 349 |
|
266 350 | impl EstablishmentPermit {
|
267 351 | /// Creates a permit for an origin without a connection bound.
|
268 352 | fn unbounded() -> Self {
|
269 353 | Self { lease: None }
|
270 354 | }
|
271 355 |
|
272 356 | /// Takes ownership of one bounded-origin capacity lease.
|
273 357 | pub(in crate::client::pool) fn bounded(lease: CapacityLease) -> Self {
|
274 358 | Self { lease: Some(lease) }
|
275 359 | }
|
276 360 |
|
277 361 | /// Transfers bounded capacity into an installed connection, when present.
|
278 362 | pub(super) fn into_lease(self) -> Option<CapacityLease> {
|
279 363 | self.lease
|
280 364 | }
|
281 365 | }
|
282 366 |
|
283 367 | impl OriginCell {
|
284 368 | /// Creates a cell before its one-time publication in a partition registry.
|
285 369 | pub(super) fn new(
|
286 370 | partition: PartitionId,
|
287 371 | origin: OriginKey,
|
288 372 | eligibility_group: EligibilityGroup,
|
289 373 | admission: Option<Arc<OriginAdmission>>,
|
290 374 | maintenance: Option<Arc<PartitionMaintenance>>,
|
291 375 | ) -> Self {
|
292 376 | Self {
|
293 377 | id: CellId::new(partition, origin),
|
294 378 | eligibility_group,
|
295 379 | admission,
|
296 380 | maintenance,
|
297 381 | state: Mutex::new(CellState::default()),
|
298 382 | }
|
299 383 | }
|
300 384 |
|
301 385 | /// Returns the next idle deadline from immutable pool policy.
|
302 - | fn idle_deadline(&self) -> Option<SystemTime> {
|
386 + | pub(in crate::client::pool) fn idle_deadline(&self) -> Option<SystemTime> {
|
303 387 | self.maintenance
|
304 388 | .as_ref()
|
305 389 | .and_then(|maintenance| maintenance.idle_deadline())
|
306 390 | }
|
307 391 |
|
308 392 | /// Publishes a newly idle deadline to partition maintenance.
|
309 393 | fn notify_maintenance(&self, deadline: Option<SystemTime>) {
|
310 394 | if let Some(maintenance) = &self.maintenance {
|
311 395 | maintenance.notify_deadline(deadline);
|
312 396 | }
|
313 397 | }
|
314 398 |
|
315 399 | pub(crate) fn id(&self) -> &CellId {
|
316 400 | &self.id
|
317 401 | }
|
318 402 |
|
319 403 | #[cfg(test)]
|
320 404 | pub(crate) fn eligibility_group(&self) -> &EligibilityGroup {
|
321 405 | &self.eligibility_group
|
322 406 | }
|
323 407 |
|
324 408 | #[cfg(test)]
|
325 409 | pub(crate) fn admission(&self) -> Option<&Arc<OriginAdmission>> {
|
326 410 | self.admission.as_ref()
|
327 411 | }
|
328 412 |
|
329 - | /// Installs a fresh H1 record selected by its launching request.
|
330 - | ///
|
331 - | /// # Panics
|
332 - | ///
|
333 - | /// Panics if this cell already contains the connection identity.
|
334 - | pub(super) fn install_selected_h1(
|
335 - | cell: &Arc<Self>,
|
336 - | connection: Arc<ConnectionState>,
|
337 - | sender: H1Sender,
|
338 - | ) -> H1Selection {
|
339 - | let (installed, availability) = {
|
340 - | let mut state = cell.state.lock();
|
341 - | let installed = state.h1.install_selected(connection, sender);
|
342 - | state.assert_consistent();
|
343 - | (installed, state.take_h1_availability_update())
|
344 - | };
|
345 - | cell.publish_h1_availability(availability);
|
346 - | match installed {
|
347 - | Ok(owner) => H1Selection::new(cell, owner),
|
348 - | Err(owner) => {
|
349 - | owner
|
350 - | .connection()
|
351 - | .logical_close(CloseReason::ProtocolClosed);
|
352 - | drop(owner);
|
353 - | panic!("duplicate HTTP/1 connection identity installed in one cell");
|
354 - | }
|
355 - | }
|
356 - | }
|
357 - |
|
358 - | /// Installs an H1 record whose launching acquisition already completed.
|
359 - | ///
|
360 - | /// # Panics
|
361 - | ///
|
362 - | /// Panics if this cell already contains the connection identity.
|
363 - | #[cfg(test)]
|
364 - | pub(super) fn install_idle_h1(
|
365 - | cell: &Arc<Self>,
|
366 - | connection: Arc<ConnectionState>,
|
367 - | sender: H1Sender,
|
368 - | ) {
|
369 - | let deadline = cell.idle_deadline();
|
370 - | let (installed, availability) = {
|
371 - | let mut state = cell.state.lock();
|
372 - | let installed = state.h1.install_idle(connection, sender, deadline);
|
373 - | state.assert_consistent();
|
374 - | (installed, state.take_h1_availability_update())
|
375 - | };
|
376 - | cell.publish_h1_availability(availability);
|
377 - | if let Err(owner) = installed {
|
378 - | owner
|
379 - | .connection()
|
380 - | .logical_close(CloseReason::ProtocolClosed);
|
381 - | drop(owner);
|
382 - | panic!("duplicate HTTP/1 connection identity installed in one cell");
|
383 - | }
|
384 - | cell.notify_maintenance(deadline);
|
385 - | }
|
386 - |
|
387 - | /// Selects the newest reusable H1 sender without origin-wide coordination.
|
388 - | pub(super) fn select_h1(cell: &Arc<Self>) -> Option<H1Selection> {
|
389 - | let (owner, availability) = {
|
390 - | let mut state = cell.state.lock();
|
391 - | let owner = state.h1.select_idle();
|
392 - | if owner.is_some() {
|
393 - | state.reuse.consume_local_turn();
|
394 - | }
|
395 - | state.assert_consistent();
|
396 - | let availability = cell
|
397 - | .admission
|
398 - | .as_ref()
|
399 - | .and_then(|_| state.take_h1_availability_update());
|
400 - | (owner, availability)
|
401 - | };
|
402 - | cell.publish_h1_availability(availability);
|
403 - | let owner = owner?;
|
404 - | Some(H1Selection::new(cell, owner))
|
405 - | }
|
406 - |
|
407 - | /// Reserves this cell's selected connection for one peer reuse operation.
|
408 - | pub(in crate::client::pool) fn install_h1_reuse(
|
409 - | cell: &Arc<Self>,
|
410 - | origin: Arc<OriginAdmission>,
|
411 - | prepared: PreparedReuseInstall,
|
412 - | ) -> Option<AdmissionAction> {
|
413 - | let decision = {
|
414 - | let mut state = cell.state.lock();
|
415 - | state.install_reuse(prepared.id)
|
416 - | };
|
417 - |
|
418 - | let result = match decision {
|
419 - | ReuseInstall::Installed => ReuseInstallResult::Installed,
|
420 - | ReuseInstall::Candidate(owner) => {
|
421 - | let provisional = ProvisionalH1::new(cell, owner);
|
422 - | ReuseInstallResult::Candidate(ReuseCandidate::new(
|
423 - | origin.clone(),
|
424 - | prepared.id,
|
425 - | cell.id.partition(),
|
426 - | provisional,
|
427 - | ))
|
428 - | }
|
429 - | ReuseInstall::Rejected(availability) => ReuseInstallResult::Rejected(availability),
|
430 - | };
|
431 - | OriginAdmission::finish_h1_reuse_install(&origin, prepared.id, cell.id.partition(), result)
|
432 - | }
|
433 - |
|
434 - | /// Clears an installed or resolving reservation after request cancellation.
|
435 - | pub(in crate::client::pool) fn cancel_h1_reuse(
|
436 - | &self,
|
437 - | reuse_id: ReuseId,
|
438 - | ) -> H1AvailabilitySnapshot {
|
439 - | self.state.lock().cancel_reuse(reuse_id)
|
440 - | }
|
441 - |
|
442 - | /// Returns a rejected provisional sender through ordinary connection-owning cell handling.
|
443 - | pub(in crate::client::pool) fn reject_h1_reuse_candidate(
|
444 - | cell: &Arc<Self>,
|
445 - | reuse_id: ReuseId,
|
446 - | provisional: ProvisionalH1,
|
447 - | ) -> H1AvailabilitySnapshot {
|
448 - | {
|
449 - | let mut state = cell.state.lock();
|
450 - | state.reject_reuse_candidate(reuse_id);
|
451 - | }
|
452 - | drop(provisional);
|
453 - | let mut state = cell.state.lock();
|
454 - | state.assert_consistent();
|
455 - | state.report_h1_availability()
|
456 - | }
|
457 - |
|
458 - | /// Revalidates a reuse operation and commits its provisional sender for dispatch.
|
459 - | pub(in crate::client::pool) fn commit_h1_reuse(
|
460 - | cell: &Arc<Self>,
|
461 - | reuse_id: ReuseId,
|
462 - | provisional: ProvisionalH1,
|
463 - | ) -> Result<H1Selection, ProvisionalH1> {
|
464 - | let (connection_cell, owner) = provisional.into_parts();
|
465 - | let committed = cell.state.lock().commit_reuse(reuse_id, &owner);
|
466 - | if committed {
|
467 - | Ok(H1Selection::new(cell, owner))
|
468 - | } else {
|
469 - | Err(ProvisionalH1::from_parts(connection_cell, owner))
|
470 - | }
|
471 - | }
|
472 - |
|
473 - | /// Closes a selected sender and records fairness only if close wins.
|
474 - | pub(in crate::client::pool) fn reclaim_h1_reuse(
|
475 - | cell: &Arc<Self>,
|
476 - | reuse_id: ReuseId,
|
477 - | provisional: ProvisionalH1,
|
478 - | ) -> Result<(H1AvailabilitySnapshot, bool), ProvisionalH1> {
|
479 - | let (connection_cell, owner) = provisional.into_parts();
|
480 - | if !cell.state.lock().reuse.names(reuse_id) {
|
481 - | return Err(ProvisionalH1::from_parts(connection_cell, owner));
|
482 - | }
|
483 - |
|
484 - | let close_won = Self::retire_h1_owner(cell, owner, CloseReason::Reclaimed);
|
485 - | let availability = {
|
486 - | let mut state = cell.state.lock();
|
487 - | let availability = state.finish_reuse(reuse_id, close_won);
|
488 - | state.assert_consistent();
|
489 - | availability
|
490 - | };
|
491 - | Ok((availability, close_won))
|
492 - | }
|
493 - |
|
494 - | /// Completes local reuse state after a requesting cell accepts the sender.
|
495 - | pub(in crate::client::pool) fn complete_h1_reuse(
|
496 - | &self,
|
497 - | reuse_id: ReuseId,
|
498 - | transferred: bool,
|
499 - | ) -> H1AvailabilitySnapshot {
|
500 - | self.state.lock().finish_reuse(reuse_id, transferred)
|
501 - | }
|
502 - |
|
503 - | /// Publishes this cell's HTTP/1 availability when the origin is bounded.
|
504 - | fn publish_h1_availability(&self, availability: Option<H1AvailabilitySnapshot>) {
|
505 - | if let (Some(admission), Some(availability)) = (&self.admission, availability) {
|
506 - | OriginAdmission::update_h1_availability(
|
507 - | admission,
|
508 - | self.id.partition(),
|
509 - | self.eligibility_group.clone(),
|
510 - | availability,
|
511 - | );
|
512 - | }
|
513 - | }
|
514 - |
|
515 - | /// Returns a reusable sender to the oldest compatible waiter or idle set.
|
516 - | ///
|
517 - | /// Demand publication, task wakeup, and any rejected-result fallback all
|
518 - | /// run after the cell lock is released. `owner` remains outside the locked
|
519 - | /// scope so sender or connection drop cannot run while the cell guard is
|
520 - | /// live.
|
521 - | fn return_h1_owner(cell: &Arc<Self>, owner: OwnedH1) {
|
522 - | let connection_id = owner.id();
|
523 - | let mut owner = Some(owner);
|
524 - | let mut installation = None;
|
525 - | let mut intercepted = None;
|
526 - | let mut rejected_reuse = None;
|
527 - | let idle_deadline = cell.idle_deadline();
|
528 - | let should_retire = {
|
529 - | let mut state = cell.state.lock();
|
530 - | let returnable = state
|
531 - | .h1
|
532 - | .accepts_return(owner.as_ref().expect("HTTP/1 owner disappeared"));
|
533 - | if !returnable {
|
534 - | if let Some(reuse_id) = state.reuse.intercept_return() {
|
535 - | let snapshot = state.finish_reuse(reuse_id, false);
|
536 - | rejected_reuse = Some((reuse_id, snapshot));
|
537 - | } else {
|
538 - | state.assert_consistent();
|
539 - | }
|
540 - | true
|
541 - | } else if let Some(reuse_id) = state.reuse.intercept_return() {
|
542 - | if state
|
543 - | .h1
|
544 - | .reserve_for_reuse(owner.as_ref().expect("HTTP/1 owner disappeared"))
|
545 - | {
|
546 - | state.assert_consistent();
|
547 - | intercepted = Some((
|
548 - | reuse_id,
|
549 - | ProvisionalH1::new(cell, owner.take().expect("HTTP/1 owner disappeared")),
|
550 - | ));
|
551 - | false
|
552 - | } else {
|
553 - | let snapshot = state.finish_reuse(reuse_id, false);
|
554 - | rejected_reuse = Some((reuse_id, snapshot));
|
555 - | true
|
556 - | }
|
557 - | } else if state.waiters.can_accept_h1()
|
558 - | && state
|
559 - | .h1
|
560 - | .commit_return_to_waiter(owner.as_ref().expect("HTTP/1 owner disappeared"))
|
561 - | {
|
562 - | state.reuse.consume_local_turn();
|
563 - | state.assert_consistent();
|
564 - | let mut returned = owner.take().expect("HTTP/1 owner disappeared");
|
565 - | returned.mark_reused();
|
566 - | installation = Some(state.waiters.install_returned_h1(
|
567 - | || AcquisitionResult::H1(H1Selection::new(cell, returned)),
|
568 - | &cell.eligibility_group,
|
569 - | ));
|
570 - | false
|
571 - | } else {
|
572 - | let returned = state.h1.return_idle(
|
573 - | owner.take().expect("HTTP/1 owner disappeared"),
|
574 - | idle_deadline,
|
575 - | );
|
576 - | match returned {
|
577 - | Ok(()) => {
|
578 - | state.assert_consistent();
|
579 - | false
|
580 - | }
|
581 - | Err(returned) => {
|
582 - | owner = Some(returned);
|
583 - | true
|
584 - | }
|
585 - | }
|
586 - | }
|
587 - | };
|
588 - |
|
589 - | if should_retire {
|
590 - | if tracing::level_enabled!(tracing::Level::TRACE) {
|
591 - | trace_h1_return(cell, connection_id, H1ReturnTrace::Rejected);
|
592 - | }
|
593 - | if let Some((reuse_id, snapshot)) = rejected_reuse {
|
594 - | let admission = cell
|
595 - | .admission
|
596 - | .as_ref()
|
597 - | .expect("an HTTP/1 reuse operation requires bounded admission");
|
598 - | OriginAdmission::reject_returned_h1_reuse(
|
599 - | admission,
|
600 - | reuse_id,
|
601 - | cell.id.partition(),
|
602 - | snapshot,
|
603 - | );
|
604 - | }
|
605 - | Self::retire_h1_owner(
|
606 - | cell,
|
607 - | owner.take().expect("retired HTTP/1 owner disappeared"),
|
608 - | CloseReason::ProtocolClosed,
|
609 - | );
|
610 - | return;
|
611 - | }
|
612 - |
|
613 - | if let Some((reuse_id, provisional)) = intercepted {
|
614 - | if tracing::level_enabled!(tracing::Level::TRACE) {
|
615 - | trace_h1_return(cell, connection_id, H1ReturnTrace::ReuseIntercepted);
|
616 - | }
|
617 - | let admission = cell
|
618 - | .admission
|
619 - | .as_ref()
|
620 - | .expect("an HTTP/1 reuse operation requires bounded admission");
|
621 - | let candidate = ReuseCandidate::new(
|
622 - | admission.clone(),
|
623 - | reuse_id,
|
624 - | cell.id.partition(),
|
625 - | provisional,
|
626 - | );
|
627 - | let action = OriginAdmission::resolve_h1_reuse(admission, reuse_id, candidate);
|
628 - | OriginAdmission::drive(action);
|
629 - | return;
|
630 - | }
|
631 - |
|
632 - | let Some(installation) = installation else {
|
633 - | if tracing::level_enabled!(tracing::Level::TRACE) {
|
634 - | trace_h1_return(cell, connection_id, H1ReturnTrace::Idle);
|
635 - | }
|
636 - | cell.notify_maintenance(idle_deadline);
|
637 - | if cell.admission.is_some() {
|
638 - | let availability = cell.state.lock().take_h1_availability_update();
|
639 - | cell.publish_h1_availability(availability);
|
640 - | }
|
641 - | return;
|
642 - | };
|
643 - | if tracing::level_enabled!(tracing::Level::TRACE) {
|
644 - | trace_h1_return(cell, connection_id, H1ReturnTrace::LocalDemand);
|
645 - | }
|
646 - | if let Some(admission) = &cell.admission {
|
647 - | for snapshot in installation.demand_updates.into_iter().flatten() {
|
648 - | OriginAdmission::publish_demand(admission, cell.id.partition(), snapshot);
|
649 - | }
|
650 - | }
|
651 - | drop(installation.returned_event);
|
652 - | if let Some(waker) = installation.waker {
|
653 - | waker.wake();
|
654 - | }
|
655 - | if cell.admission.is_some() {
|
656 - | let availability = cell.state.lock().take_h1_availability_update();
|
657 - | cell.publish_h1_availability(availability);
|
658 - | }
|
659 - | }
|
660 - |
|
661 - | /// Retires an externally owned H1 sender and removes its connection-owning cell record.
|
662 - | ///
|
663 - | /// Returns whether this path won the connection's logical-close race.
|
664 - | fn retire_h1_owner(cell: &Arc<Self>, owner: OwnedH1, reason: CloseReason) -> bool {
|
665 - | let should_close = {
|
666 - | let mut state = cell.state.lock();
|
667 - | let should_close = state.h1.close_owned(&owner);
|
668 - | state.assert_consistent();
|
669 - | should_close
|
670 - | };
|
671 - |
|
672 - | let id = owner.id();
|
673 - | let won = should_close && owner.connection().logical_close(reason);
|
674 - | drop(owner);
|
675 - |
|
676 - | let mut state = cell.state.lock();
|
677 - | state.h1.finish_close(id);
|
678 - | state.assert_consistent();
|
679 - | let availability = state.take_h1_availability_update();
|
680 - | drop(state);
|
681 - | cell.publish_h1_availability(availability);
|
682 - | won
|
683 - | }
|
684 - |
|
685 - | /// Begins close for an installed H1 generation named without its sender.
|
686 - | ///
|
687 - | /// Returns whether this signal won the connection's logical-close race.
|
688 - | fn close_h1(cell: &Arc<Self>, id: ConnectionId, reason: CloseReason) -> bool {
|
689 - | let Some((close, availability)) = ({
|
690 - | let mut state = cell.state.lock();
|
691 - | let close = state.h1.begin_close(id);
|
692 - | state.assert_consistent();
|
693 - | close.map(|close| (close, state.take_h1_availability_update()))
|
694 - | }) else {
|
695 - | return false;
|
696 - | };
|
697 - |
|
698 - | cell.publish_h1_availability(availability);
|
699 - | let remove_record = close.sender.is_some();
|
700 - | let won = close.connection.logical_close(reason);
|
701 - | drop(close.sender);
|
702 - | if remove_record {
|
703 - | let mut state = cell.state.lock();
|
704 - | state.h1.finish_close(id);
|
705 - | state.assert_consistent();
|
706 - | }
|
707 - | won
|
708 - | }
|
709 - |
|
710 - | /// Closes idle records whose maintenance deadline has elapsed.
|
413 + | /// Closes H1 records and H2 generations whose idle deadline elapsed.
|
711 414 | pub(super) fn expire_idle(cell: &Arc<Self>, now: SystemTime) {
|
712 - | let expired = cell.state.lock().h1.expired_idle(now);
|
713 - | for id in expired {
|
415 + | let (expired_h1, expired_h2) = {
|
416 + | let state = cell.state.lock();
|
417 + | (state.h1.expired_idle(now), state.h2.expired(now))
|
418 + | };
|
419 + | for id in expired_h1 {
|
714 420 | if Self::close_h1(cell, id, CloseReason::IdleTimeout) {
|
715 421 | tracing::trace!(
|
716 422 | connection_id = %id,
|
717 423 | connection_partition = ?cell.id.partition(),
|
718 424 | origin_scheme = %cell.id.origin().scheme(),
|
719 425 | origin_host = cell.id.origin().host(),
|
720 426 | origin_port = ?cell.id.origin().port(),
|
721 427 | "HTTP/1 idle connection expired"
|
722 428 | );
|
723 429 | }
|
724 430 | }
|
431 + | if let Some(generation) = expired_h2 {
|
432 + | if Self::close_h2(cell, generation, CloseReason::IdleTimeout) {
|
433 + | tracing::trace!(
|
434 + | connection_partition = ?cell.id.partition(),
|
435 + | origin_scheme = %cell.id.origin().scheme(),
|
436 + | origin_host = cell.id.origin().host(),
|
437 + | origin_port = ?cell.id.origin().port(),
|
438 + | h2_generation = ?generation,
|
439 + | "HTTP/2 idle generation expired"
|
440 + | );
|
441 + | }
|
442 + | }
|
725 443 | }
|
726 444 |
|
727 - | /// Returns this cell's nearest reusable H1 deadline.
|
445 + | /// Returns this cell's nearest reusable connection deadline.
|
728 446 | pub(super) fn nearest_idle_deadline(&self) -> Option<SystemTime> {
|
729 - | self.state.lock().h1.nearest_idle_deadline()
|
447 + | let state = self.state.lock();
|
448 + | [
|
449 + | state.h1.nearest_idle_deadline(),
|
450 + | state.h2.nearest_idle_deadline(),
|
451 + | ]
|
452 + | .into_iter()
|
453 + | .flatten()
|
454 + | .min()
|
730 455 | }
|
731 456 |
|
732 - | /// Logically closes every retained H1 record for pool shutdown.
|
733 - | pub(super) fn close_all_h1(cell: &Arc<Self>, reason: CloseReason) {
|
734 - | let ids = cell.state.lock().h1.connection_ids();
|
735 - | for id in ids {
|
457 + | /// Logically closes every retained connection for pool shutdown.
|
458 + | pub(super) fn close_all(cell: &Arc<Self>, reason: CloseReason) {
|
459 + | let (h1, h2) = {
|
460 + | let state = cell.state.lock();
|
461 + | (state.h1.connection_ids(), state.h2.generation_ids())
|
462 + | };
|
463 + | for id in h1 {
|
736 464 | Self::close_h1(cell, id, reason);
|
737 465 | }
|
466 + | for generation in h2 {
|
467 + | Self::close_h2(cell, generation, reason);
|
468 + | }
|
738 469 | }
|
739 470 |
|
740 471 | /// Applies one materialized acquisition delivery after admission unlock.
|
741 472 | ///
|
742 473 | /// Connection-cell H1 revalidation has already completed, so reserving
|
743 474 | /// the requesting cell cannot be followed by another fallible connection-
|
744 475 | /// cell transition.
|
745 476 | ///
|
746 477 | /// # Panics
|
747 478 | ///
|
748 479 | /// Panics if a waiter reserved by this function disappears or enters a
|
749 480 | /// state that cannot accept the committed acquisition event.
|
750 481 | pub(in crate::client::pool) fn receive_delivery(
|
751 482 | cell: &Arc<Self>,
|
752 483 | delivery: DeliveryGuard,
|
753 484 | ) -> Option<AdmissionAction> {
|
754 485 | let reservation = {
|
755 486 | let mut state = cell.state.lock();
|
756 487 | state
|
757 488 | .waiters
|
758 489 | .reserve_delivery_waiter(delivery.demand(), &cell.eligibility_group)
|
759 490 | };
|
760 491 |
|
761 492 | let DeliveryReservation::Reserved { waiter, successor } = reservation else {
|
762 493 | return delivery.reject(None);
|
763 494 | };
|
764 495 |
|
765 - | let (event, acknowledgement) = delivery.commit(successor);
|
766 - | let installation = {
|
496 + | let (event, mut acknowledgement) = delivery.commit(successor);
|
497 + | let (installation, suppress_successor) = {
|
767 498 | let mut state = cell.state.lock();
|
768 - | match event {
|
499 + | let installation = match event {
|
769 500 | AcquisitionEvent::Establish(permit) => {
|
770 501 | state.waiters.install_capacity(waiter, permit)
|
771 502 | }
|
772 503 | AcquisitionEvent::Complete(result) => {
|
773 504 | state.waiters.install_borrowed_h1(waiter, result)
|
774 505 | }
|
775 - | }
|
776 506 | };
|
507 + | (installation, state.h2.has_visible_h2())
|
508 + | };
|
509 + | if suppress_successor {
|
510 + | acknowledgement.suppress_h2_successor();
|
511 + | }
|
777 512 |
|
778 513 | let accepted = installation.accepted;
|
779 514 | let error = installation.error;
|
780 515 | let waker = installation.waker;
|
781 516 | let next = if accepted {
|
782 517 | drop(installation.returned_events);
|
783 518 | acknowledgement.accept()
|
784 519 | } else {
|
785 520 | acknowledgement.reject(installation.returned_events)
|
786 521 | };
|
787 522 | if let Some(waker) = waker {
|
788 523 | waker.wake();
|
789 524 | }
|
790 525 | if let Some(error) = error {
|
791 526 | match error {
|
792 527 | ResultInstallError::MissingWaiter => {
|
793 528 | panic!("reserved waiter disappeared before acquisition delivery")
|
794 529 | }
|
795 530 | ResultInstallError::UnexpectedState => {
|
796 531 | panic!("reserved waiter entered an invalid acquisition-delivery state")
|
797 532 | }
|
798 533 | }
|
799 534 | }
|
800 535 | next
|
801 536 | }
|
802 537 |
|
803 538 | /// Returns the number of retained acquisition waiters for boundary tests.
|
804 - | #[cfg(all(test, feature = "rt-tokio"))]
|
539 + | #[cfg(test)]
|
805 540 | pub(super) fn retained_waiters_for_test(&self) -> usize {
|
806 541 | self.state.lock().waiters.snapshot().retained
|
807 542 | }
|
808 543 |
|
809 - | /// Returns H1 record counts for focused ownership tests.
|
810 - | #[cfg(test)]
|
811 - | fn h1_counts(&self) -> (usize, usize) {
|
812 - | self.state.lock().h1.counts()
|
813 - | }
|
814 - |
|
815 - | /// Returns the sole installed HTTP/1 connection for focused dispatch tests.
|
816 - | #[cfg(all(test, feature = "rt-tokio"))]
|
817 - | pub(super) fn only_h1_connection_for_test(&self) -> Arc<ConnectionState> {
|
818 - | self.state.lock().h1.only_connection_for_test()
|
819 - | }
|
820 - |
|
821 544 | /// Registers one acquisition waiter in cell-local arrival order.
|
822 545 | ///
|
823 546 | /// The waiter and its demand snapshot are committed under the cell lock.
|
824 547 | /// A bounded cell publishes the snapshot to origin admission only after
|
825 548 | /// that lock is released. An unbounded cell retains only local order.
|
826 - | pub(super) fn register_waiter(&self, requirement: ProtocolRequirement) -> WaiterId {
|
827 - | let (waiter, snapshot) = {
|
828 - | let mut state = self.state.lock();
|
829 - | state.waiters.register_waiter(
|
549 + | pub(super) fn register_waiter(cell: &Arc<Self>, requirement: ProtocolRequirement) -> WaiterId {
|
550 + | let (waiter, snapshot, h2_visible) = {
|
551 + | let mut state = cell.state.lock();
|
552 + | let (waiter, snapshot) = state.waiters.register_waiter(
|
830 553 | requirement,
|
831 - | &self.eligibility_group,
|
832 - | self.admission.is_some(),
|
554 + | &cell.eligibility_group,
|
555 + | cell.admission.is_some(),
|
556 + | );
|
557 + | (
|
558 + | waiter,
|
559 + | snapshot,
|
560 + | state.h2.has_visible_h2() && requirement.accepts_h2(),
|
833 561 | )
|
834 562 | };
|
835 563 |
|
836 - | if let (Some(admission), Some(snapshot)) = (&self.admission, snapshot) {
|
837 - | OriginAdmission::publish_demand(admission, self.id.partition(), snapshot);
|
564 + | if let (Some(admission), Some(snapshot), false) = (&cell.admission, snapshot, h2_visible) {
|
565 + | OriginAdmission::publish_demand(admission, cell.id.partition(), snapshot);
|
838 566 | }
|
567 + | Self::service_h2_waiters(cell);
|
568 + | Self::service_peer_h2_waiters(cell);
|
839 569 | waiter
|
840 570 | }
|
841 571 |
|
842 572 | /// Cancels a waiter and re-examines local idle H1 state atomically.
|
843 573 | ///
|
844 574 | /// A newly compatible successor may be serviceable by an H1 that an older
|
845 575 | /// HTTP/2-only head could not use. The local sender is selected under the
|
846 576 | /// same cell lock as cancellation, so it never needs a same-cell peer
|
847 577 | /// reuse operation. Demand publication, fallback, and waking remain outside the lock.
|
848 578 | pub(super) fn cancel_waiter(cell: &Arc<Self>, waiter: WaiterId) -> bool {
|
849 579 | let transition = {
|
850 580 | let mut state = cell.state.lock();
|
581 + | state.h2.cancel_flight_participant(waiter);
|
582 + | state.h2.cancel_peer_activation(waiter);
|
583 + | state.h2.cancel_pending_waiter(waiter);
|
851 584 | state
|
852 585 | .waiters
|
853 586 | .cancel_waiter(waiter, &cell.eligibility_group)
|
854 - | .map(|cancelled| {
|
855 - | let local_install = if state.waiters.can_accept_h1() {
|
587 + | .map(|mut cancelled| {
|
588 + | let mut local_install = if state.waiters.can_accept_h1() {
|
856 589 | state.h1.select_idle().map(|owner| {
|
857 590 | state.reuse.consume_local_turn();
|
858 591 | state.waiters.install_returned_h1(
|
859 592 | || AcquisitionResult::H1(H1Selection::new(cell, owner)),
|
860 593 | &cell.eligibility_group,
|
861 594 | )
|
862 595 | })
|
863 596 | } else {
|
864 597 | None
|
865 598 | };
|
599 + | cancelled.demand_updates =
|
600 + | state.publishable_demand_updates(cancelled.demand_updates);
|
601 + | if let Some(install) = &mut local_install {
|
602 + | if let Some(waiter) = install.waiter {
|
603 + | state.h2.cancel_pending_waiter(waiter);
|
604 + | }
|
605 + | install.demand_updates = state.publishable_demand_updates(std::mem::take(
|
606 + | &mut install.demand_updates,
|
607 + | ));
|
608 + | }
|
866 609 | (cancelled, local_install)
|
867 610 | })
|
868 611 | };
|
869 612 | let Some((cancelled, local_install)) = transition else {
|
870 613 | return false;
|
871 614 | };
|
872 615 |
|
873 616 | if let Some(admission) = &cell.admission {
|
874 617 | for snapshot in cancelled
|
875 618 | .demand_updates
|
876 619 | .into_iter()
|
877 620 | .chain(
|
878 621 | local_install
|
879 622 | .as_ref()
|
880 623 | .into_iter()
|
881 624 | .flat_map(|install| install.demand_updates.iter().cloned()),
|
882 625 | )
|
883 626 | .flatten()
|
884 627 | {
|
885 628 | OriginAdmission::publish_demand(admission, cell.id.partition(), snapshot);
|
886 629 | }
|
887 630 | }
|
888 631 |
|
889 632 | // Ready results and any locally rejected event cross the lock boundary
|
890 633 | // before their fallback can re-enter the pool.
|
891 634 | drop(cancelled.returned_events);
|
892 635 | if let Some(install) = local_install {
|
893 636 | drop(install.returned_event);
|
894 637 | if let Some(waker) = install.waker {
|
895 638 | waker.wake();
|
896 639 | }
|
897 640 | }
|
898 641 |
|
899 642 | let availability = {
|
900 643 | let mut state = cell.state.lock();
|
901 644 | let local_h1_demand = state.waiters.can_accept_h1();
|
902 645 | state.reuse.clear_unused_turn(local_h1_demand);
|
903 646 | state.assert_consistent();
|
904 647 | state.take_h1_availability_update()
|
905 648 | };
|
906 649 | cell.publish_h1_availability(availability);
|
650 + | Self::service_h2_waiters(cell);
|
651 + | Self::service_peer_h2_waiters(cell);
|
907 652 | true
|
908 653 | }
|
909 654 |
|
910 655 | /// Polls the next event for one acquisition waiter.
|
911 656 | ///
|
912 657 | /// # Panics
|
913 658 | ///
|
914 659 | /// Panics if `waiter` is unknown, was cancelled, or is polled again after
|
915 660 | /// its ready result was consumed.
|
916 661 | pub(super) fn poll_waiter(
|
917 662 | &self,
|
918 663 | waiter: WaiterId,
|
919 664 | cx: &mut Context<'_>,
|
920 665 | ) -> Poll<AcquisitionEvent> {
|
921 666 | let mut state = self.state.lock();
|
922 667 | state.waiters.poll_waiter(waiter, cx)
|
923 668 | }
|
924 669 |
|
925 670 | /// Marks an establishment attempt as started before its first connector poll.
|
926 671 | pub(super) fn start_establishment(&self, waiter: WaiterId) -> bool {
|
927 672 | self.state.lock().waiters.start_establishment(waiter)
|
928 673 | }
|
929 674 |
|
930 675 | /// Commits one terminal establishment result to its launching waiter.
|
931 676 | ///
|
932 677 | /// A returned H1 may already have served or cancelled the waiter. In that
|
933 678 | /// case the losing result leaves the cell lock and follows its ordinary
|
934 679 | /// sender-return or error-drop fallback.
|
935 680 | ///
|
936 681 | /// # Panics
|
937 682 | ///
|
938 683 | /// Panics if the launching waiter disappeared or left the launching state
|
939 684 | /// before its owned establishment completed.
|
940 685 | pub(super) fn complete_establishment(&self, waiter: WaiterId, result: AcquisitionResult) {
|
941 686 | let served_with_h1 = matches!(&result, AcquisitionResult::H1(_));
|
942 687 | let installation = {
|
943 688 | let mut state = self.state.lock();
|
944 689 | let installation = state.waiters.install_establishment_result(waiter, result);
|
690 + | let served_with_h1 = served_with_h1
|
691 + | && !installation.returned_events.iter().any(|event| {
|
692 + | matches!(
|
693 + | event,
|
694 + | Some(AcquisitionEvent::Complete(AcquisitionResult::H1(_)))
|
695 + | )
|
696 + | });
|
945 697 | if installation.accepted {
|
946 698 | if served_with_h1 {
|
947 699 | state.reuse.consume_local_turn();
|
948 700 | } else {
|
949 701 | let local_h1_demand = state.waiters.can_accept_h1();
|
950 702 | state.reuse.clear_unused_turn(local_h1_demand);
|
951 703 | }
|
952 704 | state.assert_consistent();
|
953 705 | }
|
954 706 | installation
|
955 707 | };
|
956 708 | drop(installation.returned_events);
|
957 709 | if let Some(waker) = installation.waker {
|
958 710 | waker.wake();
|
959 711 | }
|
960 712 | if let Some(error) = installation.error {
|
961 713 | match error {
|
962 714 | ResultInstallError::MissingWaiter => {
|
963 715 | unreachable!("establishment result reported a missing waiter")
|
964 716 | }
|
965 717 | ResultInstallError::UnexpectedState => {
|
966 718 | panic!("establishment completed for a waiter that was not launching")
|
967 719 | }
|
968 720 | }
|
969 721 | }
|
970 722 | let availability = {
|
971 723 | let mut state = self.state.lock();
|
972 724 | state.assert_consistent();
|
973 725 | state.take_h1_availability_update()
|
974 726 | };
|
975 727 | self.publish_h1_availability(availability);
|
976 728 | }
|
977 729 |
|
978 730 | #[cfg(test)]
|
979 731 | pub(super) fn take_ready_lease(cell: &Arc<Self>, waiter: WaiterId) -> Option<CapacityLease> {
|
980 732 | let permit = match cell.take_ready_event(waiter)? {
|
981 733 | AcquisitionEvent::Establish(permit) => permit,
|
982 734 | AcquisitionEvent::Complete(_) => {
|
983 735 | panic!("capacity test received a terminal acquisition result")
|
984 736 | }
|
985 737 | };
|
986 738 | assert!(Self::cancel_waiter(cell, waiter));
|
987 739 | permit.into_lease()
|
988 740 | }
|
989 741 |
|
990 742 | #[cfg(test)]
|
991 743 | fn take_ready_h1(&self, waiter: WaiterId) -> Option<H1Selection> {
|
992 744 | match self.take_ready_event(waiter)? {
|
993 745 | AcquisitionEvent::Complete(AcquisitionResult::H1(selection)) => Some(selection),
|
746 + | AcquisitionEvent::Complete(AcquisitionResult::H2(_)) => {
|
747 + | panic!("HTTP/1 ownership test received an HTTP/2 activation")
|
748 + | }
|
994 749 | AcquisitionEvent::Complete(AcquisitionResult::Failed(_)) => {
|
995 750 | panic!("HTTP/1 ownership test received establishment failure")
|
996 751 | }
|
752 + | AcquisitionEvent::Complete(AcquisitionResult::Reacquire) => {
|
753 + | panic!("HTTP/1 ownership test received an internal reacquisition")
|
754 + | }
|
997 755 | AcquisitionEvent::Establish(_) => {
|
998 756 | panic!("HTTP/1 ownership test received establishment capacity")
|
999 757 | }
|
1000 758 | }
|
1001 759 | }
|
1002 760 |
|
1003 761 | #[cfg(test)]
|
1004 762 | fn take_ready_event(&self, waiter: WaiterId) -> Option<AcquisitionEvent> {
|
1005 763 | let mut state = self.state.lock();
|
1006 764 | match state
|
1007 765 | .waiters
|
1008 766 | .poll_waiter(waiter, &mut Context::from_waker(std::task::Waker::noop()))
|
1009 767 | {
|
1010 768 | Poll::Ready(event) => Some(event),
|
1011 769 | Poll::Pending => None,
|
1012 770 | }
|
1013 771 | }
|
1014 772 |
|
1015 773 | #[cfg(test)]
|
1016 774 | pub(super) fn register_waiter_without_publish(
|
1017 775 | &self,
|
1018 776 | requirement: ProtocolRequirement,
|
1019 777 | ) -> (WaiterId, DemandSnapshot) {
|
1020 778 | let (waiter, snapshot) =
|
1021 779 | self.state
|
1022 780 | .lock()
|
1023 781 | .waiters
|
1024 782 | .register_waiter(requirement, &self.eligibility_group, true);
|
1025 783 | (
|
1026 784 | waiter,
|
1027 785 | snapshot.expect("first unpublished waiter did not create demand"),
|
1028 786 | )
|
1029 787 | }
|
1030 788 |
|
1031 789 | #[cfg(test)]
|
1032 790 | fn snapshot(&self) -> CellSnapshot {
|
1033 791 | self.state.lock().waiters.snapshot()
|
1034 792 | }
|
1035 793 | }
|
1036 794 |
|
1037 - | /// Terminal outcome for one reusable HTTP/1 sender return.
|
1038 - | enum H1ReturnTrace {
|
1039 - | /// The owning cell no longer accepts the sender.
|
1040 - | Rejected,
|
1041 - | /// Origin admission reserved the sender for peer demand.
|
1042 - | ReuseIntercepted,
|
1043 - | /// No compatible demand exists, so the sender became idle.
|
1044 - | Idle,
|
1045 - | /// A waiter in the owning cell accepted the sender.
|
1046 - | LocalDemand,
|
1047 - | }
|
1048 - |
|
1049 - | /// Emits a committed HTTP/1 return outcome outside the cell lock.
|
1050 - | // Keep field formatting out of a return transition that can synchronously
|
1051 - | // enter admission and sender fallbacks.
|
1052 - | #[inline(never)]
|
1053 - | fn trace_h1_return(cell: &OriginCell, connection_id: ConnectionId, outcome: H1ReturnTrace) {
|
1054 - | match outcome {
|
1055 - | H1ReturnTrace::Rejected => tracing::trace!(
|
1056 - | connection_id = %connection_id,
|
1057 - | connection_partition = ?cell.id.partition(),
|
1058 - | origin_scheme = %cell.id.origin().scheme(),
|
1059 - | origin_host = cell.id.origin().host(),
|
1060 - | origin_port = ?cell.id.origin().port(),
|
1061 - | "HTTP/1 return was rejected by its connection-owning cell"
|
1062 - | ),
|
1063 - | H1ReturnTrace::ReuseIntercepted => tracing::trace!(
|
1064 - | connection_id = %connection_id,
|
1065 - | connection_partition = ?cell.id.partition(),
|
1066 - | origin_scheme = %cell.id.origin().scheme(),
|
1067 - | origin_host = cell.id.origin().host(),
|
1068 - | origin_port = ?cell.id.origin().port(),
|
1069 - | "HTTP/1 return was intercepted by a cross-cell reuse operation"
|
1070 - | ),
|
1071 - | H1ReturnTrace::Idle => tracing::trace!(
|
1072 - | connection_id = %connection_id,
|
1073 - | connection_partition = ?cell.id.partition(),
|
1074 - | origin_scheme = %cell.id.origin().scheme(),
|
1075 - | origin_host = cell.id.origin().host(),
|
1076 - | origin_port = ?cell.id.origin().port(),
|
1077 - | "HTTP/1 connection returned to idle storage"
|
1078 - | ),
|
1079 - | H1ReturnTrace::LocalDemand => tracing::trace!(
|
1080 - | connection_id = %connection_id,
|
1081 - | request_partition = ?cell.id.partition(),
|
1082 - | connection_partition = ?cell.id.partition(),
|
1083 - | origin_scheme = %cell.id.origin().scheme(),
|
1084 - | origin_host = cell.id.origin().host(),
|
1085 - | origin_port = ?cell.id.origin().port(),
|
1086 - | "HTTP/1 return satisfied local demand"
|
1087 - | ),
|
1088 - | }
|
1089 - | }
|
1090 - |
|
1091 795 | impl std::fmt::Debug for OriginCell {
|
1092 796 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
1093 797 | f.debug_struct("OriginCell")
|
1094 798 | .field("id", &self.id)
|
1095 799 | .field("eligibility_group", &self.eligibility_group)
|
1096 800 | .field("admission", &self.admission)
|
1097 801 | .field("state", &self.state)
|
1098 802 | .finish()
|
1099 803 | }
|
1100 804 | }
|
1296 1000 | Some(CloseReason::IncompleteH1Exchange),
|
1297 1001 | connection.snapshot().close_reason
|
1298 1002 | );
|
1299 1003 | }
|
1300 1004 |
|
1301 1005 | #[test]
|
1302 1006 | fn returned_h1_satisfies_an_unbounded_waiter() {
|
1303 1007 | let cell = unbounded_cell();
|
1304 1008 | let (connection, _physical) = unbounded_connection(1);
|
1305 1009 | let selection = OriginCell::install_selected_h1(&cell, connection, H1Sender::test(11));
|
1306 - | let waiter = cell.register_waiter(ProtocolRequirement::H1Compatible);
|
1010 + | let waiter = OriginCell::register_waiter(&cell, ProtocolRequirement::H1Compatible);
|
1307 1011 |
|
1308 1012 | drop(selection);
|
1309 1013 |
|
1310 1014 | let selected = cell
|
1311 1015 | .take_ready_h1(waiter)
|
1312 1016 | .expect("returned sender did not satisfy the waiter");
|
1313 1017 | assert_eq!(11, selected.test_sender_id());
|
1314 1018 | assert!(selected.is_reused());
|
1315 1019 | assert_eq!(0, cell.snapshot().retained);
|
1316 1020 | drop(selected);
|
1317 1021 | assert_eq!((1, 1), cell.h1_counts());
|
1318 1022 | }
|
1319 1023 |
|
1320 1024 | #[test]
|
1321 1025 | fn returned_h1_satisfies_bounded_demand_without_releasing_capacity() {
|
1322 1026 | let (admission, cell) = bounded_cell();
|
1323 1027 | let lease = OriginAdmission::lease_for_test(&admission);
|
1324 1028 | let (connection, _physical) = ConnectionState::bounded(connection_info(1), lease);
|
1325 1029 | let selection = OriginCell::install_selected_h1(&cell, connection, H1Sender::test(11));
|
1326 - | let waiter = cell.register_waiter(ProtocolRequirement::H1Compatible);
|
1030 + | let waiter = OriginCell::register_waiter(&cell, ProtocolRequirement::H1Compatible);
|
1327 1031 | assert_eq!(1, admission.ordered_demand_count_for_test());
|
1328 1032 |
|
1329 1033 | drop(selection);
|
1330 1034 |
|
1331 1035 | assert_eq!(0, admission.ordered_demand_count_for_test());
|
1332 1036 | assert_eq!(0, admission.available_capacity_for_test());
|
1333 1037 | let selected = cell
|
1334 1038 | .take_ready_h1(waiter)
|
1335 1039 | .expect("returned sender did not satisfy bounded demand");
|
1336 1040 | drop(selected);
|
1337 1041 | assert_eq!((1, 1), cell.h1_counts());
|
1338 1042 | }
|
1339 1043 |
|
1340 1044 | #[test]
|
1341 1045 | fn eligible_peer_borrows_an_idle_h1_without_moving_capacity() {
|
1342 1046 | let (admission, connection_cell, requesting_cell) =
|
1343 1047 | bounded_peer_cells(1, EligibilityGroup::Pool, EligibilityGroup::Pool);
|
1344 1048 | let lease = OriginAdmission::lease_for_test(&admission);
|
1345 1049 | let (connection, _physical) = ConnectionState::bounded(connection_info(1), lease);
|
1346 1050 | OriginCell::install_idle_h1(&connection_cell, connection, H1Sender::test(11));
|
1347 1051 |
|
1348 - | let waiter = requesting_cell.register_waiter(ProtocolRequirement::H1Compatible);
|
1052 + | let waiter =
|
1053 + | OriginCell::register_waiter(&requesting_cell, ProtocolRequirement::H1Compatible);
|
1349 1054 | let borrowed = requesting_cell
|
1350 1055 | .take_ready_h1(waiter)
|
1351 1056 | .expect("eligible peer did not receive the idle HTTP/1 sender");
|
1352 1057 |
|
1353 1058 | assert_eq!(11, borrowed.test_sender_id());
|
1354 1059 | assert_eq!(0, admission.available_capacity_for_test());
|
1355 1060 | assert_eq!((1, 0), connection_cell.h1_counts());
|
1356 1061 | drop(borrowed);
|
1357 1062 | assert_eq!((1, 1), connection_cell.h1_counts());
|
1358 1063 | }
|
1359 1064 |
|
1360 1065 | #[test]
|
1361 1066 | fn ineligible_peer_reclaims_h1_capacity_without_moving_the_sender() {
|
1362 1067 | let (admission, connection_cell, requesting_cell) = bounded_peer_cells(
|
1363 1068 | 1,
|
1364 1069 | EligibilityGroup::Partition(PartitionId::from_index(1)),
|
1365 1070 | EligibilityGroup::Partition(PartitionId::from_index(2)),
|
1366 1071 | );
|
1367 1072 | let lease = OriginAdmission::lease_for_test(&admission);
|
1368 1073 | let (connection, _physical) = ConnectionState::bounded(connection_info(1), lease);
|
1369 1074 | OriginCell::install_idle_h1(&connection_cell, connection.clone(), H1Sender::test(11));
|
1370 1075 |
|
1371 - | let waiter = requesting_cell.register_waiter(ProtocolRequirement::H1Compatible);
|
1076 + | let waiter =
|
1077 + | OriginCell::register_waiter(&requesting_cell, ProtocolRequirement::H1Compatible);
|
1372 1078 | let replacement = OriginCell::take_ready_lease(&requesting_cell, waiter)
|
1373 1079 | .expect("ineligible peer did not receive reclaimed capacity");
|
1374 1080 |
|
1375 1081 | assert_eq!(
|
1376 1082 | Some(CloseReason::Reclaimed),
|
1377 1083 | connection.snapshot().close_reason
|
1378 1084 | );
|
1379 1085 | assert_eq!((0, 0), connection_cell.h1_counts());
|
1380 1086 | assert_eq!(0, admission.available_capacity_for_test());
|
1381 1087 | drop(replacement);
|
1382 1088 | assert_eq!(1, admission.available_capacity_for_test());
|
1383 1089 | }
|
1384 1090 |
|
1385 1091 | #[test]
|
1386 1092 | fn installed_peer_reuse_intercepts_the_next_active_h1_return() {
|
1387 1093 | let (admission, connection_cell, requesting_cell) =
|
1388 1094 | bounded_peer_cells(1, EligibilityGroup::Pool, EligibilityGroup::Pool);
|
1389 1095 | let lease = OriginAdmission::lease_for_test(&admission);
|
1390 1096 | let (connection, _physical) = ConnectionState::bounded(connection_info(1), lease);
|
1391 1097 | let selected =
|
1392 1098 | OriginCell::install_selected_h1(&connection_cell, connection, H1Sender::test(11));
|
1393 - | let waiter = requesting_cell.register_waiter(ProtocolRequirement::H1Compatible);
|
1099 + | let waiter =
|
1100 + | OriginCell::register_waiter(&requesting_cell, ProtocolRequirement::H1Compatible);
|
1394 1101 |
|
1395 1102 | drop(selected);
|
1396 1103 |
|
1397 1104 | let borrowed = requesting_cell
|
1398 1105 | .take_ready_h1(waiter)
|
1399 1106 | .expect("installed reuse operation did not intercept the active return");
|
1400 1107 | assert_eq!(11, borrowed.test_sender_id());
|
1401 1108 | drop(borrowed);
|
1402 1109 | assert_eq!((1, 1), connection_cell.h1_counts());
|
1403 1110 | }
|
1404 1111 |
|
1405 1112 | #[test]
|
1406 1113 | fn cancelling_requesting_cell_releases_connection_reservation() {
|
1407 1114 | let (admission, connection_cell, requesting_cell) =
|
1408 1115 | bounded_peer_cells(1, EligibilityGroup::Pool, EligibilityGroup::Pool);
|
1409 1116 | let lease = OriginAdmission::lease_for_test(&admission);
|
1410 1117 | let (connection, _physical) = ConnectionState::bounded(connection_info(1), lease);
|
1411 1118 | let selected =
|
1412 1119 | OriginCell::install_selected_h1(&connection_cell, connection, H1Sender::test(11));
|
1413 - | let waiter = requesting_cell.register_waiter(ProtocolRequirement::H1Compatible);
|
1120 + | let waiter =
|
1121 + | OriginCell::register_waiter(&requesting_cell, ProtocolRequirement::H1Compatible);
|
1414 1122 |
|
1415 1123 | assert!(OriginCell::cancel_waiter(&requesting_cell, waiter));
|
1416 1124 | assert!(
|
1417 1125 | connection_cell.state.lock().reuse.is_available(),
|
1418 1126 | "requesting cell cancellation did not reconcile the installed connection-owning cell reuse operation"
|
1419 1127 | );
|
1420 1128 | drop(selected);
|
1421 1129 |
|
1422 1130 | assert_eq!((1, 1), connection_cell.h1_counts());
|
1423 1131 | assert_eq!(0, admission.ordered_demand_count_for_test());
|
1424 1132 | }
|
1425 1133 |
|
1426 1134 | #[test]
|
1427 1135 | fn cross_cell_borrow_owes_one_usable_turn_to_the_owning_cell() {
|
1428 1136 | let (admission, connection_cell, requesting_cell) =
|
1429 1137 | bounded_peer_cells(1, EligibilityGroup::Pool, EligibilityGroup::Pool);
|
1430 1138 | let lease = OriginAdmission::lease_for_test(&admission);
|
1431 1139 | let (connection, _physical) = ConnectionState::bounded(connection_info(1), lease);
|
1432 1140 | let selected =
|
1433 1141 | OriginCell::install_selected_h1(&connection_cell, connection, H1Sender::test(11));
|
1434 1142 |
|
1435 - | let requesting_waiter = requesting_cell.register_waiter(ProtocolRequirement::H1Compatible);
|
1436 - | let local_waiter = connection_cell.register_waiter(ProtocolRequirement::H1Compatible);
|
1143 + | let requesting_waiter =
|
1144 + | OriginCell::register_waiter(&requesting_cell, ProtocolRequirement::H1Compatible);
|
1145 + | let local_waiter =
|
1146 + | OriginCell::register_waiter(&connection_cell, ProtocolRequirement::H1Compatible);
|
1437 1147 | drop(selected);
|
1438 1148 |
|
1439 1149 | let borrowed = requesting_cell
|
1440 1150 | .take_ready_h1(requesting_waiter)
|
1441 1151 | .expect("older peer demand did not receive the intercepted sender");
|
1442 1152 | assert!(connection_cell.state.lock().reuse.local_turn_owed());
|
1443 1153 | drop(borrowed);
|
1444 1154 |
|
1445 1155 | let local = connection_cell
|
1446 1156 | .take_ready_h1(local_waiter)
|
1447 1157 | .expect("connection-owning cell local demand did not receive its owed turn");
|
1448 1158 | assert!(!connection_cell.state.lock().reuse.local_turn_owed());
|
1449 1159 | drop(local);
|
1450 1160 | assert_eq!((1, 1), connection_cell.h1_counts());
|
1451 1161 | }
|
1452 1162 |
|
1453 1163 | #[test]
|
1454 1164 | fn cancelling_ready_h1_returns_it_after_unlock() {
|
1455 1165 | let cell = unbounded_cell();
|
1456 1166 | let (connection, _physical) = unbounded_connection(1);
|
1457 1167 | let selection = OriginCell::install_selected_h1(&cell, connection, H1Sender::test(11));
|
1458 - | let waiter = cell.register_waiter(ProtocolRequirement::H1Compatible);
|
1168 + | let waiter = OriginCell::register_waiter(&cell, ProtocolRequirement::H1Compatible);
|
1459 1169 | drop(selection);
|
1460 1170 |
|
1461 1171 | assert!(OriginCell::cancel_waiter(&cell, waiter));
|
1462 1172 | assert_eq!((1, 1), cell.h1_counts());
|
1463 1173 | assert_eq!(
|
1464 1174 | 11,
|
1465 1175 | OriginCell::select_h1(&cell)
|
1466 1176 | .expect("cancelled result did not return its sender")
|
1467 1177 | .test_sender_id()
|
1468 1178 | );
|
1469 1179 | }
|
1470 1180 |
|
1471 1181 | #[test]
|
1472 1182 | fn returned_h1_and_establishment_complete_one_acquisition_attempt() {
|
1473 1183 | let cell = unbounded_cell();
|
1474 - | let waiter = cell.register_waiter(ProtocolRequirement::H1Compatible);
|
1184 + | let waiter = OriginCell::register_waiter(&cell, ProtocolRequirement::H1Compatible);
|
1475 1185 | let AcquisitionEvent::Establish(permit) = cell
|
1476 1186 | .take_ready_event(waiter)
|
1477 1187 | .expect("unbounded miss did not start establishment")
|
1478 1188 | else {
|
1479 1189 | panic!("unbounded miss completed before establishment started");
|
1480 1190 | };
|
1481 1191 | assert!(cell.start_establishment(waiter));
|
1482 1192 | assert!(permit.into_lease().is_none());
|
1483 1193 |
|
1484 1194 | let (returning_connection, _returning_physical) = unbounded_connection(1);
|
1485 1195 | let returning =
|
1486 1196 | OriginCell::install_selected_h1(&cell, returning_connection, H1Sender::test(11));
|
1487 1197 | let (fresh_connection, _fresh_physical) = unbounded_connection(2);
|
1488 1198 | let fresh = OriginCell::install_selected_h1(&cell, fresh_connection, H1Sender::test(22));
|
1489 1199 |
|
1490 1200 | drop(returning);
|
1491 1201 | cell.complete_establishment(waiter, AcquisitionResult::H1(fresh));
|
1492 1202 |
|
1493 1203 | assert_eq!((2, 1), cell.h1_counts());
|
1494 1204 | let winner = cell
|
1495 1205 | .take_ready_h1(waiter)
|
1496 1206 | .expect("acquisition attempt had no terminal H1");
|
1497 1207 | assert_eq!(11, winner.test_sender_id());
|
1498 1208 | drop(winner);
|
1499 1209 | assert_eq!((2, 2), cell.h1_counts());
|
1500 1210 | }
|
1501 1211 |
|
1502 1212 | #[test]
|
1503 1213 | fn bounded_attempt_remains_eligible_for_a_returned_h1() {
|
1504 1214 | let (admission, cell) = bounded_cell_with_limit(2);
|
1505 1215 | let installed_lease = OriginAdmission::lease_for_test(&admission);
|
1506 1216 | let (connection, _physical) = ConnectionState::bounded(connection_info(1), installed_lease);
|
1507 1217 | let returning = OriginCell::install_selected_h1(&cell, connection, H1Sender::test(11));
|
1508 1218 |
|
1509 - | let waiter = cell.register_waiter(ProtocolRequirement::H1Compatible);
|
1219 + | let waiter = OriginCell::register_waiter(&cell, ProtocolRequirement::H1Compatible);
|
1510 1220 | let AcquisitionEvent::Establish(permit) = cell
|
1511 1221 | .take_ready_event(waiter)
|
1512 1222 | .expect("bounded miss received no capacity")
|
1513 1223 | else {
|
1514 1224 | panic!("bounded miss completed before establishment started");
|
1515 1225 | };
|
1516 1226 | let attempt_lease = permit
|
1517 1227 | .into_lease()
|
1518 1228 | .expect("bounded establishment carried no capacity");
|
1519 1229 | assert_eq!(0, admission.available_capacity_for_test());
|
1520 1230 |
|
1521 1231 | drop(returning);
|
1522 1232 | let selected = cell
|
1523 1233 | .take_ready_h1(waiter)
|
1524 1234 | .expect("returned H1 did not beat the pending establishment");
|
1525 1235 | drop(attempt_lease);
|
1526 1236 | assert_eq!(1, admission.available_capacity_for_test());
|
1527 1237 | drop(selected);
|
1528 1238 | assert_eq!((1, 1), cell.h1_counts());
|
1529 1239 | }
|
1530 1240 |
|
1531 1241 | #[test]
|
1532 1242 | fn older_launching_waiter_precedes_younger_capacity_waiter_for_returned_h1() {
|
1533 1243 | let (admission, cell) = bounded_cell_with_limit(2);
|
1534 1244 | let installed_lease = OriginAdmission::lease_for_test(&admission);
|
1535 1245 | let (connection, _physical) = ConnectionState::bounded(connection_info(1), installed_lease);
|
1536 1246 | let returning = OriginCell::install_selected_h1(&cell, connection, H1Sender::test(11));
|
1537 1247 |
|
1538 - | let older = cell.register_waiter(ProtocolRequirement::H1Compatible);
|
1248 + | let older = OriginCell::register_waiter(&cell, ProtocolRequirement::H1Compatible);
|
1539 1249 | let AcquisitionEvent::Establish(permit) = cell
|
1540 1250 | .take_ready_event(older)
|
1541 1251 | .expect("older waiter received no establishment capacity")
|
1542 1252 | else {
|
1543 1253 | panic!("older waiter completed before establishment started");
|
1544 1254 | };
|
1545 1255 | let attempt_lease = permit
|
1546 1256 | .into_lease()
|
1547 1257 | .expect("bounded establishment carried no capacity");
|
1548 1258 |
|
1549 - | let younger = cell.register_waiter(ProtocolRequirement::H1Compatible);
|
1259 + | let younger = OriginCell::register_waiter(&cell, ProtocolRequirement::H1Compatible);
|
1550 1260 | assert!(cell.take_ready_event(younger).is_none());
|
1551 1261 |
|
1552 1262 | drop(returning);
|
1553 1263 |
|
1554 1264 | let selected = cell
|
1555 1265 | .take_ready_h1(older)
|
1556 1266 | .expect("younger capacity waiter overtook the older launching waiter");
|
1557 1267 | assert_eq!(11, selected.test_sender_id());
|
1558 1268 | assert!(cell.take_ready_event(younger).is_none());
|
1559 1269 |
|
1560 1270 | assert!(OriginCell::cancel_waiter(&cell, younger));
|
1561 1271 | drop(attempt_lease);
|
1562 1272 | drop(selected);
|
1563 1273 | assert_eq!((1, 1), cell.h1_counts());
|
1564 1274 | assert_eq!(1, admission.available_capacity_for_test());
|
1565 1275 | }
|
1566 1276 |
|
1567 1277 | #[test]
|
1568 1278 | fn establishment_failure_is_a_terminal_acquisition_result() {
|
1569 1279 | let cell = unbounded_cell();
|
1570 - | let waiter = cell.register_waiter(ProtocolRequirement::H1Compatible);
|
1280 + | let waiter = OriginCell::register_waiter(&cell, ProtocolRequirement::H1Compatible);
|
1571 1281 | let AcquisitionEvent::Establish(permit) = cell
|
1572 1282 | .take_ready_event(waiter)
|
1573 1283 | .expect("unbounded miss did not start establishment")
|
1574 1284 | else {
|
1575 1285 | panic!("unbounded miss completed before establishment started");
|
1576 1286 | };
|
1577 1287 | assert!(cell.start_establishment(waiter));
|
1578 1288 | drop(permit);
|
1579 1289 |
|
1580 1290 | cell.complete_establishment(
|
1581 1291 | waiter,
|
1582 1292 | AcquisitionResult::Failed(ConnectorError::io(
|
1583 1293 | std::io::Error::other("synthetic establishment failure").into(),
|
1584 1294 | )),
|
1585 1295 | );
|
1586 1296 |
|
1587 1297 | let AcquisitionEvent::Complete(AcquisitionResult::Failed(error)) = cell
|
1588 1298 | .take_ready_event(waiter)
|
1589 1299 | .expect("establishment failure was not delivered")
|
1590 1300 | else {
|
1591 1301 | panic!("establishment failure produced the wrong acquisition event");
|
1592 1302 | };
|
1593 1303 | assert!(error.is_io());
|
1594 1304 | assert_eq!(0, cell.snapshot().retained);
|
1595 1305 | }
|
1596 1306 |
|
1597 1307 | #[test]
|
1598 1308 | fn establishment_completion_clears_the_served_waiters_local_turn() {
|
1599 1309 | let cell = unbounded_cell();
|
1600 1310 | let reuse_id = ReuseId::for_test(1);
|
1601 1311 | {
|
1602 1312 | let mut state = cell.state.lock();
|
1603 1313 | assert!(state.reuse.install_resolving(reuse_id));
|
1604 1314 | assert!(state.reuse.complete_transfer(reuse_id, true));
|
1605 1315 | }
|
1606 - | let waiter = cell.register_waiter(ProtocolRequirement::H1Compatible);
|
1316 + | let waiter = OriginCell::register_waiter(&cell, ProtocolRequirement::H1Compatible);
|
1607 1317 | let AcquisitionEvent::Establish(permit) = cell
|
1608 1318 | .take_ready_event(waiter)
|
1609 1319 | .expect("unbounded miss did not prepare establishment")
|
1610 1320 | else {
|
1611 1321 | panic!("unbounded miss completed before establishment was prepared");
|
1612 1322 | };
|
1613 1323 | assert!(cell.start_establishment(waiter));
|
1614 1324 | drop(permit);
|
1615 1325 | let (connection, _physical) = unbounded_connection(1);
|
1616 1326 | let fresh = OriginCell::install_selected_h1(&cell, connection, H1Sender::test(11));
|
1617 1327 |
|
1618 1328 | cell.complete_establishment(waiter, AcquisitionResult::H1(fresh));
|
1619 1329 |
|
1620 1330 | assert!(!cell.state.lock().reuse.local_turn_owed());
|
1621 1331 | drop(
|
1622 1332 | cell.take_ready_h1(waiter)
|
1623 1333 | .expect("successful establishment result was not retained"),
|
1624 1334 | );
|
1625 1335 | }
|
1626 1336 |
|
1627 1337 | #[test]
|
1628 1338 | fn failed_establishment_preserves_a_turn_for_compatible_successor() {
|
1629 1339 | let cell = unbounded_cell();
|
1630 1340 | let reuse_id = ReuseId::for_test(1);
|
1631 1341 | {
|
1632 1342 | let mut state = cell.state.lock();
|
1633 1343 | assert!(state.reuse.install_resolving(reuse_id));
|
1634 1344 | assert!(state.reuse.complete_transfer(reuse_id, true));
|
1635 1345 | }
|
1636 - | let failed = cell.register_waiter(ProtocolRequirement::H1Compatible);
|
1346 + | let failed = OriginCell::register_waiter(&cell, ProtocolRequirement::H1Compatible);
|
1637 1347 | let AcquisitionEvent::Establish(permit) = cell
|
1638 1348 | .take_ready_event(failed)
|
1639 1349 | .expect("unbounded miss did not prepare establishment")
|
1640 1350 | else {
|
1641 1351 | panic!("unbounded miss completed before establishment was prepared");
|
1642 1352 | };
|
1643 1353 | assert!(cell.start_establishment(failed));
|
1644 1354 | drop(permit);
|
1645 - | let successor = cell.register_waiter(ProtocolRequirement::H1Compatible);
|
1355 + | let successor = OriginCell::register_waiter(&cell, ProtocolRequirement::H1Compatible);
|
1646 1356 |
|
1647 1357 | cell.complete_establishment(
|
1648 1358 | failed,
|
1649 1359 | AcquisitionResult::Failed(ConnectorError::io(
|
1650 1360 | std::io::Error::other("synthetic establishment failure").into(),
|
1651 1361 | )),
|
1652 1362 | );
|
1653 1363 |
|
1654 1364 | assert!(cell.state.lock().reuse.local_turn_owed());
|
1655 1365 | assert!(matches!(
|
1656 1366 | cell.take_ready_event(failed),
|
1657 1367 | Some(AcquisitionEvent::Complete(AcquisitionResult::Failed(_)))
|
1658 1368 | ));
|
1659 1369 | assert!(OriginCell::cancel_waiter(&cell, successor));
|
1660 1370 | assert!(!cell.state.lock().reuse.local_turn_owed());
|
1661 1371 | }
|
1662 1372 |
|
1663 1373 | #[test]
|
1664 1374 | fn failed_establishment_clears_a_turn_after_local_demand_drains() {
|
1665 1375 | let cell = unbounded_cell();
|
1666 1376 | let reuse_id = ReuseId::for_test(1);
|
1667 1377 | {
|
1668 1378 | let mut state = cell.state.lock();
|
1669 1379 | assert!(state.reuse.install_resolving(reuse_id));
|
1670 1380 | assert!(state.reuse.complete_transfer(reuse_id, true));
|
1671 1381 | }
|
1672 - | let waiter = cell.register_waiter(ProtocolRequirement::H1Compatible);
|
1382 + | let waiter = OriginCell::register_waiter(&cell, ProtocolRequirement::H1Compatible);
|
1673 1383 | let AcquisitionEvent::Establish(permit) = cell
|
1674 1384 | .take_ready_event(waiter)
|
1675 1385 | .expect("unbounded miss did not prepare establishment")
|
1676 1386 | else {
|
1677 1387 | panic!("unbounded miss completed before establishment was prepared");
|
1678 1388 | };
|
1679 1389 | assert!(cell.start_establishment(waiter));
|
1680 1390 | drop(permit);
|
1681 1391 |
|
1682 1392 | cell.complete_establishment(
|
1683 1393 | waiter,
|
1684 1394 | AcquisitionResult::Failed(ConnectorError::io(
|
1685 1395 | std::io::Error::other("synthetic establishment failure").into(),
|
1686 1396 | )),
|
1687 1397 | );
|
1688 1398 |
|
1689 1399 | assert!(!cell.state.lock().reuse.local_turn_owed());
|
1690 1400 | assert!(matches!(
|
1691 1401 | cell.take_ready_event(waiter),
|
1692 1402 | Some(AcquisitionEvent::Complete(AcquisitionResult::Failed(_)))
|
1693 1403 | ));
|
1694 1404 | }
|
1695 1405 |
|
1696 1406 | #[test]
|
1697 1407 | fn completion_after_waiter_cancellation_returns_the_new_h1() {
|
1698 1408 | let cell = unbounded_cell();
|
1699 - | let waiter = cell.register_waiter(ProtocolRequirement::H1Compatible);
|
1409 + | let waiter = OriginCell::register_waiter(&cell, ProtocolRequirement::H1Compatible);
|
1700 1410 | let AcquisitionEvent::Establish(permit) = cell
|
1701 1411 | .take_ready_event(waiter)
|
1702 1412 | .expect("unbounded miss did not start establishment")
|
1703 1413 | else {
|
1704 1414 | panic!("unbounded miss completed before establishment started");
|
1705 1415 | };
|
1706 1416 | assert!(cell.start_establishment(waiter));
|
1707 1417 | assert!(OriginCell::cancel_waiter(&cell, waiter));
|
1708 1418 | drop(permit);
|
1709 1419 |
|
1710 1420 | let (connection, _physical) = unbounded_connection(1);
|
1711 1421 | let fresh = OriginCell::install_selected_h1(&cell, connection, H1Sender::test(11));
|
1712 1422 | cell.complete_establishment(waiter, AcquisitionResult::H1(fresh));
|
1713 1423 |
|
1714 1424 | assert_eq!((1, 1), cell.h1_counts());
|
1715 1425 | assert_eq!(
|
1716 1426 | 11,
|
1717 1427 | OriginCell::select_h1(&cell)
|
1718 1428 | .expect("cancelled acquisition lost its completed H1")
|
1719 1429 | .test_sender_id()
|
1720 1430 | );
|
1721 1431 | }
|
1722 1432 |
|
1723 1433 | #[test]
|
1724 1434 | fn returned_h1_prevents_the_first_establishment_poll() {
|
1725 1435 | let cell = unbounded_cell();
|
1726 - | let waiter = cell.register_waiter(ProtocolRequirement::H1Compatible);
|
1436 + | let waiter = OriginCell::register_waiter(&cell, ProtocolRequirement::H1Compatible);
|
1727 1437 | let AcquisitionEvent::Establish(permit) = cell
|
1728 1438 | .take_ready_event(waiter)
|
1729 1439 | .expect("unbounded miss did not prepare establishment")
|
1730 1440 | else {
|
1731 1441 | panic!("unbounded miss completed before establishment was prepared");
|
1732 1442 | };
|
1733 1443 | let (connection, _physical) = unbounded_connection(1);
|
1734 1444 | let returning = OriginCell::install_selected_h1(&cell, connection, H1Sender::test(11));
|
1735 1445 |
|
1736 1446 | drop(returning);
|
1737 1447 | assert!(!cell.start_establishment(waiter));
|
1738 1448 | drop(permit);
|
1739 1449 | let selected = cell
|
1740 1450 | .take_ready_h1(waiter)
|
1741 1451 | .expect("returned H1 did not win before establishment started");
|
1742 1452 | assert_eq!(11, selected.test_sender_id());
|
1743 1453 | }
|
1744 1454 |
|
1745 1455 | #[test]
|
1746 1456 | fn h2_required_head_does_not_take_h1() {
|
1747 1457 | let cell = unbounded_cell();
|
1748 1458 | let (connection, _physical) = unbounded_connection(1);
|
1749 1459 | let selection = OriginCell::install_selected_h1(&cell, connection, H1Sender::test(11));
|
1750 - | let waiter = cell.register_waiter(ProtocolRequirement::H2Required);
|
1460 + | let waiter = OriginCell::register_waiter(&cell, ProtocolRequirement::H2Required);
|
1751 1461 |
|
1752 1462 | drop(selection);
|
1753 1463 |
|
1754 1464 | assert_eq!((1, 1), cell.h1_counts());
|
1755 1465 | let AcquisitionEvent::Establish(permit) = cell
|
1756 1466 | .take_ready_event(waiter)
|
1757 1467 | .expect("unbounded miss did not start establishment")
|
1758 1468 | else {
|
1759 1469 | panic!("H2-required waiter accepted an HTTP/1 selection");
|
1760 1470 | };
|
1761 1471 | assert!(OriginCell::cancel_waiter(&cell, waiter));
|
1762 1472 | drop(permit);
|
1763 1473 | }
|
1764 1474 |
|
1765 1475 | #[test]
|
1766 - | fn cancelling_h2_head_serves_compatible_successor_locally() {
|
1476 + | fn cancelling_h2_head_refunnels_reclaimed_capacity_to_successor() {
|
1767 1477 | let (admission, cell) = bounded_cell();
|
1768 1478 | let lease = OriginAdmission::lease_for_test(&admission);
|
1769 1479 | let (connection, _physical) = ConnectionState::bounded(connection_info(1), lease);
|
1770 1480 | OriginCell::install_idle_h1(&cell, connection, H1Sender::test(11));
|
1771 - | let h2_head = cell.register_waiter(ProtocolRequirement::H2Required);
|
1772 - | let h1_successor = cell.register_waiter(ProtocolRequirement::H1Compatible);
|
1481 + | let h2_head = OriginCell::register_waiter(&cell, ProtocolRequirement::H2Required);
|
1482 + | let h1_successor = OriginCell::register_waiter(&cell, ProtocolRequirement::H1Compatible);
|
1773 1483 |
|
1774 - | assert!(cell.take_ready_event(h2_head).is_none());
|
1484 + | let AcquisitionEvent::Establish(permit) = cell
|
1485 + | .take_ready_event(h2_head)
|
1486 + | .expect("HTTP/2 demand did not receive reclaimed local capacity")
|
1487 + | else {
|
1488 + | panic!("HTTP/2 demand received a protocol handle instead of capacity");
|
1489 + | };
|
1775 1490 | assert!(OriginCell::cancel_waiter(&cell, h2_head));
|
1491 + | drop(permit);
|
1776 1492 |
|
1777 - | let selected = cell
|
1778 - | .take_ready_h1(h1_successor)
|
1779 - | .expect("local idle H1 did not serve the compatible successor");
|
1780 - | assert_eq!(11, selected.test_sender_id());
|
1493 + | let successor = OriginCell::take_ready_lease(&cell, h1_successor)
|
1494 + | .expect("reclaimed capacity did not reach the compatible successor");
|
1495 + | assert_eq!((0, 0), cell.h1_counts());
|
1781 1496 | assert!(cell.state.lock().reuse.is_available());
|
1782 - | drop(selected);
|
1497 + | drop(successor);
|
1783 1498 | }
|
1784 1499 |
|
1785 1500 | #[test]
|
1786 1501 | fn close_marks_selected_record_until_sender_returns() {
|
1787 1502 | let cell = unbounded_cell();
|
1788 1503 | let (connection, _physical) = unbounded_connection(1);
|
1789 1504 | let selection =
|
1790 1505 | OriginCell::install_selected_h1(&cell, connection.clone(), H1Sender::test(11));
|
1791 1506 | let close = H1CloseHandle::new(&cell, selection.connection());
|
1792 1507 |
|
2112 1827 | .expect("cancelled crossing lost its H1 sender")
|
2113 1828 | .test_sender_id()
|
2114 1829 | );
|
2115 1830 | }
|
2116 1831 |
|
2117 1832 | #[test]
|
2118 1833 | fn stale_delivery_is_rejected_after_head_cancellation() {
|
2119 1834 | let (admission, cell) = bounded_cell();
|
2120 1835 | let (first, first_demand) =
|
2121 1836 | cell.register_waiter_without_publish(ProtocolRequirement::H2Required);
|
2122 - | let second = cell.register_waiter(ProtocolRequirement::H1Compatible);
|
1837 + | let second = OriginCell::register_waiter(&cell, ProtocolRequirement::H1Compatible);
|
2123 1838 | let stale = OriginAdmission::publish_without_driving(
|
2124 1839 | &admission,
|
2125 1840 | cell.id().partition(),
|
2126 1841 | first_demand,
|
2127 1842 | )
|
2128 1843 | .expect("first demand did not reserve capacity");
|
2129 1844 |
|
2130 1845 | assert!(OriginCell::cancel_waiter(&cell, first));
|
2131 1846 | OriginAdmission::drive(Some(AdmissionAction::Delivery(stale)));
|
2132 1847 |
|
2133 1848 | let lease = OriginCell::take_ready_lease(&cell, second)
|
2134 1849 | .expect("replacement demand did not receive refunnelled capacity");
|
2135 1850 | drop(lease);
|
2136 1851 | }
|
2137 1852 |
|
2138 1853 | #[test]
|
2139 1854 | fn cancelling_the_only_waiter_retires_admission_demand() {
|
2140 1855 | let (admission, cell, held) = saturated_bounded_cell();
|
2141 - | let waiter = cell.register_waiter(ProtocolRequirement::H1Compatible);
|
1856 + | let waiter = OriginCell::register_waiter(&cell, ProtocolRequirement::H1Compatible);
|
2142 1857 | assert_eq!(1, admission.ordered_demand_count_for_test());
|
2143 1858 |
|
2144 1859 | assert!(OriginCell::cancel_waiter(&cell, waiter));
|
2145 1860 | assert_eq!(0, admission.ordered_demand_count_for_test());
|
2146 1861 | drop(held);
|
2147 1862 | }
|
2148 1863 |
|
2149 1864 | #[test]
|
2150 1865 | fn unknown_waiter_cancellation_is_a_noop() {
|
2151 1866 | let (_admission, cell) = bounded_cell();
|
2152 1867 | assert!(!OriginCell::cancel_waiter(&cell, WaiterId(99)));
|
2153 1868 | }
|
2154 1869 |
|
2155 1870 | #[test]
|
2156 1871 | fn unbounded_waiter_registers_and_cancels_locally() {
|
2157 1872 | let cell = Arc::new(OriginCell::new(
|
2158 1873 | PartitionId::from_index(1),
|
2159 1874 | OriginKey::from_parts(Scheme::HTTPS, "example.com", None).unwrap(),
|
2160 1875 | EligibilityGroup::Pool,
|
2161 1876 | None,
|
2162 1877 | None,
|
2163 1878 | ));
|
2164 1879 |
|
2165 - | let waiter = cell.register_waiter(ProtocolRequirement::H1Compatible);
|
1880 + | let waiter = OriginCell::register_waiter(&cell, ProtocolRequirement::H1Compatible);
|
2166 1881 | assert_eq!(0, cell.snapshot().waiting);
|
2167 1882 | assert_eq!(1, cell.snapshot().retained);
|
2168 1883 | assert!(OriginCell::cancel_waiter(&cell, waiter));
|
2169 1884 | assert_eq!(0, cell.snapshot().retained);
|
2170 1885 | }
|
2171 1886 |
|
2172 1887 | #[test]
|
2173 1888 | fn delivered_waiter_is_woken_once() {
|
2174 1889 | let (_admission, cell, held) = saturated_bounded_cell();
|
2175 - | let waiter = cell.register_waiter(ProtocolRequirement::H1Compatible);
|
1890 + | let waiter = OriginCell::register_waiter(&cell, ProtocolRequirement::H1Compatible);
|
2176 1891 | let counter = StdArc::new(WakeCounter(AtomicUsize::new(0)));
|
2177 1892 | let waker = Waker::from(counter.clone());
|
2178 1893 | let mut context = Context::from_waker(&waker);
|
2179 1894 | assert!(cell.poll_waiter(waiter, &mut context).is_pending());
|
2180 1895 |
|
2181 1896 | drop(held);
|
2182 1897 | assert_eq!(1, counter.0.load(Ordering::Relaxed));
|
2183 1898 | drop(OriginCell::take_ready_lease(&cell, waiter).expect("woken waiter had no capacity"));
|
2184 1899 | assert_eq!(1, counter.0.load(Ordering::Relaxed));
|
2185 1900 | }
|
1901 + |
|
1902 + | fn install_bounded_h2(
|
1903 + | admission: &Arc<OriginAdmission>,
|
1904 + | cell: &Arc<OriginCell>,
|
1905 + | connection_id: u64,
|
1906 + | ) -> (
|
1907 + | h2::H2GenerationId,
|
1908 + | Arc<ConnectionState>,
|
1909 + | super::super::connection::PhysicalConnectionGuard,
|
1910 + | ) {
|
1911 + | let lease = OriginAdmission::lease_for_test(admission);
|
1912 + | let (connection, physical) = ConnectionState::bounded(
|
1913 + | ConnectionInfo::for_test(ConnectionId::new(connection_id), cell.id().partition()),
|
1914 + | lease,
|
1915 + | );
|
1916 + | let generation =
|
1917 + | OriginCell::install_h2_for_test(cell, connection.clone(), connection_id, None);
|
1918 + | (generation, connection, physical)
|
1919 + | }
|
1920 + |
|
1921 + | fn take_ready_h2(cell: &OriginCell, waiter: WaiterId) -> h2::H2Activation {
|
1922 + | match cell
|
1923 + | .take_ready_event(waiter)
|
1924 + | .expect("HTTP/2 waiter did not receive an activation")
|
1925 + | {
|
1926 + | AcquisitionEvent::Complete(AcquisitionResult::H2(activation)) => activation,
|
1927 + | AcquisitionEvent::Establish(_) => {
|
1928 + | panic!("HTTP/2 waiter received establishment capacity")
|
1929 + | }
|
1930 + | AcquisitionEvent::Complete(AcquisitionResult::H1(_)) => {
|
1931 + | panic!("HTTP/2 waiter received an HTTP/1 sender")
|
1932 + | }
|
1933 + | AcquisitionEvent::Complete(AcquisitionResult::Reacquire) => {
|
1934 + | panic!("HTTP/2 waiter was returned for reacquisition")
|
1935 + | }
|
1936 + | AcquisitionEvent::Complete(AcquisitionResult::Failed(error)) => {
|
1937 + | panic!("HTTP/2 waiter received an establishment failure: {error}")
|
1938 + | }
|
1939 + | }
|
1940 + | }
|
1941 + |
|
1942 + | #[test]
|
1943 + | fn eligible_peer_h2_generation_satisfies_bounded_demand() {
|
1944 + | let (admission, connection_cell, requesting_cell) =
|
1945 + | bounded_peer_cells(1, EligibilityGroup::Pool, EligibilityGroup::Pool);
|
1946 + | let (generation, connection, _physical) =
|
1947 + | install_bounded_h2(&admission, &connection_cell, 1);
|
1948 + |
|
1949 + | let waiter = OriginCell::register_waiter(&requesting_cell, ProtocolRequirement::H2Required);
|
1950 + | let activation = take_ready_h2(&requesting_cell, waiter);
|
1951 + |
|
1952 + | assert_eq!(generation, activation.generation());
|
1953 + | assert!(Arc::ptr_eq(&connection, activation.connection()));
|
1954 + | assert_eq!(0, admission.available_capacity_for_test());
|
1955 + | assert_eq!(Some(generation), connection_cell.accepting_h2_generation());
|
1956 + | drop(activation);
|
1957 + | assert_eq!(0, requesting_cell.snapshot().retained);
|
1958 + | assert!(OriginCell::close_h2(
|
1959 + | &connection_cell,
|
1960 + | generation,
|
1961 + | CloseReason::PoolDropped,
|
1962 + | ));
|
1963 + | assert_eq!(1, admission.available_capacity_for_test());
|
1964 + | }
|
1965 + |
|
1966 + | #[test]
|
1967 + | fn peer_h2_publication_respects_eligibility_group() {
|
1968 + | let groups = [
|
1969 + | (
|
1970 + | EligibilityGroup::Partition(PartitionId::from_index(1)),
|
1971 + | EligibilityGroup::Partition(PartitionId::from_index(2)),
|
1972 + | ),
|
1973 + | (
|
1974 + | EligibilityGroup::NetworkInterface(Some(Arc::from("eth0"))),
|
1975 + | EligibilityGroup::NetworkInterface(Some(Arc::from("eth1"))),
|
1976 + | ),
|
1977 + | ];
|
1978 + | for (connection_group, requesting_group) in groups {
|
1979 + | let (admission, connection_cell, requesting_cell) =
|
1980 + | bounded_peer_cells(1, connection_group, requesting_group);
|
1981 + | let (generation, _connection, _physical) =
|
1982 + | install_bounded_h2(&admission, &connection_cell, 1);
|
1983 + | let waiter =
|
1984 + | OriginCell::register_waiter(&requesting_cell, ProtocolRequirement::H2Required);
|
1985 + |
|
1986 + | assert!(requesting_cell.take_ready_event(waiter).is_none());
|
1987 + | assert_eq!(1, admission.ordered_demand_count_for_test());
|
1988 + | assert!(OriginCell::cancel_waiter(&requesting_cell, waiter));
|
1989 + | assert!(OriginCell::close_h2(
|
1990 + | &connection_cell,
|
1991 + | generation,
|
1992 + | CloseReason::PoolDropped,
|
1993 + | ));
|
1994 + | assert_eq!(1, admission.available_capacity_for_test());
|
1995 + | }
|
1996 + | }
|
1997 + |
|
1998 + | #[test]
|
1999 + | fn dropped_publication_guard_retries_the_same_demand() {
|
2000 + | let (admission, connection_cell, requesting_cell) =
|
2001 + | bounded_peer_cells(1, EligibilityGroup::Pool, EligibilityGroup::Pool);
|
2002 + | let (generation, _connection, _physical) =
|
2003 + | install_bounded_h2(&admission, &connection_cell, 1);
|
2004 + | let (waiter, demand) =
|
2005 + | requesting_cell.register_waiter_without_publish(ProtocolRequirement::H2Required);
|
2006 + | let action = OriginAdmission::publish_action_without_driving(
|
2007 + | &admission,
|
2008 + | requesting_cell.id().partition(),
|
2009 + | demand,
|
2010 + | )
|
2011 + | .expect("peer demand did not prepare publication");
|
2012 + |
|
2013 + | drop(action);
|
2014 + |
|
2015 + | drop(take_ready_h2(&requesting_cell, waiter));
|
2016 + | assert_eq!(0, admission.ordered_demand_count_for_test());
|
2017 + | assert!(OriginCell::close_h2(
|
2018 + | &connection_cell,
|
2019 + | generation,
|
2020 + | CloseReason::PoolDropped,
|
2021 + | ));
|
2022 + | }
|
2023 + |
|
2024 + | #[test]
|
2025 + | fn requesting_cell_cancellation_closes_an_in_flight_publication_fence() {
|
2026 + | let (admission, connection_cell, requesting_cell) =
|
2027 + | bounded_peer_cells(1, EligibilityGroup::Pool, EligibilityGroup::Pool);
|
2028 + | let (generation, _connection, _physical) =
|
2029 + | install_bounded_h2(&admission, &connection_cell, 1);
|
2030 + | let (waiter, demand) =
|
2031 + | requesting_cell.register_waiter_without_publish(ProtocolRequirement::H2Required);
|
2032 + | let action = OriginAdmission::publish_action_without_driving(
|
2033 + | &admission,
|
2034 + | requesting_cell.id().partition(),
|
2035 + | demand,
|
2036 + | )
|
2037 + | .expect("peer demand did not prepare publication");
|
2038 + |
|
2039 + | assert!(OriginCell::cancel_waiter(&requesting_cell, waiter));
|
2040 + | OriginAdmission::drive(Some(action));
|
2041 + |
|
2042 + | assert_eq!(0, requesting_cell.snapshot().retained);
|
2043 + | assert_eq!(0, admission.ordered_demand_count_for_test());
|
2044 + | assert_eq!(0, admission.available_capacity_for_test());
|
2045 + | assert_eq!(Some(generation), connection_cell.accepting_h2_generation());
|
2046 + | assert!(OriginCell::close_h2(
|
2047 + | &connection_cell,
|
2048 + | generation,
|
2049 + | CloseReason::PoolDropped,
|
2050 + | ));
|
2051 + | }
|
2052 + |
|
2053 + | #[test]
|
2054 + | fn stale_publication_retries_against_a_replacement_generation() {
|
2055 + | let (admission, connection_cell, requesting_cell) =
|
2056 + | bounded_peer_cells(1, EligibilityGroup::Pool, EligibilityGroup::Pool);
|
2057 + | let (first, _first_connection, _first_physical) =
|
2058 + | install_bounded_h2(&admission, &connection_cell, 1);
|
2059 + | let (waiter, demand) =
|
2060 + | requesting_cell.register_waiter_without_publish(ProtocolRequirement::H2Required);
|
2061 + | let action = OriginAdmission::publish_action_without_driving(
|
2062 + | &admission,
|
2063 + | requesting_cell.id().partition(),
|
2064 + | demand,
|
2065 + | )
|
2066 + | .expect("peer demand did not prepare publication");
|
2067 + |
|
2068 + | assert!(OriginCell::close_h2(
|
2069 + | &connection_cell,
|
2070 + | first,
|
2071 + | CloseReason::ProtocolClosed
|
2072 + | ));
|
2073 + | let (second, _second_connection, _second_physical) =
|
2074 + | install_bounded_h2(&admission, &connection_cell, 2);
|
2075 + | OriginAdmission::drive(Some(action));
|
2076 + |
|
2077 + | let activation = take_ready_h2(&requesting_cell, waiter);
|
2078 + | assert_eq!(second, activation.generation());
|
2079 + | drop(activation);
|
2080 + | assert!(OriginCell::close_h2(
|
2081 + | &connection_cell,
|
2082 + | second,
|
2083 + | CloseReason::PoolDropped
|
2084 + | ));
|
2085 + | }
|
2086 + |
|
2087 + | #[test]
|
2088 + | fn stale_peer_route_republishes_demand_to_a_replacement_generation() {
|
2089 + | let (admission, connection_cell, requesting_cell) =
|
2090 + | bounded_peer_cells(1, EligibilityGroup::Pool, EligibilityGroup::Pool);
|
2091 + | let (first, _first_connection, _first_physical) =
|
2092 + | install_bounded_h2(&admission, &connection_cell, 1);
|
2093 + | let (waiter, demand) =
|
2094 + | requesting_cell.register_waiter_without_publish(ProtocolRequirement::H2Required);
|
2095 + | assert!(OriginCell::install_h2_route(
|
2096 + | &requesting_cell,
|
2097 + | h2::H2Route::new(&connection_cell, first),
|
2098 + | &EligibilityGroup::Pool,
|
2099 + | demand.id_for_test(),
|
2100 + | ));
|
2101 + |
|
2102 + | assert!(OriginCell::close_h2(
|
2103 + | &connection_cell,
|
2104 + | first,
|
2105 + | CloseReason::ProtocolClosed
|
2106 + | ));
|
2107 + | let (second, _second_connection, _second_physical) =
|
2108 + | install_bounded_h2(&admission, &connection_cell, 2);
|
2109 + | OriginCell::service_peer_h2_waiters(&requesting_cell);
|
2110 + | let activation = take_ready_h2(&requesting_cell, waiter);
|
2111 + | assert_eq!(second, activation.generation());
|
2112 + | drop(activation);
|
2113 + | assert!(OriginCell::close_h2(
|
2114 + | &connection_cell,
|
2115 + | second,
|
2116 + | CloseReason::PoolDropped
|
2117 + | ));
|
2118 + | }
|
2119 + |
|
2120 + | #[test]
|
2121 + | fn direct_selection_removes_a_stale_peer_route() {
|
2122 + | let (admission, connection_cell, requesting_cell) =
|
2123 + | bounded_peer_cells(1, EligibilityGroup::Pool, EligibilityGroup::Pool);
|
2124 + | let (generation, _connection, _physical) =
|
2125 + | install_bounded_h2(&admission, &connection_cell, 1);
|
2126 + | let waiter = OriginCell::register_waiter(&requesting_cell, ProtocolRequirement::H2Required);
|
2127 + | drop(take_ready_h2(&requesting_cell, waiter));
|
2128 + |
|
2129 + | assert!(OriginCell::close_h2(
|
2130 + | &connection_cell,
|
2131 + | generation,
|
2132 + | CloseReason::ProtocolClosed,
|
2133 + | ));
|
2134 + | assert!(OriginCell::select_h2(&requesting_cell).is_none());
|
2135 + | assert!(
|
2136 + | !requesting_cell.state.lock().h2.has_visible_h2(),
|
2137 + | "direct selection retained a stale peer route"
|
2138 + | );
|
2139 + | }
|
2140 + |
|
2141 + | #[test]
|
2142 + | fn open_peer_route_allows_concurrent_prospective_activations() {
|
2143 + | let (admission, connection_cell, requesting_cell) =
|
2144 + | bounded_peer_cells(1, EligibilityGroup::Pool, EligibilityGroup::Pool);
|
2145 + | let (generation, _connection, _physical) =
|
2146 + | install_bounded_h2(&admission, &connection_cell, 1);
|
2147 + | let waiter = OriginCell::register_waiter(&requesting_cell, ProtocolRequirement::H2Required);
|
2148 + | drop(take_ready_h2(&requesting_cell, waiter));
|
2149 + |
|
2150 + | let first = OriginCell::select_h2(&requesting_cell)
|
2151 + | .expect("first direct peer activation was not selected");
|
2152 + | let second = OriginCell::select_h2(&requesting_cell)
|
2153 + | .expect("open peer route serialized the second activation");
|
2154 + | assert_eq!(generation, first.generation());
|
2155 + | assert_eq!(generation, second.generation());
|
2156 + | assert_eq!(Some((2, 0)), connection_cell.h2_request_counts(generation));
|
2157 + | drop(first);
|
2158 + | drop(second);
|
2159 + | assert!(OriginCell::close_h2(
|
2160 + | &connection_cell,
|
2161 + | generation,
|
2162 + | CloseReason::PoolDropped
|
2163 + | ));
|
2164 + | }
|
2165 + |
|
2166 + | #[test]
|
2167 + | fn peer_generation_gate_preserves_prepublication_order() {
|
2168 + | let (admission, connection_cell, requesting_cell) =
|
2169 + | bounded_peer_cells(1, EligibilityGroup::Pool, EligibilityGroup::Pool);
|
2170 + | let (generation, _connection, _physical) =
|
2171 + | install_bounded_h2(&admission, &connection_cell, 1);
|
2172 + | let (first, demand) =
|
2173 + | requesting_cell.register_waiter_without_publish(ProtocolRequirement::H2Required);
|
2174 + | let second = OriginCell::register_waiter(&requesting_cell, ProtocolRequirement::H2Required);
|
2175 + | let action = OriginAdmission::publish_action_without_driving(
|
2176 + | &admission,
|
2177 + | requesting_cell.id().partition(),
|
2178 + | demand,
|
2179 + | )
|
2180 + | .expect("peer demand did not prepare publication");
|
2181 + | OriginAdmission::drive(Some(action));
|
2182 + | let third = OriginCell::register_waiter(&requesting_cell, ProtocolRequirement::H2Required);
|
2183 + |
|
2184 + | let first_activation = take_ready_h2(&requesting_cell, first);
|
2185 + | assert!(requesting_cell.take_ready_event(second).is_none());
|
2186 + | assert!(requesting_cell.take_ready_event(third).is_none());
|
2187 + | drop(first_activation);
|
2188 + |
|
2189 + | let second_activation = take_ready_h2(&requesting_cell, second);
|
2190 + | assert!(requesting_cell.take_ready_event(third).is_none());
|
2191 + | drop(second_activation);
|
2192 + | drop(take_ready_h2(&requesting_cell, third));
|
2193 + | assert!(OriginCell::close_h2(
|
2194 + | &connection_cell,
|
2195 + | generation,
|
2196 + | CloseReason::PoolDropped,
|
2197 + | ));
|
2198 + | }
|
2186 2199 | }
|
2187 2200 |
|
2188 2201 | #[cfg(all(test, smithy_http_client_loom))]
|
2189 2202 | mod loom_tests {
|
2190 2203 | use super::*;
|
2191 2204 | use http_1x::uri::Scheme;
|
2192 2205 | use std::num::NonZeroUsize;
|
2193 2206 |
|
2194 2207 | fn bounded_cell() -> (Arc<OriginAdmission>, Arc<OriginCell>) {
|
2195 2208 | bounded_cell_with_limit(1)
|
2573 2587 | AcquisitionEvent::Complete(AcquisitionResult::H1(selection)) => {
|
2574 2588 | drop(selection);
|
2575 2589 | }
|
2576 2590 | AcquisitionEvent::Establish(permit) => {
|
2577 2591 | assert!(OriginCell::cancel_waiter(&requesting_cell, waiter));
|
2578 2592 | drop(permit);
|
2579 2593 | }
|
2580 2594 | AcquisitionEvent::Complete(AcquisitionResult::Failed(error)) => {
|
2581 2595 | panic!("unexpected establishment failure: {error}")
|
2582 2596 | }
|
2597 + | AcquisitionEvent::Complete(AcquisitionResult::Reacquire) => {
|
2598 + | panic!("HTTP/1 reuse model requested reacquisition")
|
2599 + | }
|
2600 + | AcquisitionEvent::Complete(AcquisitionResult::H2(_)) => {
|
2601 + | panic!("HTTP/1 reuse model received an HTTP/2 activation")
|
2602 + | }
|
2583 2603 | }
|
2584 2604 | assert_eq!(0, requesting_cell.snapshot().retained);
|
2585 2605 | assert_eq!(1, admission.available_capacity_for_test());
|
2586 2606 | admission.clear_modeled_cells_for_test();
|
2587 2607 | });
|
2588 2608 | }
|
2589 2609 |
|
2590 2610 | #[test]
|
2591 2611 | fn reclaim_and_connection_close_release_exactly_one_capacity_slot() {
|
2592 2612 | let mut model = loom::model::Builder::new();
|
2593 2613 | model.preemption_bound = Some(2);
|
2594 2614 | model.check(|| {
|
2595 2615 | let (admission, connection_cell, requesting_cell) = bounded_peer_cells(
|
2596 2616 | EligibilityGroup::Partition(PartitionId::from_index(1)),
|
2597 2617 | EligibilityGroup::Partition(PartitionId::from_index(2)),
|
2598 2618 | );
|
2599 2619 | let lease = OriginAdmission::lease_for_test(&admission);
|
2600 2620 | let (connection, _physical) = ConnectionState::bounded(connection_info(1), lease);
|
2601 2621 | let returning = OriginCell::install_selected_h1(
|
2602 2622 | &connection_cell,
|
2603 2623 | connection.clone(),
|
2604 2624 | H1Sender::test(11),
|
2605 2625 | );
|
2606 2626 | let close = H1CloseHandle::new(&connection_cell, &connection);
|
2607 - | let waiter = requesting_cell.register_waiter(ProtocolRequirement::H1Compatible);
|
2608 - | let local_waiter = connection_cell.register_waiter(ProtocolRequirement::H1Compatible);
|
2627 + | let waiter =
|
2628 + | OriginCell::register_waiter(&requesting_cell, ProtocolRequirement::H1Compatible);
|
2629 + | let local_waiter =
|
2630 + | OriginCell::register_waiter(&connection_cell, ProtocolRequirement::H1Compatible);
|
2609 2631 |
|
2610 2632 | let returning = loom::thread::spawn(move || drop(returning));
|
2611 2633 | let closing = loom::thread::spawn(move || close.close(CloseReason::Poisoned));
|
2612 2634 | returning.join().unwrap();
|
2613 2635 | let close_won = closing.join().unwrap();
|
2614 2636 |
|
2615 2637 | assert_eq!(
|
2616 2638 | !close_won,
|
2617 2639 | connection_cell.state.lock().reuse.local_turn_owed(),
|
2618 2640 | "only a completed reclaim may earn the connection-owning cell a local fairness turn"
|
2619 2641 | );
|
2620 2642 |
|
2621 2643 | let replacement = OriginCell::take_ready_lease(&requesting_cell, waiter)
|
2622 2644 | .expect("close/reclaim race did not deliver released capacity");
|
2623 2645 | assert_eq!((0, 0), connection_cell.h1_counts());
|
2624 2646 | assert_eq!(0, admission.available_capacity_for_test());
|
2625 2647 | assert!(OriginCell::cancel_waiter(&connection_cell, local_waiter));
|
2626 2648 | drop(replacement);
|
2627 2649 | assert_eq!(1, admission.available_capacity_for_test());
|
2628 2650 | admission.clear_modeled_cells_for_test();
|
2629 2651 | });
|
2630 2652 | }
|
2631 2653 |
|
2632 2654 | #[test]
|
2633 2655 | fn delivery_and_cancellation_race_refunnels_capacity() {
|
2634 2656 | loom::model(|| {
|
2635 2657 | let (admission, cell) = bounded_cell();
|
2636 2658 | let (first, demand) =
|
2637 2659 | cell.register_waiter_without_publish(ProtocolRequirement::H1Compatible);
|
2638 2660 | let mut delivery =
|
2639 2661 | OriginAdmission::publish_without_driving(&admission, cell.id().partition(), demand)
|
2640 2662 | .unwrap();
|
2641 2663 | assert!(
|
2642 2664 | delivery.materialize_for_test(),
|
2643 2665 | "capacity delivery did not materialize"
|
2644 2666 | );
|
2645 2667 |
|
2646 2668 | let delivery_cell = cell.clone();
|
2647 2669 | let deliver = loom::thread::spawn(move || {
|
2648 2670 | drop(OriginCell::receive_delivery(&delivery_cell, delivery));
|
2649 2671 | });
|
2650 2672 | let cancel_cell = cell.clone();
|
2651 2673 | let cancel =
|
2652 2674 | loom::thread::spawn(move || OriginCell::cancel_waiter(&cancel_cell, first));
|
2653 2675 | deliver.join().unwrap();
|
2654 2676 | cancel.join().unwrap();
|
2655 2677 |
|
2656 2678 | assert_eq!(1, admission.available_capacity_for_test());
|
2657 2679 | admission.clear_modeled_cells_for_test();
|
2658 2680 | });
|
2659 2681 | }
|
2682 + |
|
2683 + | #[test]
|
2684 + | fn h2_route_installation_races_requesting_cell_cancellation() {
|
2685 + | let mut model = loom::model::Builder::new();
|
2686 + | model.preemption_bound = Some(2);
|
2687 + | model.check(|| {
|
2688 + | let (admission, connection_cell, requesting_cell) =
|
2689 + | bounded_peer_cells(EligibilityGroup::Pool, EligibilityGroup::Pool);
|
2690 + | let lease = OriginAdmission::lease_for_test(&admission);
|
2691 + | let (connection, _physical) = ConnectionState::bounded(connection_info(1), lease);
|
2692 + | let generation = OriginCell::install_h2_for_test(&connection_cell, connection, 1, None);
|
2693 + | let (waiter, demand) =
|
2694 + | requesting_cell.register_waiter_without_publish(ProtocolRequirement::H2Required);
|
2695 + | let route = h2::H2Route::new(&connection_cell, generation);
|
2696 + |
|
2697 + | let install_cell = requesting_cell.clone();
|
2698 + | let installing = loom::thread::spawn(move || {
|
2699 + | OriginCell::install_h2_route(
|
2700 + | &install_cell,
|
2701 + | route,
|
2702 + | &EligibilityGroup::Pool,
|
2703 + | demand.id_for_test(),
|
2704 + | )
|
2705 + | });
|
2706 + | let cancel_cell = requesting_cell.clone();
|
2707 + | let cancelling =
|
2708 + | loom::thread::spawn(move || OriginCell::cancel_waiter(&cancel_cell, waiter));
|
2709 + | let _installed = installing.join().unwrap();
|
2710 + | assert!(cancelling.join().unwrap());
|
2711 + |
|
2712 + | assert_eq!(0, requesting_cell.snapshot().retained);
|
2713 + | assert_eq!(0, admission.ordered_demand_count_for_test());
|
2714 + | assert_eq!(0, admission.available_capacity_for_test());
|
2715 + | assert!(OriginCell::close_h2(
|
2716 + | &connection_cell,
|
2717 + | generation,
|
2718 + | CloseReason::PoolDropped,
|
2719 + | ));
|
2720 + | assert_eq!(1, admission.available_capacity_for_test());
|
2721 + | admission.clear_modeled_cells_for_test();
|
2722 + | });
|
2723 + | }
|
2724 + |
|
2725 + | #[test]
|
2726 + | fn h2_publication_acknowledgement_races_generation_close_and_route_service() {
|
2727 + | let mut model = loom::model::Builder::new();
|
2728 + | model.preemption_bound = Some(3);
|
2729 + | model.check(|| {
|
2730 + | let (admission, connection_cell, requesting_cell) =
|
2731 + | bounded_peer_cells(EligibilityGroup::Pool, EligibilityGroup::Pool);
|
2732 + | let lease = OriginAdmission::lease_for_test(&admission);
|
2733 + | let (connection, _physical) = ConnectionState::bounded(connection_info(1), lease);
|
2734 + | let generation = OriginCell::install_h2_for_test(&connection_cell, connection, 1, None);
|
2735 + | let (waiter, demand) =
|
2736 + | requesting_cell.register_waiter_without_publish(ProtocolRequirement::H2Required);
|
2737 + | let action = OriginAdmission::publish_action_without_driving(
|
2738 + | &admission,
|
2739 + | requesting_cell.id().partition(),
|
2740 + | demand,
|
2741 + | )
|
2742 + | .expect("peer demand did not prepare publication");
|
2743 + |
|
2744 + | let publishing = loom::thread::spawn(move || {
|
2745 + | OriginAdmission::drive(Some(action));
|
2746 + | });
|
2747 + | let closing_cell = connection_cell.clone();
|
2748 + | let closing = loom::thread::spawn(move || {
|
2749 + | OriginCell::close_h2(&closing_cell, generation, CloseReason::Poisoned)
|
2750 + | });
|
2751 + | let servicing_cell = requesting_cell.clone();
|
2752 + | let servicing = loom::thread::spawn(move || {
|
2753 + | OriginCell::service_peer_h2_waiters(&servicing_cell);
|
2754 + | });
|
2755 + |
|
2756 + | publishing.join().unwrap();
|
2757 + | assert!(closing.join().unwrap());
|
2758 + | servicing.join().unwrap();
|
2759 + |
|
2760 + | assert!(
|
2761 + | OriginCell::cancel_waiter(&requesting_cell, waiter),
|
2762 + | "publication acknowledgement lost the live demand"
|
2763 + | );
|
2764 + | assert_eq!(0, requesting_cell.snapshot().retained);
|
2765 + | assert_eq!(0, admission.ordered_demand_count_for_test());
|
2766 + | assert_eq!(1, admission.available_capacity_for_test());
|
2767 + | admission.clear_modeled_cells_for_test();
|
2768 + | });
|
2769 + | }
|
2770 + |
|
2771 + | #[test]
|
2772 + | fn h2_publication_close_race_preserves_capacity_ownership() {
|
2773 + | let mut model = loom::model::Builder::new();
|
2774 + | model.preemption_bound = Some(2);
|
2775 + | model.check(|| {
|
2776 + | let (admission, connection_cell, requesting_cell) =
|
2777 + | bounded_peer_cells(EligibilityGroup::Pool, EligibilityGroup::Pool);
|
2778 + | let lease = OriginAdmission::lease_for_test(&admission);
|
2779 + | let (connection, _physical) = ConnectionState::bounded(connection_info(1), lease);
|
2780 + | let generation = OriginCell::install_h2_for_test(&connection_cell, connection, 1, None);
|
2781 + | let (waiter, demand) =
|
2782 + | requesting_cell.register_waiter_without_publish(ProtocolRequirement::H2Required);
|
2783 + | let action = OriginAdmission::publish_action_without_driving(
|
2784 + | &admission,
|
2785 + | requesting_cell.id().partition(),
|
2786 + | demand,
|
2787 + | )
|
2788 + | .expect("peer demand did not prepare publication");
|
2789 + |
|
2790 + | let publishing = loom::thread::spawn(move || {
|
2791 + | OriginAdmission::drive(Some(action));
|
2792 + | });
|
2793 + | let closing_cell = connection_cell.clone();
|
2794 + | let closing = loom::thread::spawn(move || {
|
2795 + | OriginCell::close_h2(&closing_cell, generation, CloseReason::Poisoned)
|
2796 + | });
|
2797 + | publishing.join().unwrap();
|
2798 + | assert!(closing.join().unwrap());
|
2799 + |
|
2800 + | assert!(OriginCell::cancel_waiter(&requesting_cell, waiter));
|
2801 + | assert_eq!(0, requesting_cell.snapshot().retained);
|
2802 + | assert_eq!(0, admission.ordered_demand_count_for_test());
|
2803 + | assert_eq!(1, admission.available_capacity_for_test());
|
2804 + | admission.clear_modeled_cells_for_test();
|
2805 + | });
|
2806 + | }
|
2660 2807 | }
|