9 9 | //! current head. That demand may be satisfied either by origin-wide connection
|
10 10 | //! capacity or by an existing HTTP/2 connection in its eligibility group.
|
11 11 | //! These resources require different orderings.
|
12 12 | //!
|
13 13 | //! For example, the origin order may contain `A(group X), B(group Y)` while
|
14 14 | //! only group Y has a reusable HTTP/2 connection. A remains first for the next
|
15 15 | //! available connection permit, while the group-Y connection can serve B
|
16 16 | //! without consuming that permit or delaying A. The group order finds B
|
17 17 | //! directly instead of scanning every partition.
|
18 18 | //!
|
19 - | //! The same demand occupies both orders because capacity and HTTP/2 publication
|
20 - | //! can race to satisfy it. Reserving either position retains a fence in both
|
21 - | //! orders until the requesting cell accepts or rejects the handoff. The other
|
22 - | //! resource therefore cannot serve the same demand while the first handoff is
|
23 - | //! running outside the admission lock.
|
24 - |
|
25 - | use super::{
|
26 - | DeliveryAckResult, DeliveryId, DemandId, DemandSnapshot, DemandState, IntrusiveLinks,
|
27 - | IntrusiveOrder, Permit, ProtocolRequirement,
|
28 - | };
|
19 + | //! The same demand occupies both orders because capacity delivery and HTTP/2
|
20 + | //! route installation can race to satisfy it. Assigning either position keeps
|
21 + | //! both positions attached until the requesting cell accepts or refuses the
|
22 + | //! handoff. The other resource cannot select the same demand while the first
|
23 + | //! handoff runs outside the admission lock.
|
24 + |
|
25 + | use super::{CapacityPermit, IntrusiveLinks, IntrusiveOrder};
|
29 26 | use crate::client::pool::partition::{EligibilityGroup, PartitionId};
|
30 27 | use std::collections::HashMap;
|
31 28 |
|
29 + | /// Protocol capability required by the head waiter in a cell.
|
30 + | #[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
31 + | pub(in crate::client::pool) enum ProtocolRequirement {
|
32 + | /// The waiter requires HTTP/1 wire semantics.
|
33 + | H1Required,
|
34 + | /// The waiter may dispatch over HTTP/1 or HTTP/2.
|
35 + | H1Compatible,
|
36 + | /// The waiter requires HTTP/2.
|
37 + | H2Required,
|
38 + | }
|
39 + |
|
40 + | impl ProtocolRequirement {
|
41 + | pub(in crate::client::pool) fn accepts_h1(self) -> bool {
|
42 + | self != Self::H2Required
|
43 + | }
|
44 + |
|
45 + | pub(in crate::client::pool) fn accepts_h2(self) -> bool {
|
46 + | self != Self::H1Required
|
47 + | }
|
48 + | }
|
49 + |
|
50 + | /// Identity of one cell-local demand generation.
|
51 + | #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
52 + | pub(in crate::client::pool) struct DemandId(u64);
|
53 + |
|
54 + | impl DemandId {
|
55 + | pub(in crate::client::pool) const fn from_u64(value: u64) -> Self {
|
56 + | Self(value)
|
57 + | }
|
58 + | }
|
59 + |
|
60 + | /// Strict ordering of complete snapshots within one [`DemandId`].
|
61 + | #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
62 + | pub(in crate::client::pool) struct SnapshotVersion(u64);
|
63 + |
|
64 + | impl SnapshotVersion {
|
65 + | pub(in crate::client::pool) const INITIAL: Self = Self(0);
|
66 + |
|
67 + | pub(in crate::client::pool) fn next(self) -> Self {
|
68 + | Self(
|
69 + | self.0
|
70 + | .checked_add(1)
|
71 + | .expect("demand snapshot version exhausted"),
|
72 + | )
|
73 + | }
|
74 + | }
|
75 + |
|
76 + | /// Complete state submitted for one demand identity.
|
77 + | #[derive(Clone, Debug, Eq, PartialEq)]
|
78 + | pub(in crate::client::pool) enum DemandState {
|
79 + | Active {
|
80 + | requirement: ProtocolRequirement,
|
81 + | eligibility_group: EligibilityGroup,
|
82 + | },
|
83 + | Inactive,
|
84 + | }
|
85 + |
|
86 + | /// Versioned replacement state for one cell's current demand generation.
|
87 + | #[derive(Clone, Debug, Eq, PartialEq)]
|
88 + | pub(in crate::client::pool) struct DemandSnapshot {
|
89 + | pub(super) id: DemandId,
|
90 + | pub(super) version: SnapshotVersion,
|
91 + | pub(super) state: DemandState,
|
92 + | }
|
93 + |
|
94 + | impl DemandSnapshot {
|
95 + | pub(in crate::client::pool) fn active(
|
96 + | id: DemandId,
|
97 + | version: SnapshotVersion,
|
98 + | requirement: ProtocolRequirement,
|
99 + | eligibility_group: EligibilityGroup,
|
100 + | ) -> Self {
|
101 + | Self {
|
102 + | id,
|
103 + | version,
|
104 + | state: DemandState::Active {
|
105 + | requirement,
|
106 + | eligibility_group,
|
107 + | },
|
108 + | }
|
109 + | }
|
110 + |
|
111 + | pub(in crate::client::pool) fn accepts_h2(&self) -> bool {
|
112 + | matches!(
|
113 + | self.state,
|
114 + | DemandState::Active { requirement, .. } if requirement.accepts_h2()
|
115 + | )
|
116 + | }
|
117 + |
|
118 + | pub(in crate::client::pool) fn inactive(id: DemandId, version: SnapshotVersion) -> Self {
|
119 + | Self {
|
120 + | id,
|
121 + | version,
|
122 + | state: DemandState::Inactive,
|
123 + | }
|
124 + | }
|
125 + |
|
126 + | #[cfg(test)]
|
127 + | pub(in crate::client::pool) fn id_for_test(&self) -> DemandId {
|
128 + | self.id
|
129 + | }
|
130 + |
|
131 + | pub(in crate::client::pool) fn is_active(&self) -> bool {
|
132 + | matches!(self.state, DemandState::Active { .. })
|
133 + | }
|
134 + |
|
135 + | fn is_newer_than(&self, current: &Self) -> bool {
|
136 + | self.id > current.id || (self.id == current.id && self.version > current.version)
|
137 + | }
|
138 + | }
|
139 + |
|
140 + | /// Never-reused identity of one resource-to-demand assignment.
|
141 + | #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
142 + | pub(super) struct DemandAssignmentId(pub(super) u64);
|
143 + |
|
144 + | /// Exact demand temporarily assigned while a resource crosses lock domains.
|
145 + | #[derive(Clone, Debug, Eq, PartialEq)]
|
146 + | pub(super) struct DemandAssignment {
|
147 + | pub(super) id: DemandAssignmentId,
|
148 + | pub(super) requester: PartitionId,
|
149 + | pub(super) demand: DemandId,
|
150 + | }
|
151 + |
|
152 + | /// Admission's settlement of one detached demand assignment.
|
153 + | #[derive(Clone, Debug, Eq, PartialEq)]
|
154 + | pub(super) enum DemandAssignmentOutcome {
|
155 + | Accepted { successor: Option<DemandSnapshot> },
|
156 + | RetrySamePosition,
|
157 + | Refused { successor: Option<DemandSnapshot> },
|
158 + | }
|
159 + |
|
32 160 | /// Cross-cell demand records and their scheduling orders.
|
33 161 | ///
|
34 162 | /// At every completed transition:
|
35 163 | ///
|
36 164 | /// - `records` owns the newest snapshot for every retained cell.
|
37 165 | /// - the origin order and the record's eligibility-group order contain exactly
|
38 - | /// the `Queued` and `Delivering` records;
|
39 - | /// - links for both views live inside those ordered residence variants;
|
40 - | /// - an origin delivery fences the origin head; and
|
41 - | /// - an HTTP/2 publication fences its eligibility-group head.
|
166 + | /// the `Queued` and `PendingAssignment` records;
|
167 + | /// - links for both orders live inside those schedule states;
|
168 + | /// - an origin assignment owns the origin head; and
|
169 + | /// - an HTTP/2 route assignment owns its eligibility-group head.
|
42 170 | ///
|
43 171 | /// Admission coordinates capacity extraction with this schedule while holding
|
44 172 | /// the same origin lock.
|
45 173 | #[derive(Debug, Default)]
|
46 174 | pub(super) struct DemandSchedule {
|
47 175 | /// Latest demand and scheduling residence for each retained cell.
|
48 176 | records: HashMap<PartitionId, DemandRecord>,
|
49 177 | /// Origin-wide order used by one-to-one capacity and HTTP/1 reuse.
|
50 178 | origin_order: IntrusiveOrder<PartitionId>,
|
51 179 | /// Reusable-HTTP/2 demand order for each connection-reuse group.
|
52 180 | group_orders: HashMap<EligibilityGroup, IntrusiveOrder<PartitionId>>,
|
53 181 | }
|
54 182 |
|
55 183 | /// Complete origin-order head used to choose one HTTP/1 reuse action.
|
56 184 | #[derive(Clone, Debug)]
|
57 185 | pub(super) struct QueuedDemand {
|
58 186 | /// Cell whose oldest waiter owns this demand generation.
|
59 - | pub(super) requesting_partition: PartitionId,
|
187 + | pub(super) requester: PartitionId,
|
60 188 | /// Demand identity revalidated at each crossing.
|
61 189 | pub(super) demand: DemandId,
|
62 190 | /// Protocol capability required by the requesting cell waiter.
|
63 191 | pub(super) requirement: ProtocolRequirement,
|
64 192 | /// Connection-owning cells whose H1 senders may satisfy this demand.
|
65 193 | pub(super) eligibility_group: EligibilityGroup,
|
66 194 | }
|
67 195 |
|
68 196 | /// Latest snapshot and scheduling residence for one stable cell.
|
69 197 | #[derive(Debug)]
|
70 198 | struct DemandRecord {
|
71 - | /// Newest complete publication observed for the cell.
|
72 - | pub(super) latest: DemandSnapshot,
|
73 - | /// Stable group retained while an inactive replacement crosses a fence.
|
74 - | group: Option<EligibilityGroup>,
|
199 + | /// Newest complete snapshot observed for the cell.
|
200 + | latest: DemandSnapshot,
|
201 + | /// Stable group retained while an inactive replacement crosses an assignment.
|
202 + | eligibility_group: Option<EligibilityGroup>,
|
75 203 | /// Scheduling residence, including links while ordered.
|
76 - | residence: DemandResidence,
|
204 + | schedule_state: DemandScheduleState,
|
77 205 | }
|
78 206 |
|
79 207 | /// Links retained by one demand in both scheduling views.
|
80 208 | #[derive(Clone, Debug)]
|
81 209 | struct DemandLinks {
|
82 210 | /// Position in origin-wide capacity and HTTP/1 order.
|
83 211 | origin: IntrusiveLinks<PartitionId>,
|
84 212 | /// Position in the demand's all-protocol eligibility-group order.
|
85 213 | group: IntrusiveLinks<PartitionId>,
|
86 214 | }
|
87 215 |
|
88 - | /// Order whose head is fenced by one delivery crossing.
|
216 + | /// Order whose head is held by one detached demand assignment.
|
89 217 | #[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
90 - | enum DeliveryView {
|
218 + | enum DemandOrder {
|
91 219 | /// Capacity or HTTP/1 delivery selected from origin order.
|
92 220 | Origin,
|
93 - | /// HTTP/2 publication selected from eligibility-group order.
|
94 - | Group,
|
95 - | }
|
96 - |
|
97 - | /// Selects one of a demand residence's intrusive link sets.
|
98 - | #[derive(Clone, Copy)]
|
99 - | enum DemandOrderView {
|
100 - | /// Origin-wide capacity and HTTP/1 order.
|
101 - | Origin,
|
102 - | /// Eligibility-group all-protocol order.
|
221 + | /// HTTP/2 route selected from eligibility-group order.
|
103 222 | Group,
|
104 223 | }
|
105 224 |
|
106 - | /// Admission residence for one partition's latest demand.
|
225 + | /// Scheduling state for one partition's latest demand.
|
107 226 | ///
|
108 227 | /// ```text
|
109 - | /// Idle -- active publication ------------------------------> Queued
|
110 - | /// Queued -- reserve head ----------------------------------> Delivering
|
111 - | /// Queued -- inactive or replacement publication ----------> Idle
|
112 - | /// Delivering -- retry unchanged demand --------------------> Queued
|
113 - | /// Delivering -- accept, reject, or replacement retry ------> Idle
|
114 - | /// Idle -- active successor remains ------------------------> Queued
|
228 + | /// Unscheduled -- active snapshot --------------------------> Queued
|
229 + | /// Queued -- prepare assignment ----------------------------> PendingAssignment
|
230 + | /// Queued -- inactive or replacement snapshot -------------> Unscheduled
|
231 + | /// PendingAssignment -- retry unchanged demand -------------> Queued
|
232 + | /// PendingAssignment -- accept/refuse/replacement retry ----> Unscheduled
|
233 + | /// Unscheduled -- active successor remains -----------------> Queued
|
115 234 | /// ```
|
116 235 | #[derive(Clone, Debug)]
|
117 - | enum DemandResidence {
|
236 + | enum DemandScheduleState {
|
118 237 | /// The cell has no demand represented in scheduling.
|
119 - | Idle,
|
238 + | Unscheduled,
|
120 239 | /// The demand is waiting in origin order.
|
121 240 | Queued {
|
122 241 | /// Demand generation represented by this residence.
|
123 242 | demand: DemandId,
|
124 243 | /// Origin-wide and eligibility-group scheduling links.
|
125 244 | links: DemandLinks,
|
126 245 | },
|
127 - | /// One delivery or publication guard fences this demand.
|
128 - | Delivering {
|
129 - | /// Demand generation fenced at one scheduling head.
|
130 - | demand: DemandId,
|
131 - | /// Snapshot version current when admission created the fence.
|
246 + | /// One delivery or route guard owns this demand assignment.
|
247 + | PendingAssignment {
|
248 + | /// Exact assignment allowed to settle this state.
|
249 + | assignment: DemandAssignment,
|
250 + | /// Snapshot version current when admission created the assignment.
|
132 251 | version: super::SnapshotVersion,
|
133 - | /// Delivery allowed to complete this fence.
|
134 - | delivery: DeliveryId,
|
135 - | /// Scheduling view whose head owns this crossing.
|
252 + | /// Scheduling order whose head selected this assignment.
|
136 253 | #[cfg_attr(
|
137 254 | not(debug_assertions),
|
138 255 | allow(
|
139 256 | dead_code,
|
140 257 | reason = "the scheduling view is retained for debug invariant checks"
|
141 258 | )
|
142 259 | )]
|
143 - | view: DeliveryView,
|
260 + | selected_order: DemandOrder,
|
144 261 | /// Both scheduling positions retained until acknowledgement.
|
145 262 | links: DemandLinks,
|
146 263 | },
|
147 264 | }
|
148 265 |
|
149 - | impl DemandResidence {
|
266 + | impl DemandScheduleState {
|
150 267 | #[cfg(any(debug_assertions, test))]
|
151 268 | /// Borrows one view's links while this record is ordered.
|
152 - | fn links(&self, view: DemandOrderView) -> Option<&IntrusiveLinks<PartitionId>> {
|
269 + | fn links(&self, order: DemandOrder) -> Option<&IntrusiveLinks<PartitionId>> {
|
153 270 | let links = match self {
|
154 - | Self::Idle => return None,
|
155 - | Self::Queued { links, .. } | Self::Delivering { links, .. } => links,
|
271 + | Self::Unscheduled => return None,
|
272 + | Self::Queued { links, .. } | Self::PendingAssignment { links, .. } => links,
|
156 273 | };
|
157 - | match view {
|
158 - | DemandOrderView::Origin => Some(&links.origin),
|
159 - | DemandOrderView::Group => Some(&links.group),
|
274 + | match order {
|
275 + | DemandOrder::Origin => Some(&links.origin),
|
276 + | DemandOrder::Group => Some(&links.group),
|
160 277 | }
|
161 278 | }
|
162 279 |
|
163 280 | /// Mutably borrows one view's links from an ordered record.
|
164 281 | ///
|
165 282 | /// # Panics
|
166 283 | ///
|
167 284 | /// Panics when called for an idle record.
|
168 - | fn links_mut(&mut self, view: DemandOrderView) -> &mut IntrusiveLinks<PartitionId> {
|
285 + | fn links_mut(&mut self, order: DemandOrder) -> &mut IntrusiveLinks<PartitionId> {
|
169 286 | let links = match self {
|
170 - | Self::Idle => panic!("idle demand has no scheduling links"),
|
171 - | Self::Queued { links, .. } | Self::Delivering { links, .. } => links,
|
287 + | Self::Unscheduled => panic!("unscheduled demand has no scheduling links"),
|
288 + | Self::Queued { links, .. } | Self::PendingAssignment { links, .. } => links,
|
172 289 | };
|
173 - | match view {
|
174 - | DemandOrderView::Origin => &mut links.origin,
|
175 - | DemandOrderView::Group => &mut links.group,
|
290 + | match order {
|
291 + | DemandOrder::Origin => &mut links.origin,
|
292 + | DemandOrder::Group => &mut links.group,
|
176 293 | }
|
177 294 | }
|
178 295 |
|
179 296 | /// Detaches both link sets while moving a record out of scheduling.
|
180 297 | fn into_links(self) -> Option<DemandLinks> {
|
181 298 | match self {
|
182 - | Self::Idle => None,
|
183 - | Self::Queued { links, .. } | Self::Delivering { links, .. } => Some(links),
|
299 + | Self::Unscheduled => None,
|
300 + | Self::Queued { links, .. } | Self::PendingAssignment { links, .. } => Some(links),
|
184 301 | }
|
185 302 | }
|
186 303 | }
|
187 304 |
|
188 - | /// Demand reserved at the order head for one capacity delivery.
|
189 - | pub(super) struct ScheduledDemand {
|
190 - | /// Selected destination cell.
|
191 - | pub(super) requesting_partition: PartitionId,
|
192 - | /// Demand identity current when the crossing was prepared.
|
193 - | pub(super) demand: DemandId,
|
194 - | }
|
195 - |
|
196 305 | /// Connection capacity extracted under admission lock before crossing to a cell.
|
197 306 | #[derive(Debug)]
|
198 307 | pub(super) struct PreparedCapacityDelivery {
|
199 - | /// Permit transferred into the delivery guard.
|
200 - | pub(super) permit: Permit,
|
201 - | /// Fence identity allocated for this crossing.
|
202 - | pub(super) delivery: DeliveryId,
|
203 - | /// Selected destination cell.
|
204 - | pub(super) requesting_partition: PartitionId,
|
205 - | /// Demand identity current when the crossing was prepared.
|
206 - | pub(super) demand: DemandId,
|
308 + | pub(super) assignment: DemandAssignment,
|
309 + | pub(super) permit: CapacityPermit,
|
207 310 | }
|
208 311 |
|
209 312 | impl DemandSchedule {
|
210 - | /// Applies a complete snapshot and updates its cell's scheduling residence.
|
211 - | pub(super) fn publish(&mut self, requesting_partition: PartitionId, snapshot: DemandSnapshot) {
|
212 - | if let Some(current) = self.records.get(&requesting_partition) {
|
313 + | /// Applies a complete snapshot and updates its cell's scheduling state.
|
314 + | pub(super) fn apply_snapshot(&mut self, requester: PartitionId, snapshot: DemandSnapshot) {
|
315 + | if let Some(current) = self.records.get(&requester) {
|
213 316 | if !snapshot.is_newer_than(¤t.latest) {
|
214 317 | return;
|
215 318 | }
|
216 319 | } else {
|
217 320 | self.records.insert(
|
218 - | requesting_partition,
|
321 + | requester,
|
219 322 | DemandRecord {
|
220 323 | latest: snapshot.clone(),
|
221 - | group: None,
|
222 - | residence: DemandResidence::Idle,
|
324 + | eligibility_group: None,
|
325 + | schedule_state: DemandScheduleState::Unscheduled,
|
223 326 | },
|
224 327 | );
|
225 328 | }
|
226 329 |
|
227 330 | let should_remove = matches!(
|
228 331 | &self
|
229 332 | .records
|
230 - | .get(&requesting_partition)
|
231 - | .expect("published demand record disappeared")
|
232 - | .residence,
|
233 - | DemandResidence::Queued { demand, .. }
|
333 + | .get(&requester)
|
334 + | .expect("demand record disappeared")
|
335 + | .schedule_state,
|
336 + | DemandScheduleState::Queued { demand, .. }
|
234 337 | if *demand != snapshot.id || !snapshot.is_active()
|
235 338 | );
|
236 339 | if should_remove {
|
237 - | self.remove_from_order(&requesting_partition);
|
340 + | self.remove_from_order(&requester);
|
238 341 | }
|
239 342 |
|
240 343 | if let DemandState::Active {
|
241 344 | eligibility_group, ..
|
242 345 | } = &snapshot.state
|
243 346 | {
|
244 347 | self.records
|
245 - | .get_mut(&requesting_partition)
|
246 - | .expect("published demand record disappeared")
|
247 - | .group = Some(eligibility_group.clone());
|
348 + | .get_mut(&requester)
|
349 + | .expect("demand record disappeared")
|
350 + | .eligibility_group = Some(eligibility_group.clone());
|
248 351 | }
|
249 352 | self.records
|
250 - | .get_mut(&requesting_partition)
|
251 - | .expect("published demand record disappeared")
|
353 + | .get_mut(&requester)
|
354 + | .expect("demand record disappeared")
|
252 355 | .latest = snapshot;
|
253 356 |
|
254 357 | let record = self
|
255 358 | .records
|
256 - | .get(&requesting_partition)
|
257 - | .expect("published demand record disappeared");
|
258 - | if record.latest.is_active() && matches!(&record.residence, DemandResidence::Idle) {
|
259 - | self.enqueue(requesting_partition);
|
359 + | .get(&requester)
|
360 + | .expect("demand record disappeared");
|
361 + | if record.latest.is_active()
|
362 + | && matches!(&record.schedule_state, DemandScheduleState::Unscheduled)
|
363 + | {
|
364 + | self.enqueue(requester);
|
260 365 | }
|
261 366 | self.assert_consistent();
|
262 367 | }
|
263 368 |
|
264 369 | /// Appends an idle active demand to both scheduling orders.
|
265 - | fn enqueue(&mut self, requesting_partition: PartitionId) {
|
370 + | fn enqueue(&mut self, requester: PartitionId) {
|
266 371 | let group = self
|
267 - | .group_for(&requesting_partition)
|
372 + | .group_for(&requester)
|
268 373 | .expect("enqueued demand was inactive");
|
269 - | let origin = self.origin_order.push_back(requesting_partition);
|
374 + | let origin = self.origin_order.push_back(requester);
|
270 375 | let group_links = self
|
271 376 | .group_orders
|
272 377 | .entry(group)
|
273 378 | .or_default()
|
274 - | .push_back(requesting_partition);
|
379 + | .push_back(requester);
|
275 380 | if let Some(previous) = origin.previous {
|
276 381 | self.records
|
277 382 | .get_mut(&previous)
|
278 383 | .expect("origin demand tail disappeared")
|
279 - | .residence
|
280 - | .links_mut(DemandOrderView::Origin)
|
281 - | .next = Some(requesting_partition);
|
384 + | .schedule_state
|
385 + | .links_mut(DemandOrder::Origin)
|
386 + | .next = Some(requester);
|
282 387 | }
|
283 388 | if let Some(previous) = group_links.previous {
|
284 389 | self.records
|
285 390 | .get_mut(&previous)
|
286 391 | .expect("group demand tail disappeared")
|
287 - | .residence
|
288 - | .links_mut(DemandOrderView::Group)
|
289 - | .next = Some(requesting_partition);
|
392 + | .schedule_state
|
393 + | .links_mut(DemandOrder::Group)
|
394 + | .next = Some(requester);
|
290 395 | }
|
291 396 |
|
292 397 | let record = self
|
293 398 | .records
|
294 - | .get_mut(&requesting_partition)
|
399 + | .get_mut(&requester)
|
295 400 | .expect("queued demand record disappeared");
|
296 - | debug_assert!(matches!(record.residence, DemandResidence::Idle));
|
401 + | debug_assert!(matches!(
|
402 + | record.schedule_state,
|
403 + | DemandScheduleState::Unscheduled
|
404 + | ));
|
297 405 | let demand = record.latest.id;
|
298 - | record.residence = DemandResidence::Queued {
|
406 + | record.schedule_state = DemandScheduleState::Queued {
|
299 407 | demand,
|
300 408 | links: DemandLinks {
|
301 409 | origin,
|
302 410 | group: group_links,
|
303 411 | },
|
304 412 | };
|
305 413 | }
|
306 414 |
|
307 415 | /// Removes an ordered demand from both views and leaves its record idle.
|
308 - | fn remove_from_order(&mut self, requesting_partition: &PartitionId) {
|
416 + | fn remove_from_order(&mut self, requester: &PartitionId) {
|
309 417 | let group = self
|
310 - | .group_for(requesting_partition)
|
418 + | .group_for(requester)
|
311 419 | .expect("removed demand had no eligibility group");
|
312 - | let residence = {
|
420 + | let schedule_state = {
|
313 421 | let record = self
|
314 422 | .records
|
315 - | .get_mut(requesting_partition)
|
423 + | .get_mut(requester)
|
316 424 | .expect("removed demand record disappeared");
|
317 - | std::mem::replace(&mut record.residence, DemandResidence::Idle)
|
425 + | std::mem::replace(&mut record.schedule_state, DemandScheduleState::Unscheduled)
|
318 426 | };
|
319 - | let links = residence
|
427 + | let links = schedule_state
|
320 428 | .into_links()
|
321 429 | .expect("removed demand had no scheduling links");
|
322 430 |
|
323 431 | if let Some(previous) = links.origin.previous {
|
324 432 | self.records
|
325 433 | .get_mut(&previous)
|
326 434 | .expect("previous origin demand disappeared")
|
327 - | .residence
|
328 - | .links_mut(DemandOrderView::Origin)
|
435 + | .schedule_state
|
436 + | .links_mut(DemandOrder::Origin)
|
329 437 | .next = links.origin.next;
|
330 438 | }
|
331 439 | if let Some(next) = links.origin.next {
|
332 440 | self.records
|
333 441 | .get_mut(&next)
|
334 442 | .expect("next origin demand disappeared")
|
335 - | .residence
|
336 - | .links_mut(DemandOrderView::Origin)
|
443 + | .schedule_state
|
444 + | .links_mut(DemandOrder::Origin)
|
337 445 | .previous = links.origin.previous;
|
338 446 | }
|
339 - | self.origin_order
|
340 - | .remove(*requesting_partition, links.origin);
|
447 + | self.origin_order.remove(*requester, links.origin);
|
341 448 |
|
342 449 | if let Some(previous) = links.group.previous {
|
343 450 | self.records
|
344 451 | .get_mut(&previous)
|
345 452 | .expect("previous group demand disappeared")
|
346 - | .residence
|
347 - | .links_mut(DemandOrderView::Group)
|
453 + | .schedule_state
|
454 + | .links_mut(DemandOrder::Group)
|
348 455 | .next = links.group.next;
|
349 456 | }
|
350 457 | if let Some(next) = links.group.next {
|
351 458 | self.records
|
352 459 | .get_mut(&next)
|
353 460 | .expect("next group demand disappeared")
|
354 - | .residence
|
355 - | .links_mut(DemandOrderView::Group)
|
461 + | .schedule_state
|
462 + | .links_mut(DemandOrder::Group)
|
356 463 | .previous = links.group.previous;
|
357 464 | }
|
358 465 | let order = self
|
359 466 | .group_orders
|
360 467 | .get_mut(&group)
|
361 468 | .expect("ordered demand lost its eligibility-group order");
|
362 - | order.remove(*requesting_partition, links.group);
|
469 + | order.remove(*requester, links.group);
|
363 470 | if order.len() == 0 {
|
364 471 | self.group_orders.remove(&group);
|
365 472 | }
|
366 473 | }
|
367 474 |
|
368 475 | /// Returns whether the head can begin a new one-to-one delivery.
|
369 476 | pub(super) fn head_is_queued(&self) -> bool {
|
370 477 | let Some(head) = self.origin_order.head() else {
|
371 478 | return false;
|
372 479 | };
|
373 480 | matches!(
|
374 481 | &self
|
375 482 | .records
|
376 483 | .get(&head)
|
377 484 | .expect("order head disappeared")
|
378 - | .residence,
|
379 - | DemandResidence::Queued { .. }
|
485 + | .schedule_state,
|
486 + | DemandScheduleState::Queued { .. }
|
380 487 | )
|
381 488 | }
|
382 489 |
|
383 490 | /// Returns the complete origin-order head when it may begin reuse.
|
384 491 | pub(super) fn queued_head(&self) -> Option<QueuedDemand> {
|
385 492 | let head = self.origin_order.head()?;
|
386 493 | let record = self.records.get(&head).expect("order head disappeared");
|
387 - | let DemandResidence::Queued { demand, .. } = &record.residence else {
|
494 + | let DemandScheduleState::Queued { demand, .. } = &record.schedule_state else {
|
388 495 | return None;
|
389 496 | };
|
390 497 | let DemandState::Active {
|
391 - | head: requirement,
|
498 + | requirement,
|
392 499 | eligibility_group,
|
393 500 | } = &record.latest.state
|
394 501 | else {
|
395 502 | unreachable!("queued demand became inactive");
|
396 503 | };
|
397 504 | Some(QueuedDemand {
|
398 - | requesting_partition: head,
|
505 + | requester: head,
|
399 506 | demand: *demand,
|
400 507 | requirement: *requirement,
|
401 508 | eligibility_group: eligibility_group.clone(),
|
402 509 | })
|
403 510 | }
|
404 511 |
|
405 512 | /// Returns one eligibility-group head when it may receive H2 visibility.
|
406 513 | pub(super) fn queued_group_head(&self, group: &EligibilityGroup) -> Option<QueuedDemand> {
|
407 514 | let head = self.group_orders.get(group)?.head()?;
|
408 515 | let record = self
|
409 516 | .records
|
410 517 | .get(&head)
|
411 518 | .expect("group demand head disappeared");
|
412 - | let DemandResidence::Queued { demand, .. } = &record.residence else {
|
519 + | let DemandScheduleState::Queued { demand, .. } = &record.schedule_state else {
|
413 520 | return None;
|
414 521 | };
|
415 522 | let DemandState::Active {
|
416 - | head: requirement,
|
523 + | requirement,
|
417 524 | eligibility_group,
|
418 525 | } = &record.latest.state
|
419 526 | else {
|
420 527 | unreachable!("queued group demand became inactive");
|
421 528 | };
|
422 529 | debug_assert_eq!(eligibility_group, group);
|
423 530 | Some(QueuedDemand {
|
424 - | requesting_partition: head,
|
531 + | requester: head,
|
425 532 | demand: *demand,
|
426 533 | requirement: *requirement,
|
427 534 | eligibility_group: eligibility_group.clone(),
|
428 535 | })
|
429 536 | }
|
430 537 |
|
431 538 | /// Returns the latest eligibility group retained for one cell.
|
432 - | pub(super) fn group_for(&self, requesting_partition: &PartitionId) -> Option<EligibilityGroup> {
|
539 + | pub(super) fn group_for(&self, requester: &PartitionId) -> Option<EligibilityGroup> {
|
433 540 | self.records
|
434 - | .get(requesting_partition)
|
435 - | .and_then(|record| record.group.clone())
|
541 + | .get(requester)
|
542 + | .and_then(|record| record.eligibility_group.clone())
|
436 543 | }
|
437 544 |
|
438 545 | /// Returns whether `requesting_partition` still has this demand queued for a new action.
|
439 - | pub(super) fn is_current_queued(
|
440 - | &self,
|
441 - | requesting_partition: &PartitionId,
|
442 - | demand: DemandId,
|
443 - | ) -> bool {
|
444 - | self.records
|
445 - | .get(requesting_partition)
|
446 - | .is_some_and(|record| {
|
447 - | record.latest.id == demand
|
448 - | && record.latest.is_active()
|
449 - | && matches!(
|
450 - | record.residence,
|
451 - | DemandResidence::Queued {
|
452 - | demand: current,
|
453 - | ..
|
454 - | } if current == demand
|
455 - | )
|
456 - | })
|
546 + | pub(super) fn is_current_queued(&self, requester: &PartitionId, demand: DemandId) -> bool {
|
547 + | self.records.get(requester).is_some_and(|record| {
|
548 + | record.latest.id == demand
|
549 + | && record.latest.is_active()
|
550 + | && matches!(
|
551 + | record.schedule_state,
|
552 + | DemandScheduleState::Queued {
|
553 + | demand: current,
|
554 + | ..
|
555 + | } if current == demand
|
556 + | )
|
557 + | })
|
457 558 | }
|
458 559 |
|
459 - | /// Fences one reuse operation's demand while it remains the origin-order head.
|
460 - | pub(super) fn reserve_reuse_demand(
|
560 + | /// Assigns exact origin-head demand after an H1 match resolves.
|
561 + | pub(super) fn prepare_h1_assignment(
|
461 562 | &mut self,
|
462 - | requesting_partition: &PartitionId,
|
563 + | requester: &PartitionId,
|
463 564 | demand: DemandId,
|
464 - | delivery: DeliveryId,
|
465 - | ) -> Option<ScheduledDemand> {
|
466 - | if !self.is_current_queued(requesting_partition, demand) {
|
565 + | assignment_id: DemandAssignmentId,
|
566 + | ) -> Option<DemandAssignment> {
|
567 + | if !self.is_current_queued(requester, demand) || self.origin_order.head()? != *requester {
|
467 568 | return None;
|
468 569 | }
|
469 - | let head = self.origin_order.head()?;
|
470 - | if head != *requesting_partition {
|
471 - | return None;
|
472 - | }
|
473 - | self.reserve_origin_head(delivery)
|
570 + | self.prepare_assignment(*requester, assignment_id, DemandOrder::Origin)
|
474 571 | }
|
475 572 |
|
476 - | /// Fences one eligibility-group head for an H2 publication.
|
477 - | pub(super) fn reserve_group_head(
|
573 + | /// Assigns one exact eligibility-group head to an H2 route.
|
574 + | pub(super) fn prepare_group_assignment(
|
478 575 | &mut self,
|
479 - | group: &EligibilityGroup,
|
480 - | requesting_partition: &PartitionId,
|
576 + | eligibility_group: &EligibilityGroup,
|
577 + | requester: &PartitionId,
|
481 578 | demand: DemandId,
|
482 - | delivery: DeliveryId,
|
483 - | ) -> Option<ScheduledDemand> {
|
484 - | if !self.is_current_queued(requesting_partition, demand)
|
485 - | || self.group_orders.get(group)?.head() != Some(*requesting_partition)
|
579 + | assignment_id: DemandAssignmentId,
|
580 + | ) -> Option<DemandAssignment> {
|
581 + | if !self.is_current_queued(requester, demand)
|
582 + | || self.group_orders.get(eligibility_group)?.head() != Some(*requester)
|
486 583 | {
|
487 584 | return None;
|
488 585 | }
|
489 - | self.reserve(*requesting_partition, delivery, DeliveryView::Group)
|
586 + | self.prepare_assignment(*requester, assignment_id, DemandOrder::Group)
|
490 587 | }
|
491 588 |
|
492 - | /// Changes the origin head into a delivery fence at the same positions.
|
493 - | pub(super) fn reserve_origin_head(&mut self, delivery: DeliveryId) -> Option<ScheduledDemand> {
|
494 - | let head = self.origin_order.head()?;
|
495 - | self.reserve(head, delivery, DeliveryView::Origin)
|
589 + | /// Assigns the origin-order head to one capacity delivery.
|
590 + | pub(super) fn prepare_origin_assignment(
|
591 + | &mut self,
|
592 + | assignment_id: DemandAssignmentId,
|
593 + | ) -> Option<DemandAssignment> {
|
594 + | let requester = self.origin_order.head()?;
|
595 + | self.prepare_assignment(requester, assignment_id, DemandOrder::Origin)
|
496 596 | }
|
497 597 |
|
498 - | /// Changes one queued demand into a delivery fence.
|
499 - | fn reserve(
|
598 + | fn prepare_assignment(
|
500 599 | &mut self,
|
501 - | requesting_partition: PartitionId,
|
502 - | delivery: DeliveryId,
|
503 - | view: DeliveryView,
|
504 - | ) -> Option<ScheduledDemand> {
|
600 + | requester: PartitionId,
|
601 + | assignment_id: DemandAssignmentId,
|
602 + | selected_order: DemandOrder,
|
603 + | ) -> Option<DemandAssignment> {
|
505 604 | let record = self
|
506 605 | .records
|
507 - | .get_mut(&requesting_partition)
|
606 + | .get_mut(&requester)
|
508 607 | .expect("order head disappeared");
|
509 - | let residence = std::mem::replace(&mut record.residence, DemandResidence::Idle);
|
510 - | match residence {
|
511 - | DemandResidence::Queued { demand, links } => {
|
608 + | let schedule_state =
|
609 + | std::mem::replace(&mut record.schedule_state, DemandScheduleState::Unscheduled);
|
610 + | match schedule_state {
|
611 + | DemandScheduleState::Queued { demand, links } => {
|
512 612 | debug_assert_eq!(record.latest.id, demand);
|
513 613 | debug_assert!(record.latest.is_active());
|
514 - | record.residence = DemandResidence::Delivering {
|
614 + | let assignment = DemandAssignment {
|
615 + | id: assignment_id,
|
616 + | requester,
|
515 617 | demand,
|
618 + | };
|
619 + | record.schedule_state = DemandScheduleState::PendingAssignment {
|
620 + | assignment: assignment.clone(),
|
516 621 | version: record.latest.version,
|
517 - | delivery,
|
518 - | view,
|
622 + | selected_order,
|
519 623 | links,
|
520 624 | };
|
521 625 | self.assert_consistent();
|
522 - | Some(ScheduledDemand {
|
523 - | requesting_partition,
|
524 - | demand,
|
525 - | })
|
626 + | Some(assignment)
|
526 627 | }
|
527 - | residence => {
|
528 - | record.residence = residence;
|
628 + | schedule_state => {
|
629 + | record.schedule_state = schedule_state;
|
529 630 | None
|
530 631 | }
|
531 632 | }
|
532 633 | }
|
533 634 |
|
534 - | /// Returns whether the test-observed delivery fence still names this demand.
|
535 - | ///
|
536 - | /// Production reservation revalidates the demand identity through
|
537 - | /// `AcquisitionQueue::reserve_delivery_waiter`; this helper only exposes
|
538 - | /// admission-side fence state to focused tests.
|
539 635 | #[cfg(test)]
|
540 - | pub(super) fn delivery_is_current(
|
541 - | &self,
|
542 - | delivery: DeliveryId,
|
543 - | requesting_partition: &PartitionId,
|
544 - | demand: DemandId,
|
545 - | ) -> bool {
|
546 - | let Some(record) = self.records.get(requesting_partition) else {
|
636 + | pub(super) fn assignment_is_current(&self, assignment: &DemandAssignment) -> bool {
|
637 + | let Some(record) = self.records.get(&assignment.requester) else {
|
547 638 | return false;
|
548 639 | };
|
549 640 | matches!(
|
550 - | &record.residence,
|
551 - | DemandResidence::Delivering {
|
552 - | demand: current_demand,
|
553 - | delivery: current_delivery,
|
554 - | ..
|
555 - | } if *current_delivery == delivery
|
556 - | && *current_demand == demand
|
557 - | && record.latest.id == demand
|
558 - | && record.latest.is_active()
|
641 + | &record.schedule_state,
|
642 + | DemandScheduleState::PendingAssignment { assignment: current, .. }
|
643 + | if current == assignment
|
644 + | && record.latest.id == assignment.demand
|
645 + | && record.latest.is_active()
|
559 646 | )
|
560 647 | }
|
561 648 |
|
562 - | /// Closes one delivery fence and resolves the demand's next residence.
|
563 - | ///
|
564 - | /// `Accepted` consumes the delivered generation and installs a newer
|
565 - | /// successor when the requesting cell supplied one. `RetrySameResidence`
|
566 - | /// restores the same generation at its existing order position when it
|
567 - | /// remains live; if it was replaced, the latest active generation is
|
568 - | /// appended as new demand. `Rejected` removes the fenced generation after
|
569 - | /// its permit has been refunnelled and applies the same successor
|
570 - | /// arbitration as acceptance.
|
571 - | pub(super) fn finish_delivery(
|
649 + | /// Settles one detached assignment and resolves the demand's next state.
|
650 + | pub(super) fn settle_assignment(
|
572 651 | &mut self,
|
573 - | delivery: DeliveryId,
|
574 - | requesting_partition: &PartitionId,
|
575 - | result: DeliveryAckResult,
|
652 + | assignment: &DemandAssignment,
|
653 + | outcome: DemandAssignmentOutcome,
|
576 654 | ) {
|
577 - | let Some(record) = self.records.get(requesting_partition) else {
|
655 + | let requester = &assignment.requester;
|
656 + | let Some(record) = self.records.get(requester) else {
|
578 657 | return;
|
579 658 | };
|
580 - | let delivered_demand = match &record.residence {
|
581 - | DemandResidence::Delivering {
|
582 - | demand,
|
659 + | let assigned = match &record.schedule_state {
|
660 + | DemandScheduleState::PendingAssignment {
|
661 + | assignment: current,
|
583 662 | version,
|
584 - | delivery: current,
|
585 663 | ..
|
586 - | } if *current == delivery => (*demand, *version),
|
664 + | } if current == assignment => (current.demand, *version),
|
587 665 | _ => return,
|
588 666 | };
|
589 - | let (delivered_demand, delivered_version) = delivered_demand;
|
667 + | let (assigned_demand, assigned_version) = assigned;
|
590 668 |
|
591 - | match result {
|
592 - | DeliveryAckResult::RetrySameResidence => {
|
669 + | match outcome {
|
670 + | DemandAssignmentOutcome::RetrySamePosition => {
|
593 671 | let record = self
|
594 672 | .records
|
595 - | .get(requesting_partition)
|
596 - | .expect("delivery demand record disappeared");
|
597 - | if record.latest.id == delivered_demand && record.latest.is_active() {
|
673 + | .get(requester)
|
674 + | .expect("assigned demand record disappeared");
|
675 + | if record.latest.id == assigned_demand && record.latest.is_active() {
|
598 676 | let record = self
|
599 677 | .records
|
600 - | .get_mut(requesting_partition)
|
601 - | .expect("delivery demand record disappeared");
|
602 - | let residence = std::mem::replace(&mut record.residence, DemandResidence::Idle);
|
603 - | let DemandResidence::Delivering {
|
604 - | demand,
|
605 - | version: _,
|
606 - | delivery: current,
|
607 - | view: _,
|
678 + | .get_mut(requester)
|
679 + | .expect("assigned demand record disappeared");
|
680 + | let schedule_state = std::mem::replace(
|
681 + | &mut record.schedule_state,
|
682 + | DemandScheduleState::Unscheduled,
|
683 + | );
|
684 + | let DemandScheduleState::PendingAssignment {
|
685 + | assignment: current,
|
608 686 | links,
|
609 - | } = residence
|
687 + | ..
|
688 + | } = schedule_state
|
610 689 | else {
|
611 - | unreachable!("delivery fence disappeared");
|
690 + | unreachable!("demand assignment disappeared");
|
691 + | };
|
692 + | debug_assert_eq!(current, *assignment);
|
693 + | record.schedule_state = DemandScheduleState::Queued {
|
694 + | demand: current.demand,
|
695 + | links,
|
612 696 | };
|
613 - | debug_assert_eq!(current, delivery);
|
614 - | record.residence = DemandResidence::Queued { demand, links };
|
615 697 | self.assert_consistent();
|
616 698 | return;
|
617 699 | }
|
618 700 |
|
619 - | self.remove_from_order(requesting_partition);
|
701 + | self.remove_from_order(requester);
|
620 702 | if self
|
621 703 | .records
|
622 - | .get(requesting_partition)
|
623 - | .expect("delivery demand record disappeared")
|
704 + | .get(requester)
|
705 + | .expect("assigned demand record disappeared")
|
624 706 | .latest
|
625 707 | .is_active()
|
626 708 | {
|
627 - | self.enqueue(*requesting_partition);
|
709 + | self.enqueue(*requester);
|
628 710 | }
|
629 711 | }
|
630 - | DeliveryAckResult::Accepted { successor }
|
631 - | | DeliveryAckResult::Rejected { successor } => {
|
632 - | self.remove_from_order(requesting_partition);
|
712 + | DemandAssignmentOutcome::Accepted { successor }
|
713 + | | DemandAssignmentOutcome::Refused { successor } => {
|
714 + | self.remove_from_order(requester);
|
633 715 |
|
634 716 | let install_successor = successor.as_ref().is_some_and(|successor| {
|
635 - | successor.id > delivered_demand
|
717 + | successor.id > assigned_demand
|
636 718 | && successor.is_newer_than(
|
637 719 | &self
|
638 720 | .records
|
639 - | .get(requesting_partition)
|
640 - | .expect("delivery demand record disappeared")
|
721 + | .get(requester)
|
722 + | .expect("assigned demand record disappeared")
|
641 723 | .latest,
|
642 724 | )
|
643 725 | });
|
644 726 | if install_successor {
|
645 727 | self.records
|
646 - | .get_mut(requesting_partition)
|
647 - | .expect("delivery demand record disappeared")
|
728 + | .get_mut(requester)
|
729 + | .expect("assigned demand record disappeared")
|
648 730 | .latest = successor.expect("validated successor disappeared");
|
649 731 | } else {
|
650 732 | let retirement =
|
651 - | DemandSnapshot::inactive(delivered_demand, delivered_version.next());
|
733 + | DemandSnapshot::inactive(assigned_demand, assigned_version.next());
|
652 734 | let record = self
|
653 735 | .records
|
654 - | .get_mut(requesting_partition)
|
655 - | .expect("delivery demand record disappeared");
|
736 + | .get_mut(requester)
|
737 + | .expect("assigned demand record disappeared");
|
656 738 | if retirement.is_newer_than(&record.latest) {
|
657 739 | record.latest = retirement;
|
658 740 | }
|
659 741 | }
|
660 742 |
|
661 743 | if self
|
662 744 | .records
|
663 - | .get(requesting_partition)
|
664 - | .expect("delivery demand record disappeared")
|
745 + | .get(requester)
|
746 + | .expect("assigned demand record disappeared")
|
665 747 | .latest
|
666 748 | .is_active()
|
667 749 | {
|
668 - | self.enqueue(*requesting_partition);
|
750 + | self.enqueue(*requester);
|
669 751 | }
|
670 752 | }
|
671 753 | }
|
672 754 | self.assert_consistent();
|
673 755 | }
|
674 756 |
|
675 - | /// Returns the latest complete snapshot retained for `requesting_partition`.
|
757 + | /// Returns the latest complete snapshot retained for `requester`.
|
676 758 | #[cfg(test)]
|
677 - | pub(super) fn latest_for_test(
|
678 - | &self,
|
679 - | requesting_partition: &PartitionId,
|
680 - | ) -> Option<&DemandSnapshot> {
|
681 - | self.records
|
682 - | .get(requesting_partition)
|
683 - | .map(|record| &record.latest)
|
759 + | pub(super) fn latest_for_test(&self, requester: &PartitionId) -> Option<&DemandSnapshot> {
|
760 + | self.records.get(requester).map(|record| &record.latest)
|
684 761 | }
|
685 762 |
|
686 763 | #[cfg(test)]
|
687 764 | pub(super) fn len(&self) -> usize {
|
688 765 | self.origin_order.len()
|
689 766 | }
|
690 767 |
|
691 768 | #[cfg(test)]
|
692 769 | pub(super) fn queued_len(&self) -> usize {
|
693 770 | self.records
|
694 771 | .values()
|
695 - | .filter(|record| matches!(&record.residence, DemandResidence::Queued { .. }))
|
772 + | .filter(|record| matches!(&record.schedule_state, DemandScheduleState::Queued { .. }))
|
696 773 | .count()
|
697 774 | }
|
698 775 |
|
699 776 | #[cfg(test)]
|
700 - | pub(super) fn delivering_len(&self) -> usize {
|
777 + | pub(super) fn pending_assignment_count(&self) -> usize {
|
701 778 | self.records
|
702 779 | .values()
|
703 - | .filter(|record| matches!(&record.residence, DemandResidence::Delivering { .. }))
|
780 + | .filter(|record| {
|
781 + | matches!(
|
782 + | &record.schedule_state,
|
783 + | DemandScheduleState::PendingAssignment { .. }
|
784 + | )
|
785 + | })
|
704 786 | .count()
|
705 787 | }
|
706 788 |
|
707 - | /// Checks residence, link, length, group, and fence relationships.
|
789 + | /// Checks residence, link, length, group, and assignment relationships.
|
708 790 | fn assert_consistent(&self) {
|
709 791 | #[cfg(any(debug_assertions, test))]
|
710 792 | {
|
711 793 | if std::thread::panicking() {
|
712 794 | return;
|
713 795 | }
|
714 796 | self.assert_consistent_debug();
|
715 797 | }
|
716 798 | }
|
717 799 |
|
718 800 | #[cfg(any(debug_assertions, test))]
|
719 801 | fn assert_consistent_debug(&self) {
|
720 802 | let ordered_records = self
|
721 803 | .records
|
722 804 | .values()
|
723 - | .filter(|record| record.residence.links(DemandOrderView::Origin).is_some())
|
805 + | .filter(|record| record.schedule_state.links(DemandOrder::Origin).is_some())
|
724 806 | .count();
|
725 - | let origin_deliveries = self
|
807 + | let origin_assignments = self
|
726 808 | .records
|
727 809 | .values()
|
728 810 | .filter(|record| {
|
729 811 | matches!(
|
730 - | record.residence,
|
731 - | DemandResidence::Delivering {
|
732 - | view: DeliveryView::Origin,
|
812 + | record.schedule_state,
|
813 + | DemandScheduleState::PendingAssignment {
|
814 + | selected_order: DemandOrder::Origin,
|
733 815 | ..
|
734 816 | }
|
735 817 | )
|
736 818 | })
|
737 819 | .count();
|
738 820 | assert!(
|
739 - | origin_deliveries <= 1,
|
740 - | "more than one origin delivery fence was active"
|
821 + | origin_assignments <= 1,
|
822 + | "more than one origin demand assignment was active"
|
741 823 | );
|
742 824 | self.origin_order.assert_consistent(
|
743 825 | ordered_records,
|
744 826 | self.records.len(),
|
745 827 | "origin demand order",
|
746 828 | |requesting_partition| {
|
747 829 | let record = self
|
748 830 | .records
|
749 831 | .get(&requesting_partition)
|
750 832 | .expect("origin-ordered demand disappeared");
|
751 - | match &record.residence {
|
752 - | DemandResidence::Idle => {
|
753 - | unreachable!("origin-ordered demand became idle")
|
833 + | match &record.schedule_state {
|
834 + | DemandScheduleState::Unscheduled => {
|
835 + | unreachable!("origin-ordered demand became unscheduled")
|
754 836 | }
|
755 - | DemandResidence::Queued { demand, .. } => {
|
837 + | DemandScheduleState::Queued { demand, .. } => {
|
756 838 | assert!(record.latest.is_active(), "queued demand became inactive");
|
757 839 | assert_eq!(
|
758 840 | record.latest.id, *demand,
|
759 - | "queued residence did not match its latest demand"
|
841 + | "queued schedule state did not match its latest demand"
|
760 842 | );
|
761 843 | }
|
762 - | DemandResidence::Delivering { demand, view, .. } => {
|
763 - | if *view == DeliveryView::Origin {
|
844 + | DemandScheduleState::PendingAssignment {
|
845 + | assignment,
|
846 + | selected_order,
|
847 + | ..
|
848 + | } => {
|
849 + | if *selected_order == DemandOrder::Origin {
|
764 850 | assert_eq!(
|
765 851 | Some(requesting_partition),
|
766 852 | self.origin_order.head(),
|
767 - | "origin delivery fence moved away from its head"
|
853 + | "origin demand assignment moved away from its head"
|
768 854 | );
|
769 855 | }
|
770 856 | assert!(
|
771 - | record.latest.id >= *demand,
|
772 - | "delivery fence named a future demand"
|
857 + | record.latest.id >= assignment.demand,
|
858 + | "demand assignment named a future demand"
|
773 859 | );
|
774 860 | }
|
775 861 | }
|
776 862 | *record
|
777 - | .residence
|
778 - | .links(DemandOrderView::Origin)
|
863 + | .schedule_state
|
864 + | .links(DemandOrder::Origin)
|
779 865 | .expect("origin-ordered demand lost its links")
|
780 866 | },
|
781 867 | );
|
782 868 |
|
783 869 | let group_ordered_records = self
|
784 870 | .records
|
785 871 | .values()
|
786 - | .filter(|record| record.residence.links(DemandOrderView::Group).is_some())
|
872 + | .filter(|record| record.schedule_state.links(DemandOrder::Group).is_some())
|
787 873 | .count();
|
788 874 | for record in self
|
789 875 | .records
|
790 876 | .values()
|
791 - | .filter(|record| record.residence.links(DemandOrderView::Group).is_some())
|
877 + | .filter(|record| record.schedule_state.links(DemandOrder::Group).is_some())
|
792 878 | {
|
793 879 | let group = record
|
794 - | .group
|
880 + | .eligibility_group
|
795 881 | .as_ref()
|
796 882 | .expect("group-ordered demand lost its eligibility group");
|
797 883 | assert!(
|
798 884 | self.group_orders.contains_key(group),
|
799 885 | "ordered demand lost its eligibility-group order"
|
800 886 | );
|
801 887 | }
|
802 888 | assert_eq!(
|
803 889 | group_ordered_records,
|
804 890 | self.group_orders
|
805 891 | .values()
|
806 892 | .map(IntrusiveOrder::len)
|
807 893 | .sum::<usize>(),
|
808 894 | "eligibility-group orders did not contain every ordered demand"
|
809 895 | );
|
810 896 |
|
811 897 | for (group, order) in &self.group_orders {
|
812 - | let group_deliveries = self
|
898 + | let group_assignments = self
|
813 899 | .records
|
814 900 | .values()
|
815 901 | .filter(|record| {
|
816 - | record.group.as_ref() == Some(group)
|
902 + | record.eligibility_group.as_ref() == Some(group)
|
817 903 | && matches!(
|
818 - | record.residence,
|
819 - | DemandResidence::Delivering {
|
820 - | view: DeliveryView::Group,
|
904 + | record.schedule_state,
|
905 + | DemandScheduleState::PendingAssignment {
|
906 + | selected_order: DemandOrder::Group,
|
821 907 | ..
|
822 908 | }
|
823 909 | )
|
824 910 | })
|
825 911 | .count();
|
826 912 | assert!(
|
827 - | group_deliveries <= 1,
|
828 - | "more than one group delivery fence was active"
|
913 + | group_assignments <= 1,
|
914 + | "more than one group demand assignment was active"
|
829 915 | );
|
830 916 | let expected = self
|
831 917 | .records
|
832 918 | .values()
|
833 919 | .filter(|record| {
|
834 - | record.residence.links(DemandOrderView::Group).is_some()
|
835 - | && record.group.as_ref() == Some(group)
|
920 + | record.schedule_state.links(DemandOrder::Group).is_some()
|
921 + | && record.eligibility_group.as_ref() == Some(group)
|
836 922 | })
|
837 923 | .count();
|
838 924 | order.assert_consistent(
|
839 925 | expected,
|
840 926 | self.records.len(),
|
841 927 | "eligibility-group demand order",
|
842 928 | |requesting_partition| {
|
843 929 | let record = self
|
844 930 | .records
|
845 931 | .get(&requesting_partition)
|
846 932 | .expect("group-ordered demand disappeared");
|
847 933 | assert_eq!(
|
848 - | record.group.as_ref(),
|
934 + | record.eligibility_group.as_ref(),
|
849 935 | Some(group),
|
850 936 | "demand occupied the wrong eligibility-group order"
|
851 937 | );
|
852 938 | if matches!(
|
853 - | record.residence,
|
854 - | DemandResidence::Delivering {
|
855 - | view: DeliveryView::Group,
|
939 + | record.schedule_state,
|
940 + | DemandScheduleState::PendingAssignment {
|
941 + | selected_order: DemandOrder::Group,
|
856 942 | ..
|
857 943 | }
|
858 944 | ) {
|
859 945 | assert_eq!(
|
860 946 | Some(requesting_partition),
|
861 947 | order.head(),
|
862 - | "group publication fence moved away from its head"
|
948 + | "group route assignment moved away from its head"
|
863 949 | );
|
864 950 | }
|
865 951 | *record
|
866 - | .residence
|
867 - | .links(DemandOrderView::Group)
|
952 + | .schedule_state
|
953 + | .links(DemandOrder::Group)
|
868 954 | .expect("group-ordered demand lost its links")
|
869 955 | },
|
870 956 | );
|
871 957 | }
|
872 958 | }
|
873 959 | }
|
874 960 |
|
875 961 | #[cfg(all(test, not(smithy_http_client_loom)))]
|
876 962 | mod tests {
|
877 963 | use super::*;
|
878 964 |
|
879 965 | fn partition(index: usize) -> PartitionId {
|
880 966 | PartitionId::from_index(index)
|
881 967 | }
|
882 968 |
|
883 969 | fn active(id: u64, group: EligibilityGroup) -> DemandSnapshot {
|
884 970 | DemandSnapshot::active(
|
885 971 | DemandId::from_u64(id),
|
886 972 | super::super::SnapshotVersion::INITIAL,
|
887 973 | ProtocolRequirement::H1Compatible,
|
888 974 | group,
|
889 975 | )
|
890 976 | }
|
891 977 |
|
892 978 | #[test]
|
893 979 | fn origin_and_group_orders_repair_independently() {
|
894 980 | let pool = EligibilityGroup::Pool;
|
895 981 | let isolated = EligibilityGroup::Partition(partition(2));
|
896 982 | let mut schedule = DemandSchedule::default();
|
897 - | schedule.publish(partition(1), active(1, pool.clone()));
|
898 - | schedule.publish(partition(2), active(2, isolated.clone()));
|
899 - | schedule.publish(partition(3), active(3, pool.clone()));
|
983 + | schedule.apply_snapshot(partition(1), active(1, pool.clone()));
|
984 + | schedule.apply_snapshot(partition(2), active(2, isolated.clone()));
|
985 + | schedule.apply_snapshot(partition(3), active(3, pool.clone()));
|
900 986 |
|
901 987 | assert_eq!(
|
902 988 | Some(partition(1)),
|
903 - | schedule.queued_head().map(|head| head.requesting_partition)
|
989 + | schedule.queued_head().map(|head| head.requester)
|
904 990 | );
|
905 991 | assert_eq!(
|
906 992 | Some(partition(1)),
|
907 - | schedule
|
908 - | .queued_group_head(&pool)
|
909 - | .map(|head| head.requesting_partition)
|
993 + | schedule.queued_group_head(&pool).map(|head| head.requester)
|
910 994 | );
|
911 995 | assert_eq!(
|
912 996 | Some(partition(2)),
|
913 997 | schedule
|
914 998 | .queued_group_head(&isolated)
|
915 - | .map(|head| head.requesting_partition)
|
999 + | .map(|head| head.requester)
|
916 1000 | );
|
917 1001 |
|
918 - | let delivery = DeliveryId(7);
|
919 - | schedule
|
920 - | .reserve_group_head(&pool, &partition(1), DemandId::from_u64(1), delivery)
|
1002 + | let assignment = schedule
|
1003 + | .prepare_group_assignment(
|
1004 + | &pool,
|
1005 + | &partition(1),
|
1006 + | DemandId::from_u64(1),
|
1007 + | DemandAssignmentId(7),
|
1008 + | )
|
921 1009 | .expect("pool group head should reserve");
|
922 1010 | assert!(schedule.queued_group_head(&pool).is_none());
|
923 1011 | assert_eq!(
|
924 1012 | Some(partition(2)),
|
925 1013 | schedule
|
926 1014 | .queued_group_head(&isolated)
|
927 - | .map(|head| head.requesting_partition)
|
1015 + | .map(|head| head.requester)
|
928 1016 | );
|
929 1017 | assert!(schedule.queued_head().is_none());
|
930 1018 |
|
931 - | schedule.finish_delivery(
|
932 - | delivery,
|
933 - | &partition(1),
|
934 - | DeliveryAckResult::Accepted { successor: None },
|
1019 + | schedule.settle_assignment(
|
1020 + | &assignment,
|
1021 + | DemandAssignmentOutcome::Accepted { successor: None },
|
935 1022 | );
|
936 1023 | assert_eq!(
|
937 1024 | Some(partition(2)),
|
938 - | schedule.queued_head().map(|head| head.requesting_partition)
|
1025 + | schedule.queued_head().map(|head| head.requester)
|
939 1026 | );
|
940 1027 | assert_eq!(
|
941 1028 | Some(partition(3)),
|
942 - | schedule
|
943 - | .queued_group_head(&pool)
|
944 - | .map(|head| head.requesting_partition)
|
1029 + | schedule.queued_group_head(&pool).map(|head| head.requester)
|
945 1030 | );
|
946 1031 | }
|
947 1032 |
|
948 1033 | #[test]
|
949 1034 | fn retry_preserves_both_order_positions() {
|
950 1035 | let group = EligibilityGroup::Pool;
|
951 1036 | let mut schedule = DemandSchedule::default();
|
952 - | schedule.publish(partition(1), active(1, group.clone()));
|
953 - | schedule.publish(partition(2), active(2, group.clone()));
|
1037 + | schedule.apply_snapshot(partition(1), active(1, group.clone()));
|
1038 + | schedule.apply_snapshot(partition(2), active(2, group.clone()));
|
954 1039 |
|
955 - | let delivery = DeliveryId(9);
|
956 - | schedule
|
957 - | .reserve_group_head(&group, &partition(1), DemandId::from_u64(1), delivery)
|
1040 + | let assignment = schedule
|
1041 + | .prepare_group_assignment(
|
1042 + | &group,
|
1043 + | &partition(1),
|
1044 + | DemandId::from_u64(1),
|
1045 + | DemandAssignmentId(9),
|
1046 + | )
|
958 1047 | .expect("group head should reserve");
|
959 - | schedule.finish_delivery(
|
960 - | delivery,
|
961 - | &partition(1),
|
962 - | DeliveryAckResult::RetrySameResidence,
|
963 - | );
|
1048 + | schedule.settle_assignment(&assignment, DemandAssignmentOutcome::RetrySamePosition);
|
964 1049 |
|
965 1050 | assert_eq!(
|
966 1051 | Some(partition(1)),
|
967 - | schedule.queued_head().map(|head| head.requesting_partition)
|
1052 + | schedule.queued_head().map(|head| head.requester)
|
968 1053 | );
|
969 1054 | assert_eq!(
|
970 1055 | Some(partition(1)),
|
971 1056 | schedule
|
972 1057 | .queued_group_head(&group)
|
973 - | .map(|head| head.requesting_partition)
|
1058 + | .map(|head| head.requester)
|
974 1059 | );
|
975 1060 | }
|
976 1061 |
|
977 1062 | #[test]
|
978 - | fn inactive_snapshot_during_group_fence_retires_without_losing_group() {
|
1063 + | fn inactive_snapshot_during_group_assignment_retires_without_losing_group() {
|
979 1064 | let group = EligibilityGroup::Pool;
|
980 1065 | let mut schedule = DemandSchedule::default();
|
981 - | schedule.publish(partition(1), active(1, group.clone()));
|
982 - | let delivery = DeliveryId(11);
|
983 - | schedule
|
984 - | .reserve_group_head(&group, &partition(1), DemandId::from_u64(1), delivery)
|
1066 + | schedule.apply_snapshot(partition(1), active(1, group.clone()));
|
1067 + | let assignment = schedule
|
1068 + | .prepare_group_assignment(
|
1069 + | &group,
|
1070 + | &partition(1),
|
1071 + | DemandId::from_u64(1),
|
1072 + | DemandAssignmentId(11),
|
1073 + | )
|
985 1074 | .expect("group head should reserve");
|
986 - | schedule.publish(
|
1075 + | schedule.apply_snapshot(
|
987 1076 | partition(1),
|
988 1077 | DemandSnapshot::inactive(
|
989 1078 | DemandId::from_u64(1),
|
990 1079 | super::super::SnapshotVersion::INITIAL.next(),
|
991 1080 | ),
|
992 1081 | );
|
993 1082 |
|
994 - | schedule.finish_delivery(
|
995 - | delivery,
|
996 - | &partition(1),
|
997 - | DeliveryAckResult::RetrySameResidence,
|
998 - | );
|
1083 + | schedule.settle_assignment(&assignment, DemandAssignmentOutcome::RetrySamePosition);
|
999 1084 | assert_eq!(0, schedule.len());
|
1000 1085 | assert!(schedule.queued_group_head(&group).is_none());
|
1001 1086 | }
|
1002 1087 |
|
1003 1088 | #[test]
|
1004 - | fn accepted_fence_does_not_retire_a_newer_active_publication() {
|
1089 + | fn accepted_assignment_does_not_retire_a_newer_active_snapshot() {
|
1005 1090 | let group = EligibilityGroup::Pool;
|
1006 1091 | let partition = partition(1);
|
1007 1092 | let demand = DemandId::from_u64(1);
|
1008 1093 | let initial = super::super::SnapshotVersion::INITIAL;
|
1009 1094 | let mut schedule = DemandSchedule::default();
|
1010 - | schedule.publish(
|
1095 + | schedule.apply_snapshot(
|
1011 1096 | partition,
|
1012 1097 | DemandSnapshot::active(
|
1013 1098 | demand,
|
1014 1099 | initial,
|
1015 1100 | ProtocolRequirement::H2Required,
|
1016 1101 | group.clone(),
|
1017 1102 | ),
|
1018 1103 | );
|
1019 - | let delivery = DeliveryId(12);
|
1020 - | schedule
|
1021 - | .reserve_group_head(&group, &partition, demand, delivery)
|
1104 + | let assignment = schedule
|
1105 + | .prepare_group_assignment(&group, &partition, demand, DemandAssignmentId(12))
|
1022 1106 | .expect("group head should reserve");
|
1023 1107 | let republished = DemandSnapshot::active(
|
1024 1108 | demand,
|
1025 1109 | initial.next().next(),
|
1026 1110 | ProtocolRequirement::H2Required,
|
1027 1111 | group.clone(),
|
1028 1112 | );
|
1029 - | schedule.publish(partition, republished.clone());
|
1113 + | schedule.apply_snapshot(partition, republished.clone());
|
1030 1114 |
|
1031 - | schedule.finish_delivery(
|
1032 - | delivery,
|
1033 - | &partition,
|
1034 - | DeliveryAckResult::Accepted { successor: None },
|
1115 + | schedule.settle_assignment(
|
1116 + | &assignment,
|
1117 + | DemandAssignmentOutcome::Accepted { successor: None },
|
1035 1118 | );
|
1036 1119 |
|
1037 1120 | assert_eq!(Some(&republished), schedule.latest_for_test(&partition));
|
1038 1121 | assert_eq!(
|
1039 1122 | Some(partition),
|
1040 - | schedule.queued_head().map(|head| head.requesting_partition)
|
1123 + | schedule.queued_head().map(|head| head.requester)
|
1041 1124 | );
|
1042 1125 | assert_eq!(
|
1043 1126 | Some(partition),
|
1044 1127 | schedule
|
1045 1128 | .queued_group_head(&group)
|
1046 - | .map(|head| head.requesting_partition)
|
1129 + | .map(|head| head.requester)
|
1047 1130 | );
|
1048 1131 | }
|
1049 1132 |
|
1050 1133 | #[test]
|
1051 - | fn group_reservation_rejects_a_stale_demand_identity() {
|
1134 + | fn group_assignment_rejects_a_stale_demand_identity() {
|
1052 1135 | let group = EligibilityGroup::Pool;
|
1053 1136 | let partition = partition(1);
|
1054 1137 | let mut schedule = DemandSchedule::default();
|
1055 - | schedule.publish(partition, active(1, group.clone()));
|
1056 - | schedule.publish(partition, active(2, group.clone()));
|
1138 + | schedule.apply_snapshot(partition, active(1, group.clone()));
|
1139 + | schedule.apply_snapshot(partition, active(2, group.clone()));
|
1057 1140 |
|
1058 1141 | assert!(schedule
|
1059 - | .reserve_group_head(&group, &partition, DemandId::from_u64(1), DeliveryId(13),)
|
1142 + | .prepare_group_assignment(
|
1143 + | &group,
|
1144 + | &partition,
|
1145 + | DemandId::from_u64(1),
|
1146 + | DemandAssignmentId(13),
|
1147 + | )
|
1060 1148 | .is_none());
|
1061 1149 | assert_eq!(
|
1062 1150 | Some(DemandId::from_u64(2)),
|
1063 1151 | schedule
|
1064 1152 | .queued_group_head(&group)
|
1065 1153 | .map(|queued| queued.demand)
|
1066 1154 | );
|
1067 1155 | }
|
1068 1156 |
|
1069 1157 | #[test]
|
1070 - | fn stale_acknowledgement_does_not_close_a_newer_delivery_fence() {
|
1158 + | fn stale_settlement_does_not_close_a_newer_assignment() {
|
1071 1159 | let group = EligibilityGroup::Pool;
|
1072 1160 | let partition = partition(1);
|
1073 1161 | let demand = DemandId::from_u64(1);
|
1074 1162 | let mut schedule = DemandSchedule::default();
|
1075 - | schedule.publish(partition, active(1, group.clone()));
|
1076 - | schedule
|
1077 - | .reserve_group_head(&group, &partition, demand, DeliveryId(14))
|
1163 + | schedule.apply_snapshot(partition, active(1, group.clone()));
|
1164 + | let assignment = schedule
|
1165 + | .prepare_group_assignment(&group, &partition, demand, DemandAssignmentId(14))
|
1078 1166 | .expect("group head should reserve");
|
1167 + | let stale = DemandAssignment {
|
1168 + | id: DemandAssignmentId(15),
|
1169 + | requester: partition,
|
1170 + | demand,
|
1171 + | };
|
1079 1172 |
|
1080 - | schedule.finish_delivery(
|
1081 - | DeliveryId(15),
|
1082 - | &partition,
|
1083 - | DeliveryAckResult::Accepted { successor: None },
|
1173 + | schedule.settle_assignment(
|
1174 + | &stale,
|
1175 + | DemandAssignmentOutcome::Accepted { successor: None },
|
1084 1176 | );
|
1085 1177 |
|
1086 - | assert!(schedule.delivery_is_current(DeliveryId(14), &partition, demand));
|
1087 - | assert!(!schedule.delivery_is_current(DeliveryId(15), &partition, demand));
|
1178 + | assert!(schedule.assignment_is_current(&assignment));
|
1179 + | assert!(!schedule.assignment_is_current(&stale));
|
1088 1180 | assert_eq!(
|
1089 1181 | Some(&active(1, group)),
|
1090 1182 | schedule.latest_for_test(&partition)
|
1091 1183 | );
|
1092 1184 | }
|
1093 1185 |
|
1094 1186 | #[test]
|
1095 1187 | #[should_panic(expected = "ordered demand lost its eligibility-group order")]
|
1096 1188 | fn consistency_check_rejects_an_orphaned_group_link() {
|
1097 1189 | let group = EligibilityGroup::Pool;
|
1098 1190 | let mut schedule = DemandSchedule::default();
|
1099 - | schedule.publish(partition(1), active(1, group.clone()));
|
1191 + | schedule.apply_snapshot(partition(1), active(1, group.clone()));
|
1100 1192 |
|
1101 1193 | schedule.group_orders.remove(&group);
|
1102 1194 | schedule.assert_consistent();
|
1103 1195 | }
|
1104 1196 | }
|