1use super::{
4 acquire_session_frames_with_backing, enter_sequence_dispatch, fmt, invalid_resource,
5 issue_batch_step_id, record_step_admission_profile, sequence_dispatch_is_poisoned,
6 sequence_slot_active, sequence_slot_is_poisoned, sequence_slot_poisoned_drained,
7 sequence_slot_poisoned_undrained, session_frame_capture_candidates,
8 step_admission_profile_start, AbandonedSequenceMetadata, AbandonedSequenceRecoveryError,
9 ActiveSequenceAbortDisposition, ActiveSequenceAbortReceipt, AdmissionFitPolicy,
10 AdmittedSequenceResources, AdmittedStepParticipant, AllocationLifetime, Arc, AtomicU64,
11 BatchCapacityClaimDecision, BatchParticipantAuthority, BoundExecutionStream,
12 BoundExecutionStreamState, ClaimedBackingTransaction, DeviceRuntime,
13 ExecutionBatchParticipants, ExecutionLane, ExecutionStreamCreationError,
14 LaneBackingPrepareDecision, LogicalAdmissionCoordinatorId, LogicalBackingSliceAuthority,
15 Ordering, RequestIdentity, RunId, SequenceAuthorityId, SequenceBackingSnapshot,
16 SequenceExecutionAuthoritySource, Serialize, StepAdmissionBackingDeferral,
17 StepResourceAdmissionDecision, StepResourceAdmissionProfilePhase, StepResourceAdmissionRequest,
18 StepResourceLease, StreamState, TrustedPlanRuntimeEvidence, VNextError,
19 SEQUENCE_DISPATCH_POISONED_BIT,
20};
21use std::time::Duration;
22
23impl<R> ExecutionBatchParticipants<R>
24where
25 R: DeviceRuntime,
26{
27 pub fn try_begin_step(
28 &self,
29 request: StepResourceAdmissionRequest,
30 lane: &Arc<ExecutionLane<R>>,
31 ) -> Result<StepResourceAdmissionDecision<R>, VNextError> {
32 self.try_begin_step_inner::<false, _>(request, lane, |_, _| {})
33 }
34
35 pub fn try_begin_step_profiled<F>(
36 &self,
37 request: StepResourceAdmissionRequest,
38 lane: &Arc<ExecutionLane<R>>,
39 observer: F,
40 ) -> Result<StepResourceAdmissionDecision<R>, VNextError>
41 where
42 F: FnMut(StepResourceAdmissionProfilePhase, Duration),
43 {
44 self.try_begin_step_inner::<true, _>(request, lane, observer)
45 }
46
47 fn try_begin_step_inner<const PROFILE: bool, F>(
48 &self,
49 request: StepResourceAdmissionRequest,
50 lane: &Arc<ExecutionLane<R>>,
51 mut observer: F,
52 ) -> Result<StepResourceAdmissionDecision<R>, VNextError>
53 where
54 F: FnMut(StepResourceAdmissionProfilePhase, Duration),
55 {
56 let phase_started = step_admission_profile_start::<PROFILE>();
57 let _lifecycle = self.sessions[0]
58 .resources()
59 .request
60 .plan
61 .resources
62 .read_lifecycle("begin an execution step")?;
63 let StepResourceAdmissionRequest {
64 work_shape,
65 fit_policy,
66 pressure_action,
67 reusable_execution_bucket_id,
68 } = request;
69 let work_fingerprint = work_shape.fingerprint().to_owned();
70 let expected_participants = self
71 .sessions
72 .iter()
73 .map(|session| {
74 BatchParticipantAuthority::new(
75 session.sequence_authority(),
76 session.request_authority(),
77 )
78 })
79 .collect::<Vec<_>>();
80 if work_shape.participants() != expected_participants {
81 return Err(invalid_resource(
82 "step work authority differs from its exact participant set",
83 ));
84 }
85 let immediate_shape = work_shape.immediate_shape();
86 let fit_shape = match fit_policy {
87 AdmissionFitPolicy::ImmediateOnly => immediate_shape,
88 AdmissionFitPolicy::FullInputMustFit => work_shape.fit_shape(),
89 };
90 let plan = &self.sessions[0].resources().request.plan;
91 if !Arc::ptr_eq(plan.runtime(), lane.runtime_arc())
92 || plan.runtime().descriptor() != lane.descriptor()
93 || !lane.is_reusable()
94 {
95 return Err(invalid_resource(
96 "step admission requires the reusable execution lane bound to its plan runtime",
97 ));
98 }
99 let reusable_execution_bucket = reusable_execution_bucket_id
100 .as_ref()
101 .map(|bucket_id| {
102 plan.reusable_execution_bucket(bucket_id)
103 .map(|resolved| resolved.bucket().clone())
104 .ok_or_else(|| {
105 invalid_resource(
106 "step reusable execution bucket is not owned by its immutable plan",
107 )
108 })
109 })
110 .transpose()?;
111 if reusable_execution_bucket.as_ref().is_some_and(|bucket| {
112 let capacity = bucket.capacity();
113 !capacity.covers(fit_shape.sequences(), fit_shape.tokens(), fit_shape.pages())
114 }) {
115 return Err(invalid_resource(
116 "step work shape exceeds its selected reusable execution bucket",
117 ));
118 }
119 record_step_admission_profile::<PROFILE, _>(
120 &mut observer,
121 StepResourceAdmissionProfilePhase::AuthorityAndPolicyValidate,
122 phase_started,
123 );
124
125 let phase_started = step_admission_profile_start::<PROFILE>();
126 let (demand, requested_slices) = plan.scoped_demand(
127 AllocationLifetime::Step,
128 None,
129 immediate_shape,
130 fit_shape,
131 reusable_execution_bucket.as_ref(),
132 fit_policy,
133 pressure_action,
134 )?;
135 record_step_admission_profile::<PROFILE, _>(
136 &mut observer,
137 StepResourceAdmissionProfilePhase::DemandEvaluate,
138 phase_started,
139 );
140
141 let phase_started = step_admission_profile_start::<PROFILE>();
142 let prepared = match plan.prepare_lane_stable_backing_slices(lane, requested_slices)? {
143 LaneBackingPrepareDecision::Prepared(prepared) => prepared,
144 LaneBackingPrepareDecision::Deferred(deferred) => {
145 record_step_admission_profile::<PROFILE, _>(
146 &mut observer,
147 StepResourceAdmissionProfilePhase::BackingClaim,
148 phase_started,
149 );
150 return Ok(StepResourceAdmissionDecision::BackingDeferred(
151 StepAdmissionBackingDeferral::new(
152 deferred,
153 self.sessions.clone(),
154 work_fingerprint,
155 )?,
156 ));
157 }
158 };
159 record_step_admission_profile::<PROFILE, _>(
160 &mut observer,
161 StepResourceAdmissionProfilePhase::BackingClaim,
162 phase_started,
163 );
164
165 let phase_started = step_admission_profile_start::<PROFILE>();
166 let logical_capacity = if demand.immediate_claim().is_empty() {
167 None
168 } else {
169 let parents = self
170 .sessions
171 .iter()
172 .map(|session| session.resources().logical_lease())
173 .collect::<Vec<_>>();
174 match plan
175 .logical_admission()
176 .try_claim_for_sequences(&parents, &demand)?
177 {
178 BatchCapacityClaimDecision::Claimed(capacity) => {
179 let parents_match = capacity
180 .parents()
181 .iter()
182 .map(|parent| (parent.sequence(), parent.request()))
183 .eq(self.sessions.iter().map(|session| {
184 (session.sequence_authority(), session.request_authority())
185 }));
186 if !plan
187 .logical_admission()
188 .owns_batch_capacity_claim(&capacity)
189 || !parents_match
190 {
191 return Err(invalid_resource(
192 "step admission returned capacity for another participant set",
193 ));
194 }
195 Some(capacity)
196 }
197 BatchCapacityClaimDecision::Deferred(deferred) => {
198 record_step_admission_profile::<PROFILE, _>(
199 &mut observer,
200 StepResourceAdmissionProfilePhase::LogicalCapacityClaim,
201 phase_started,
202 );
203 return Ok(StepResourceAdmissionDecision::Deferred(deferred));
204 }
205 BatchCapacityClaimDecision::PermanentRejected(rejected) => {
206 record_step_admission_profile::<PROFILE, _>(
207 &mut observer,
208 StepResourceAdmissionProfilePhase::LogicalCapacityClaim,
209 phase_started,
210 );
211 return Ok(StepResourceAdmissionDecision::PermanentRejected(rejected));
212 }
213 }
214 };
215 record_step_admission_profile::<PROFILE, _>(
216 &mut observer,
217 StepResourceAdmissionProfilePhase::LogicalCapacityClaim,
218 phase_started,
219 );
220
221 let phase_started = step_admission_profile_start::<PROFILE>();
222 let committed_backing = prepared.commit();
223 let claimed_backing = ClaimedBackingTransaction::new_lane_stable(
224 work_shape,
225 demand,
226 logical_capacity,
227 committed_backing,
228 )?;
229 record_step_admission_profile::<PROFILE, _>(
230 &mut observer,
231 StepResourceAdmissionProfilePhase::TransactionValidateAndFingerprint,
232 phase_started,
233 );
234
235 let phase_started = step_admission_profile_start::<PROFILE>();
236 let batch_step_id = issue_batch_step_id()?;
237 let candidates = session_frame_capture_candidates(&self.sessions);
238 let captured_frames = acquire_session_frames_with_backing(&candidates, batch_step_id)?;
239 let participants = self
240 .sessions
241 .iter()
242 .cloned()
243 .zip(captured_frames)
244 .map(|(session, captured)| AdmittedStepParticipant {
245 frame: captured.hold,
246 backing_snapshot: captured.backing_snapshot,
247 session,
248 })
249 .collect();
250 let decision = StepResourceAdmissionDecision::Admitted(Arc::new(StepResourceLease::new(
251 participants,
252 Arc::clone(lane),
253 reusable_execution_bucket,
254 batch_step_id,
255 claimed_backing,
256 )?));
257 record_step_admission_profile::<PROFILE, _>(
258 &mut observer,
259 StepResourceAdmissionProfilePhase::FrameCaptureAndLease,
260 phase_started,
261 );
262 Ok(decision)
263 }
264}
265
266impl<R> AdmittedSequenceResources<R>
267where
268 R: DeviceRuntime,
269{
270 fn validate_runtime(&self, context: &'static str) -> Result<(), VNextError> {
271 let descriptor = self.request.plan.runtime().descriptor();
272 descriptor.validate()?;
273 if descriptor.id != *self.request.plan.device_id()
274 || descriptor.runtime_implementation_fingerprint
275 != self.request.plan.runtime_implementation_fingerprint()
276 {
277 return Err(invalid_resource(format!(
278 "{context} runtime differs from the trusted plan/runtime binding"
279 )));
280 }
281 Ok(())
282 }
283
284 pub fn create_execution_stream(
285 self: &Arc<Self>,
286 ) -> Result<BoundExecutionStream<R>, ExecutionStreamCreationError<R::Error>> {
287 let _lifecycle = self
288 .request
289 .plan
290 .resources
291 .read_lifecycle("create an execution stream")
292 .map_err(ExecutionStreamCreationError::Contract)?;
293 if self.is_poisoned() {
294 return Err(ExecutionStreamCreationError::Contract(invalid_resource(
295 "poisoned logical sequence cannot create an execution stream",
296 )));
297 }
298 self.validate_runtime("execution stream creation preflight")
299 .map_err(ExecutionStreamCreationError::Contract)?;
300 let stream = self
301 .request
302 .plan
303 .runtime()
304 .create_stream()
305 .map_err(ExecutionStreamCreationError::Runtime)?;
306 self.validate_runtime("execution stream creation completion")
307 .map_err(ExecutionStreamCreationError::Contract)?;
308 if self.request.plan.runtime().stream_state(&stream) != StreamState::Ready {
309 return Err(ExecutionStreamCreationError::Contract(invalid_resource(
310 "new execution stream is not ready",
311 )));
312 }
313 Ok(BoundExecutionStream {
314 runtime: Arc::clone(self.request.plan.runtime()),
315 coordinator_id: self.coordinator_id(),
316 sequence_authority: self.sequence_authority(),
317 stream: Some(stream),
318 state: BoundExecutionStreamState::Ready,
319 sequence_recovery: Arc::clone(&self.sequence_recovery),
320 sequence_dispatch_gate: Arc::clone(&self.sequence_dispatch_gate),
321 abandoned_sequence: None,
322 resources: Arc::clone(self),
323 })
324 }
325
326 pub fn activate<'resources, 'exec>(
327 &'resources self,
328 stream: &'exec mut BoundExecutionStream<R>,
329 ) -> Result<ActiveSequencePermit<'resources, 'exec, R>, VNextError> {
330 let _lifecycle = self
331 .request
332 .plan
333 .resources
334 .read_lifecycle("activate an execution stream")?;
335 if self.is_poisoned() {
336 return Err(invalid_resource(
337 "poisoned logical sequence cannot be activated",
338 ));
339 }
340 self.validate_runtime("logical sequence activation")?;
341 if !Arc::ptr_eq(self.request.plan.runtime(), &stream.runtime)
342 || !std::ptr::eq(self, Arc::as_ref(&stream.resources))
343 || stream.coordinator_id != self.coordinator_id()
344 || stream.sequence_authority != self.sequence_authority()
345 || !Arc::ptr_eq(&self.sequence_recovery, &stream.sequence_recovery)
346 || !Arc::ptr_eq(&self.sequence_dispatch_gate, &stream.sequence_dispatch_gate)
347 {
348 return Err(invalid_resource(
349 "execution stream belongs to another logical sequence authority",
350 ));
351 }
352 if stream.state != BoundExecutionStreamState::Ready
353 || stream.abandoned_sequence.is_some()
354 || self.request.plan.runtime().stream_state(stream.stream()) != StreamState::Ready
355 {
356 return Err(invalid_resource(
357 "logical sequence activation requires one core-ready stream",
358 ));
359 }
360 let mut authority_source = self.lock_authority_source()?;
361 let selecting_legacy = match *authority_source {
362 SequenceExecutionAuthoritySource::Unselected => true,
363 SequenceExecutionAuthoritySource::LegacyStream => false,
364 SequenceExecutionAuthoritySource::SequenceSession => {
365 return Err(invalid_resource(
366 "logical sequence execution authority is permanently selected for sequence sessions",
367 ));
368 }
369 SequenceExecutionAuthoritySource::FailClosed => {
370 return Err(invalid_resource(
371 "logical sequence execution authority selector is fail-closed",
372 ));
373 }
374 };
375 let backing_snapshot = self.backing_snapshot()?;
376 let epoch = match self.next_activation_epoch.fetch_update(
377 Ordering::AcqRel,
378 Ordering::Acquire,
379 |epoch| epoch.checked_add(1).filter(|next| *next <= (u64::MAX >> 2)),
380 ) {
381 Ok(epoch) => epoch,
382 Err(_) => {
383 *authority_source = SequenceExecutionAuthoritySource::FailClosed;
384 return Err(invalid_resource("active sequence epoch space is exhausted"));
385 }
386 };
387 let active_state = sequence_slot_active(epoch);
388 if let Err(actual) =
389 self.state
390 .compare_exchange(0, active_state, Ordering::AcqRel, Ordering::Acquire)
391 {
392 if selecting_legacy {
393 *authority_source = SequenceExecutionAuthoritySource::FailClosed;
394 }
395 return Err(if sequence_slot_is_poisoned(actual) {
396 invalid_resource("logical sequence was abandoned and is poisoned")
397 } else {
398 invalid_resource("logical sequence already owns an active stream")
399 });
400 }
401 let slot = self.sequence_authority().sparse_id();
402 let recovery_metadata = AbandonedSequenceMetadata {
403 plan: self.request.plan.evidence(),
404 sequence_authority: self.sequence_authority(),
405 run_id: self.run_id().clone(),
406 request_id: self.request_id().clone(),
407 slot,
408 activation_epoch: epoch,
409 runtime_implementation_fingerprint: self
410 .request
411 .plan
412 .runtime_implementation_fingerprint()
413 .to_owned(),
414 state: Arc::clone(&self.state),
415 sequence_dispatch_gate: Arc::clone(&self.sequence_dispatch_gate),
416 drained: false,
417 };
418 let recovery_key = recovery_metadata.key();
419 self.sequence_recovery.register(recovery_metadata);
420 stream.abandoned_sequence = Some(recovery_key);
421 stream.state = BoundExecutionStreamState::InUse;
422 *authority_source = SequenceExecutionAuthoritySource::LegacyStream;
423 Ok(ActiveSequencePermit {
424 resources: self,
425 backing_snapshot,
426 epoch,
427 state: Arc::clone(&self.state),
428 stream,
429 runtime_fingerprint: self
430 .request
431 .plan
432 .runtime_implementation_fingerprint()
433 .to_owned(),
434 stream_drained: false,
435 completed: false,
436 })
437 }
438
439 pub fn recover_abandoned_sequence(
440 &self,
441 ) -> Result<ActiveSequenceAbortReceipt, AbandonedSequenceRecoveryError<R::Error>> {
442 self.sequence_recovery.recover(
443 self.request.plan.runtime(),
444 self.sequence_authority().sparse_id(),
445 )
446 }
447}
448
449#[must_use = "an active sequence permit must live until asynchronous work is complete"]
453pub struct ActiveSequencePermit<'resources, 'exec, R>
454where
455 R: DeviceRuntime,
456{
457 resources: &'resources AdmittedSequenceResources<R>,
458 backing_snapshot: Arc<SequenceBackingSnapshot<R>>,
459 epoch: u64,
460 state: Arc<AtomicU64>,
461 stream: &'exec mut BoundExecutionStream<R>,
462 runtime_fingerprint: String,
463 stream_drained: bool,
464 completed: bool,
465}
466
467impl<'resources, 'exec, R> ActiveSequencePermit<'resources, 'exec, R>
468where
469 R: DeviceRuntime,
470{
471 pub fn resources(&self) -> &'resources AdmittedSequenceResources<R> {
472 self.resources
473 }
474
475 pub fn run_id(&self) -> &RunId {
476 self.resources.run_id()
477 }
478
479 pub fn request_id(&self) -> &RequestIdentity {
480 self.resources.request_id()
481 }
482
483 pub fn sequence_authority(&self) -> SequenceAuthorityId {
484 self.resources.sequence_authority()
485 }
486
487 pub fn coordinator_id(&self) -> LogicalAdmissionCoordinatorId {
488 self.resources.coordinator_id()
489 }
490
491 pub fn backing_slices(&self) -> &[LogicalBackingSliceAuthority] {
492 self.backing_snapshot.backing_slices()
493 }
494
495 pub const fn activation_epoch(&self) -> u64 {
496 self.epoch
497 }
498
499 pub fn runtime_implementation_fingerprint(&self) -> &str {
500 &self.runtime_fingerprint
501 }
502
503 pub(crate) fn with_runtime_and_stream<T>(
504 &mut self,
505 action: impl FnOnce(&R, &mut R::Stream) -> T,
506 ) -> Result<T, VNextError> {
507 if self.stream.state != BoundExecutionStreamState::InUse {
508 return Err(invalid_resource(
509 "operation dispatch requires one core-owned in-use stream",
510 ));
511 }
512 let _dispatch_guard = enter_sequence_dispatch(&self.resources.sequence_dispatch_gate)?;
513 Ok(action(
514 self.resources.request.plan.runtime(),
515 self.stream.stream_mut(),
516 ))
517 }
518
519 pub fn synchronize(
523 mut self,
524 ) -> Result<
525 SynchronizedSequencePermit<'resources, 'exec, R>,
526 SequenceSynchronizationFailure<'resources, 'exec, R>,
527 > {
528 let preflight = self
529 .resources
530 .validate_runtime("sequence synchronization preflight")
531 .and_then(|()| {
532 if self
533 .resources
534 .request
535 .plan
536 .runtime()
537 .descriptor()
538 .runtime_implementation_fingerprint
539 == self.runtime_fingerprint
540 {
541 Ok(())
542 } else {
543 Err(invalid_resource(
544 "sequence synchronization runtime differs from its activation snapshot",
545 ))
546 }
547 });
548
549 let runtime_error = match self
553 .resources
554 .request
555 .plan
556 .runtime()
557 .synchronize(self.stream.stream_mut())
558 {
559 Ok(()) => None,
560 Err(error) => Some(error),
561 };
562 let stream_ready = self
563 .resources
564 .request
565 .plan
566 .runtime()
567 .stream_state(self.stream.stream())
568 == StreamState::Ready;
569 self.stream_drained = runtime_error.is_none() && stream_ready;
570 if self.stream_drained {
571 self.stream
572 .sequence_recovery
573 .set_drained((self.sequence_authority().sparse_id(), self.epoch), true);
574 }
575 let completion = self
576 .resources
577 .validate_runtime("sequence synchronization completion")
578 .and_then(|()| {
579 if stream_ready {
580 Ok(())
581 } else {
582 Err(invalid_resource(
583 "sequence synchronization did not return the bound stream to ready",
584 ))
585 }
586 });
587 let error = preflight
588 .err()
589 .map(SequenceSynchronizationError::Contract)
590 .or_else(|| runtime_error.map(SequenceSynchronizationError::Runtime))
591 .or_else(|| completion.err().map(SequenceSynchronizationError::Contract));
592 if let Some(error) = error {
593 return Err(SequenceSynchronizationFailure {
594 permit: Some(self),
595 error,
596 });
597 }
598 self.stream.state = BoundExecutionStreamState::Ready;
599 Ok(SynchronizedSequencePermit { permit: Some(self) })
600 }
601}
602
603#[derive(Debug)]
604pub enum SequenceSynchronizationError<E> {
605 Contract(VNextError),
606 Runtime(E),
607}
608
609#[must_use = "failed sequence synchronization must be retried or retained"]
613pub struct SequenceSynchronizationFailure<'resources, 'exec, R>
614where
615 R: DeviceRuntime,
616{
617 permit: Option<ActiveSequencePermit<'resources, 'exec, R>>,
618 error: SequenceSynchronizationError<R::Error>,
619}
620
621impl<R> fmt::Debug for SequenceSynchronizationFailure<'_, '_, R>
622where
623 R: DeviceRuntime,
624{
625 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
626 formatter
627 .debug_struct("SequenceSynchronizationFailure")
628 .field("error", &self.error)
629 .finish_non_exhaustive()
630 }
631}
632
633impl<'resources, 'exec, R> SequenceSynchronizationFailure<'resources, 'exec, R>
634where
635 R: DeviceRuntime,
636{
637 pub fn error(&self) -> &SequenceSynchronizationError<R::Error> {
638 &self.error
639 }
640
641 pub fn retry(
642 mut self,
643 ) -> Result<
644 SynchronizedSequencePermit<'resources, 'exec, R>,
645 SequenceSynchronizationFailure<'resources, 'exec, R>,
646 > {
647 self.permit
648 .take()
649 .expect("synchronization failure owns its active permit")
650 .synchronize()
651 }
652}
653
654#[must_use = "a synchronized sequence must be completed or aborted"]
657pub struct SynchronizedSequencePermit<'resources, 'exec, R>
658where
659 R: DeviceRuntime,
660{
661 permit: Option<ActiveSequencePermit<'resources, 'exec, R>>,
662}
663
664impl<R> SynchronizedSequencePermit<'_, '_, R>
665where
666 R: DeviceRuntime,
667{
668 pub fn complete(mut self) -> Result<ActiveSequenceCompletionReceipt, VNextError> {
669 let mut permit = self
670 .permit
671 .take()
672 .expect("synchronized sequence owns its active permit");
673 let sequence_poisoned =
674 sequence_dispatch_is_poisoned(&permit.resources.sequence_dispatch_gate);
675 let terminal_state = if sequence_poisoned {
676 sequence_slot_poisoned_drained(permit.epoch)
677 } else {
678 0
679 };
680 permit
681 .state
682 .compare_exchange(
683 sequence_slot_active(permit.epoch),
684 terminal_state,
685 Ordering::AcqRel,
686 Ordering::Acquire,
687 )
688 .map_err(|_| invalid_resource("active sequence epoch is no longer completable"))?;
689 permit
690 .stream
691 .sequence_recovery
692 .clear((permit.sequence_authority().sparse_id(), permit.epoch));
693 permit.stream.abandoned_sequence = None;
694 permit.stream.state = BoundExecutionStreamState::Ready;
695 permit.completed = true;
696 if sequence_poisoned {
697 return Err(invalid_resource(
698 "sequence cannot complete successfully after its dispatch authority was poisoned",
699 ));
700 }
701 Ok(ActiveSequenceCompletionReceipt {
702 plan: permit.resources.request.plan.evidence(),
703 sequence_authority: permit.sequence_authority(),
704 run_id: permit.run_id().clone(),
705 request_id: permit.request_id().clone(),
706 activation_epoch: permit.epoch,
707 runtime_implementation_fingerprint: permit.runtime_fingerprint.clone(),
708 })
709 }
710
711 pub fn abort(mut self) -> Result<ActiveSequenceAbortReceipt, VNextError> {
714 let mut permit = self
715 .permit
716 .take()
717 .expect("synchronized sequence owns its active permit");
718 permit
719 .state
720 .compare_exchange(
721 sequence_slot_active(permit.epoch),
722 sequence_slot_poisoned_drained(permit.epoch),
723 Ordering::AcqRel,
724 Ordering::Acquire,
725 )
726 .map_err(|_| invalid_resource("active sequence epoch is no longer abortable"))?;
727 permit
728 .resources
729 .sequence_dispatch_gate
730 .fetch_or(SEQUENCE_DISPATCH_POISONED_BIT, Ordering::AcqRel);
731 permit
732 .stream
733 .sequence_recovery
734 .clear((permit.sequence_authority().sparse_id(), permit.epoch));
735 permit.stream.abandoned_sequence = None;
736 permit.stream.state = BoundExecutionStreamState::Ready;
737 permit.completed = true;
738 Ok(ActiveSequenceAbortReceipt {
739 plan: permit.resources.request.plan.evidence(),
740 sequence_authority: permit.sequence_authority(),
741 run_id: permit.run_id().clone(),
742 request_id: permit.request_id().clone(),
743 activation_epoch: permit.epoch,
744 runtime_implementation_fingerprint: permit.runtime_fingerprint.clone(),
745 disposition: ActiveSequenceAbortDisposition::SynchronizedAndPoisoned,
746 })
747 }
748}
749
750#[derive(Debug, Serialize)]
754#[must_use = "sequence completion evidence must be recorded by execution"]
755pub struct ActiveSequenceCompletionReceipt {
756 plan: TrustedPlanRuntimeEvidence,
757 sequence_authority: SequenceAuthorityId,
758 run_id: RunId,
759 request_id: RequestIdentity,
760 activation_epoch: u64,
761 runtime_implementation_fingerprint: String,
762}
763
764impl ActiveSequenceCompletionReceipt {
765 pub fn plan(&self) -> &TrustedPlanRuntimeEvidence {
766 &self.plan
767 }
768
769 pub fn run_id(&self) -> &RunId {
770 &self.run_id
771 }
772
773 pub fn request_id(&self) -> &RequestIdentity {
774 &self.request_id
775 }
776
777 pub const fn sequence_authority(&self) -> SequenceAuthorityId {
778 self.sequence_authority
779 }
780
781 pub const fn activation_epoch(&self) -> u64 {
782 self.activation_epoch
783 }
784
785 pub fn runtime_implementation_fingerprint(&self) -> &str {
786 &self.runtime_implementation_fingerprint
787 }
788}
789
790impl<R> Drop for ActiveSequencePermit<'_, '_, R>
791where
792 R: DeviceRuntime,
793{
794 fn drop(&mut self) {
795 if !self.completed {
796 let poisoned_state = if self.stream_drained {
797 sequence_slot_poisoned_drained(self.epoch)
798 } else {
799 sequence_slot_poisoned_undrained(self.epoch)
800 };
801 let result = self.state.compare_exchange(
802 sequence_slot_active(self.epoch),
803 poisoned_state,
804 Ordering::AcqRel,
805 Ordering::Acquire,
806 );
807 debug_assert!(result.is_ok(), "active sequence slot guard lost ownership");
808 if result.is_ok() {
809 self.resources
810 .sequence_dispatch_gate
811 .fetch_or(SEQUENCE_DISPATCH_POISONED_BIT, Ordering::AcqRel);
812 self.stream.state = BoundExecutionStreamState::Poisoned;
813 self.stream.sequence_recovery.set_drained(
814 (self.sequence_authority().sparse_id(), self.epoch),
815 self.stream_drained,
816 );
817 }
818 }
819 }
820}