1use super::{
2 invalid_resource, AllocationLifetime, Arc, BTreeMap, Mutex, PlanNode, RequestAuthorityId,
3 ResourceId, Serialize, TensorAccess, VNextError,
4};
5use std::mem;
6use tokio::sync::watch;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
9#[serde(rename_all = "snake_case")]
10pub enum RequestStateHazardAccess {
11 Read,
12 Write,
13}
14
15impl RequestStateHazardAccess {
16 fn merge(self, other: Self) -> Self {
17 if self == Self::Write || other == Self::Write {
18 Self::Write
19 } else {
20 Self::Read
21 }
22 }
23
24 fn from_tensor_access(access: TensorAccess) -> Self {
25 match access {
26 TensorAccess::Read => Self::Read,
27 TensorAccess::Write | TensorAccess::ReadWrite => Self::Write,
28 }
29 }
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
33struct RequestStateHazardClaimSpec {
34 resource_id: ResourceId,
35 access: RequestStateHazardAccess,
36}
37
38pub(super) struct RequestStateHazardLayout {
42 node_claims: Vec<Arc<[RequestStateHazardClaimSpec]>>,
43 full_plan_claims: Arc<[RequestStateHazardClaimSpec]>,
44 resource_ids: Arc<[ResourceId]>,
45}
46
47enum SelectedRequestStateHazardClaims<'a> {
48 Borrowed(&'a [RequestStateHazardClaimSpec]),
49 Owned(Vec<RequestStateHazardClaimSpec>),
50}
51
52impl SelectedRequestStateHazardClaims<'_> {
53 fn as_slice(&self) -> &[RequestStateHazardClaimSpec] {
54 match self {
55 Self::Borrowed(claims) => claims,
56 Self::Owned(claims) => claims,
57 }
58 }
59}
60
61impl RequestStateHazardLayout {
62 fn compile(nodes: &[PlanNode]) -> Result<Self, VNextError> {
63 let mut full_plan = BTreeMap::<ResourceId, RequestStateHazardAccess>::new();
64 let mut node_claims = Vec::with_capacity(nodes.len());
65 for node in nodes {
66 let mut claims = BTreeMap::<ResourceId, RequestStateHazardAccess>::new();
67 for effect in node
68 .state_effects()
69 .iter()
70 .filter(|effect| effect.lifetime() == AllocationLifetime::Request)
71 {
72 let access = RequestStateHazardAccess::from_tensor_access(effect.access());
73 if effect.resource_ids().is_empty() {
74 return Err(invalid_resource(format!(
75 "request state `{}` has no physical hazard closure",
76 effect.state_id()
77 )));
78 }
79 for resource_id in effect.resource_ids() {
80 claims
81 .entry(resource_id.clone())
82 .and_modify(|current| *current = current.merge(access))
83 .or_insert(access);
84 full_plan
85 .entry(resource_id.clone())
86 .and_modify(|current| *current = current.merge(access))
87 .or_insert(access);
88 }
89 }
90 node_claims.push(Arc::from(
91 claims
92 .into_iter()
93 .map(|(resource_id, access)| RequestStateHazardClaimSpec {
94 resource_id,
95 access,
96 })
97 .collect::<Vec<_>>(),
98 ));
99 }
100 let full_plan_claims = full_plan
101 .iter()
102 .map(|(resource_id, access)| RequestStateHazardClaimSpec {
103 resource_id: resource_id.clone(),
104 access: *access,
105 })
106 .collect::<Vec<_>>();
107 let resource_ids = full_plan.keys().cloned().collect::<Vec<_>>();
108 Ok(Self {
109 node_claims,
110 full_plan_claims: Arc::from(full_plan_claims),
111 resource_ids: Arc::from(resource_ids),
112 })
113 }
114
115 fn is_empty(&self) -> bool {
116 self.resource_ids.is_empty()
117 }
118
119 fn selected(
120 &self,
121 node_indices: &[usize],
122 ) -> Result<SelectedRequestStateHazardClaims<'_>, VNextError> {
123 if node_indices.is_empty()
124 || node_indices.windows(2).any(|pair| pair[0] >= pair[1])
125 || node_indices
126 .last()
127 .is_some_and(|index| *index >= self.node_claims.len())
128 {
129 return Err(invalid_resource(
130 "request-state hazard scope must be non-empty, canonical, and plan-bound",
131 ));
132 }
133 if node_indices.len() == self.node_claims.len()
134 && node_indices.iter().copied().eq(0..self.node_claims.len())
135 {
136 return Ok(SelectedRequestStateHazardClaims::Borrowed(
137 &self.full_plan_claims,
138 ));
139 }
140 if node_indices.len() == 1 {
141 return Ok(SelectedRequestStateHazardClaims::Borrowed(
142 &self.node_claims[node_indices[0]],
143 ));
144 }
145 let mut merged = BTreeMap::<ResourceId, RequestStateHazardAccess>::new();
146 for &node_index in node_indices {
147 for claim in self.node_claims[node_index].iter() {
148 merged
149 .entry(claim.resource_id.clone())
150 .and_modify(|current| *current = current.merge(claim.access))
151 .or_insert(claim.access);
152 }
153 }
154 Ok(SelectedRequestStateHazardClaims::Owned(
155 merged
156 .into_iter()
157 .map(|(resource_id, access)| RequestStateHazardClaimSpec {
158 resource_id,
159 access,
160 })
161 .collect(),
162 ))
163 }
164}
165
166#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
167struct RequestStateHazardKey {
168 request: RequestAuthorityId,
169 resource_id: ResourceId,
170}
171
172#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
173#[serde(rename_all = "snake_case")]
174pub enum RequestStateHazardPoisonCause {
175 FailedButQuiescent,
176 IndeterminateAfterDrain,
177 InFlightOwnerDropped,
178 CoordinatorInvariant,
179}
180
181#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
182pub struct RequestStateHazardPoison {
183 request: RequestAuthorityId,
184 resource_id: ResourceId,
185 cause: RequestStateHazardPoisonCause,
186 value_generation: u64,
187 availability_generation: u64,
188}
189
190impl RequestStateHazardPoison {
191 pub const fn request(&self) -> RequestAuthorityId {
192 self.request
193 }
194
195 pub fn resource_id(&self) -> &ResourceId {
196 &self.resource_id
197 }
198
199 pub const fn cause(&self) -> RequestStateHazardPoisonCause {
200 self.cause
201 }
202
203 pub const fn value_generation(&self) -> u64 {
204 self.value_generation
205 }
206
207 pub const fn availability_generation(&self) -> u64 {
208 self.availability_generation
209 }
210}
211
212struct RequestStateHazardCell {
213 readers: u32,
214 writer: bool,
215 waiting_writers: u32,
216 value_generation: u64,
217 availability_generation: u64,
218 poison: Option<RequestStateHazardPoisonCause>,
219}
220
221struct RequestStateHazardCoordinatorState {
222 cells: BTreeMap<RequestStateHazardKey, RequestStateHazardCell>,
223 change_epoch: u64,
224 globally_poisoned: bool,
225}
226
227pub(super) struct RequestStateHazardCoordinator {
231 layout: RequestStateHazardLayout,
232 state: Mutex<RequestStateHazardCoordinatorState>,
233 changed: watch::Sender<u64>,
234}
235
236pub(super) struct RequestStateHazardParticipant<O> {
237 coordinator: Arc<RequestStateHazardCoordinator>,
238 request: RequestAuthorityId,
239 owner: O,
240}
241
242impl<O> RequestStateHazardParticipant<O> {
243 pub(super) fn new(
244 coordinator: Arc<RequestStateHazardCoordinator>,
245 request: RequestAuthorityId,
246 owner: O,
247 ) -> Self {
248 Self {
249 coordinator,
250 request,
251 owner,
252 }
253 }
254}
255
256impl RequestStateHazardCoordinator {
257 pub(super) fn compile(nodes: &[PlanNode]) -> Result<Arc<Self>, VNextError> {
258 let layout = RequestStateHazardLayout::compile(nodes)?;
259 let (changed, _) = watch::channel(1);
260 Ok(Arc::new(Self {
261 layout,
262 state: Mutex::new(RequestStateHazardCoordinatorState {
263 cells: BTreeMap::new(),
264 change_epoch: 1,
265 globally_poisoned: false,
266 }),
267 changed,
268 }))
269 }
270
271 pub(super) fn is_empty(&self) -> bool {
272 self.layout.is_empty()
273 }
274
275 pub(super) fn register_request(
276 self: &Arc<Self>,
277 request: RequestAuthorityId,
278 ) -> Result<Option<RequestStateHazardRegistration>, VNextError> {
279 if self.is_empty() {
280 return Ok(None);
281 }
282 let mut state = self
283 .state
284 .lock()
285 .map_err(|_| invalid_resource("request-state hazard coordinator is poisoned"))?;
286 if state.globally_poisoned {
287 return Err(invalid_resource(
288 "request-state hazard coordinator is fail-closed",
289 ));
290 }
291 let mut inserted = Vec::with_capacity(self.layout.resource_ids.len());
292 for resource_id in self.layout.resource_ids.iter() {
293 let key = RequestStateHazardKey {
294 request,
295 resource_id: resource_id.clone(),
296 };
297 if state.cells.contains_key(&key) {
298 for key in inserted {
299 state.cells.remove(&key);
300 }
301 state.globally_poisoned = true;
302 return Err(invalid_resource(
303 "request-state hazard registration reused a live request authority",
304 ));
305 }
306 state.cells.insert(
307 key.clone(),
308 RequestStateHazardCell {
309 readers: 0,
310 writer: false,
311 waiting_writers: 0,
312 value_generation: 0,
313 availability_generation: 1,
314 poison: None,
315 },
316 );
317 inserted.push(key);
318 }
319 Ok(Some(RequestStateHazardRegistration {
320 coordinator: Arc::clone(self),
321 request,
322 registered: true,
323 }))
324 }
325
326 fn unregister_request(&self, request: RequestAuthorityId) -> Result<(), VNextError> {
327 let mut state = self
328 .state
329 .lock()
330 .map_err(|_| invalid_resource("request-state hazard coordinator is poisoned"))?;
331 let keys = self
332 .layout
333 .resource_ids
334 .iter()
335 .map(|resource_id| RequestStateHazardKey {
336 request,
337 resource_id: resource_id.clone(),
338 })
339 .collect::<Vec<_>>();
340 let valid = keys.iter().all(|key| {
341 state
342 .cells
343 .get(key)
344 .is_some_and(|cell| cell.readers == 0 && !cell.writer && cell.waiting_writers == 0)
345 });
346 if !valid {
347 state.globally_poisoned = true;
348 return Err(invalid_resource(
349 "request-state hazard registration dropped with live claims or waiters",
350 ));
351 }
352 for key in keys {
353 state.cells.remove(&key);
354 }
355 Ok(())
356 }
357
358 pub(super) fn try_acquire<O>(
359 self: &Arc<Self>,
360 participants: &[RequestStateHazardParticipant<O>],
361 node_indices: &[usize],
362 ) -> Result<RequestStateHazardAcquireDecision<O>, VNextError>
363 where
364 O: Clone,
365 {
366 let selected = self.layout.selected(node_indices)?;
367 let claims = selected.as_slice();
368 if claims.is_empty() {
369 return Ok(RequestStateHazardAcquireDecision::Acquired(None));
370 }
371 let mut requests = BTreeMap::<RequestAuthorityId, (O, u32)>::new();
372 for participant in participants {
373 if !Arc::ptr_eq(&participant.coordinator, self) {
374 return Err(invalid_resource(
375 "request-state hazard participant belongs to another plan coordinator",
376 ));
377 }
378 let request_authority = participant.request;
379 if let Some((_, sibling_count)) = requests.get_mut(&request_authority) {
380 *sibling_count = sibling_count
381 .checked_add(1)
382 .ok_or_else(|| invalid_resource("request-state sibling count exceeds u32"))?;
383 } else {
384 requests.insert(request_authority, (participant.owner.clone(), 1));
385 }
386 }
387 if requests.is_empty() {
388 return Err(invalid_resource(
389 "request-state hazard acquisition requires participants",
390 ));
391 }
392 if claims
393 .iter()
394 .any(|claim| claim.access == RequestStateHazardAccess::Write)
395 {
396 if let Some((request, (_, sibling_count))) =
397 requests.iter().find(|(_, (_, count))| *count > 1)
398 {
399 return Ok(RequestStateHazardAcquireDecision::SplitRequired(
400 RequestStateHazardSplitRequired {
401 request: *request,
402 sibling_count: *sibling_count,
403 resource_ids: claims
404 .iter()
405 .filter(|claim| claim.access == RequestStateHazardAccess::Write)
406 .map(|claim| claim.resource_id.clone())
407 .collect(),
408 },
409 ));
410 }
411 }
412
413 let requested = requests
414 .keys()
415 .flat_map(|request| {
416 claims
417 .iter()
418 .map(move |claim| ActiveRequestStateHazardClaim {
419 key: RequestStateHazardKey {
420 request: *request,
421 resource_id: claim.resource_id.clone(),
422 },
423 access: claim.access,
424 })
425 })
426 .collect::<Vec<_>>();
427 let mut state = self
428 .state
429 .lock()
430 .map_err(|_| invalid_resource("request-state hazard coordinator is poisoned"))?;
431 if state.globally_poisoned {
432 return Err(invalid_resource(
433 "request-state hazard coordinator is fail-closed",
434 ));
435 }
436 let mut blockers = Vec::new();
437 for claim in &requested {
438 let cell = state.cells.get(&claim.key).ok_or_else(|| {
439 invalid_resource("request-state hazard claim has no live request registration")
440 })?;
441 if let Some(cause) = cell.poison {
442 return Ok(RequestStateHazardAcquireDecision::Poisoned(
443 RequestStateHazardPoison {
444 request: claim.key.request,
445 resource_id: claim.key.resource_id.clone(),
446 cause,
447 value_generation: cell.value_generation,
448 availability_generation: cell.availability_generation,
449 },
450 ));
451 }
452 let conflict = match claim.access {
453 RequestStateHazardAccess::Read => cell.writer || cell.waiting_writers != 0,
454 RequestStateHazardAccess::Write => cell.writer || cell.readers != 0,
455 };
456 if conflict {
457 blockers.push(RequestStateHazardBlocker {
458 request: claim.key.request,
459 resource_id: claim.key.resource_id.clone(),
460 requested_access: claim.access,
461 active_readers: cell.readers,
462 active_writer: cell.writer,
463 waiting_writers: cell.waiting_writers,
464 availability_generation: cell.availability_generation,
465 });
466 }
467 }
468 if !blockers.is_empty() {
469 return Ok(RequestStateHazardAcquireDecision::Deferred(
470 RequestStateHazardDeferral {
471 coordinator: Arc::clone(self),
472 observed_change_epoch: state.change_epoch,
473 blockers,
474 },
475 ));
476 }
477 if requested.iter().any(|claim| {
478 claim.access == RequestStateHazardAccess::Read
479 && state
480 .cells
481 .get(&claim.key)
482 .is_some_and(|cell| cell.readers == u32::MAX)
483 }) {
484 state.globally_poisoned = true;
485 return Err(invalid_resource("request-state reader count is exhausted"));
486 }
487 for claim in &requested {
488 let cell = state
489 .cells
490 .get_mut(&claim.key)
491 .expect("validated request-state hazard cell remains registered");
492 match claim.access {
493 RequestStateHazardAccess::Read => {
494 cell.readers += 1;
495 }
496 RequestStateHazardAccess::Write => cell.writer = true,
497 }
498 }
499 drop(state);
500 Ok(RequestStateHazardAcquireDecision::Acquired(Some(
501 RequestStateHazardPermit {
502 coordinator: Arc::clone(self),
503 requests: Some(requests.into_values().map(|(request, _)| request).collect()),
504 claims: requested,
505 phase: RequestStateHazardPermitPhase::Prepared,
506 finished: false,
507 },
508 )))
509 }
510
511 fn release(
512 &self,
513 claims: &[ActiveRequestStateHazardClaim],
514 disposition: RequestStateHazardReleaseDisposition,
515 ) -> Result<(), VNextError> {
516 let mut state = self
517 .state
518 .lock()
519 .map_err(|_| invalid_resource("request-state hazard coordinator is poisoned"))?;
520 let valid = claims.iter().all(|claim| {
521 state
522 .cells
523 .get(&claim.key)
524 .is_some_and(|cell| match claim.access {
525 RequestStateHazardAccess::Read => cell.readers > 0 && !cell.writer,
526 RequestStateHazardAccess::Write => cell.writer && cell.readers == 0,
527 })
528 });
529 if !valid {
530 state.globally_poisoned = true;
531 return Err(invalid_resource(
532 "request-state hazard release does not own its exact active claims",
533 ));
534 }
535 let next_change_epoch = state
536 .change_epoch
537 .checked_add(1)
538 .ok_or_else(|| invalid_resource("request-state hazard change epoch is exhausted"))?;
539 if claims.iter().any(|claim| {
540 state.cells.get(&claim.key).is_some_and(|cell| {
541 cell.availability_generation == u64::MAX
542 || (claim.access == RequestStateHazardAccess::Write
543 && disposition == RequestStateHazardReleaseDisposition::Succeeded
544 && cell.value_generation == u64::MAX)
545 })
546 }) {
547 state.globally_poisoned = true;
548 return Err(invalid_resource(
549 "request-state hazard generation is exhausted",
550 ));
551 }
552 for claim in claims {
553 let cell = state
554 .cells
555 .get_mut(&claim.key)
556 .expect("validated request-state hazard cell remains registered");
557 match claim.access {
558 RequestStateHazardAccess::Read => cell.readers -= 1,
559 RequestStateHazardAccess::Write => {
560 cell.writer = false;
561 match disposition {
562 RequestStateHazardReleaseDisposition::Succeeded => {
563 cell.value_generation += 1;
564 }
565 RequestStateHazardReleaseDisposition::PreparedAbandoned => {}
566 RequestStateHazardReleaseDisposition::FailedButQuiescent => {
567 cell.poison = Some(RequestStateHazardPoisonCause::FailedButQuiescent);
568 }
569 RequestStateHazardReleaseDisposition::IndeterminateAfterDrain => {
570 cell.poison =
571 Some(RequestStateHazardPoisonCause::IndeterminateAfterDrain);
572 }
573 RequestStateHazardReleaseDisposition::InFlightOwnerDropped => {
574 cell.poison = Some(RequestStateHazardPoisonCause::InFlightOwnerDropped);
575 }
576 }
577 }
578 }
579 cell.availability_generation += 1;
580 }
581 state.change_epoch = next_change_epoch;
582 self.changed.send_replace(next_change_epoch);
583 Ok(())
584 }
585
586 fn recheck(&self, blockers: &[RequestStateHazardBlocker]) -> Result<bool, VNextError> {
587 let state = self
588 .state
589 .lock()
590 .map_err(|_| invalid_resource("request-state hazard coordinator is poisoned"))?;
591 if state.globally_poisoned {
592 return Err(invalid_resource(
593 "request-state hazard coordinator is fail-closed",
594 ));
595 }
596 for blocker in blockers {
597 let key = RequestStateHazardKey {
598 request: blocker.request,
599 resource_id: blocker.resource_id.clone(),
600 };
601 let cell = state.cells.get(&key).ok_or_else(|| {
602 invalid_resource("request-state hazard waiter lost its request registration")
603 })?;
604 if let Some(cause) = cell.poison {
605 return Err(invalid_resource(format!(
606 "request-state resource `{}` is poisoned by {cause:?}",
607 blocker.resource_id
608 )));
609 }
610 let conflict = match blocker.requested_access {
611 RequestStateHazardAccess::Read => cell.writer || cell.waiting_writers != 0,
612 RequestStateHazardAccess::Write => cell.writer || cell.readers != 0,
613 };
614 if conflict && cell.availability_generation == blocker.availability_generation {
615 return Ok(false);
616 }
617 if conflict {
618 return Ok(false);
619 }
620 }
621 Ok(true)
622 }
623
624 fn register_writer_waiters(
625 &self,
626 blockers: &[RequestStateHazardBlocker],
627 ) -> Result<(), VNextError> {
628 let mut state = self
629 .state
630 .lock()
631 .map_err(|_| invalid_resource("request-state hazard coordinator is poisoned"))?;
632 let writer_keys = blockers
633 .iter()
634 .filter(|blocker| blocker.requested_access == RequestStateHazardAccess::Write)
635 .map(|blocker| RequestStateHazardKey {
636 request: blocker.request,
637 resource_id: blocker.resource_id.clone(),
638 })
639 .collect::<Vec<_>>();
640 if writer_keys.iter().any(|key| {
641 state
642 .cells
643 .get(key)
644 .is_none_or(|cell| cell.waiting_writers == u32::MAX)
645 }) {
646 state.globally_poisoned = true;
647 return Err(invalid_resource(
648 "request-state writer waiter count is exhausted",
649 ));
650 }
651 for key in writer_keys {
652 state
653 .cells
654 .get_mut(&key)
655 .expect("validated request-state writer waiter remains registered")
656 .waiting_writers += 1;
657 }
658 Ok(())
659 }
660
661 fn unregister_writer_waiters(&self, blockers: &[RequestStateHazardBlocker]) {
662 let Ok(mut state) = self.state.lock() else {
663 return;
664 };
665 let mut changed = false;
666 for blocker in blockers
667 .iter()
668 .filter(|blocker| blocker.requested_access == RequestStateHazardAccess::Write)
669 {
670 let key = RequestStateHazardKey {
671 request: blocker.request,
672 resource_id: blocker.resource_id.clone(),
673 };
674 let Some(cell) = state.cells.get_mut(&key) else {
675 state.globally_poisoned = true;
676 continue;
677 };
678 if cell.waiting_writers == 0 {
679 state.globally_poisoned = true;
680 } else {
681 cell.waiting_writers -= 1;
682 changed = true;
683 }
684 }
685 if changed {
686 let Some(next_change_epoch) = state.change_epoch.checked_add(1) else {
687 state.globally_poisoned = true;
688 return;
689 };
690 state.change_epoch = next_change_epoch;
691 self.changed.send_replace(next_change_epoch);
692 }
693 }
694}
695
696pub(super) struct RequestStateHazardRegistration {
697 coordinator: Arc<RequestStateHazardCoordinator>,
698 request: RequestAuthorityId,
699 registered: bool,
700}
701
702impl Drop for RequestStateHazardRegistration {
703 fn drop(&mut self) {
704 if self.registered {
705 let _ = self.coordinator.unregister_request(self.request);
706 self.registered = false;
707 }
708 }
709}
710
711#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
712pub struct RequestStateHazardBlocker {
713 request: RequestAuthorityId,
714 resource_id: ResourceId,
715 requested_access: RequestStateHazardAccess,
716 active_readers: u32,
717 active_writer: bool,
718 waiting_writers: u32,
719 availability_generation: u64,
720}
721
722impl RequestStateHazardBlocker {
723 pub const fn request(&self) -> RequestAuthorityId {
724 self.request
725 }
726
727 pub fn resource_id(&self) -> &ResourceId {
728 &self.resource_id
729 }
730
731 pub const fn requested_access(&self) -> RequestStateHazardAccess {
732 self.requested_access
733 }
734
735 pub const fn active_readers(&self) -> u32 {
736 self.active_readers
737 }
738
739 pub const fn active_writer(&self) -> bool {
740 self.active_writer
741 }
742
743 pub const fn waiting_writers(&self) -> u32 {
744 self.waiting_writers
745 }
746
747 pub const fn availability_generation(&self) -> u64 {
748 self.availability_generation
749 }
750}
751
752#[derive(Clone, Serialize)]
753pub struct RequestStateHazardDeferral {
754 #[serde(skip)]
755 coordinator: Arc<RequestStateHazardCoordinator>,
756 observed_change_epoch: u64,
757 blockers: Vec<RequestStateHazardBlocker>,
758}
759
760impl std::fmt::Debug for RequestStateHazardDeferral {
761 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
762 formatter
763 .debug_struct("RequestStateHazardDeferral")
764 .field("observed_change_epoch", &self.observed_change_epoch)
765 .field("blockers", &self.blockers)
766 .finish()
767 }
768}
769
770impl RequestStateHazardDeferral {
771 pub const fn observed_change_epoch(&self) -> u64 {
772 self.observed_change_epoch
773 }
774
775 pub fn blockers(&self) -> &[RequestStateHazardBlocker] {
776 &self.blockers
777 }
778
779 pub fn register_waiter(&self) -> Result<RequestStateHazardWaitRegistration, VNextError> {
780 let receiver = self.coordinator.changed.subscribe();
781 self.coordinator.register_writer_waiters(&self.blockers)?;
782 let ready = match self.coordinator.recheck(&self.blockers) {
783 Ok(ready) => ready,
784 Err(error) => {
785 self.coordinator.unregister_writer_waiters(&self.blockers);
786 return Err(error);
787 }
788 };
789 Ok(RequestStateHazardWaitRegistration {
790 coordinator: Arc::clone(&self.coordinator),
791 blockers: self.blockers.clone(),
792 receiver,
793 ready,
794 registered: true,
795 })
796 }
797}
798
799#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
800pub struct RequestStateHazardSplitRequired {
801 request: RequestAuthorityId,
802 sibling_count: u32,
803 resource_ids: Vec<ResourceId>,
804}
805
806impl RequestStateHazardSplitRequired {
807 pub const fn request(&self) -> RequestAuthorityId {
808 self.request
809 }
810
811 pub const fn sibling_count(&self) -> u32 {
812 self.sibling_count
813 }
814
815 pub fn resource_ids(&self) -> &[ResourceId] {
816 &self.resource_ids
817 }
818}
819
820#[must_use = "a request-state hazard waiter must be awaited or dropped"]
821pub struct RequestStateHazardWaitRegistration {
822 coordinator: Arc<RequestStateHazardCoordinator>,
823 blockers: Vec<RequestStateHazardBlocker>,
824 receiver: watch::Receiver<u64>,
825 ready: bool,
826 registered: bool,
827}
828
829impl RequestStateHazardWaitRegistration {
830 pub async fn wait_for_change(mut self) -> Result<u64, VNextError> {
831 loop {
832 if self.ready || self.coordinator.recheck(&self.blockers)? {
833 return Ok(*self.receiver.borrow_and_update());
834 }
835 self.receiver.changed().await.map_err(|_| {
836 invalid_resource("request-state hazard coordinator closed while waiting")
837 })?;
838 }
839 }
840}
841
842impl Drop for RequestStateHazardWaitRegistration {
843 fn drop(&mut self) {
844 if self.registered {
845 self.coordinator.unregister_writer_waiters(&self.blockers);
846 self.registered = false;
847 }
848 }
849}
850
851#[derive(Debug, Clone, Copy, PartialEq, Eq)]
852pub(crate) enum RequestStateHazardTerminalDisposition {
853 Succeeded,
854 FailedButQuiescent,
855 IndeterminateAfterDrain,
856}
857
858#[derive(Debug, Clone, Copy, PartialEq, Eq)]
859enum RequestStateHazardReleaseDisposition {
860 PreparedAbandoned,
861 Succeeded,
862 FailedButQuiescent,
863 IndeterminateAfterDrain,
864 InFlightOwnerDropped,
865}
866
867#[derive(Debug, Clone, Copy, PartialEq, Eq)]
868enum RequestStateHazardPermitPhase {
869 Prepared,
870 InFlight,
871 Indeterminate,
872}
873
874struct ActiveRequestStateHazardClaim {
875 key: RequestStateHazardKey,
876 access: RequestStateHazardAccess,
877}
878
879#[must_use = "request-state hazards must remain owned through the device fence"]
880pub struct RequestStateHazardPermit<O> {
881 coordinator: Arc<RequestStateHazardCoordinator>,
882 requests: Option<Vec<O>>,
883 claims: Vec<ActiveRequestStateHazardClaim>,
884 phase: RequestStateHazardPermitPhase,
885 finished: bool,
886}
887
888impl<O> RequestStateHazardPermit<O> {
889 pub fn claim_count(&self) -> usize {
890 self.claims.len()
891 }
892
893 pub(crate) fn mark_submission_fence_installed(&mut self) -> Result<(), VNextError> {
894 if self.finished || self.phase != RequestStateHazardPermitPhase::Prepared {
895 return Err(invalid_resource(
896 "request-state hazards cannot install a second submission fence",
897 ));
898 }
899 self.phase = RequestStateHazardPermitPhase::InFlight;
900 Ok(())
901 }
902
903 pub(crate) fn mark_submission_indeterminate(&mut self) {
904 if !self.finished {
905 self.phase = RequestStateHazardPermitPhase::Indeterminate;
906 }
907 }
908
909 pub(crate) fn finish(
910 &mut self,
911 disposition: RequestStateHazardTerminalDisposition,
912 ) -> Result<(), VNextError> {
913 if self.finished || self.phase == RequestStateHazardPermitPhase::Prepared {
914 return Err(invalid_resource(
915 "request-state hazard terminalization requires one installed or indeterminate submission",
916 ));
917 }
918 let disposition = match disposition {
919 RequestStateHazardTerminalDisposition::Succeeded => {
920 RequestStateHazardReleaseDisposition::Succeeded
921 }
922 RequestStateHazardTerminalDisposition::FailedButQuiescent => {
923 RequestStateHazardReleaseDisposition::FailedButQuiescent
924 }
925 RequestStateHazardTerminalDisposition::IndeterminateAfterDrain => {
926 RequestStateHazardReleaseDisposition::IndeterminateAfterDrain
927 }
928 };
929 self.release(disposition)
930 }
931
932 fn release(
933 &mut self,
934 disposition: RequestStateHazardReleaseDisposition,
935 ) -> Result<(), VNextError> {
936 self.coordinator.release(&self.claims, disposition)?;
937 self.finished = true;
938 self.claims.clear();
939 drop(self.requests.take());
940 Ok(())
941 }
942}
943
944impl<O> Drop for RequestStateHazardPermit<O> {
945 fn drop(&mut self) {
946 if self.finished {
947 return;
948 }
949 let disposition = match self.phase {
950 RequestStateHazardPermitPhase::Prepared => {
951 RequestStateHazardReleaseDisposition::PreparedAbandoned
952 }
953 RequestStateHazardPermitPhase::InFlight
954 | RequestStateHazardPermitPhase::Indeterminate => {
955 RequestStateHazardReleaseDisposition::InFlightOwnerDropped
956 }
957 };
958 if self.release(disposition).is_err() {
959 mem::forget(self.requests.take());
962 }
963 }
964}
965
966pub(super) enum RequestStateHazardAcquireDecision<O> {
967 Acquired(Option<RequestStateHazardPermit<O>>),
968 Deferred(RequestStateHazardDeferral),
969 SplitRequired(RequestStateHazardSplitRequired),
970 Poisoned(RequestStateHazardPoison),
971}