40 40 | //! DeliveryCancelled --delivery-> removed; return payloads
|
41 41 | //! ReadyToEstablish / Ready -----> removed; return the held event
|
42 42 | //! Launching --------------------> removed; task settles separately
|
43 43 | //! ```
|
44 44 | //!
|
45 45 | //! Admission updates, returned values, and task wakes are detached for the
|
46 46 | //! caller to process after releasing the cell lock.
|
47 47 |
|
48 48 | use super::super::admission::{DemandId, DemandSnapshot, ProtocolRequirement, SnapshotVersion};
|
49 49 | use super::super::partition::EligibilityGroup;
|
50 - | use super::{AcquisitionEvent, AcquisitionResult, EstablishmentPermit};
|
50 + | use super::{AcquisitionOutcome, AcquisitionStep, EstablishmentPermit};
|
51 51 | use std::collections::{BTreeSet, HashMap};
|
52 52 | use std::num::NonZeroUsize;
|
53 53 | use std::task::{Context, Poll, Waker};
|
54 54 |
|
55 55 | /// Local waiter identity within one cell.
|
56 56 | #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
57 57 | pub(in crate::client::pool) struct WaiterId(pub(in crate::client::pool) u64);
|
58 58 |
|
59 59 | /// Complete acquisition state protected by one origin cell lock.
|
60 60 | ///
|
61 61 | /// At every completed transition:
|
62 62 | ///
|
63 63 | /// - `records` owns every retained acquisition attempt.
|
64 64 | /// - [`WaitingQueueState::Active`] contains exactly the linked
|
65 65 | /// [`WaiterState::Waiting`] records.
|
66 66 | /// - The active demand identifies and describes the FIFO head.
|
67 - | /// - `h1_candidates` contains exactly each H1-compatible
|
67 + | /// - `h1_compatible_waiters` contains exactly each H1-compatible
|
68 68 | /// [`WaiterState::DeliveryPending`] without a pending H1,
|
69 69 | /// [`WaiterState::ReadyToEstablish`], or [`WaiterState::Launching`] attempt.
|
70 - | /// - `h2_activation_candidates` contains every delivery-pending, ready-to-establish, or
|
70 + | /// - `h2_compatible_waiters` contains every delivery-pending, ready-to-establish, or
|
71 71 | /// launching attempt that has not received another result. The waiting FIFO
|
72 72 | /// head is compared with this set without duplicating its queue residence.
|
73 73 | /// - Delivery-pending, ready-to-establish, launching, delivery-cancelled, and
|
74 74 | /// ready attempts remain in `records` but never in the bounded FIFO.
|
75 75 | ///
|
76 76 | /// Transitions return admission publications, values requiring fallback, and
|
77 77 | /// wakers to the caller; none runs while this state is locked.
|
78 78 | #[derive(Debug, Default)]
|
79 79 | pub(super) struct AcquisitionQueue {
|
80 80 | /// Every attempt still waiting, launching, or holding a terminal result.
|
81 81 | records: HashMap<WaiterId, WaiterRecord>,
|
82 82 | /// FIFO endpoints and demand for the records currently waiting.
|
83 83 | waiting: WaitingQueueState,
|
84 84 | /// H1-compatible attempts still competing with a returned sender.
|
85 - | h1_candidates: BTreeSet<WaiterId>,
|
85 + | h1_compatible_waiters: BTreeSet<WaiterId>,
|
86 86 | /// Attempts outside the FIFO that may accept an H2 activation.
|
87 - | h2_activation_candidates: BTreeSet<WaiterId>,
|
87 + | h2_compatible_waiters: BTreeSet<WaiterId>,
|
88 88 | /// Next cell-local waiter identity.
|
89 - | next_waiter: u64,
|
89 + | next_waiter_id: u64,
|
90 90 | /// Next identity for a head-waiter demand generation.
|
91 91 | next_demand_id: u64,
|
92 92 | }
|
93 93 |
|
94 94 | /// Owner-runtime progress of one submitted establishment task.
|
95 95 | #[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
96 96 | enum EstablishmentPhase {
|
97 97 | /// The task was submitted but has not begun polling the connector.
|
98 98 | Submitted,
|
99 99 | /// The owner runtime polled the task and the pool owns completion.
|
100 100 | Started,
|
101 101 | }
|
102 102 |
|
103 103 | /// Whether the cell has an active FIFO and aggregate head demand.
|
104 104 | #[derive(Debug, Default)]
|
105 105 | enum WaitingQueueState {
|
106 106 | /// No waiter is linked and no demand is active.
|
107 107 | #[default]
|
108 108 | Empty,
|
109 109 | /// A nonempty FIFO represented by its endpoints, length, and head demand.
|
110 110 | Active {
|
111 111 | /// Oldest waiting record.
|
112 112 | head: WaiterId,
|
113 113 | /// Youngest waiting record.
|
114 114 | tail: WaiterId,
|
115 115 | /// Number of records linked from `head` through `tail`.
|
116 116 | len: NonZeroUsize,
|
117 117 | /// Demand generation represented by `head`.
|
118 118 | demand: DemandTicket,
|
119 119 | },
|
120 120 | }
|
121 121 |
|
122 - | /// One request retained while waiting for or owning an acquisition result.
|
122 + | /// One request retained while waiting for or owning an acquisition outcome.
|
123 123 | #[derive(Debug)]
|
124 124 | struct WaiterRecord {
|
125 125 | /// Protocol requirement published while this waiter is the head.
|
126 126 | requirement: ProtocolRequirement,
|
127 - | /// Queue residence, delivery state, and any owned result.
|
127 + | /// Queue residence, delivery state, and any owned outcome.
|
128 128 | state: WaiterState,
|
129 129 | }
|
130 130 |
|
131 131 | /// Authoritative cell-local ownership state for one waiter.
|
132 132 | #[derive(Debug)]
|
133 133 | enum WaiterState {
|
134 134 | /// The waiter is linked in the cell-local FIFO.
|
135 135 | Waiting {
|
136 136 | /// Older waiting record, or `None` at the head.
|
137 137 | previous: Option<WaiterId>,
|
138 138 | /// Newer waiting record, or `None` at the tail.
|
139 139 | next: Option<WaiterId>,
|
140 - | /// Latest task waiting for an acquisition result.
|
140 + | /// Latest task waiting for an acquisition step.
|
141 141 | waker: Option<Waker>,
|
142 142 | },
|
143 143 | /// A committed capacity or reuse delivery reserved this waiter but has
|
144 144 | /// not installed its payload.
|
145 145 | DeliveryPending {
|
146 146 | /// Latest task waiting for the crossing delivery.
|
147 147 | waker: Option<Waker>,
|
148 - | /// Local protocol result that won while delivery was crossing.
|
149 - | pending_result: Option<AcquisitionResult>,
|
148 + | /// Local protocol outcome that won while delivery was crossing.
|
149 + | pending_result: Option<AcquisitionOutcome>,
|
150 150 | },
|
151 151 | /// Cancellation won after reservation while the committed delivery was
|
152 152 | /// outside the lock.
|
153 153 | DeliveryCancelled {
|
154 154 | /// Task detached for wake after the crossing payload is returned.
|
155 155 | waker: Option<Waker>,
|
156 - | /// Protocol result returned after the crossing closes, if one arrived first.
|
157 - | pending_result: Option<AcquisitionResult>,
|
156 + | /// Protocol outcome returned after the crossing closes, if one arrived first.
|
157 + | pending_result: Option<AcquisitionOutcome>,
|
158 158 | },
|
159 159 | /// Capacity is ready to start an establishment attempt.
|
160 160 | ReadyToEstablish {
|
161 161 | /// Optional bounded-origin capacity owned until connection install.
|
162 162 | permit: EstablishmentPermit,
|
163 163 | },
|
164 164 | /// Establishment was submitted to the owner runtime and may be unpolled.
|
165 165 | Launching {
|
166 166 | /// Ownership phase used to distinguish submission from first poll.
|
167 167 | phase: EstablishmentPhase,
|
168 168 | /// Latest task waiting for the attempt or returned H1 to complete.
|
169 169 | waker: Option<Waker>,
|
170 170 | },
|
171 - | /// The waiter owns its terminal acquisition result.
|
172 - | Ready(AcquisitionResult),
|
171 + | /// The waiter owns its terminal acquisition outcome.
|
172 + | Ready(AcquisitionOutcome),
|
173 173 | }
|
174 174 |
|
175 175 | /// Cell-local ownership of the aggregate head demand.
|
176 176 | #[derive(Debug)]
|
177 177 | struct DemandTicket {
|
178 178 | /// Identity of this head-waiter generation.
|
179 179 | id: DemandId,
|
180 180 | /// Version assigned to the next complete publication for this demand
|
181 181 | /// identity.
|
182 182 | version: SnapshotVersion,
|
183 183 | /// Protocol capability required by this head waiter.
|
184 184 | requirement: ProtocolRequirement,
|
185 185 | }
|
186 186 |
|
187 187 | impl DemandTicket {
|
188 188 | /// Creates the complete active state published for this head generation.
|
189 189 | fn snapshot(&self, eligibility_group: &EligibilityGroup) -> DemandSnapshot {
|
190 190 | DemandSnapshot::active(
|
191 191 | self.id,
|
192 192 | self.version,
|
193 193 | self.requirement,
|
194 194 | eligibility_group.clone(),
|
195 195 | )
|
196 196 | }
|
197 197 | }
|
198 198 |
|
199 199 | impl AcquisitionQueue {
|
200 200 | /// Registers one acquisition attempt.
|
201 201 | ///
|
202 202 | /// An unbounded attempt starts ready to establish. A bounded attempt joins
|
203 203 | /// the capacity FIFO and publishes demand only when it becomes the head.
|
204 204 | pub(super) fn register_waiter(
|
205 205 | &mut self,
|
206 206 | requirement: ProtocolRequirement,
|
207 207 | eligibility_group: &EligibilityGroup,
|
208 208 | bounded: bool,
|
209 209 | ) -> (WaiterId, Option<DemandSnapshot>) {
|
210 210 | let waiter = self.take_waiter_id();
|
211 211 | if !bounded {
|
212 212 | self.records.insert(
|
213 213 | waiter,
|
214 214 | WaiterRecord {
|
215 215 | requirement,
|
216 216 | state: WaiterState::ReadyToEstablish {
|
217 217 | permit: EstablishmentPermit::unbounded(),
|
218 218 | },
|
219 219 | },
|
220 220 | );
|
221 - | self.add_protocol_candidates(waiter, requirement);
|
221 + | self.add_protocol_waiters(waiter, requirement);
|
222 222 | self.assert_consistent();
|
223 223 | return (waiter, None);
|
224 224 | }
|
225 225 |
|
226 226 | let previous = match &self.waiting {
|
227 227 | WaitingQueueState::Empty => None,
|
228 228 | WaitingQueueState::Active { tail, .. } => Some(*tail),
|
229 229 | };
|
230 230 | let initial_demand = previous.is_none().then(|| self.new_demand(requirement));
|
231 231 |
|
232 232 | if let Some(previous) = previous {
|
233 233 | let previous = self
|
234 234 | .records
|
235 235 | .get_mut(&previous)
|
236 236 | .expect("waiting tail disappeared");
|
237 237 | let WaiterState::Waiting { next, .. } = &mut previous.state else {
|
238 238 | unreachable!("waiting tail left the waiting state");
|
239 239 | };
|
240 240 | debug_assert!(next.is_none());
|
241 241 | *next = Some(waiter);
|
242 242 | }
|
243 243 |
|
244 244 | let replaced = self.records.insert(
|
245 245 | waiter,
|
246 246 | WaiterRecord {
|
247 247 | requirement,
|
248 248 | state: WaiterState::Waiting {
|
249 249 | previous,
|
250 250 | next: None,
|
251 251 | waker: None,
|
252 252 | },
|
253 253 | },
|
254 254 | );
|
255 255 | debug_assert!(replaced.is_none());
|
256 256 |
|
257 257 | let snapshot = match (&mut self.waiting, initial_demand) {
|
258 258 | (waiting @ WaitingQueueState::Empty, Some(demand)) => {
|
259 259 | let snapshot = demand.snapshot(eligibility_group);
|
260 260 | *waiting = WaitingQueueState::Active {
|
261 261 | head: waiter,
|
262 262 | tail: waiter,
|
263 263 | len: NonZeroUsize::MIN,
|
264 264 | demand,
|
265 265 | };
|
266 266 | Some(snapshot)
|
267 267 | }
|
268 268 | (WaitingQueueState::Active { tail, len, .. }, None) => {
|
269 269 | *tail = waiter;
|
270 270 | *len = len.checked_add(1).expect("waiter queue length exhausted");
|
271 271 | None
|
272 272 | }
|
273 273 | _ => unreachable!("waiter queue occupancy changed during registration"),
|
274 274 | };
|
275 275 | self.assert_consistent();
|
276 276 | (waiter, snapshot)
|
277 277 | }
|
278 278 |
|
279 279 | /// Returns the oldest acquisition attempt that can accept HTTP/1.
|
280 280 | ///
|
281 - | /// A bounded waiter remains a candidate after receiving capacity so a
|
281 + | /// A bounded waiter remains compatible after receiving capacity so a
|
282 282 | /// returned sender can still beat its lazy or pool-owned establishment
|
283 283 | /// attempt. Comparing waiter identities preserves arrival order between
|
284 284 | /// that set and the current capacity-waiting head.
|
285 - | fn oldest_h1_candidate(&self) -> Option<WaiterId> {
|
285 + | fn oldest_h1_compatible_waiter(&self) -> Option<WaiterId> {
|
286 286 | let waiting = match &self.waiting {
|
287 287 | WaitingQueueState::Active { head, .. }
|
288 288 | if self.records[head].requirement.accepts_h1() =>
|
289 289 | {
|
290 290 | Some(*head)
|
291 291 | }
|
292 292 | WaitingQueueState::Empty | WaitingQueueState::Active { .. } => None,
|
293 293 | };
|
294 - | match (waiting, self.h1_candidates.first().copied()) {
|
294 + | match (waiting, self.h1_compatible_waiters.first().copied()) {
|
295 295 | (Some(waiting), Some(launching)) => Some(waiting.min(launching)),
|
296 296 | (Some(waiting), None) => Some(waiting),
|
297 297 | (None, launching) => launching,
|
298 298 | }
|
299 299 | }
|
300 300 |
|
301 301 | /// Returns whether a returned H1 can satisfy a live acquisition attempt.
|
302 - | pub(super) fn can_accept_h1(&self) -> bool {
|
303 - | self.oldest_h1_candidate().is_some()
|
302 + | pub(super) fn has_h1_compatible_waiter(&self) -> bool {
|
303 + | self.oldest_h1_compatible_waiter().is_some()
|
304 304 | }
|
305 305 |
|
306 306 | /// Returns whether a previously admitted local attempt still accepts H1.
|
307 307 | ///
|
308 - | /// These candidates left the capacity FIFO before its current head and
|
308 + | /// These waiters left the capacity FIFO before its current head and
|
309 309 | /// therefore precede cross-cell reuse for that newer aggregate demand.
|
310 - | pub(super) fn has_prior_h1_candidate(&self) -> bool {
|
311 - | !self.h1_candidates.is_empty()
|
310 + | pub(super) fn has_prior_h1_waiter(&self) -> bool {
|
311 + | !self.h1_compatible_waiters.is_empty()
|
312 312 | }
|
313 313 |
|
314 314 | /// Returns the oldest attempt that may accept an HTTP/2 activation.
|
315 - | pub(super) fn oldest_h2_candidate(&self) -> Option<WaiterId> {
|
315 + | pub(super) fn oldest_h2_compatible_waiter(&self) -> Option<WaiterId> {
|
316 316 | let waiting = match &self.waiting {
|
317 317 | WaitingQueueState::Active { head, .. }
|
318 318 | if self.records[head].requirement.accepts_h2() =>
|
319 319 | {
|
320 320 | Some(*head)
|
321 321 | }
|
322 322 | WaitingQueueState::Active { .. } => None,
|
323 323 | WaitingQueueState::Empty => None,
|
324 324 | };
|
325 - | match (waiting, self.h2_activation_candidates.first().copied()) {
|
325 + | match (waiting, self.h2_compatible_waiters.first().copied()) {
|
326 326 | (Some(waiting), Some(active)) => Some(waiting.min(active)),
|
327 327 | (Some(waiting), None) => Some(waiting),
|
328 328 | (None, active) => active,
|
329 329 | }
|
330 330 | }
|
331 331 |
|
332 - | /// Advances the active demand beyond an accepted route publication.
|
332 + | /// Advances the active demand beyond an accepted peer route.
|
333 333 | ///
|
334 334 | /// Admission retires the published version when it acknowledges route
|
335 335 | /// visibility. The waiter remains queued locally while the route gate
|
336 336 | /// offers activations. Reserving the following version here ensures that
|
337 337 | /// route invalidation can republish the same demand generation without an
|
338 338 | /// older inactive snapshot winning.
|
339 - | pub(super) fn suppress_published_demand(&mut self, demand: DemandId) -> bool {
|
339 + | pub(super) fn supersede_demand_snapshot(&mut self, demand: DemandId) -> bool {
|
340 340 | let WaitingQueueState::Active {
|
341 341 | demand: current, ..
|
342 342 | } = &mut self.waiting
|
343 343 | else {
|
344 344 | return false;
|
345 345 | };
|
346 346 | if current.id != demand {
|
347 347 | return false;
|
348 348 | }
|
349 349 | let acknowledged = current.version.next();
|
350 350 | current.version = acknowledged.next();
|
351 351 | self.assert_consistent();
|
352 352 | true
|
353 353 | }
|
354 354 |
|
355 355 | /// Returns the current head demand for publication after route loss.
|
356 356 | pub(super) fn current_demand_snapshot(
|
357 357 | &self,
|
358 358 | eligibility_group: &EligibilityGroup,
|
359 359 | ) -> Option<DemandSnapshot> {
|
360 360 | match &self.waiting {
|
361 361 | WaitingQueueState::Empty => None,
|
362 362 | WaitingQueueState::Active { demand, .. } => Some(demand.snapshot(eligibility_group)),
|
363 363 | }
|
364 364 | }
|
365 365 |
|
366 - | /// Returns whether `waiter` is still the oldest H2 activation candidate.
|
367 - | pub(super) fn is_oldest_h2_candidate(&self, waiter: WaiterId) -> bool {
|
368 - | self.oldest_h2_candidate() == Some(waiter)
|
366 + | /// Returns whether `waiter` is still the oldest H2-compatible waiter.
|
367 + | pub(super) fn is_oldest_h2_compatible_waiter(&self, waiter: WaiterId) -> bool {
|
368 + | self.oldest_h2_compatible_waiter() == Some(waiter)
|
369 369 | }
|
370 370 |
|
371 - | /// Returns the newest waiter identity committed before a publication.
|
372 - | pub(super) fn publication_cutoff(&self) -> Option<WaiterId> {
|
371 + | /// Returns the newest waiter identity committed before peer route installation.
|
372 + | pub(super) fn route_cutoff(&self) -> Option<WaiterId> {
|
373 373 | (!self.records.is_empty()).then(|| {
|
374 374 | WaiterId(
|
375 - | self.next_waiter
|
375 + | self.next_waiter_id
|
376 376 | .checked_sub(1)
|
377 377 | .expect("nonempty waiter set had no allocated identity"),
|
378 378 | )
|
379 379 | })
|
380 380 | }
|
381 381 |
|
382 382 | /// Returns whether an acquisition at or before `cutoff` still needs H2.
|
383 - | pub(super) fn has_h2_candidate_through(&self, cutoff: WaiterId) -> bool {
|
384 - | self.oldest_h2_candidate()
|
383 + | pub(super) fn has_h2_compatible_waiter_through(&self, cutoff: WaiterId) -> bool {
|
384 + | self.oldest_h2_compatible_waiter()
|
385 385 | .is_some_and(|waiter| waiter <= cutoff)
|
386 386 | }
|
387 387 |
|
388 388 | /// Returns whether any acquisition must precede a new direct H2 arrival.
|
389 - | pub(super) fn has_h2_candidate(&self) -> bool {
|
390 - | self.oldest_h2_candidate().is_some()
|
389 + | pub(super) fn has_h2_compatible_waiter(&self) -> bool {
|
390 + | self.oldest_h2_compatible_waiter().is_some()
|
391 391 | }
|
392 392 |
|
393 393 | /// Returns whether one transferred generation waiter remains launchable.
|
394 - | pub(super) fn is_launching_h2_candidate(&self, waiter: WaiterId) -> bool {
|
395 - | self.h2_activation_candidates.contains(&waiter)
|
394 + | pub(super) fn is_launching_h2_waiter(&self, waiter: WaiterId) -> bool {
|
395 + | self.h2_compatible_waiters.contains(&waiter)
|
396 396 | && self.records.get(&waiter).is_some_and(|record| {
|
397 397 | record.requirement.accepts_h2()
|
398 398 | && matches!(record.state, WaiterState::Launching { .. })
|
399 399 | })
|
400 400 | }
|
401 401 |
|
402 - | /// Adds one waiter to each protocol index its requirement accepts.
|
403 - | fn add_protocol_candidates(&mut self, waiter: WaiterId, requirement: ProtocolRequirement) {
|
402 + | /// Adds one waiter to each compatible-protocol index.
|
403 + | fn add_protocol_waiters(&mut self, waiter: WaiterId, requirement: ProtocolRequirement) {
|
404 404 | if requirement.accepts_h1() {
|
405 - | self.h1_candidates.insert(waiter);
|
405 + | self.h1_compatible_waiters.insert(waiter);
|
406 406 | }
|
407 407 | if requirement.accepts_h2() {
|
408 - | self.h2_activation_candidates.insert(waiter);
|
408 + | self.h2_compatible_waiters.insert(waiter);
|
409 409 | }
|
410 410 | }
|
411 411 |
|
412 - | /// Removes one waiter from both protocol candidate indexes.
|
413 - | fn remove_protocol_candidates(&mut self, waiter: WaiterId) {
|
414 - | self.h1_candidates.remove(&waiter);
|
415 - | self.h2_activation_candidates.remove(&waiter);
|
412 + | /// Removes one waiter from both compatible-protocol indexes.
|
413 + | fn remove_protocol_waiters(&mut self, waiter: WaiterId) {
|
414 + | self.h1_compatible_waiters.remove(&waiter);
|
415 + | self.h2_compatible_waiters.remove(&waiter);
|
416 416 | }
|
417 417 |
|
418 418 | /// Commits a returned HTTP/1 sender to the oldest compatible attempt.
|
419 419 | ///
|
420 420 | /// Payload construction is delayed until all panic-capable requesting-cell checks
|
421 421 | /// have completed. Once constructed, the result is either state-owned or
|
422 422 | /// returned to the caller for cleanup after the cell lock is released.
|
423 - | pub(super) fn install_returned_h1(
|
423 + | pub(super) fn offer_returned_h1(
|
424 424 | &mut self,
|
425 - | result: impl FnOnce() -> AcquisitionResult,
|
425 + | result: impl FnOnce() -> AcquisitionOutcome,
|
426 426 | eligibility_group: &EligibilityGroup,
|
427 - | ) -> WaiterInstall {
|
428 - | let Some(waiter) = self.oldest_h1_candidate() else {
|
429 - | return WaiterInstall::rejected(result());
|
427 + | ) -> (Option<WaiterId>, WaiterResolution) {
|
428 + | let Some(waiter) = self.oldest_h1_compatible_waiter() else {
|
429 + | return (None, WaiterResolution::refused(result()));
|
430 430 | };
|
431 - | self.install_protocol_result(waiter, result, eligibility_group)
|
431 + | (
|
432 + | Some(waiter),
|
433 + | self.assign_protocol_outcome(waiter, result, eligibility_group),
|
434 + | )
|
432 435 | }
|
433 436 |
|
434 437 | /// Installs a selected protocol result through the shared waiter transition.
|
435 - | fn install_protocol_result(
|
438 + | fn assign_protocol_outcome(
|
436 439 | &mut self,
|
437 440 | waiter: WaiterId,
|
438 - | result: impl FnOnce() -> AcquisitionResult,
|
441 + | result: impl FnOnce() -> AcquisitionOutcome,
|
439 442 | eligibility_group: &EligibilityGroup,
|
440 - | ) -> WaiterInstall {
|
443 + | ) -> WaiterResolution {
|
441 444 | if matches!(
|
442 445 | self.records.get(&waiter).map(|record| &record.state),
|
443 446 | Some(WaiterState::Waiting { .. })
|
444 447 | ) {
|
445 448 | let removed = self.pop_head(eligibility_group);
|
446 449 | debug_assert_eq!(removed.waiter, waiter);
|
447 450 | let record = self
|
448 451 | .records
|
449 452 | .get_mut(&waiter)
|
450 453 | .expect("selected requesting waiter disappeared");
|
451 454 | let WaiterState::Waiting { waker, .. } = &mut record.state else {
|
452 455 | unreachable!("selected requesting waiter left the waiting state");
|
453 456 | };
|
454 457 | let waker = waker.take();
|
455 458 | record.state = WaiterState::Ready(result());
|
456 459 | let retired =
|
457 460 | DemandSnapshot::inactive(removed.demand.id, removed.demand.version.next());
|
458 461 | self.assert_consistent();
|
459 - | return WaiterInstall {
|
460 - | waiter: Some(waiter),
|
462 + | return WaiterResolution {
|
461 463 | demand_updates: [Some(retired), removed.successor],
|
462 - | returned_event: None,
|
464 + | returned_step: None,
|
463 465 | waker,
|
464 466 | };
|
465 467 | }
|
466 468 |
|
467 469 | let record = self
|
468 470 | .records
|
469 471 | .get_mut(&waiter)
|
470 - | .expect("selected candidate waiter disappeared");
|
471 - | let (returned_event, waker) = match &mut record.state {
|
472 + | .expect("selected compatible waiter disappeared");
|
473 + | let (returned_step, waker) = match &mut record.state {
|
472 474 | WaiterState::DeliveryPending {
|
473 475 | waker: _,
|
474 476 | pending_result,
|
475 477 | } => {
|
476 478 | debug_assert!(pending_result.is_none());
|
477 479 | *pending_result = Some(result());
|
478 480 | (None, None)
|
479 481 | }
|
480 482 | WaiterState::ReadyToEstablish { .. } | WaiterState::Launching { .. } => {
|
481 483 | let previous = std::mem::replace(&mut record.state, WaiterState::Ready(result()));
|
482 484 | match previous {
|
483 485 | WaiterState::ReadyToEstablish { permit } => {
|
484 - | (Some(AcquisitionEvent::Establish(permit)), None)
|
486 + | (Some(AcquisitionStep::StartEstablishment(permit)), None)
|
485 487 | }
|
486 488 | WaiterState::Launching { waker, .. } => (None, waker),
|
487 - | _ => unreachable!("selected candidate changed state under the cell lock"),
|
489 + | _ => {
|
490 + | unreachable!("selected compatible waiter changed state under the cell lock")
|
491 + | }
|
488 492 | }
|
489 493 | }
|
490 494 | WaiterState::Waiting { .. }
|
491 495 | | WaiterState::DeliveryCancelled { .. }
|
492 496 | | WaiterState::Ready(_) => {
|
493 497 | unreachable!("selected waiter was not eligible for a protocol result")
|
494 498 | }
|
495 499 | };
|
496 - | self.remove_protocol_candidates(waiter);
|
497 - | if returned_event.is_none() {
|
500 + | self.remove_protocol_waiters(waiter);
|
501 + | if returned_step.is_none() {
|
498 502 | // The result is state-owned and no permit is detached, so an
|
499 503 | // invariant failure cannot drop a pool value under this lock.
|
500 504 | self.assert_consistent();
|
501 505 | }
|
502 - | WaiterInstall {
|
503 - | waiter: Some(waiter),
|
506 + | WaiterResolution {
|
504 507 | demand_updates: [None, None],
|
505 - | returned_event,
|
508 + | returned_step,
|
506 509 | waker,
|
507 510 | }
|
508 511 | }
|
509 512 |
|
510 513 | /// Cancels one waiter and detaches all cross-lock cleanup work.
|
511 514 | ///
|
512 515 | /// Removing the head retires its demand and starts a successor generation.
|
513 516 | /// Removing another waiting record leaves the active demand unchanged.
|
514 - | /// Cancellation during delivery leaves a marker for result installation to
|
515 - | /// observe. A ready result is returned to the caller for cleanup after unlock.
|
517 + | /// Cancellation while delivery is pending leaves a marker for the exact
|
518 + | /// waiter commit to observe. A ready step is returned for cleanup after unlock.
|
516 519 | pub(super) fn cancel_waiter(
|
517 520 | &mut self,
|
518 521 | waiter: WaiterId,
|
519 522 | eligibility_group: &EligibilityGroup,
|
520 523 | ) -> Option<WaiterCancellation> {
|
521 524 | let state = &self.records.get(&waiter)?.state;
|
522 525 | let cancellation = if matches!(state, WaiterState::Waiting { .. }) {
|
523 526 | let is_head = matches!(
|
524 527 | self.waiting,
|
525 528 | WaitingQueueState::Active { head, .. } if head == waiter
|
526 529 | );
|
527 530 | let demand_updates = if is_head {
|
528 531 | let removed = self.pop_head(eligibility_group);
|
529 532 | debug_assert_eq!(waiter, removed.waiter);
|
530 533 | let record = self
|
531 534 | .records
|
532 535 | .remove(&waiter)
|
533 536 | .expect("cancelled head waiter disappeared");
|
534 537 | debug_assert!(matches!(record.state, WaiterState::Waiting { .. }));
|
535 538 | let retired =
|
536 539 | DemandSnapshot::inactive(removed.demand.id, removed.demand.version.next());
|
537 540 | [Some(retired), removed.successor]
|
538 541 | } else {
|
539 542 | self.remove_non_head(waiter);
|
540 543 | [None, None]
|
541 544 | };
|
542 545 | WaiterCancellation {
|
543 546 | demand_updates,
|
544 - | returned_events: [None, None],
|
547 + | returned_steps: [None, None],
|
545 548 | }
|
546 549 | } else if matches!(state, WaiterState::DeliveryPending { .. }) {
|
547 - | self.remove_protocol_candidates(waiter);
|
550 + | self.remove_protocol_waiters(waiter);
|
548 551 | let record = self.records.get_mut(&waiter)?;
|
549 552 | let WaiterState::DeliveryPending {
|
550 553 | waker,
|
551 554 | pending_result,
|
552 555 | } = &mut record.state
|
553 556 | else {
|
554 557 | unreachable!("delivery-pending waiter changed state under the cell lock");
|
555 558 | };
|
556 559 | let waker = waker.take();
|
557 560 | let pending_result = pending_result.take();
|
558 561 | record.state = WaiterState::DeliveryCancelled {
|
559 562 | waker,
|
560 563 | pending_result,
|
561 564 | };
|
562 565 | WaiterCancellation {
|
563 566 | demand_updates: [None, None],
|
564 - | returned_events: [None, None],
|
567 + | returned_steps: [None, None],
|
565 568 | }
|
566 569 | } else if matches!(
|
567 570 | state,
|
568 571 | WaiterState::ReadyToEstablish { .. } | WaiterState::Ready(_)
|
569 572 | ) {
|
570 573 | // Validate before detaching the event. Once it is local,
|
571 574 | // this method must return without another panic-capable check.
|
572 575 | self.assert_consistent();
|
573 576 | let record = self.records.remove(&waiter)?;
|
574 - | self.remove_protocol_candidates(waiter);
|
577 + | self.remove_protocol_waiters(waiter);
|
575 578 | let event = match record.state {
|
576 - | WaiterState::ReadyToEstablish { permit } => AcquisitionEvent::Establish(permit),
|
577 - | WaiterState::Ready(result) => AcquisitionEvent::Complete(result),
|
579 + | WaiterState::ReadyToEstablish { permit } => {
|
580 + | AcquisitionStep::StartEstablishment(permit)
|
581 + | }
|
582 + | WaiterState::Ready(result) => AcquisitionStep::Resolved(result),
|
578 583 | _ => unreachable!("ready waiter changed state under the cell lock"),
|
579 584 | };
|
580 585 | return Some(WaiterCancellation {
|
581 586 | demand_updates: [None, None],
|
582 - | returned_events: [Some(event), None],
|
587 + | returned_steps: [Some(event), None],
|
583 588 | });
|
584 589 | } else if matches!(state, WaiterState::Launching { .. }) {
|
585 590 | self.assert_consistent();
|
586 - | self.remove_protocol_candidates(waiter);
|
591 + | self.remove_protocol_waiters(waiter);
|
587 592 | self.records.remove(&waiter)?;
|
588 593 | return Some(WaiterCancellation {
|
589 594 | demand_updates: [None, None],
|
590 - | returned_events: [None, None],
|
595 + | returned_steps: [None, None],
|
591 596 | });
|
592 597 | } else {
|
593 598 | debug_assert!(matches!(state, WaiterState::DeliveryCancelled { .. }));
|
594 599 | return None;
|
595 600 | };
|
596 601 |
|
597 602 | self.assert_consistent();
|
598 603 | Some(cancellation)
|
599 604 | }
|
600 605 |
|
601 606 | /// Returns the next event or records the latest waker for a pending waiter.
|
602 607 | ///
|
603 608 | /// # Panics
|
604 609 | ///
|
605 610 | /// Panics if `waiter` is unknown, was cancelled, or was already consumed
|
606 611 | /// by an earlier ready poll.
|
607 612 | pub(super) fn poll_waiter(
|
608 613 | &mut self,
|
609 614 | waiter: WaiterId,
|
610 615 | cx: &mut Context<'_>,
|
611 - | ) -> Poll<AcquisitionEvent> {
|
616 + | ) -> Poll<AcquisitionStep> {
|
612 617 | if matches!(
|
613 618 | self.records.get(&waiter).map(|record| &record.state),
|
614 619 | Some(WaiterState::ReadyToEstablish { .. })
|
615 620 | ) {
|
616 621 | self.assert_consistent();
|
617 622 | let record = self
|
618 623 | .records
|
619 624 | .get_mut(&waiter)
|
620 625 | .expect("ready establishment waiter disappeared");
|
621 626 | let previous = std::mem::replace(
|
622 627 | &mut record.state,
|
623 628 | WaiterState::Launching {
|
624 629 | phase: EstablishmentPhase::Submitted,
|
625 630 | waker: None,
|
626 631 | },
|
627 632 | );
|
628 633 | let WaiterState::ReadyToEstablish { permit } = previous else {
|
629 634 | unreachable!("ready establishment waiter changed state under the cell lock");
|
630 635 | };
|
631 - | return Poll::Ready(AcquisitionEvent::Establish(permit));
|
636 + | return Poll::Ready(AcquisitionStep::StartEstablishment(permit));
|
632 637 | }
|
633 638 |
|
634 639 | if matches!(
|
635 640 | self.records.get(&waiter).map(|record| &record.state),
|
636 641 | Some(WaiterState::Ready(_))
|
637 642 | ) {
|
638 643 | return Poll::Ready(
|
639 644 | self.take_ready_result(waiter)
|
640 - | .map(AcquisitionEvent::Complete)
|
641 - | .expect("ready waiter lost its acquisition result"),
|
645 + | .map(AcquisitionStep::Resolved)
|
646 + | .expect("ready waiter lost its acquisition outcome"),
|
642 647 | );
|
643 648 | }
|
644 649 |
|
645 650 | let record = self
|
646 651 | .records
|
647 652 | .get_mut(&waiter)
|
648 653 | .expect("polled a cancelled, consumed, or unknown acquisition waiter");
|
649 654 | let waker = match &mut record.state {
|
650 655 | WaiterState::Waiting { waker, .. }
|
651 656 | | WaiterState::DeliveryPending { waker, .. }
|
723 728 | .expect("reserved queue head disappeared");
|
724 729 | let WaiterState::Waiting { waker, .. } = &mut record.state else {
|
725 730 | unreachable!("reserved queue head left the waiting state");
|
726 731 | };
|
727 732 | let waker = waker.take();
|
728 733 | let requirement = record.requirement;
|
729 734 | record.state = WaiterState::DeliveryPending {
|
730 735 | waker,
|
731 736 | pending_result: None,
|
732 737 | };
|
733 - | self.add_protocol_candidates(removed.waiter, requirement);
|
738 + | self.add_protocol_waiters(removed.waiter, requirement);
|
734 739 |
|
735 740 | self.assert_consistent();
|
736 741 | DeliveryReservation::Reserved {
|
737 742 | waiter: removed.waiter,
|
738 743 | successor: removed.successor,
|
739 744 | }
|
740 745 | }
|
741 746 |
|
742 747 | /// Completes the unlocked capacity crossing without panicking while the
|
743 748 | /// incoming permit remains a droppable local.
|
744 749 | ///
|
745 750 | /// A live receiver takes ownership of the permit before another
|
746 751 | /// panic-capable operation. If an H1 return already won, both resources
|
747 752 | /// leave the lock as separate events and the permit is refunnelled.
|
748 - | pub(super) fn install_capacity(
|
753 + | pub(super) fn commit_capacity(
|
749 754 | &mut self,
|
750 755 | waiter: WaiterId,
|
751 756 | permit: EstablishmentPermit,
|
752 - | ) -> ResultInstall {
|
757 + | ) -> CellCommitOutcome {
|
753 758 | let Some(record) = self.records.get_mut(&waiter) else {
|
754 - | return ResultInstall::invalid(
|
755 - | AcquisitionEvent::Establish(permit),
|
756 - | ResultInstallError::MissingWaiter,
|
759 + | return CellCommitOutcome::invalid(
|
760 + | AcquisitionStep::StartEstablishment(permit),
|
761 + | CellCommitError::MissingWaiter,
|
757 762 | );
|
758 763 | };
|
759 764 |
|
760 765 | match &mut record.state {
|
761 766 | WaiterState::DeliveryPending {
|
762 767 | waker,
|
763 768 | pending_result,
|
764 769 | } => {
|
765 770 | let waker = waker.take();
|
766 771 | if let Some(result) = pending_result.take() {
|
767 772 | record.state = WaiterState::Ready(result);
|
768 - | self.remove_protocol_candidates(waiter);
|
773 + | self.remove_protocol_waiters(waiter);
|
769 774 | // The rejected permit must cross the lock boundary even
|
770 775 | // if other state is already inconsistent.
|
771 - | return ResultInstall {
|
772 - | returned_events: [Some(AcquisitionEvent::Establish(permit)), None],
|
776 + | return CellCommitOutcome::Refused {
|
777 + | returned: [Some(AcquisitionStep::StartEstablishment(permit)), None],
|
773 778 | waker,
|
774 - | error: None,
|
775 - | accepted: false,
|
776 779 | };
|
777 780 | }
|
778 781 | record.state = WaiterState::ReadyToEstablish { permit };
|
779 782 | // The permit is state-owned before this check can panic.
|
780 783 | self.assert_consistent();
|
781 - | ResultInstall {
|
782 - | returned_events: [None, None],
|
783 - | waker,
|
784 - | error: None,
|
785 - | accepted: true,
|
786 - | }
|
784 + | CellCommitOutcome::Committed { waker }
|
787 785 | }
|
788 786 | WaiterState::DeliveryCancelled {
|
789 787 | waker,
|
790 788 | pending_result,
|
791 789 | } => {
|
792 790 | let waker = waker.take();
|
793 791 | let pending_result = pending_result.take();
|
794 792 | self.records.remove(&waiter);
|
795 - | ResultInstall {
|
796 - | returned_events: [
|
797 - | pending_result.map(AcquisitionEvent::Complete),
|
798 - | Some(AcquisitionEvent::Establish(permit)),
|
793 + | CellCommitOutcome::Refused {
|
794 + | returned: [
|
795 + | pending_result.map(AcquisitionStep::Resolved),
|
796 + | Some(AcquisitionStep::StartEstablishment(permit)),
|
799 797 | ],
|
800 798 | waker,
|
801 - | error: None,
|
802 - | accepted: false,
|
803 799 | }
|
804 800 | }
|
805 801 | WaiterState::Waiting { .. }
|
806 802 | | WaiterState::ReadyToEstablish { .. }
|
807 803 | | WaiterState::Launching { .. }
|
808 - | | WaiterState::Ready(_) => ResultInstall::invalid(
|
809 - | AcquisitionEvent::Establish(permit),
|
810 - | ResultInstallError::UnexpectedState,
|
804 + | | WaiterState::Ready(_) => CellCommitOutcome::invalid(
|
805 + | AcquisitionStep::StartEstablishment(permit),
|
806 + | CellCommitError::UnexpectedState,
|
811 807 | ),
|
812 808 | }
|
813 809 | }
|
814 810 |
|
815 811 | /// Installs a borrowed HTTP/1 result after its requesting waiter was reserved.
|
816 812 | ///
|
817 813 | /// A local return may have won while the borrowed sender crossed its
|
818 814 | /// owning-cell and requesting-cell locks. In that case the local result stays ready and
|
819 815 | /// the borrowed result leaves the lock for ordinary owning-cell return.
|
820 - | pub(super) fn install_borrowed_h1(
|
816 + | pub(super) fn commit_borrowed_h1(
|
821 817 | &mut self,
|
822 818 | waiter: WaiterId,
|
823 - | result: AcquisitionResult,
|
824 - | ) -> ResultInstall {
|
819 + | result: AcquisitionOutcome,
|
820 + | ) -> CellCommitOutcome {
|
825 821 | let Some(record) = self.records.get_mut(&waiter) else {
|
826 - | return ResultInstall::invalid(
|
827 - | AcquisitionEvent::Complete(result),
|
828 - | ResultInstallError::MissingWaiter,
|
822 + | return CellCommitOutcome::invalid(
|
823 + | AcquisitionStep::Resolved(result),
|
824 + | CellCommitError::MissingWaiter,
|
829 825 | );
|
830 826 | };
|
831 827 |
|
832 828 | match &mut record.state {
|
833 829 | WaiterState::DeliveryPending {
|
834 830 | waker,
|
835 831 | pending_result,
|
836 832 | } => {
|
837 833 | let waker = waker.take();
|
838 834 | if let Some(local_result) = pending_result.take() {
|
839 835 | record.state = WaiterState::Ready(local_result);
|
840 - | self.remove_protocol_candidates(waiter);
|
836 + | self.remove_protocol_waiters(waiter);
|
841 837 | // The rejected borrowed sender must cross the lock
|
842 838 | // boundary before any panic-capable consistency check.
|
843 - | return ResultInstall {
|
844 - | returned_events: [Some(AcquisitionEvent::Complete(result)), None],
|
839 + | return CellCommitOutcome::Refused {
|
840 + | returned: [Some(AcquisitionStep::Resolved(result)), None],
|
845 841 | waker,
|
846 - | error: None,
|
847 - | accepted: false,
|
848 842 | };
|
849 843 | }
|
850 844 | record.state = WaiterState::Ready(result);
|
851 - | self.remove_protocol_candidates(waiter);
|
845 + | self.remove_protocol_waiters(waiter);
|
852 846 | self.assert_consistent();
|
853 - | ResultInstall {
|
854 - | returned_events: [None, None],
|
855 - | waker,
|
856 - | error: None,
|
857 - | accepted: true,
|
858 - | }
|
847 + | CellCommitOutcome::Committed { waker }
|
859 848 | }
|
860 849 | WaiterState::DeliveryCancelled {
|
861 850 | waker,
|
862 851 | pending_result,
|
863 852 | } => {
|
864 853 | let waker = waker.take();
|
865 854 | let pending_result = pending_result.take();
|
866 855 | self.records.remove(&waiter);
|
867 - | ResultInstall {
|
868 - | returned_events: [
|
869 - | pending_result.map(AcquisitionEvent::Complete),
|
870 - | Some(AcquisitionEvent::Complete(result)),
|
856 + | CellCommitOutcome::Refused {
|
857 + | returned: [
|
858 + | pending_result.map(AcquisitionStep::Resolved),
|
859 + | Some(AcquisitionStep::Resolved(result)),
|
871 860 | ],
|
872 861 | waker,
|
873 - | error: None,
|
874 - | accepted: false,
|
875 862 | }
|
876 863 | }
|
877 864 | WaiterState::Waiting { .. }
|
878 865 | | WaiterState::ReadyToEstablish { .. }
|
879 866 | | WaiterState::Launching { .. }
|
880 - | | WaiterState::Ready(_) => ResultInstall::invalid(
|
881 - | AcquisitionEvent::Complete(result),
|
882 - | ResultInstallError::UnexpectedState,
|
867 + | | WaiterState::Ready(_) => CellCommitOutcome::invalid(
|
868 + | AcquisitionStep::Resolved(result),
|
869 + | CellCommitError::UnexpectedState,
|
883 870 | ),
|
884 871 | }
|
885 872 | }
|
886 873 |
|
887 874 | /// Commits one terminal establishment result when its waiter is still live.
|
888 875 | ///
|
889 876 | /// Completion may occur from either launch phase: normal execution starts
|
890 877 | /// the connector first, while dropping an unpolled submitted task installs
|
891 878 | /// a terminal runtime error.
|
892 - | pub(super) fn install_establishment_result(
|
879 + | pub(super) fn commit_establishment(
|
893 880 | &mut self,
|
894 881 | waiter: WaiterId,
|
895 - | result: AcquisitionResult,
|
896 - | ) -> ResultInstall {
|
882 + | result: AcquisitionOutcome,
|
883 + | ) -> CellCommitOutcome {
|
897 884 | let Some(record) = self.records.get_mut(&waiter) else {
|
898 - | return ResultInstall::rejected(AcquisitionEvent::Complete(result));
|
885 + | return CellCommitOutcome::refused(AcquisitionStep::Resolved(result));
|
899 886 | };
|
900 887 | if matches!(record.state, WaiterState::Ready(_)) {
|
901 - | return ResultInstall::rejected(AcquisitionEvent::Complete(result));
|
888 + | return CellCommitOutcome::refused(AcquisitionStep::Resolved(result));
|
902 889 | }
|
903 890 | if !matches!(record.state, WaiterState::Launching { .. }) {
|
904 - | return ResultInstall::invalid(
|
905 - | AcquisitionEvent::Complete(result),
|
906 - | ResultInstallError::UnexpectedState,
|
891 + | return CellCommitOutcome::invalid(
|
892 + | AcquisitionStep::Resolved(result),
|
893 + | CellCommitError::UnexpectedState,
|
907 894 | );
|
908 895 | }
|
909 896 | let WaiterState::Launching { waker, .. } = &mut record.state else {
|
910 897 | unreachable!("validated launching waiter changed state")
|
911 898 | };
|
912 899 | let waker = waker.take();
|
913 900 | record.state = WaiterState::Ready(result);
|
914 - | self.remove_protocol_candidates(waiter);
|
901 + | self.remove_protocol_waiters(waiter);
|
915 902 | self.assert_consistent();
|
916 - | ResultInstall {
|
917 - | returned_events: [None, None],
|
918 - | waker,
|
919 - | error: None,
|
920 - | accepted: true,
|
921 - | }
|
903 + | CellCommitOutcome::Committed { waker }
|
922 904 | }
|
923 905 |
|
924 906 | /// Removes a ready waiter and transfers ownership of its result.
|
925 - | pub(super) fn take_ready_result(&mut self, waiter: WaiterId) -> Option<AcquisitionResult> {
|
907 + | pub(super) fn take_ready_result(&mut self, waiter: WaiterId) -> Option<AcquisitionOutcome> {
|
926 908 | if !matches!(
|
927 909 | self.records.get(&waiter).map(|record| &record.state),
|
928 910 | Some(WaiterState::Ready(_))
|
929 911 | ) {
|
930 912 | return None;
|
931 913 | }
|
932 914 |
|
933 915 | // Validate before detaching the result. Returning it must be the last
|
934 916 | // operation performed while the cell lock is held.
|
935 917 | self.assert_consistent();
|
936 918 | let record = self.records.remove(&waiter)?;
|
937 919 | let WaiterState::Ready(result) = record.state else {
|
938 920 | unreachable!("ready waiter changed state under the cell lock");
|
939 921 | };
|
940 922 | Some(result)
|
941 923 | }
|
942 924 |
|
943 925 | /// Offers an H2 activation to the oldest compatible acquisition.
|
944 926 | ///
|
945 - | /// `cutoff` retains publication priority. A later waiter is left pending
|
927 + | /// `cutoff` retains peer-route priority. A later waiter is left pending
|
946 928 | /// until every live acquisition at or before the cutoff has received an
|
947 929 | /// activation opportunity.
|
948 - | pub(super) fn install_h2(
|
930 + | pub(super) fn offer_h2_activation(
|
949 931 | &mut self,
|
950 932 | cutoff: Option<WaiterId>,
|
951 - | result: impl FnOnce(WaiterId) -> AcquisitionResult,
|
933 + | result: impl FnOnce(WaiterId) -> AcquisitionOutcome,
|
952 934 | eligibility_group: &EligibilityGroup,
|
953 - | ) -> WaiterInstall {
|
954 - | let Some(waiter) = self.oldest_h2_candidate() else {
|
955 - | return WaiterInstall::empty();
|
935 + | ) -> (Option<WaiterId>, WaiterResolution) {
|
936 + | let Some(waiter) = self.oldest_h2_compatible_waiter() else {
|
937 + | return (None, WaiterResolution::empty());
|
956 938 | };
|
957 939 | if cutoff.is_some_and(|cutoff| waiter > cutoff) {
|
958 - | return WaiterInstall::empty();
|
940 + | return (None, WaiterResolution::empty());
|
959 941 | }
|
960 942 |
|
961 - | self.install_protocol_result(waiter, || result(waiter), eligibility_group)
|
943 + | (
|
944 + | Some(waiter),
|
945 + | self.assign_protocol_outcome(waiter, || result(waiter), eligibility_group),
|
946 + | )
|
962 947 | }
|
963 948 |
|
964 949 | /// Unlinks the FIFO head and installs any successor demand.
|
965 950 | ///
|
966 951 | /// The head record remains in `records`; the caller either changes it to
|
967 952 | /// `DeliveryPending` or removes it for cancellation.
|
968 953 | fn pop_head(&mut self, eligibility_group: &EligibilityGroup) -> RemovedHead {
|
969 954 | let waiting = std::mem::take(&mut self.waiting);
|
970 955 | let WaitingQueueState::Active {
|
971 956 | head,
|
1183 1168 | traversed,
|
1184 1169 | "waiter queue length did not match its links"
|
1185 1170 | );
|
1186 1171 | assert_eq!(
|
1187 1172 | waiting_records, traversed,
|
1188 1173 | "waiting record was not reachable from the queue head"
|
1189 1174 | );
|
1190 1175 | }
|
1191 1176 | }
|
1192 1177 |
|
1193 - | let expected_h1_candidates = self
|
1178 + | let expected_h1_waiters = self
|
1194 1179 | .records
|
1195 1180 | .iter()
|
1196 1181 | .filter_map(|(waiter, record)| {
|
1197 1182 | (record.requirement.accepts_h1()
|
1198 1183 | && matches!(
|
1199 1184 | record.state,
|
1200 1185 | WaiterState::DeliveryPending {
|
1201 1186 | pending_result: None,
|
1202 1187 | ..
|
1203 1188 | } | WaiterState::ReadyToEstablish { .. }
|
1204 1189 | | WaiterState::Launching { .. }
|
1205 1190 | ))
|
1206 1191 | .then_some(*waiter)
|
1207 1192 | })
|
1208 1193 | .collect::<BTreeSet<_>>();
|
1209 1194 | assert_eq!(
|
1210 - | expected_h1_candidates, self.h1_candidates,
|
1211 - | "HTTP/1 acquisition candidates did not match waiter state"
|
1195 + | expected_h1_waiters, self.h1_compatible_waiters,
|
1196 + | "HTTP/1 compatible-waiter index did not match waiter state"
|
1212 1197 | );
|
1213 1198 |
|
1214 - | let expected_h2_activation_candidates = self
|
1199 + | let expected_h2_waiters = self
|
1215 1200 | .records
|
1216 1201 | .iter()
|
1217 1202 | .filter_map(|(waiter, record)| {
|
1218 1203 | (record.requirement.accepts_h2()
|
1219 1204 | && matches!(
|
1220 1205 | record.state,
|
1221 1206 | WaiterState::DeliveryPending {
|
1222 1207 | pending_result: None,
|
1223 1208 | ..
|
1224 1209 | } | WaiterState::ReadyToEstablish { .. }
|
1225 1210 | | WaiterState::Launching { .. }
|
1226 1211 | ))
|
1227 1212 | .then_some(*waiter)
|
1228 1213 | })
|
1229 1214 | .collect::<BTreeSet<_>>();
|
1230 1215 | assert_eq!(
|
1231 - | expected_h2_activation_candidates, self.h2_activation_candidates,
|
1232 - | "HTTP/2 acquisition candidates did not match waiter state"
|
1216 + | expected_h2_waiters, self.h2_compatible_waiters,
|
1217 + | "HTTP/2 compatible-waiter index did not match waiter state"
|
1233 1218 | );
|
1234 1219 | }
|
1235 1220 |
|
1236 1221 | #[cfg(test)]
|
1237 - | pub(super) fn snapshot(&self) -> CellSnapshot {
|
1222 + | pub(super) fn probe(&self) -> AcquisitionProbe {
|
1238 1223 | let (waiting, demand) = match &self.waiting {
|
1239 1224 | WaitingQueueState::Empty => (0, None),
|
1240 1225 | WaitingQueueState::Active { len, demand, .. } => (len.get(), Some(demand.id)),
|
1241 1226 | };
|
1242 - | CellSnapshot {
|
1227 + | AcquisitionProbe {
|
1243 1228 | waiting,
|
1244 1229 | retained: self.records.len(),
|
1245 1230 | demand,
|
1246 1231 | }
|
1247 1232 | }
|
1248 1233 | }
|
1249 1234 |
|
1250 1235 | /// Result of reserving a requesting waiter for one unlocked delivery.
|
1251 1236 | pub(super) enum DeliveryReservation {
|
1252 1237 | /// The current head was reserved and may have a successor demand.
|
1253 1238 | Reserved {
|
1254 1239 | waiter: WaiterId,
|
1255 1240 | successor: Option<DemandSnapshot>,
|
1256 1241 | },
|
1257 1242 | /// The delivery no longer matches live cell demand.
|
1258 1243 | Rejected,
|
1259 1244 | }
|
1260 1245 |
|
1261 1246 | /// Values detached from cell state by cancellation.
|
1262 1247 | pub(super) struct WaiterCancellation {
|
1263 1248 | /// Demand retirement and optional successor published after unlocking.
|
1264 1249 | pub(super) demand_updates: [Option<DemandSnapshot>; 2],
|
1265 1250 | /// Intermediate and terminal events returned for cleanup after unlocking.
|
1266 - | pub(super) returned_events: [Option<AcquisitionEvent>; 2],
|
1251 + | pub(super) returned_steps: [Option<AcquisitionStep>; 2],
|
1267 1252 | }
|
1268 1253 |
|
1269 - | /// Values detached after installing a selected protocol result.
|
1270 - | pub(super) struct WaiterInstall {
|
1271 - | /// Waiter that accepted the result, when one remained eligible.
|
1272 - | pub(super) waiter: Option<WaiterId>,
|
1254 + | /// Values detached after offering a selected protocol result.
|
1255 + | pub(super) struct WaiterResolution {
|
1273 1256 | /// Demand retirement and optional successor published after unlocking.
|
1274 1257 | pub(super) demand_updates: [Option<DemandSnapshot>; 2],
|
1275 1258 | /// Establishment permit or rejected result returned after unlocking.
|
1276 - | pub(super) returned_event: Option<AcquisitionEvent>,
|
1259 + | pub(super) returned_step: Option<AcquisitionStep>,
|
1277 1260 | /// Waiting task woken after demand publication.
|
1278 1261 | pub(super) waker: Option<Waker>,
|
1279 1262 | }
|
1280 1263 |
|
1281 - | impl WaiterInstall {
|
1264 + | impl WaiterResolution {
|
1282 1265 | /// Preserves a result when no live attempt can accept HTTP/1.
|
1283 - | fn rejected(result: AcquisitionResult) -> Self {
|
1266 + | fn refused(result: AcquisitionOutcome) -> Self {
|
1284 1267 | Self {
|
1285 - | waiter: None,
|
1286 1268 | demand_updates: [None, None],
|
1287 - | returned_event: Some(AcquisitionEvent::Complete(result)),
|
1269 + | returned_step: Some(AcquisitionStep::Resolved(result)),
|
1288 1270 | waker: None,
|
1289 1271 | }
|
1290 1272 | }
|
1291 1273 |
|
1292 1274 | /// Reports that no live attempt can accept an HTTP/2 activation.
|
1293 1275 | fn empty() -> Self {
|
1294 1276 | Self {
|
1295 - | waiter: None,
|
1296 1277 | demand_updates: [None, None],
|
1297 - | returned_event: None,
|
1278 + | returned_step: None,
|
1298 1279 | waker: None,
|
1299 1280 | }
|
1300 1281 | }
|
1301 1282 | }
|
1302 1283 |
|
1303 1284 | /// Invalid state observed while a committed event was crossing to the cell.
|
1304 1285 | #[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
1305 - | pub(super) enum ResultInstallError {
|
1286 + | pub(super) enum CellCommitError {
|
1306 1287 | /// The waiter reserved for the delivery no longer exists.
|
1307 1288 | MissingWaiter,
|
1308 1289 | /// The waiter exists but cannot receive the reserved delivery.
|
1309 1290 | UnexpectedState,
|
1310 1291 | }
|
1311 1292 |
|
1312 - | /// Values produced after installing or rejecting a committed event.
|
1313 - | pub(super) struct ResultInstall {
|
1314 - | /// Events rejected by cancellation and returned after unlocking.
|
1315 - | ///
|
1316 - | /// When both are present, the H1 fallback precedes the capacity fallback
|
1317 - | /// so successor demand can reuse the connection before starting another.
|
1318 - | pub(super) returned_events: [Option<AcquisitionEvent>; 2],
|
1319 - | /// Waiting task woken after the delivery fence closes.
|
1320 - | pub(super) waker: Option<Waker>,
|
1321 - | /// Invalid state reported only after the returned result runs its fallback.
|
1322 - | pub(super) error: Option<ResultInstallError>,
|
1323 - | /// Whether cell state became authoritative for the incoming event.
|
1324 - | pub(super) accepted: bool,
|
1293 + | /// Result of committing one exact acquisition step to a reserved waiter.
|
1294 + | pub(super) enum CellCommitOutcome {
|
1295 + | /// Cell state accepted the step and became authoritative for it.
|
1296 + | Committed {
|
1297 + | /// Waiting task woken after assignment settlement.
|
1298 + | waker: Option<Waker>,
|
1299 + | },
|
1300 + | /// The waiter no longer accepted the step; fallback remains valid.
|
1301 + | Refused {
|
1302 + | /// Steps returned after unlocking.
|
1303 + | returned: [Option<AcquisitionStep>; 2],
|
1304 + | /// Waiting task woken after assignment settlement.
|
1305 + | waker: Option<Waker>,
|
1306 + | },
|
1307 + | /// The reserved waiter disappeared or entered an impossible state.
|
1308 + | Invalid {
|
1309 + | /// Steps returned before reporting the invariant failure.
|
1310 + | returned: [Option<AcquisitionStep>; 2],
|
1311 + | /// Waiting task, if one remained attached.
|
1312 + | waker: Option<Waker>,
|
1313 + | /// Invalid cell transition observed under the lock.
|
1314 + | error: CellCommitError,
|
1315 + | },
|
1325 1316 | }
|
1326 1317 |
|
1327 - | impl ResultInstall {
|
1328 - | /// Preserves the incoming result for unlocked cleanup after invalid state.
|
1329 - | fn invalid(event: AcquisitionEvent, error: ResultInstallError) -> Self {
|
1330 - | Self {
|
1331 - | returned_events: [Some(event), None],
|
1318 + | impl CellCommitOutcome {
|
1319 + | /// Preserves the incoming step for unlocked cleanup after invalid state.
|
1320 + | fn invalid(step: AcquisitionStep, error: CellCommitError) -> Self {
|
1321 + | Self::Invalid {
|
1322 + | returned: [Some(step), None],
|
1332 1323 | waker: None,
|
1333 - | error: Some(error),
|
1334 - | accepted: false,
|
1324 + | error,
|
1335 1325 | }
|
1336 1326 | }
|
1337 1327 |
|
1338 - | /// Preserves a losing result for its ordinary unlocked fallback.
|
1339 - | fn rejected(event: AcquisitionEvent) -> Self {
|
1340 - | Self {
|
1341 - | returned_events: [Some(event), None],
|
1328 + | /// Preserves a losing step for its ordinary unlocked fallback.
|
1329 + | fn refused(step: AcquisitionStep) -> Self {
|
1330 + | Self::Refused {
|
1331 + | returned: [Some(step), None],
|
1342 1332 | waker: None,
|
1343 - | error: None,
|
1344 - | accepted: false,
|
1345 1333 | }
|
1346 1334 | }
|
1347 1335 | }
|
1348 1336 |
|
1349 1337 | /// FIFO state detached while advancing the active head.
|
1350 1338 | struct RemovedHead {
|
1351 1339 | /// Identity of the unlinked waiter.
|
1352 1340 | waiter: WaiterId,
|
1353 1341 | /// Retired demand generation that represented this head.
|
1354 1342 | demand: DemandTicket,
|
1355 1343 | /// New demand published if another waiter became the head.
|
1356 1344 | successor: Option<DemandSnapshot>,
|
1357 1345 | }
|
1358 1346 |
|
1359 1347 | #[cfg(test)]
|
1360 1348 | #[derive(Debug)]
|
1361 - | pub(super) struct CellSnapshot {
|
1349 + | pub(super) struct AcquisitionProbe {
|
1362 1350 | pub(super) waiting: usize,
|
1363 1351 | pub(super) retained: usize,
|
1364 1352 | pub(super) demand: Option<DemandId>,
|
1365 1353 | }
|
1366 1354 |
|
1367 1355 | #[cfg(all(test, not(smithy_http_client_loom)))]
|
1368 1356 | mod tests {
|
1369 1357 | use super::*;
|
1370 1358 |
|
1371 1359 | #[test]
|
1372 - | fn h1_required_waiter_is_not_an_h2_candidate() {
|
1360 + | fn h1_required_waiter_is_not_h2_compatible() {
|
1373 1361 | let mut queue = AcquisitionQueue::default();
|
1374 1362 | let (waiter, snapshot) = queue.register_waiter(
|
1375 1363 | ProtocolRequirement::H1Required,
|
1376 1364 | &EligibilityGroup::Pool,
|
1377 1365 | false,
|
1378 1366 | );
|
1379 1367 |
|
1380 1368 | assert!(snapshot.is_none());
|
1381 - | assert_eq!(Some(waiter), queue.oldest_h1_candidate());
|
1382 - | assert_eq!(None, queue.oldest_h2_candidate());
|
1369 + | assert_eq!(Some(waiter), queue.oldest_h1_compatible_waiter());
|
1370 + | assert_eq!(None, queue.oldest_h2_compatible_waiter());
|
1383 1371 | }
|
1384 1372 |
|
1385 1373 | #[test]
|
1386 1374 | fn head_cancellation_uses_the_successors_protocol_requirement() {
|
1387 1375 | let mut queue = AcquisitionQueue::default();
|
1388 1376 | let (head, initial) = queue.register_waiter(
|
1389 1377 | ProtocolRequirement::H2Required,
|
1390 1378 | &EligibilityGroup::Pool,
|
1391 1379 | true,
|
1392 1380 | );
|