1use super::{
2 begin_participant_flights_dispatch, begin_submission_wave_participant_flights_dispatch,
3 finalize_session_frames, fmt, invalid_resource, issue_batch_invocation_id,
4 poison_session_frame, prepare_participant_flights, prepare_submission_wave_participant_flights,
5 reset_participant_flights_after_definitely_not_submitted,
6 reset_submission_wave_participant_flights_after_definitely_not_submitted,
7 session_participant_key, ActiveInvocationWaveGuard, ActiveSequenceFrame, AdmissionDeferred,
8 AdmissionFitPolicy, AdmissionPressureAction, AdmissionRejected, AdmittedRequestResources,
9 AdmittedSequenceResources, AdmittedStepParticipant, AllocationLifetime, Arc, AtomicU64,
10 BackingInitializationEncodeError, BackingPrepareDecision, BatchCapacityClaimDecision,
11 BatchInvocationId, BatchParticipantAuthority, BatchParticipantTokenSpan, BatchStepId,
12 BatchWorkShape, ClaimedBackingTransaction, ClaimedSubmissionWaveBacking,
13 DeferredDeviceCleanupDomainId, DeviceCommandBatch, DeviceRuntime, Digest,
14 DynamicBackingDeferred, DynamicDeferredMaintenanceOutcome, DynamicResourceDescriptor,
15 ExecutionLane, ExecutionLaneId, InvocationRegistry, InvocationResourceAdmissionRequest,
16 LaneBackingPrepareDecision, LogicalAdmissionCoordinatorId, LogicalBackingBufferView,
17 LogicalBackingSliceAuthority, LogicalBatchCapacityLease, NodeId, Ordering,
18 ParticipantFlightCandidate, ParticipantFlightPhase, ParticipantNodeKey, PlanBackingDeferral,
19 PlanCapacityWaitRegistration, PreparedBackingInitializations, PreparedParticipantFlightHold,
20 PreparedSubmissionWaveParticipantFlightHold, RequestStateHazardAcquireDecision,
21 RequestStateHazardDeferral, RequestStateHazardParticipant, RequestStateHazardPermit,
22 RequestStateHazardPoison, RequestStateHazardSplitRequired,
23 RequestStateHazardTerminalDisposition, ResourceId, SequenceAuthorityId,
24 SequenceBackingSnapshot, SequenceRecoveryRegistry, SequenceSessionEpoch,
25 SequenceSessionFingerprint, Serialize, Sha256, StaticProvisioningLease,
26 StepFinalizationFailure, StepFrameFinalization, StepParticipantFrameAssignment,
27 StepParticipantRetirement, StepParticipantRetirementDisposition, StepResourceLease,
28 StepRetirementReceipt, TokenSpanWork, TrustedPlanRuntimeEvidence, VNextError,
29 SEQUENCE_DISPATCH_POISONED_BIT,
30};
31use crate::vnext::ReusableExecutionBucketSpec;
32use std::time::{Duration, Instant};
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum StepResourceAdmissionProfilePhase {
36 AuthorityAndPolicyValidate,
37 DemandEvaluate,
38 BackingClaim,
39 LogicalCapacityClaim,
40 TransactionValidateAndFingerprint,
41 FrameCaptureAndLease,
42}
43
44#[inline(always)]
45pub(super) fn step_admission_profile_start<const PROFILE: bool>() -> Option<Instant> {
46 PROFILE.then(Instant::now)
47}
48
49#[inline(always)]
50pub(super) fn record_step_admission_profile<const PROFILE: bool, F>(
51 observer: &mut F,
52 phase: StepResourceAdmissionProfilePhase,
53 started: Option<Instant>,
54) where
55 F: FnMut(StepResourceAdmissionProfilePhase, Duration),
56{
57 if PROFILE {
58 observer(
59 phase,
60 started
61 .expect("profiled step admission phase owns a start instant")
62 .elapsed(),
63 );
64 }
65}
66
67#[derive(Debug)]
68pub enum ExecutionStreamCreationError<E> {
69 Contract(VNextError),
70 Runtime(E),
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub(super) enum BoundExecutionStreamState {
78 Ready,
79 InUse,
80 Poisoned,
81}
82
83#[must_use = "an execution stream must be activated through its resource lease"]
84pub struct BoundExecutionStream<R>
85where
86 R: DeviceRuntime,
87{
88 pub(super) runtime: Arc<R>,
91 pub(super) coordinator_id: LogicalAdmissionCoordinatorId,
92 pub(super) sequence_authority: SequenceAuthorityId,
93 pub(super) stream: Option<R::Stream>,
94 pub(super) state: BoundExecutionStreamState,
95 pub(super) sequence_recovery: Arc<SequenceRecoveryRegistry<R>>,
96 pub(super) sequence_dispatch_gate: Arc<AtomicU64>,
97 pub(super) abandoned_sequence: Option<(u32, u64)>,
98 pub(super) resources: Arc<AdmittedSequenceResources<R>>,
99}
100
101impl<R> BoundExecutionStream<R>
102where
103 R: DeviceRuntime,
104{
105 pub(super) fn stream(&self) -> &R::Stream {
106 self.stream
107 .as_ref()
108 .expect("bound execution stream retains its raw stream")
109 }
110
111 pub(super) fn stream_mut(&mut self) -> &mut R::Stream {
112 self.stream
113 .as_mut()
114 .expect("bound execution stream retains its raw stream")
115 }
116}
117
118impl<R> Drop for BoundExecutionStream<R>
119where
120 R: DeviceRuntime,
121{
122 fn drop(&mut self) {
123 let Some(key) = self.abandoned_sequence.take() else {
124 return;
125 };
126 self.sequence_dispatch_gate
127 .fetch_or(SEQUENCE_DISPATCH_POISONED_BIT, Ordering::AcqRel);
128 if let Some(stream) = self.stream.take() {
129 self.sequence_recovery.attach_stream(key, stream);
130 }
131 }
132}
133
134impl<R> StepResourceLease<R>
135where
136 R: DeviceRuntime,
137{
138 pub(super) fn new(
139 participants: Vec<AdmittedStepParticipant<R>>,
140 execution_lane: Arc<ExecutionLane<R>>,
141 reusable_execution_bucket: Option<ReusableExecutionBucketSpec>,
142 batch_step_id: BatchStepId,
143 claimed_backing: ClaimedBackingTransaction,
144 ) -> Result<Self, VNextError> {
145 if participants.is_empty() {
146 return Err(invalid_resource(
147 "step resources require a non-empty participant set",
148 ));
149 }
150 let coordinator = participants[0]
151 .session
152 .resources()
153 .request
154 .plan
155 .logical_admission();
156 if claimed_backing.work_shape().participants().len() != participants.len()
157 || claimed_backing
158 .work_shape()
159 .participants()
160 .iter()
161 .zip(&participants)
162 .any(|(authority, participant)| {
163 authority.sequence_authority() != participant.session.sequence_authority()
164 || authority.request_authority() != participant.session.request_authority()
165 })
166 {
167 return Err(invalid_resource(
168 "step work shape differs from its exact batch participants",
169 ));
170 }
171 if let Some(capacity) = claimed_backing.logical_capacity() {
172 let parents_match = capacity
173 .parents()
174 .iter()
175 .map(|parent| (parent.sequence(), parent.request()))
176 .eq(participants.iter().map(|participant| {
177 (
178 participant.session.sequence_authority(),
179 participant.session.request_authority(),
180 )
181 }));
182 if !coordinator.owns_batch_capacity_claim(capacity) || !parents_match {
183 return Err(invalid_resource(
184 "step capacity authority differs from its exact batch participants",
185 ));
186 }
187 }
188 Ok(Self {
189 claimed_backing,
190 participants,
191 invocation_registry: Arc::new(InvocationRegistry::default()),
192 execution_lane,
193 reusable_execution_bucket,
194 batch_step_id,
195 finalized: false,
196 })
197 }
198
199 pub const fn batch_step_id(&self) -> BatchStepId {
200 self.batch_step_id
201 }
202
203 pub fn execution_lane(&self) -> &Arc<ExecutionLane<R>> {
204 &self.execution_lane
205 }
206
207 pub fn reusable_execution_bucket(&self) -> Option<&ReusableExecutionBucketSpec> {
208 self.reusable_execution_bucket.as_ref()
209 }
210
211 pub fn try_retire_normal(
212 self: Arc<Self>,
213 ) -> Result<StepRetirementReceipt, StepFinalizationFailure<R>> {
214 Self::try_finalize(self, StepFrameFinalization::Commit)
215 }
216
217 pub fn try_abort(self: Arc<Self>) -> Result<StepRetirementReceipt, StepFinalizationFailure<R>> {
218 Self::try_finalize(self, StepFrameFinalization::Abort)
219 }
220
221 pub fn try_rollback_unsubmitted(
225 self: Arc<Self>,
226 ) -> Result<StepRetirementReceipt, StepFinalizationFailure<R>> {
227 Self::try_finalize(self, StepFrameFinalization::RollbackUnsubmitted)
228 }
229
230 fn try_finalize(
231 step: Arc<Self>,
232 finalization: StepFrameFinalization,
233 ) -> Result<StepRetirementReceipt, StepFinalizationFailure<R>> {
234 let mut step = match Arc::try_unwrap(step) {
235 Ok(step) => step,
236 Err(step) => {
237 return Err(StepFinalizationFailure {
238 step,
239 error: invalid_resource(
240 "step cannot finalize while an invocation or scheduler clone retains it",
241 ),
242 });
243 }
244 };
245 if finalization == StepFrameFinalization::RollbackUnsubmitted {
246 if let Err(error) = step.invocation_registry.ensure_pristine_for_step_rollback() {
247 return Err(StepFinalizationFailure {
248 step: Arc::new(step),
249 error,
250 });
251 }
252 }
253 let dispositions = match step.finalize_participants(finalization) {
254 Ok(dispositions) => dispositions,
255 Err(error) => {
256 return Err(StepFinalizationFailure {
257 step: Arc::new(step),
258 error,
259 });
260 }
261 };
262 let participants = step
263 .participants
264 .iter()
265 .zip(dispositions)
266 .map(|(participant, disposition)| StepParticipantRetirement {
267 assignment: StepParticipantFrameAssignment::new(
268 participant.session.sequence_authority(),
269 participant.session.request_authority(),
270 participant.frame.frame_id,
271 ),
272 disposition,
273 })
274 .collect();
275 Ok(StepRetirementReceipt {
276 batch_step_id: step.batch_step_id,
277 participants,
278 })
279 }
280
281 fn finalize_participants(
282 &mut self,
283 finalization: StepFrameFinalization,
284 ) -> Result<Vec<StepParticipantRetirementDisposition>, VNextError> {
285 if self.finalized {
286 return Err(invalid_resource("step resources are already finalized"));
287 }
288 let mut holds = self
289 .participants
290 .iter_mut()
291 .map(|participant| &mut participant.frame)
292 .collect::<Vec<_>>();
293 let dispositions = finalize_session_frames(&mut holds, finalization)?;
294 self.finalized = true;
295 Ok(dispositions)
296 }
297
298 pub fn participant_count(&self) -> u32 {
299 u32::try_from(self.participants.len())
300 .expect("step participant count was validated before admission")
301 }
302
303 pub fn coordinator_id(&self) -> LogicalAdmissionCoordinatorId {
304 self.participants[0].session.resources().coordinator_id()
305 }
306
307 pub fn participants(
308 &self,
309 ) -> impl ExactSizeIterator<Item = &Arc<AdmittedSequenceResources<R>>> {
310 self.participants
311 .iter()
312 .map(|participant| participant.session.resources())
313 }
314
315 pub(crate) fn participant_backing_snapshot(
316 &self,
317 participant: BatchParticipantAuthority,
318 ) -> Result<&Arc<SequenceBackingSnapshot<R>>, VNextError> {
319 let index = self
320 .participants
321 .binary_search_by_key(&participant.canonical_key(), |candidate| {
322 session_participant_key(&candidate.session)
323 })
324 .map_err(|_| invalid_resource("step does not own that backing participant"))?;
325 self.participants
326 .get(index)
327 .map(|participant| &participant.backing_snapshot)
328 .ok_or_else(|| invalid_resource("step backing participant mapping is inconsistent"))
329 }
330
331 pub(crate) fn participant_backing_view(
332 &self,
333 authority: BatchParticipantAuthority,
334 resource_id: &ResourceId,
335 ) -> Result<LogicalBackingBufferView<'_, R::Buffer>, VNextError> {
336 let index = self
337 .participants
338 .binary_search_by_key(&authority.canonical_key(), |candidate| {
339 session_participant_key(&candidate.session)
340 })
341 .map_err(|_| invalid_resource("step does not own that backing participant"))?;
342 let participant = self
343 .participants
344 .get(index)
345 .ok_or_else(|| invalid_resource("step backing participant mapping is inconsistent"))?;
346 let authorities = participant.backing_snapshot.backing_slices_for(resource_id);
347 if !authorities.is_empty() {
348 return participant
349 .session
350 .resources()
351 .request
352 .plan
353 .dynamic_pools()
354 .view_many(authorities);
355 }
356 participant
357 .session
358 .resources()
359 .request
360 .backing_view(resource_id)
361 }
362
363 pub fn participant_frames(
364 &self,
365 ) -> impl ExactSizeIterator<Item = StepParticipantFrameAssignment> + '_ {
366 self.participants.iter().map(|participant| {
367 StepParticipantFrameAssignment::new(
368 participant.session.sequence_authority(),
369 participant.session.request_authority(),
370 participant.frame.frame_id,
371 )
372 })
373 }
374
375 pub fn backing_slices(&self) -> &[LogicalBackingSliceAuthority] {
376 self.claimed_backing.backing_slices()
377 }
378
379 pub fn logical_capacity(&self) -> Option<&LogicalBatchCapacityLease> {
380 self.claimed_backing.logical_capacity()
381 }
382
383 pub fn work_shape(&self) -> &BatchWorkShape {
384 self.claimed_backing.work_shape()
385 }
386
387 pub fn bind_invocation_work_shape(
388 &self,
389 mut participant_tokens: Vec<(BatchParticipantAuthority, TokenSpanWork)>,
390 ) -> Result<BatchWorkShape, VNextError> {
391 participant_tokens.sort_by_key(|(participant, _)| participant.canonical_key());
392 if participant_tokens.is_empty()
393 || participant_tokens
394 .windows(2)
395 .any(|pair| pair[0].0.canonical_key() == pair[1].0.canonical_key())
396 || participant_tokens.iter().any(|(authority, _)| {
397 self.participants
398 .binary_search_by_key(&authority.canonical_key(), |participant| {
399 session_participant_key(&participant.session)
400 })
401 .is_err()
402 })
403 {
404 return Err(invalid_resource(
405 "invocation token work must bind a unique non-empty step participant subset",
406 ));
407 }
408 BatchWorkShape::new(
409 participant_tokens
410 .into_iter()
411 .map(|(participant, token_span)| {
412 BatchParticipantTokenSpan::new(participant, token_span)
413 })
414 .collect(),
415 )
416 }
417
418 pub fn bind_all_invocation_work_shape(
419 &self,
420 token_spans: Vec<TokenSpanWork>,
421 ) -> Result<BatchWorkShape, VNextError> {
422 if token_spans.len() != self.participants.len() {
423 return Err(invalid_resource(
424 "invocation token work count differs from all step participants",
425 ));
426 }
427 self.bind_invocation_work_shape(
428 self.participants
429 .iter()
430 .zip(token_spans)
431 .map(|(participant, token_span)| {
432 (
433 BatchParticipantAuthority::new(
434 participant.session.sequence_authority(),
435 participant.session.request_authority(),
436 ),
437 token_span,
438 )
439 })
440 .collect(),
441 )
442 }
443
444 pub fn shared_all_invocation_work_shape(
447 &self,
448 token_spans: &[TokenSpanWork],
449 ) -> Result<Arc<BatchWorkShape>, VNextError> {
450 let work_shape = self.claimed_backing.work_shape();
451 if token_spans.len() != self.participants.len()
452 || work_shape
453 .participant_work()
454 .iter()
455 .zip(token_spans)
456 .any(|(bound, requested)| bound.token_span() != requested)
457 {
458 return Err(invalid_resource(
459 "submission wave token work differs from its admitted step work authority",
460 ));
461 }
462 Ok(Arc::clone(self.claimed_backing.work_shape_arc()))
463 }
464
465 pub fn claimed_backing(&self) -> &ClaimedBackingTransaction {
466 &self.claimed_backing
467 }
468
469 pub fn static_provisioning(&self) -> Option<&StaticProvisioningLease<R>> {
470 self.participants[0]
471 .session
472 .resources()
473 .static_provisioning()
474 }
475
476 pub fn plan_evidence(&self) -> TrustedPlanRuntimeEvidence {
477 self.participants[0].session.resources().plan_evidence()
478 }
479
480 pub(crate) fn backing_view(
481 &self,
482 resource_id: &ResourceId,
483 ) -> Result<LogicalBackingBufferView<'_, R::Buffer>, VNextError> {
484 if let Some(authority) = self
485 .claimed_backing
486 .backing_slices()
487 .iter()
488 .find(|authority| authority.resource_id() == resource_id)
489 {
490 return self.participants[0]
491 .session
492 .resources()
493 .request
494 .plan
495 .dynamic_pools()
496 .view(authority);
497 }
498 Err(invalid_resource(format!(
499 "resource `{resource_id}` is not step-shared backing"
500 )))
501 }
502
503 pub(crate) fn dynamic_descriptor(
504 &self,
505 resource_id: &ResourceId,
506 ) -> Result<&DynamicResourceDescriptor, VNextError> {
507 let pools = self.participants[0]
508 .session
509 .resources()
510 .request
511 .plan
512 .dynamic_pools();
513 let mut matches = pools.domains.iter().filter_map(|domain| {
514 domain
515 .descriptors
516 .binary_search_by(|descriptor| descriptor.base_resource_id().cmp(resource_id))
517 .ok()
518 .map(|index| &domain.descriptors[index])
519 });
520 let descriptor = matches.next().ok_or_else(|| {
521 invalid_resource(format!(
522 "resource `{resource_id}` has no dynamic plan descriptor"
523 ))
524 })?;
525 if matches.next().is_some() {
526 return Err(invalid_resource(format!(
527 "resource `{resource_id}` has duplicate dynamic plan descriptors"
528 )));
529 }
530 Ok(descriptor)
531 }
532
533 pub(crate) fn participant_backing_views(
534 &self,
535 resource_id: &ResourceId,
536 ) -> Result<Vec<(SequenceAuthorityId, LogicalBackingBufferView<'_, R::Buffer>)>, VNextError>
537 {
538 self.participants
539 .iter()
540 .map(|participant| {
541 let authority = BatchParticipantAuthority::new(
542 participant.session.sequence_authority(),
543 participant.session.request_authority(),
544 );
545 Ok((
546 participant.session.sequence_authority(),
547 self.participant_backing_view(authority, resource_id)?,
548 ))
549 })
550 .collect()
551 }
552
553 pub fn try_admit_invocation(
554 self: &Arc<Self>,
555 request: InvocationResourceAdmissionRequest,
556 ) -> Result<InvocationResourceAdmissionDecision<R>, VNextError> {
557 let _lifecycle = self.participants[0]
558 .session
559 .resources()
560 .request
561 .plan
562 .resources
563 .read_lifecycle("admit a step invocation")?;
564 if self.claimed_backing.has_shared_physical_claims() {
565 return Err(invalid_resource(
566 "shared Step activation backing requires an ordered single-fence submission wave",
567 ));
568 }
569 let deferred_node_id = request.node_id().clone();
570 let deferred_work_fingerprint = request.work_shape().fingerprint().to_owned();
571 let prepared = match self.prepare_invocation_scope(request)? {
572 PreparedInvocationScopeDecision::Prepared(prepared) => prepared,
573 PreparedInvocationScopeDecision::Deferred(deferred) => {
574 return Ok(InvocationResourceAdmissionDecision::Deferred(deferred));
575 }
576 PreparedInvocationScopeDecision::BackingDeferred(deferred) => {
577 return Ok(InvocationResourceAdmissionDecision::BackingDeferred(
578 InvocationAdmissionBackingDeferral::new(
579 Arc::clone(self),
580 deferred,
581 deferred_node_id,
582 deferred_work_fingerprint,
583 )?,
584 ));
585 }
586 PreparedInvocationScopeDecision::PermanentRejected(rejected) => {
587 return Ok(InvocationResourceAdmissionDecision::PermanentRejected(
588 rejected,
589 ));
590 }
591 PreparedInvocationScopeDecision::RequestStateDeferred(deferred) => {
592 return Ok(InvocationResourceAdmissionDecision::RequestStateDeferred(
593 deferred,
594 ));
595 }
596 PreparedInvocationScopeDecision::RequestStateSplitRequired(split) => {
597 return Ok(InvocationResourceAdmissionDecision::RequestStateSplitRequired(split));
598 }
599 PreparedInvocationScopeDecision::RequestStatePoisoned(poison) => {
600 return Ok(InvocationResourceAdmissionDecision::RequestStatePoisoned(
601 poison,
602 ));
603 }
604 };
605 let PreparedInvocationScope {
606 participants,
607 participant_frames,
608 node_id,
609 claimed_backing,
610 flight_candidates,
611 request_state_hazards,
612 } = prepared;
613 let batch_invocation_id = issue_batch_invocation_id()?;
614 let prepared_participant_flights =
615 prepare_participant_flights(&flight_candidates, &node_id)?;
616 let topology_keys = participant_frames
617 .iter()
618 .map(|assignment| {
619 ParticipantNodeKey::new(
620 assignment.participant(),
621 assignment.frame_id(),
622 node_id.clone(),
623 )
624 })
625 .collect();
626 let active_wave = self.invocation_registry.enter(
627 topology_keys,
628 batch_invocation_id,
629 claimed_backing.work_shape().fingerprint(),
630 )?;
631 Ok(InvocationResourceAdmissionDecision::Admitted(
632 InvocationResourceLease::new(
633 Arc::clone(self),
634 participants,
635 participant_frames,
636 node_id,
637 batch_invocation_id,
638 claimed_backing,
639 prepared_participant_flights,
640 active_wave,
641 request_state_hazards,
642 )?,
643 ))
644 }
645
646 pub fn try_prepare_submission_wave(
647 self: &Arc<Self>,
648 requests: Vec<InvocationResourceAdmissionRequest>,
649 ) -> Result<StepSubmissionWaveAdmissionDecision<R>, VNextError> {
650 let plan = &self.participants[0].session.resources().request.plan;
651 let plan_nodes = plan.nodes();
652 if requests.is_empty()
653 || requests.len() != plan_nodes.len()
654 || requests
655 .iter()
656 .zip(plan_nodes)
657 .any(|(request, node)| request.node_id() != node.id())
658 {
659 return Err(invalid_resource(
660 "submission wave must cover every plan node exactly once in immutable plan order",
661 ));
662 }
663 let fit_policy = requests[0].fit_policy();
664 let pressure_action = requests[0].pressure_action();
665 if requests.iter().any(|request| {
666 request.fit_policy() != fit_policy || request.pressure_action() != pressure_action
667 }) {
668 return Err(invalid_resource(
669 "submission wave nodes require one admission fit and pressure policy",
670 ));
671 }
672 let work_shape = Arc::clone(&requests[0].work_shape);
673 if requests
674 .iter()
675 .any(|request| request.work_shape.as_ref() != work_shape.as_ref())
676 {
677 return Err(invalid_resource(
678 "submission wave nodes must share one canonical work authority",
679 ));
680 }
681 self.prepare_full_plan_submission_wave(work_shape, fit_policy, pressure_action)
682 }
683
684 pub fn try_prepare_determinism_submission_wave(
687 self: &Arc<Self>,
688 requests: Vec<InvocationResourceAdmissionRequest>,
689 ) -> Result<StepSubmissionWaveAdmissionDecision<R>, VNextError> {
690 let plan = &self.participants[0].session.resources().request.plan;
691 let plan_nodes = plan.nodes();
692 if requests.is_empty() {
693 return Err(invalid_resource(
694 "determinism submission wave requires a non-empty plan node scope",
695 ));
696 }
697 let node_indices = requests
698 .iter()
699 .map(|request| {
700 plan_nodes
701 .iter()
702 .position(|node| node.id() == request.node_id())
703 .ok_or_else(|| {
704 invalid_resource(
705 "determinism submission wave references an unknown plan node",
706 )
707 })
708 })
709 .collect::<Result<Vec<_>, _>>()?;
710 if node_indices.windows(2).any(|pair| pair[0] >= pair[1]) {
711 return Err(invalid_resource(
712 "determinism submission wave nodes must be unique and in immutable plan order",
713 ));
714 }
715 let fit_policy = requests[0].fit_policy();
716 let pressure_action = requests[0].pressure_action();
717 if requests.iter().any(|request| {
718 request.fit_policy() != fit_policy || request.pressure_action() != pressure_action
719 }) {
720 return Err(invalid_resource(
721 "determinism submission wave nodes require one admission fit and pressure policy",
722 ));
723 }
724 let work_shape = Arc::clone(&requests[0].work_shape);
725 if requests
726 .iter()
727 .any(|request| request.work_shape.as_ref() != work_shape.as_ref())
728 {
729 return Err(invalid_resource(
730 "determinism submission wave nodes must share one canonical work authority",
731 ));
732 }
733 self.prepare_submission_wave(
734 work_shape,
735 fit_policy,
736 pressure_action,
737 &node_indices,
738 SubmissionWavePurpose::DeterminismProbe,
739 )
740 }
741
742 pub fn try_prepare_full_plan_submission_wave(
747 self: &Arc<Self>,
748 work_shape: Arc<BatchWorkShape>,
749 fit_policy: AdmissionFitPolicy,
750 pressure_action: AdmissionPressureAction,
751 ) -> Result<StepSubmissionWaveAdmissionDecision<R>, VNextError> {
752 self.prepare_full_plan_submission_wave(work_shape, fit_policy, pressure_action)
753 }
754
755 fn prepare_full_plan_submission_wave(
756 self: &Arc<Self>,
757 work_shape: Arc<BatchWorkShape>,
758 fit_policy: AdmissionFitPolicy,
759 pressure_action: AdmissionPressureAction,
760 ) -> Result<StepSubmissionWaveAdmissionDecision<R>, VNextError> {
761 let node_count = self.participants[0]
762 .session
763 .resources()
764 .request
765 .plan
766 .nodes()
767 .len();
768 let node_indices = (0..node_count).collect::<Vec<_>>();
769 self.prepare_submission_wave(
770 work_shape,
771 fit_policy,
772 pressure_action,
773 &node_indices,
774 SubmissionWavePurpose::FullPlan,
775 )
776 }
777
778 fn prepare_submission_wave(
779 self: &Arc<Self>,
780 work_shape: Arc<BatchWorkShape>,
781 fit_policy: AdmissionFitPolicy,
782 pressure_action: AdmissionPressureAction,
783 node_indices: &[usize],
784 purpose: SubmissionWavePurpose,
785 ) -> Result<StepSubmissionWaveAdmissionDecision<R>, VNextError> {
786 let _lifecycle = self.participants[0]
787 .session
788 .resources()
789 .request
790 .plan
791 .resources
792 .read_lifecycle("prepare a step submission wave")?;
793 let plan = &self.participants[0].session.resources().request.plan;
794 let plan_nodes = plan.nodes();
795 if plan_nodes.is_empty()
796 || node_indices.is_empty()
797 || node_indices.windows(2).any(|pair| pair[0] >= pair[1])
798 || node_indices
799 .last()
800 .is_some_and(|node_index| *node_index >= plan_nodes.len())
801 {
802 return Err(invalid_resource(
803 "submission wave requires a non-empty canonical immutable-plan node scope",
804 ));
805 }
806 if work_shape.participants().len() != self.participants.len()
807 || work_shape
808 .participants()
809 .iter()
810 .zip(&self.participants)
811 .any(|(authority, participant)| {
812 authority.canonical_key() != session_participant_key(&participant.session)
813 })
814 {
815 return Err(invalid_resource(
816 "submission wave must bind every step participant exactly once",
817 ));
818 }
819 let participant_authority =
820 Arc::new(self.prepare_participant_authority(Arc::clone(&work_shape), fit_policy)?);
821 let prepared_nodes = node_indices
822 .iter()
823 .copied()
824 .map(|plan_node_index| {
825 PreparedStepSubmissionNode::new(plan_node_index, Arc::clone(&participant_authority))
826 })
827 .collect::<Vec<_>>();
828 let request_participants = participant_authority
829 .participants
830 .iter()
831 .map(|participant| {
832 let request = Arc::clone(participant.request_resources());
833 RequestStateHazardParticipant::new(
834 Arc::clone(&request.plan.dynamic_pools().request_state_hazards),
835 request.request_authority(),
836 request,
837 )
838 })
839 .collect::<Vec<_>>();
840 let request_state_hazards = match plan
841 .dynamic_pools()
842 .request_state_hazards
843 .try_acquire(&request_participants, node_indices)?
844 {
845 RequestStateHazardAcquireDecision::Acquired(permit) => permit,
846 RequestStateHazardAcquireDecision::Deferred(deferred) => {
847 return Ok(StepSubmissionWaveAdmissionDecision::RequestStateDeferred(
848 deferred,
849 ));
850 }
851 RequestStateHazardAcquireDecision::SplitRequired(split) => {
852 return Ok(StepSubmissionWaveAdmissionDecision::RequestStateSplitRequired(split));
853 }
854 RequestStateHazardAcquireDecision::Poisoned(poison) => {
855 return Ok(StepSubmissionWaveAdmissionDecision::RequestStatePoisoned(
856 poison,
857 ));
858 }
859 };
860 let immediate_shape = work_shape.immediate_shape();
861 let fit_shape = match fit_policy {
862 AdmissionFitPolicy::ImmediateOnly => immediate_shape,
863 AdmissionFitPolicy::FullInputMustFit => work_shape.fit_shape(),
864 };
865 let (demand, requested_slices) = plan.submission_wave_demand(
866 immediate_shape,
867 fit_shape,
868 self.reusable_execution_bucket.as_ref(),
869 fit_policy,
870 pressure_action,
871 )?;
872 let prepared_backing = match plan
873 .prepare_lane_stable_backing_slices(&self.execution_lane, requested_slices)?
874 {
875 LaneBackingPrepareDecision::Prepared(prepared) => prepared,
876 LaneBackingPrepareDecision::Deferred(deferred) => {
877 let deferred_node_work_fingerprints = prepared_nodes
878 .iter()
879 .map(|node| {
880 (
881 node.node_id().clone(),
882 node.work_shape().fingerprint().to_owned(),
883 )
884 })
885 .collect();
886 return Ok(StepSubmissionWaveAdmissionDecision::BackingDeferred(
887 StepSubmissionWaveBackingDeferral::new(
888 Arc::clone(self),
889 deferred,
890 deferred_node_work_fingerprints,
891 )?,
892 ));
893 }
894 };
895 let logical_capacity = if demand.immediate_claim().is_empty() {
896 None
897 } else {
898 let parents = self
899 .participants
900 .iter()
901 .map(|participant| participant.session.resources().logical_lease())
902 .collect::<Vec<_>>();
903 match plan
904 .logical_admission()
905 .try_claim_for_sequences(&parents, &demand)?
906 {
907 BatchCapacityClaimDecision::Claimed(capacity) => {
908 if !plan
909 .logical_admission()
910 .owns_batch_capacity_claim(&capacity)
911 {
912 return Err(invalid_resource(
913 "submission wave admission returned foreign capacity authority",
914 ));
915 }
916 Some(capacity)
917 }
918 BatchCapacityClaimDecision::Deferred(deferred) => {
919 return Ok(StepSubmissionWaveAdmissionDecision::Deferred(deferred));
920 }
921 BatchCapacityClaimDecision::PermanentRejected(rejected) => {
922 return Ok(StepSubmissionWaveAdmissionDecision::PermanentRejected(
923 rejected,
924 ));
925 }
926 }
927 };
928 let committed_backing = prepared_backing.commit();
929 let has_program_binding_nodes = prepared_nodes.iter().any(|node| {
930 plan_nodes[node.plan_node_index()]
931 .binding_resource()
932 .is_some()
933 });
934 let program_binding_layout = match (
935 self.reusable_execution_bucket.as_ref(),
936 has_program_binding_nodes,
937 ) {
938 (Some(bucket), true) => Some(
939 plan.dynamic_pools()
940 .program_binding_layout(bucket.bucket_id())
941 .cloned()
942 .ok_or_else(|| {
943 invalid_resource(
944 "reusable submission wave has no compiled program binding layout",
945 )
946 })?,
947 ),
948 _ => None,
949 };
950 let claimed_backing = ClaimedSubmissionWaveBacking::new(
951 plan.plan_hash().clone(),
952 prepared_nodes.len(),
953 plan_nodes.len(),
954 Arc::clone(&work_shape),
955 demand,
956 logical_capacity,
957 committed_backing,
958 program_binding_layout,
959 )?;
960 let wave_fingerprint =
961 submission_wave_fingerprint(self, &prepared_nodes, &claimed_backing, purpose)?;
962 let batch_invocation_id = issue_batch_invocation_id()?;
963 let prepared_participant_flights =
964 prepare_submission_wave_participant_flights(&participant_authority.flight_candidates)?;
965 let covered_participant_nodes = prepared_nodes
966 .len()
967 .checked_mul(participant_authority.participants.len())
968 .ok_or_else(|| invalid_resource("submission wave topology size exceeds usize"))?;
969 let active_wave = self.invocation_registry.enter_submission_wave(
970 covered_participant_nodes,
971 batch_invocation_id,
972 &wave_fingerprint,
973 )?;
974 Ok(StepSubmissionWaveAdmissionDecision::Prepared(
975 PreparedStepSubmissionWave {
976 claimed_backing,
977 initializations: None,
978 request_state_hazards,
979 nodes: prepared_nodes,
980 prepared_participant_flights,
981 active_wave,
982 step: Arc::clone(self),
983 execution_lane_id: self.execution_lane.id(),
984 batch_invocation_id,
985 fingerprint: wave_fingerprint,
986 purpose,
987 },
988 ))
989 }
990
991 fn prepare_invocation_scope(
992 &self,
993 request: InvocationResourceAdmissionRequest,
994 ) -> Result<PreparedInvocationScopeDecision<R>, VNextError> {
995 let PreparedInvocationMetadata {
996 participants,
997 participant_frames,
998 participant_session_identities: _,
999 flight_candidates,
1000 node_id,
1001 work_shape,
1002 fit_policy,
1003 pressure_action,
1004 } = self.prepare_invocation_metadata(request)?;
1005 let plan = &participants[0].request.plan;
1006 let node_index = plan
1007 .nodes()
1008 .iter()
1009 .position(|node| node.id() == &node_id)
1010 .ok_or_else(|| invalid_resource("invocation references an unknown plan node"))?;
1011 let request_participants = participants
1012 .iter()
1013 .map(|participant| {
1014 let request = Arc::clone(participant.request_resources());
1015 RequestStateHazardParticipant::new(
1016 Arc::clone(&request.plan.dynamic_pools().request_state_hazards),
1017 request.request_authority(),
1018 request,
1019 )
1020 })
1021 .collect::<Vec<_>>();
1022 let request_state_hazards = match plan
1023 .dynamic_pools()
1024 .request_state_hazards
1025 .try_acquire(&request_participants, &[node_index])?
1026 {
1027 RequestStateHazardAcquireDecision::Acquired(permit) => permit,
1028 RequestStateHazardAcquireDecision::Deferred(deferred) => {
1029 return Ok(PreparedInvocationScopeDecision::RequestStateDeferred(
1030 deferred,
1031 ));
1032 }
1033 RequestStateHazardAcquireDecision::SplitRequired(split) => {
1034 return Ok(PreparedInvocationScopeDecision::RequestStateSplitRequired(
1035 split,
1036 ));
1037 }
1038 RequestStateHazardAcquireDecision::Poisoned(poison) => {
1039 return Ok(PreparedInvocationScopeDecision::RequestStatePoisoned(
1040 poison,
1041 ));
1042 }
1043 };
1044 let immediate_shape = work_shape.immediate_shape();
1045 let fit_shape = match fit_policy {
1046 AdmissionFitPolicy::ImmediateOnly => immediate_shape,
1047 AdmissionFitPolicy::FullInputMustFit => work_shape.fit_shape(),
1048 };
1049 let (demand, requested_slices) = plan.scoped_demand(
1050 AllocationLifetime::Invocation,
1051 Some(&node_id),
1052 immediate_shape,
1053 fit_shape,
1054 self.reusable_execution_bucket.as_ref(),
1055 fit_policy,
1056 pressure_action,
1057 )?;
1058 let prepared = match plan.prepare_backing_slices(requested_slices)? {
1059 BackingPrepareDecision::Prepared(prepared) => prepared,
1060 BackingPrepareDecision::Deferred(deferred) => {
1061 return Ok(PreparedInvocationScopeDecision::BackingDeferred(deferred));
1062 }
1063 };
1064 let logical_capacity = if demand.immediate_claim().is_empty() {
1065 None
1066 } else {
1067 let parents = participants
1068 .iter()
1069 .map(|participant| participant.logical_lease())
1070 .collect::<Vec<_>>();
1071 match plan
1072 .logical_admission()
1073 .try_claim_for_sequences(&parents, &demand)?
1074 {
1075 BatchCapacityClaimDecision::Claimed(capacity) => {
1076 let parents_match = capacity
1077 .parents()
1078 .iter()
1079 .map(|parent| (parent.sequence(), parent.request()))
1080 .eq(participants.iter().map(|participant| {
1081 (
1082 participant.sequence_authority(),
1083 participant.request_authority(),
1084 )
1085 }));
1086 if !plan
1087 .logical_admission()
1088 .owns_batch_capacity_claim(&capacity)
1089 || !parents_match
1090 {
1091 return Err(invalid_resource(
1092 "invocation admission returned capacity for another participant set",
1093 ));
1094 }
1095 Some(capacity)
1096 }
1097 BatchCapacityClaimDecision::Deferred(deferred) => {
1098 return Ok(PreparedInvocationScopeDecision::Deferred(deferred));
1099 }
1100 BatchCapacityClaimDecision::PermanentRejected(rejected) => {
1101 return Ok(PreparedInvocationScopeDecision::PermanentRejected(rejected));
1102 }
1103 }
1104 };
1105 let backing_slices = prepared.commit();
1106 let claimed_backing = ClaimedBackingTransaction::new(
1107 work_shape,
1108 demand,
1109 logical_capacity,
1110 backing_slices,
1111 None,
1112 )?;
1113 Ok(PreparedInvocationScopeDecision::Prepared(
1114 PreparedInvocationScope {
1115 participants,
1116 participant_frames,
1117 node_id,
1118 claimed_backing,
1119 flight_candidates,
1120 request_state_hazards,
1121 },
1122 ))
1123 }
1124
1125 fn prepare_invocation_metadata(
1126 &self,
1127 request: InvocationResourceAdmissionRequest,
1128 ) -> Result<PreparedInvocationMetadata<R>, VNextError> {
1129 let InvocationResourceAdmissionRequest {
1130 node_id,
1131 work_shape,
1132 fit_policy,
1133 pressure_action,
1134 } = request;
1135 let PreparedParticipantAuthority {
1136 plan_evidence: _,
1137 participants,
1138 participant_frames,
1139 participant_session_identities,
1140 flight_candidates,
1141 work_shape,
1142 } = self.prepare_participant_authority(work_shape, fit_policy)?;
1143 Ok(PreparedInvocationMetadata {
1144 participants,
1145 participant_frames,
1146 participant_session_identities,
1147 flight_candidates,
1148 node_id,
1149 work_shape,
1150 fit_policy,
1151 pressure_action,
1152 })
1153 }
1154
1155 fn prepare_participant_authority(
1156 &self,
1157 work_shape: Arc<BatchWorkShape>,
1158 fit_policy: AdmissionFitPolicy,
1159 ) -> Result<PreparedParticipantAuthority<R>, VNextError> {
1160 let immediate_shape = work_shape.immediate_shape();
1161 let fit_shape = match fit_policy {
1162 AdmissionFitPolicy::ImmediateOnly => immediate_shape,
1163 AdmissionFitPolicy::FullInputMustFit => work_shape.fit_shape(),
1164 };
1165 let participant_sessions = work_shape
1166 .participants()
1167 .iter()
1168 .map(|authority| {
1169 self.participants
1170 .binary_search_by_key(&authority.canonical_key(), |participant| {
1171 session_participant_key(&participant.session)
1172 })
1173 .map(|index| Arc::clone(&self.participants[index].session))
1174 .map_err(|_| {
1175 invalid_resource(
1176 "invocation participant is not a member of its execution frame",
1177 )
1178 })
1179 })
1180 .collect::<Result<Vec<_>, _>>()?;
1181 let mut participant_frames = Vec::with_capacity(participant_sessions.len());
1182 let mut flight_candidates = Vec::with_capacity(participant_sessions.len());
1183 for participant in &participant_sessions {
1184 let key = session_participant_key(participant);
1185 let index = self
1186 .participants
1187 .binary_search_by_key(&key, |step_participant| {
1188 session_participant_key(&step_participant.session)
1189 })
1190 .map_err(|_| {
1191 invalid_resource("invocation participant lost its execution-frame assignment")
1192 })?;
1193 let step_participant = &self.participants[index];
1194 participant_frames.push(StepParticipantFrameAssignment::new(
1195 participant.sequence_authority(),
1196 participant.request_authority(),
1197 step_participant.frame.frame_id,
1198 ));
1199 flight_candidates.push(ParticipantFlightCandidate {
1200 slot: Arc::clone(&participant.slot),
1201 epoch: participant.epoch,
1202 fingerprint: participant.fingerprint.clone(),
1203 frame: ActiveSequenceFrame {
1204 frame_id: step_participant.frame.frame_id,
1205 batch_step_id: self.batch_step_id,
1206 },
1207 participant: BatchParticipantAuthority::new(
1208 participant.sequence_authority(),
1209 participant.request_authority(),
1210 ),
1211 });
1212 }
1213 let participants = participant_sessions
1214 .iter()
1215 .map(|participant| Arc::clone(participant.resources()))
1216 .collect::<Vec<_>>();
1217 let participant_count = u32::try_from(participants.len())
1218 .map_err(|_| invalid_resource("invocation participant count exceeds u32"))?;
1219 if participant_count == 0
1220 || immediate_shape.sequences() != participant_count
1221 || fit_shape.sequences() != participant_count
1222 {
1223 return Err(invalid_resource(
1224 "invocation shape sequence count differs from its exact participant set",
1225 ));
1226 }
1227 let participant_authorities = participants
1228 .iter()
1229 .map(|participant| {
1230 BatchParticipantAuthority::new(
1231 participant.sequence_authority(),
1232 participant.request_authority(),
1233 )
1234 })
1235 .collect::<Vec<_>>();
1236 if work_shape.participants() != participant_authorities {
1237 return Err(invalid_resource(
1238 "invocation work authority differs from selected participants",
1239 ));
1240 }
1241 let participant_session_identities = flight_candidates
1242 .iter()
1243 .map(|candidate| (candidate.epoch, candidate.fingerprint.clone()))
1244 .collect();
1245 Ok(PreparedParticipantAuthority {
1246 plan_evidence: self.plan_evidence(),
1247 participants,
1248 participant_frames,
1249 participant_session_identities,
1250 flight_candidates,
1251 work_shape,
1252 })
1253 }
1254}
1255
1256impl<R> Drop for StepResourceLease<R>
1257where
1258 R: DeviceRuntime,
1259{
1260 fn drop(&mut self) {
1261 if !self.finalized {
1262 for participant in &self.participants {
1263 poison_session_frame(&participant.frame);
1264 }
1265 }
1266 }
1267}
1268
1269pub enum InvocationResourceAdmissionDecision<R>
1270where
1271 R: DeviceRuntime,
1272{
1273 Admitted(InvocationResourceLease<R>),
1274 Deferred(AdmissionDeferred),
1275 BackingDeferred(InvocationAdmissionBackingDeferral<R>),
1276 PermanentRejected(AdmissionRejected),
1277 RequestStateDeferred(RequestStateHazardDeferral),
1278 RequestStateSplitRequired(RequestStateHazardSplitRequired),
1279 RequestStatePoisoned(RequestStateHazardPoison),
1280}
1281
1282#[must_use = "invocation backing deferral retains its exact step parent"]
1285pub struct InvocationAdmissionBackingDeferral<R>
1286where
1287 R: DeviceRuntime,
1288{
1289 backing: PlanBackingDeferral<R>,
1290 step: Arc<StepResourceLease<R>>,
1291 node_id: NodeId,
1292 work_fingerprint: String,
1293}
1294
1295impl<R> InvocationAdmissionBackingDeferral<R>
1296where
1297 R: DeviceRuntime,
1298{
1299 fn new(
1300 step: Arc<StepResourceLease<R>>,
1301 evidence: DynamicBackingDeferred,
1302 node_id: NodeId,
1303 work_fingerprint: String,
1304 ) -> Result<Self, VNextError> {
1305 let resources = Arc::clone(
1306 &step.participants[0]
1307 .session
1308 .resources()
1309 .request
1310 .plan
1311 .resources,
1312 );
1313 Ok(Self {
1314 backing: PlanBackingDeferral::new(resources, evidence)?,
1315 step,
1316 node_id,
1317 work_fingerprint,
1318 })
1319 }
1320
1321 pub fn evidence(&self) -> &DynamicBackingDeferred {
1322 self.backing.evidence()
1323 }
1324
1325 pub fn node_id(&self) -> &NodeId {
1326 &self.node_id
1327 }
1328
1329 pub fn work_fingerprint(&self) -> &str {
1330 &self.work_fingerprint
1331 }
1332
1333 pub fn maintain(&self) -> Result<DynamicDeferredMaintenanceOutcome, VNextError> {
1334 if self.step.finalized {
1335 return Err(invalid_resource(
1336 "finalized step cannot maintain invocation backing",
1337 ));
1338 }
1339 self.backing.maintain()
1340 }
1341
1342 pub fn register_waiter(&self) -> Result<PlanCapacityWaitRegistration<R>, VNextError> {
1343 self.backing.register_waiter()
1344 }
1345}
1346
1347enum PreparedInvocationScopeDecision<R>
1348where
1349 R: DeviceRuntime,
1350{
1351 Prepared(PreparedInvocationScope<R>),
1352 Deferred(AdmissionDeferred),
1353 BackingDeferred(DynamicBackingDeferred),
1354 PermanentRejected(AdmissionRejected),
1355 RequestStateDeferred(RequestStateHazardDeferral),
1356 RequestStateSplitRequired(RequestStateHazardSplitRequired),
1357 RequestStatePoisoned(RequestStateHazardPoison),
1358}
1359
1360struct PreparedInvocationScope<R>
1361where
1362 R: DeviceRuntime,
1363{
1364 claimed_backing: ClaimedBackingTransaction,
1365 participants: Vec<Arc<AdmittedSequenceResources<R>>>,
1366 participant_frames: Vec<StepParticipantFrameAssignment>,
1367 flight_candidates: Vec<ParticipantFlightCandidate>,
1368 node_id: NodeId,
1369 request_state_hazards: Option<RequestStateHazardPermit<Arc<AdmittedRequestResources<R>>>>,
1370}
1371
1372struct PreparedInvocationMetadata<R>
1373where
1374 R: DeviceRuntime,
1375{
1376 participants: Vec<Arc<AdmittedSequenceResources<R>>>,
1377 participant_frames: Vec<StepParticipantFrameAssignment>,
1378 participant_session_identities: Vec<(SequenceSessionEpoch, SequenceSessionFingerprint)>,
1379 flight_candidates: Vec<ParticipantFlightCandidate>,
1380 node_id: NodeId,
1381 work_shape: Arc<BatchWorkShape>,
1382 fit_policy: AdmissionFitPolicy,
1383 pressure_action: AdmissionPressureAction,
1384}
1385
1386struct PreparedParticipantAuthority<R>
1387where
1388 R: DeviceRuntime,
1389{
1390 plan_evidence: TrustedPlanRuntimeEvidence,
1391 participants: Vec<Arc<AdmittedSequenceResources<R>>>,
1392 participant_frames: Vec<StepParticipantFrameAssignment>,
1393 participant_session_identities: Vec<(SequenceSessionEpoch, SequenceSessionFingerprint)>,
1394 flight_candidates: Vec<ParticipantFlightCandidate>,
1395 work_shape: Arc<BatchWorkShape>,
1396}
1397
1398fn submission_wave_fingerprint<R>(
1399 step: &StepResourceLease<R>,
1400 nodes: &[PreparedStepSubmissionNode<R>],
1401 claimed_backing: &ClaimedSubmissionWaveBacking,
1402 purpose: SubmissionWavePurpose,
1403) -> Result<String, VNextError>
1404where
1405 R: DeviceRuntime,
1406{
1407 #[derive(Serialize)]
1408 struct WaveNodeInput<'a> {
1409 plan_node_index: usize,
1410 node_id: &'a NodeId,
1411 }
1412
1413 #[derive(Serialize)]
1414 struct WaveInput<'a> {
1415 domain: &'static str,
1416 purpose: SubmissionWavePurpose,
1417 batch_step_id: BatchStepId,
1418 plan_hash: &'a super::PlanHash,
1419 node_count: usize,
1420 nodes: &'a [WaveNodeInput<'a>],
1421 step_backing_fingerprint: &'a str,
1422 invocation_backing_fingerprint: &'a str,
1423 participant_frames: &'a [StepParticipantFrameAssignment],
1424 work_fingerprint: &'a str,
1425 }
1426
1427 let first = nodes
1428 .first()
1429 .ok_or_else(|| invalid_resource("submission wave fingerprint requires plan nodes"))?;
1430 if nodes.len() != claimed_backing.node_count()
1431 || nodes
1432 .iter()
1433 .any(|node| !Arc::ptr_eq(&node.participant_authority, &first.participant_authority))
1434 || nodes
1435 .windows(2)
1436 .any(|pair| pair[0].plan_node_index() >= pair[1].plan_node_index())
1437 || first.work_shape() != claimed_backing.work_shape()
1438 {
1439 return Err(invalid_resource(
1440 "submission wave topology differs from its shared plan/work authority",
1441 ));
1442 }
1443 let node_scope = nodes
1444 .iter()
1445 .map(|node| WaveNodeInput {
1446 plan_node_index: node.plan_node_index(),
1447 node_id: node.node_id(),
1448 })
1449 .collect::<Vec<_>>();
1450 let bytes = serde_json::to_vec(&WaveInput {
1451 domain: "ferrum.runtime-vnext.step-submission-wave.v4",
1452 purpose,
1453 batch_step_id: step.batch_step_id,
1454 plan_hash: claimed_backing.plan_hash(),
1455 node_count: claimed_backing.node_count(),
1456 nodes: &node_scope,
1457 step_backing_fingerprint: step.claimed_backing.fingerprint(),
1458 invocation_backing_fingerprint: claimed_backing.fingerprint(),
1459 participant_frames: first.participant_frames(),
1460 work_fingerprint: claimed_backing.work_shape().fingerprint(),
1461 })
1462 .map_err(|error| {
1463 invalid_resource(format!(
1464 "submission wave fingerprint encode failed: {error}"
1465 ))
1466 })?;
1467 Ok(format!("{:x}", Sha256::digest(bytes)))
1468}
1469
1470#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1471#[serde(rename_all = "snake_case")]
1472pub(crate) enum SubmissionWavePurpose {
1473 FullPlan,
1474 DeterminismProbe,
1475}
1476
1477pub enum StepSubmissionWaveAdmissionDecision<R>
1478where
1479 R: DeviceRuntime,
1480{
1481 Prepared(PreparedStepSubmissionWave<R>),
1482 Deferred(AdmissionDeferred),
1483 BackingDeferred(StepSubmissionWaveBackingDeferral<R>),
1484 PermanentRejected(AdmissionRejected),
1485 RequestStateDeferred(RequestStateHazardDeferral),
1486 RequestStateSplitRequired(RequestStateHazardSplitRequired),
1487 RequestStatePoisoned(RequestStateHazardPoison),
1488}
1489
1490#[must_use = "submission-wave backing deferral retains its exact step parent"]
1492pub struct StepSubmissionWaveBackingDeferral<R>
1493where
1494 R: DeviceRuntime,
1495{
1496 backing: PlanBackingDeferral<R>,
1497 step: Arc<StepResourceLease<R>>,
1498 node_work_fingerprints: Vec<(NodeId, String)>,
1499}
1500
1501impl<R> StepSubmissionWaveBackingDeferral<R>
1502where
1503 R: DeviceRuntime,
1504{
1505 fn new(
1506 step: Arc<StepResourceLease<R>>,
1507 evidence: DynamicBackingDeferred,
1508 node_work_fingerprints: Vec<(NodeId, String)>,
1509 ) -> Result<Self, VNextError> {
1510 let resources = Arc::clone(
1511 &step.participants[0]
1512 .session
1513 .resources()
1514 .request
1515 .plan
1516 .resources,
1517 );
1518 Ok(Self {
1519 backing: PlanBackingDeferral::new(resources, evidence)?,
1520 step,
1521 node_work_fingerprints,
1522 })
1523 }
1524
1525 pub fn evidence(&self) -> &DynamicBackingDeferred {
1526 self.backing.evidence()
1527 }
1528
1529 pub fn node_work_fingerprints(&self) -> &[(NodeId, String)] {
1530 &self.node_work_fingerprints
1531 }
1532
1533 pub fn maintain(&self) -> Result<DynamicDeferredMaintenanceOutcome, VNextError> {
1534 if self.step.finalized {
1535 return Err(invalid_resource(
1536 "finalized step cannot maintain submission-wave backing",
1537 ));
1538 }
1539 self.backing.maintain()
1540 }
1541
1542 pub fn register_waiter(&self) -> Result<PlanCapacityWaitRegistration<R>, VNextError> {
1543 self.backing.register_waiter()
1544 }
1545}
1546
1547pub struct PreparedStepSubmissionNode<R>
1551where
1552 R: DeviceRuntime,
1553{
1554 participant_authority: Arc<PreparedParticipantAuthority<R>>,
1555 node_index: usize,
1556}
1557
1558impl<R> PreparedStepSubmissionNode<R>
1559where
1560 R: DeviceRuntime,
1561{
1562 fn new(node_index: usize, participant_authority: Arc<PreparedParticipantAuthority<R>>) -> Self {
1563 Self {
1564 participant_authority,
1565 node_index,
1566 }
1567 }
1568
1569 pub fn node_id(&self) -> &NodeId {
1570 self.participant_authority.participants[0]
1571 .request
1572 .plan
1573 .nodes()
1574 .get(self.node_index)
1575 .expect("prepared submission node index was derived from the immutable plan")
1576 .id()
1577 }
1578
1579 pub(crate) const fn plan_node_index(&self) -> usize {
1580 self.node_index
1581 }
1582
1583 pub fn participant_count(&self) -> u32 {
1584 u32::try_from(self.participant_authority.participants.len())
1585 .expect("wave participant count was validated before admission")
1586 }
1587
1588 pub fn participants(
1589 &self,
1590 ) -> impl ExactSizeIterator<Item = &Arc<AdmittedSequenceResources<R>>> {
1591 self.participant_authority.participants.iter()
1592 }
1593
1594 pub fn participant_frames(&self) -> &[StepParticipantFrameAssignment] {
1595 &self.participant_authority.participant_frames
1596 }
1597
1598 pub fn work_shape(&self) -> &BatchWorkShape {
1599 self.participant_authority.work_shape.as_ref()
1600 }
1601
1602 pub fn plan_evidence(&self) -> TrustedPlanRuntimeEvidence {
1603 self.participant_authority.plan_evidence.clone()
1604 }
1605
1606 pub(crate) fn plan_evidence_ref(&self) -> &TrustedPlanRuntimeEvidence {
1607 &self.participant_authority.plan_evidence
1608 }
1609
1610 pub(crate) fn runtime(&self) -> &Arc<R> {
1611 &self.participant_authority.participants[0]
1612 .request
1613 .plan
1614 .runtime()
1615 }
1616
1617 pub(crate) fn participant_session_identities(
1618 &self,
1619 ) -> impl ExactSizeIterator<Item = (SequenceSessionEpoch, &SequenceSessionFingerprint)> {
1620 self.participant_authority
1621 .participant_session_identities
1622 .iter()
1623 .map(|(epoch, fingerprint)| (*epoch, fingerprint))
1624 }
1625}
1626
1627#[must_use = "a prepared submission wave must be dispatched or explicitly dropped"]
1632pub struct PreparedStepSubmissionWave<R>
1633where
1634 R: DeviceRuntime,
1635{
1636 claimed_backing: ClaimedSubmissionWaveBacking,
1638 initializations: Option<PreparedBackingInitializations>,
1639 request_state_hazards: Option<RequestStateHazardPermit<Arc<AdmittedRequestResources<R>>>>,
1640 nodes: Vec<PreparedStepSubmissionNode<R>>,
1641 prepared_participant_flights: Vec<PreparedSubmissionWaveParticipantFlightHold>,
1642 active_wave: ActiveInvocationWaveGuard,
1643 step: Arc<StepResourceLease<R>>,
1644 execution_lane_id: ExecutionLaneId,
1645 batch_invocation_id: BatchInvocationId,
1646 fingerprint: String,
1647 purpose: SubmissionWavePurpose,
1648}
1649
1650impl<R> PreparedStepSubmissionWave<R>
1651where
1652 R: DeviceRuntime,
1653{
1654 pub fn batch_step_id(&self) -> BatchStepId {
1655 self.step.batch_step_id()
1656 }
1657
1658 pub const fn batch_invocation_id(&self) -> BatchInvocationId {
1659 self.batch_invocation_id
1660 }
1661
1662 pub const fn execution_lane_id(&self) -> ExecutionLaneId {
1663 self.execution_lane_id
1664 }
1665
1666 pub fn fingerprint(&self) -> &str {
1667 &self.fingerprint
1668 }
1669
1670 pub fn nodes(&self) -> &[PreparedStepSubmissionNode<R>] {
1671 &self.nodes
1672 }
1673
1674 pub(crate) const fn purpose(&self) -> SubmissionWavePurpose {
1675 self.purpose
1676 }
1677
1678 pub fn claimed_backing(&self) -> &ClaimedSubmissionWaveBacking {
1679 &self.claimed_backing
1680 }
1681
1682 pub fn node_count(&self) -> usize {
1683 self.nodes.len()
1684 }
1685
1686 pub fn prepared_participant_flight_count(&self) -> usize {
1687 self.prepared_participant_flights.len()
1688 }
1689
1690 pub fn node_participant_projection_count(&self) -> usize {
1691 self.nodes
1692 .iter()
1693 .map(|node| node.participant_count() as usize)
1694 .sum()
1695 }
1696
1697 pub fn physical_invocation_ledger_entry_count(&self) -> usize {
1698 self.active_wave.physical_entry_count()
1699 }
1700
1701 pub fn step_resources(&self) -> &Arc<StepResourceLease<R>> {
1702 &self.step
1703 }
1704
1705 pub(crate) fn runtime(&self) -> &Arc<R> {
1706 self.nodes[0].runtime()
1707 }
1708
1709 pub(crate) fn deferred_cleanup_domain(&self) -> DeferredDeviceCleanupDomainId {
1710 self.nodes[0].participant_authority.participants[0]
1711 .request
1712 .plan
1713 .resources
1714 .deferred_cleanup_domain
1715 }
1716
1717 pub fn has_shared_step_backing(&self) -> bool {
1718 self.step.claimed_backing().has_shared_physical_claims()
1719 }
1720
1721 pub(crate) fn backing_view(
1722 &self,
1723 node_index: usize,
1724 resource_id: &ResourceId,
1725 ) -> Result<LogicalBackingBufferView<'_, R::Buffer>, VNextError> {
1726 let node = self
1727 .nodes
1728 .get(node_index)
1729 .ok_or_else(|| invalid_resource("submission wave node index is out of bounds"))?;
1730 if let Some(authority) = self
1731 .claimed_backing
1732 .backing_slices()
1733 .iter()
1734 .find(|authority| authority.resource_id() == resource_id)
1735 {
1736 return node.participant_authority.participants[0]
1737 .request
1738 .plan
1739 .dynamic_pools()
1740 .view(authority);
1741 }
1742 self.step.backing_view(resource_id)
1743 }
1744
1745 pub(crate) fn begin_dispatch(&mut self) -> Result<(), VNextError> {
1746 match &self.initializations {
1747 Some(initializations) => initializations.ensure_wave(&self.fingerprint)?,
1748 None => {
1749 self.initializations = Some(PreparedBackingInitializations::prepare(
1750 &self.step,
1751 &self.fingerprint,
1752 )?);
1753 }
1754 }
1755 begin_submission_wave_participant_flights_dispatch(&mut self.prepared_participant_flights)
1756 }
1757
1758 pub(crate) fn encode_backing_initializations(
1759 &self,
1760 runtime: &R,
1761 commands: &mut DeviceCommandBatch<R::Command>,
1762 ) -> Result<usize, BackingInitializationEncodeError<R::Error>> {
1763 self.initializations
1764 .as_ref()
1765 .ok_or_else(|| {
1766 BackingInitializationEncodeError::Contract(invalid_resource(
1767 "submission wave initialization was not prepared",
1768 ))
1769 })?
1770 .encode(&self.step, runtime, commands)
1771 }
1772
1773 pub(crate) fn mark_submission_fence_installed(&mut self) -> Result<(), VNextError> {
1774 self.initializations
1775 .as_mut()
1776 .ok_or_else(|| invalid_resource("submission wave initialization was not prepared"))?
1777 .mark_in_flight()?;
1778 self.active_wave.mark_in_flight()?;
1779 if let Some(hazards) = &mut self.request_state_hazards {
1780 hazards.mark_submission_fence_installed()?;
1781 }
1782 Ok(())
1783 }
1784
1785 pub(crate) fn mark_submission_indeterminate(&mut self) {
1786 if let Some(initializations) = &mut self.initializations {
1787 initializations.mark_indeterminate();
1788 }
1789 if let Some(hazards) = &mut self.request_state_hazards {
1790 hazards.mark_submission_indeterminate();
1791 }
1792 }
1793
1794 pub(crate) fn finish_backing_initializations(
1795 &mut self,
1796 succeeded: bool,
1797 ) -> Result<(), VNextError> {
1798 self.initializations
1799 .as_mut()
1800 .ok_or_else(|| invalid_resource("submission wave initialization was not prepared"))?
1801 .finish(succeeded)
1802 }
1803
1804 pub(crate) fn finish_request_state_hazards(
1805 &mut self,
1806 disposition: RequestStateHazardTerminalDisposition,
1807 ) -> Result<(), VNextError> {
1808 match &mut self.request_state_hazards {
1809 Some(hazards) => hazards.finish(disposition),
1810 None => Ok(()),
1811 }
1812 }
1813
1814 pub(crate) fn definitely_not_submitted(
1815 mut self,
1816 ) -> Result<DefinitelyNotSubmittedWaveRetryAuthority<R>, VNextError> {
1817 self.active_wave.mark_not_submitted()?;
1818 reset_submission_wave_participant_flights_after_definitely_not_submitted(
1819 &mut self.prepared_participant_flights,
1820 )?;
1821 let topology_fingerprint = self.fingerprint.clone();
1822 let prior_attempt = self.batch_invocation_id;
1823 Ok(DefinitelyNotSubmittedWaveRetryAuthority {
1824 wave: Some(self),
1825 topology_fingerprint,
1826 prior_attempt,
1827 })
1828 }
1829
1830 fn prepare_definitely_not_submitted_retry(
1831 &mut self,
1832 fresh_attempt: BatchInvocationId,
1833 topology_fingerprint: &str,
1834 ) -> Result<(), VNextError> {
1835 if self.fingerprint != topology_fingerprint
1836 || self
1837 .prepared_participant_flights
1838 .iter()
1839 .any(|hold| hold.phase != ParticipantFlightPhase::Prepared)
1840 {
1841 return Err(invalid_resource(
1842 "definitely-not-submitted wave retry topology changed",
1843 ));
1844 }
1845 self.initializations
1846 .as_ref()
1847 .ok_or_else(|| invalid_resource("wave retry lost backing initialization authority"))?
1848 .ensure_wave(topology_fingerprint)?;
1849 self.active_wave.prepare_retry(fresh_attempt)?;
1850 self.batch_invocation_id = fresh_attempt;
1851 Ok(())
1852 }
1853}
1854
1855#[must_use = "a definitely-not-submitted wave retry must be retried or retired"]
1856pub struct DefinitelyNotSubmittedWaveRetryAuthority<R>
1857where
1858 R: DeviceRuntime,
1859{
1860 wave: Option<PreparedStepSubmissionWave<R>>,
1861 topology_fingerprint: String,
1862 prior_attempt: BatchInvocationId,
1863}
1864
1865impl<R> fmt::Debug for DefinitelyNotSubmittedWaveRetryAuthority<R>
1866where
1867 R: DeviceRuntime,
1868{
1869 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1870 formatter
1871 .debug_struct("DefinitelyNotSubmittedWaveRetryAuthority")
1872 .field("prior_attempt", &self.prior_attempt)
1873 .field("topology_fingerprint", &self.topology_fingerprint)
1874 .finish_non_exhaustive()
1875 }
1876}
1877
1878impl<R> DefinitelyNotSubmittedWaveRetryAuthority<R>
1879where
1880 R: DeviceRuntime,
1881{
1882 pub const fn prior_attempt(&self) -> BatchInvocationId {
1883 self.prior_attempt
1884 }
1885
1886 pub fn topology_fingerprint(&self) -> &str {
1887 &self.topology_fingerprint
1888 }
1889
1890 pub fn retry(mut self) -> Result<PreparedStepSubmissionWave<R>, VNextError> {
1891 let fresh_attempt = issue_batch_invocation_id()?;
1892 let wave = self
1893 .wave
1894 .as_mut()
1895 .ok_or_else(|| invalid_resource("wave retry authority no longer owns its wave"))?;
1896 wave.prepare_definitely_not_submitted_retry(fresh_attempt, &self.topology_fingerprint)?;
1897 self.wave
1898 .take()
1899 .ok_or_else(|| invalid_resource("validated wave retry authority lost its wave"))
1900 }
1901}
1902
1903#[must_use = "prepared invocation resources must be dispatched or explicitly dropped"]
1907pub struct InvocationResourceLease<R>
1908where
1909 R: DeviceRuntime,
1910{
1911 claimed_backing: ClaimedBackingTransaction,
1914 initializations: Option<PreparedBackingInitializations>,
1915 request_state_hazards: Option<RequestStateHazardPermit<Arc<AdmittedRequestResources<R>>>>,
1916 prepared_participant_flights: Vec<PreparedParticipantFlightHold>,
1917 active_wave: ActiveInvocationWaveGuard,
1918 participants: Vec<Arc<AdmittedSequenceResources<R>>>,
1919 participant_frames: Vec<StepParticipantFrameAssignment>,
1920 step: Arc<StepResourceLease<R>>,
1921 node_id: NodeId,
1922 batch_invocation_id: BatchInvocationId,
1923}
1924
1925impl<R> InvocationResourceLease<R>
1926where
1927 R: DeviceRuntime,
1928{
1929 fn new(
1930 step: Arc<StepResourceLease<R>>,
1931 participants: Vec<Arc<AdmittedSequenceResources<R>>>,
1932 participant_frames: Vec<StepParticipantFrameAssignment>,
1933 node_id: NodeId,
1934 batch_invocation_id: BatchInvocationId,
1935 claimed_backing: ClaimedBackingTransaction,
1936 prepared_participant_flights: Vec<PreparedParticipantFlightHold>,
1937 active_wave: ActiveInvocationWaveGuard,
1938 request_state_hazards: Option<RequestStateHazardPermit<Arc<AdmittedRequestResources<R>>>>,
1939 ) -> Result<Self, VNextError> {
1940 if participants.is_empty() {
1941 return Err(invalid_resource(
1942 "invocation resources require a non-empty participant set",
1943 ));
1944 }
1945 if participant_frames.len() != participants.len()
1946 || prepared_participant_flights.len() != participants.len()
1947 || participant_frames
1948 .iter()
1949 .zip(&participants)
1950 .any(|(assignment, participant)| {
1951 assignment.sequence_authority() != participant.sequence_authority()
1952 || assignment.request_authority() != participant.request_authority()
1953 })
1954 {
1955 return Err(invalid_resource(
1956 "invocation frame mapping differs from its exact participant set",
1957 ));
1958 }
1959 if claimed_backing.work_shape().participants().len() != participants.len()
1960 || claimed_backing
1961 .work_shape()
1962 .participants()
1963 .iter()
1964 .zip(&participants)
1965 .any(|(authority, participant)| {
1966 authority.sequence_authority() != participant.sequence_authority()
1967 || authority.request_authority() != participant.request_authority()
1968 })
1969 || claimed_backing.work_shape().immediate_tokens()
1970 > step.work_shape().immediate_tokens()
1971 || claimed_backing.work_shape().immediate_pages() > step.work_shape().immediate_pages()
1972 || claimed_backing.work_shape().fit_tokens() > step.work_shape().fit_tokens()
1973 || claimed_backing.work_shape().fit_pages() > step.work_shape().fit_pages()
1974 {
1975 return Err(invalid_resource(
1976 "invocation work shape differs from participants or exceeds its step",
1977 ));
1978 }
1979 if let Some(capacity) = claimed_backing.logical_capacity() {
1980 let coordinator = participants[0].request.plan.logical_admission();
1981 let parents_match = capacity
1982 .parents()
1983 .iter()
1984 .map(|parent| (parent.sequence(), parent.request()))
1985 .eq(participants.iter().map(|participant| {
1986 (
1987 participant.sequence_authority(),
1988 participant.request_authority(),
1989 )
1990 }));
1991 if !coordinator.owns_batch_capacity_claim(capacity) || !parents_match {
1992 return Err(invalid_resource(
1993 "invocation capacity authority differs from its exact participants",
1994 ));
1995 }
1996 }
1997 Ok(Self {
1998 claimed_backing,
1999 initializations: None,
2000 request_state_hazards,
2001 prepared_participant_flights,
2002 active_wave,
2003 participants,
2004 participant_frames,
2005 step,
2006 node_id,
2007 batch_invocation_id,
2008 })
2009 }
2010
2011 pub fn node_id(&self) -> &NodeId {
2012 &self.node_id
2013 }
2014
2015 pub const fn batch_invocation_id(&self) -> BatchInvocationId {
2016 self.batch_invocation_id
2017 }
2018
2019 pub fn batch_step_id(&self) -> BatchStepId {
2020 self.step.batch_step_id()
2021 }
2022
2023 pub fn participant_count(&self) -> u32 {
2024 u32::try_from(self.participants.len())
2025 .expect("invocation participant count was validated before admission")
2026 }
2027
2028 pub fn prepared_participant_count(&self) -> u32 {
2029 u32::try_from(self.prepared_participant_flights.len())
2030 .expect("prepared participant count was validated at construction")
2031 }
2032
2033 pub(crate) fn participant_session_identities(
2034 &self,
2035 ) -> impl ExactSizeIterator<Item = (SequenceSessionEpoch, &SequenceSessionFingerprint)> {
2036 self.prepared_participant_flights
2037 .iter()
2038 .map(|hold| (hold.epoch, &hold.fingerprint))
2039 }
2040
2041 pub(crate) fn begin_dispatch(&mut self) -> Result<(), VNextError> {
2042 let topology_fingerprint = self.retry_topology_fingerprint()?;
2043 match &self.initializations {
2044 Some(initializations) => {
2045 initializations.ensure_wave(&topology_fingerprint)?;
2046 }
2047 None => {
2048 self.initializations = Some(PreparedBackingInitializations::prepare(
2049 &self.step,
2050 &topology_fingerprint,
2051 )?);
2052 }
2053 }
2054 begin_participant_flights_dispatch(&mut self.prepared_participant_flights)
2055 }
2056
2057 pub(crate) fn encode_backing_initializations(
2058 &self,
2059 runtime: &R,
2060 commands: &mut DeviceCommandBatch<R::Command>,
2061 ) -> Result<usize, BackingInitializationEncodeError<R::Error>> {
2062 self.initializations
2063 .as_ref()
2064 .ok_or_else(|| {
2065 BackingInitializationEncodeError::Contract(invalid_resource(
2066 "invocation backing initialization was not prepared",
2067 ))
2068 })?
2069 .encode(&self.step, runtime, commands)
2070 }
2071
2072 pub(crate) fn mark_submission_fence_installed(&mut self) -> Result<(), VNextError> {
2073 self.initializations
2074 .as_mut()
2075 .ok_or_else(|| invalid_resource("invocation initialization was not prepared"))?
2076 .mark_in_flight()?;
2077 self.active_wave.mark_in_flight()?;
2078 if let Some(hazards) = &mut self.request_state_hazards {
2079 hazards.mark_submission_fence_installed()?;
2080 }
2081 Ok(())
2082 }
2083
2084 pub(crate) fn mark_submission_indeterminate(&mut self) {
2085 if let Some(initializations) = &mut self.initializations {
2086 initializations.mark_indeterminate();
2087 }
2088 if let Some(hazards) = &mut self.request_state_hazards {
2089 hazards.mark_submission_indeterminate();
2090 }
2091 }
2092
2093 pub(crate) fn finish_backing_initializations(
2094 &mut self,
2095 succeeded: bool,
2096 ) -> Result<(), VNextError> {
2097 self.initializations
2098 .as_mut()
2099 .ok_or_else(|| invalid_resource("invocation initialization was not prepared"))?
2100 .finish(succeeded)
2101 }
2102
2103 pub(crate) fn finish_request_state_hazards(
2104 &mut self,
2105 disposition: RequestStateHazardTerminalDisposition,
2106 ) -> Result<(), VNextError> {
2107 match &mut self.request_state_hazards {
2108 Some(hazards) => hazards.finish(disposition),
2109 None => Ok(()),
2110 }
2111 }
2112
2113 pub(crate) fn definitely_not_submitted(
2114 mut self,
2115 ) -> Result<DefinitelyNotSubmittedRetryAuthority<R>, VNextError> {
2116 self.active_wave.mark_not_submitted()?;
2117 reset_participant_flights_after_definitely_not_submitted(
2118 &mut self.prepared_participant_flights,
2119 )?;
2120 let topology_fingerprint = self.retry_topology_fingerprint()?;
2121 let work_fingerprint = self.work_shape().fingerprint().to_owned();
2122 let prior_attempt = self.batch_invocation_id;
2123 Ok(DefinitelyNotSubmittedRetryAuthority {
2124 invocation: Some(self),
2125 topology_fingerprint,
2126 work_fingerprint,
2127 prior_attempt,
2128 })
2129 }
2130
2131 fn prepare_definitely_not_submitted_retry(
2132 &mut self,
2133 fresh_attempt: BatchInvocationId,
2134 topology_fingerprint: &str,
2135 work_fingerprint: &str,
2136 ) -> Result<(), VNextError> {
2137 if self.retry_topology_fingerprint()? != topology_fingerprint
2138 || self.work_shape().fingerprint() != work_fingerprint
2139 || self
2140 .prepared_participant_flights
2141 .iter()
2142 .any(|hold| hold.phase != ParticipantFlightPhase::Prepared)
2143 {
2144 return Err(invalid_resource(
2145 "definitely-not-submitted retry topology or work fingerprint changed",
2146 ));
2147 }
2148 self.initializations
2149 .as_ref()
2150 .ok_or_else(|| {
2151 invalid_resource("invocation retry lost backing initialization authority")
2152 })?
2153 .ensure_wave(topology_fingerprint)?;
2154 self.active_wave.prepare_retry(fresh_attempt)?;
2155 self.batch_invocation_id = fresh_attempt;
2156 Ok(())
2157 }
2158
2159 fn retry_topology_fingerprint(&self) -> Result<String, VNextError> {
2160 #[derive(Serialize)]
2161 struct FingerprintInput<'a> {
2162 domain: &'static str,
2163 node_id: &'a NodeId,
2164 participant_frames: &'a [StepParticipantFrameAssignment],
2165 work_fingerprint: &'a str,
2166 }
2167 let bytes = serde_json::to_vec(&FingerprintInput {
2168 domain: "ferrum.runtime-vnext.invocation-retry-topology.v1",
2169 node_id: &self.node_id,
2170 participant_frames: &self.participant_frames,
2171 work_fingerprint: self.work_shape().fingerprint(),
2172 })
2173 .map_err(|error| {
2174 invalid_resource(format!(
2175 "invocation retry topology fingerprint encode failed: {error}"
2176 ))
2177 })?;
2178 Ok(format!("{:x}", Sha256::digest(bytes)))
2179 }
2180
2181 pub fn coordinator_id(&self) -> LogicalAdmissionCoordinatorId {
2182 self.participants[0].coordinator_id()
2183 }
2184
2185 pub fn participants(
2186 &self,
2187 ) -> impl ExactSizeIterator<Item = &Arc<AdmittedSequenceResources<R>>> {
2188 self.participants.iter()
2189 }
2190
2191 pub fn participant_frames(&self) -> &[StepParticipantFrameAssignment] {
2192 &self.participant_frames
2193 }
2194
2195 pub fn step_resources(&self) -> &Arc<StepResourceLease<R>> {
2196 &self.step
2197 }
2198
2199 pub(crate) fn participant_backing_snapshot(
2200 &self,
2201 index: usize,
2202 ) -> Result<&Arc<SequenceBackingSnapshot<R>>, VNextError> {
2203 let participant = self.participants.get(index).ok_or_else(|| {
2204 invalid_resource("invocation backing participant index is out of range")
2205 })?;
2206 self.step
2207 .participant_backing_snapshot(BatchParticipantAuthority::new(
2208 participant.sequence_authority(),
2209 participant.request_authority(),
2210 ))
2211 }
2212
2213 pub fn backing_slices(&self) -> &[LogicalBackingSliceAuthority] {
2214 self.claimed_backing.backing_slices()
2215 }
2216
2217 pub fn logical_capacity(&self) -> Option<&LogicalBatchCapacityLease> {
2218 self.claimed_backing.logical_capacity()
2219 }
2220
2221 pub fn work_shape(&self) -> &BatchWorkShape {
2222 self.claimed_backing.work_shape()
2223 }
2224
2225 pub fn claimed_backing(&self) -> &ClaimedBackingTransaction {
2226 &self.claimed_backing
2227 }
2228
2229 pub fn plan_evidence(&self) -> TrustedPlanRuntimeEvidence {
2230 self.step.plan_evidence()
2231 }
2232
2233 pub(crate) fn runtime(&self) -> &Arc<R> {
2234 self.participants[0].request.plan.runtime()
2235 }
2236
2237 pub(crate) fn deferred_cleanup_domain(&self) -> DeferredDeviceCleanupDomainId {
2238 self.participants[0]
2239 .request
2240 .plan
2241 .resources
2242 .deferred_cleanup_domain
2243 }
2244
2245 pub(crate) fn backing_view(
2246 &self,
2247 resource_id: &ResourceId,
2248 ) -> Result<LogicalBackingBufferView<'_, R::Buffer>, VNextError> {
2249 if let Some(authority) = self
2250 .claimed_backing
2251 .backing_slices()
2252 .iter()
2253 .find(|authority| authority.resource_id() == resource_id)
2254 {
2255 return self.step.participants[0]
2256 .session
2257 .resources()
2258 .request
2259 .plan
2260 .dynamic_pools()
2261 .view(authority);
2262 }
2263 self.step.backing_view(resource_id)
2264 }
2265
2266 pub(crate) fn participant_backing_views(
2267 &self,
2268 resource_id: &ResourceId,
2269 ) -> Result<Vec<(SequenceAuthorityId, LogicalBackingBufferView<'_, R::Buffer>)>, VNextError>
2270 {
2271 self.participants
2272 .iter()
2273 .map(|participant| {
2274 let authority = BatchParticipantAuthority::new(
2275 participant.sequence_authority(),
2276 participant.request_authority(),
2277 );
2278 Ok((
2279 participant.sequence_authority(),
2280 self.step.participant_backing_view(authority, resource_id)?,
2281 ))
2282 })
2283 .collect()
2284 }
2285}
2286
2287#[must_use = "a definitely-not-submitted retry authority must be retried or retired"]
2291pub struct DefinitelyNotSubmittedRetryAuthority<R>
2292where
2293 R: DeviceRuntime,
2294{
2295 invocation: Option<InvocationResourceLease<R>>,
2296 topology_fingerprint: String,
2297 work_fingerprint: String,
2298 prior_attempt: BatchInvocationId,
2299}
2300
2301impl<R> fmt::Debug for DefinitelyNotSubmittedRetryAuthority<R>
2302where
2303 R: DeviceRuntime,
2304{
2305 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2306 formatter
2307 .debug_struct("DefinitelyNotSubmittedRetryAuthority")
2308 .field("prior_attempt", &self.prior_attempt)
2309 .field("topology_fingerprint", &self.topology_fingerprint)
2310 .field("work_fingerprint", &self.work_fingerprint)
2311 .finish_non_exhaustive()
2312 }
2313}
2314
2315impl<R> DefinitelyNotSubmittedRetryAuthority<R>
2316where
2317 R: DeviceRuntime,
2318{
2319 pub const fn prior_attempt(&self) -> BatchInvocationId {
2320 self.prior_attempt
2321 }
2322
2323 pub fn topology_fingerprint(&self) -> &str {
2324 &self.topology_fingerprint
2325 }
2326
2327 pub fn work_fingerprint(&self) -> &str {
2328 &self.work_fingerprint
2329 }
2330
2331 pub fn retry(mut self) -> Result<InvocationResourceLease<R>, VNextError> {
2332 let fresh_attempt = issue_batch_invocation_id()?;
2333 let invocation = self
2334 .invocation
2335 .as_mut()
2336 .ok_or_else(|| invalid_resource("retry authority no longer owns its invocation"))?;
2337 invocation.prepare_definitely_not_submitted_retry(
2338 fresh_attempt,
2339 &self.topology_fingerprint,
2340 &self.work_fingerprint,
2341 )?;
2342 Ok(self
2343 .invocation
2344 .take()
2345 .expect("validated retry authority still owns its invocation"))
2346 }
2347}