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(
239 &candidates,
240 batch_step_id,
241 claimed_backing.work_shape(),
242 )?;
243 let participants = self
244 .sessions
245 .iter()
246 .cloned()
247 .zip(captured_frames)
248 .map(|(session, captured)| AdmittedStepParticipant {
249 frame: captured.hold,
250 backing_snapshot: captured.backing_snapshot,
251 session,
252 })
253 .collect();
254 let decision = StepResourceAdmissionDecision::Admitted(Arc::new(StepResourceLease::new(
255 participants,
256 Arc::clone(lane),
257 reusable_execution_bucket,
258 batch_step_id,
259 claimed_backing,
260 )?));
261 record_step_admission_profile::<PROFILE, _>(
262 &mut observer,
263 StepResourceAdmissionProfilePhase::FrameCaptureAndLease,
264 phase_started,
265 );
266 Ok(decision)
267 }
268}
269
270impl<R> AdmittedSequenceResources<R>
271where
272 R: DeviceRuntime,
273{
274 fn validate_runtime(&self, context: &'static str) -> Result<(), VNextError> {
275 let descriptor = self.request.plan.runtime().descriptor();
276 descriptor.validate()?;
277 if descriptor.id != *self.request.plan.device_id()
278 || descriptor.runtime_implementation_fingerprint
279 != self.request.plan.runtime_implementation_fingerprint()
280 {
281 return Err(invalid_resource(format!(
282 "{context} runtime differs from the trusted plan/runtime binding"
283 )));
284 }
285 Ok(())
286 }
287
288 pub fn create_execution_stream(
289 self: &Arc<Self>,
290 ) -> Result<BoundExecutionStream<R>, ExecutionStreamCreationError<R::Error>> {
291 let _lifecycle = self
292 .request
293 .plan
294 .resources
295 .read_lifecycle("create an execution stream")
296 .map_err(ExecutionStreamCreationError::Contract)?;
297 if self.is_poisoned() {
298 return Err(ExecutionStreamCreationError::Contract(invalid_resource(
299 "poisoned logical sequence cannot create an execution stream",
300 )));
301 }
302 self.validate_runtime("execution stream creation preflight")
303 .map_err(ExecutionStreamCreationError::Contract)?;
304 let stream = self
305 .request
306 .plan
307 .runtime()
308 .create_stream()
309 .map_err(ExecutionStreamCreationError::Runtime)?;
310 self.validate_runtime("execution stream creation completion")
311 .map_err(ExecutionStreamCreationError::Contract)?;
312 if self.request.plan.runtime().stream_state(&stream) != StreamState::Ready {
313 return Err(ExecutionStreamCreationError::Contract(invalid_resource(
314 "new execution stream is not ready",
315 )));
316 }
317 Ok(BoundExecutionStream {
318 runtime: Arc::clone(self.request.plan.runtime()),
319 coordinator_id: self.coordinator_id(),
320 sequence_authority: self.sequence_authority(),
321 stream: Some(stream),
322 state: BoundExecutionStreamState::Ready,
323 sequence_recovery: Arc::clone(&self.sequence_recovery),
324 sequence_dispatch_gate: Arc::clone(&self.sequence_dispatch_gate),
325 abandoned_sequence: None,
326 resources: Arc::clone(self),
327 })
328 }
329
330 pub fn activate<'resources, 'exec>(
331 &'resources self,
332 stream: &'exec mut BoundExecutionStream<R>,
333 ) -> Result<ActiveSequencePermit<'resources, 'exec, R>, VNextError> {
334 let _lifecycle = self
335 .request
336 .plan
337 .resources
338 .read_lifecycle("activate an execution stream")?;
339 if self.is_poisoned() {
340 return Err(invalid_resource(
341 "poisoned logical sequence cannot be activated",
342 ));
343 }
344 self.validate_runtime("logical sequence activation")?;
345 if !Arc::ptr_eq(self.request.plan.runtime(), &stream.runtime)
346 || !std::ptr::eq(self, Arc::as_ref(&stream.resources))
347 || stream.coordinator_id != self.coordinator_id()
348 || stream.sequence_authority != self.sequence_authority()
349 || !Arc::ptr_eq(&self.sequence_recovery, &stream.sequence_recovery)
350 || !Arc::ptr_eq(&self.sequence_dispatch_gate, &stream.sequence_dispatch_gate)
351 {
352 return Err(invalid_resource(
353 "execution stream belongs to another logical sequence authority",
354 ));
355 }
356 if stream.state != BoundExecutionStreamState::Ready
357 || stream.abandoned_sequence.is_some()
358 || self.request.plan.runtime().stream_state(stream.stream()) != StreamState::Ready
359 {
360 return Err(invalid_resource(
361 "logical sequence activation requires one core-ready stream",
362 ));
363 }
364 let mut authority_source = self.lock_authority_source()?;
365 let selecting_legacy = match *authority_source {
366 SequenceExecutionAuthoritySource::Unselected => true,
367 SequenceExecutionAuthoritySource::LegacyStream => false,
368 SequenceExecutionAuthoritySource::SequenceSession => {
369 return Err(invalid_resource(
370 "logical sequence execution authority is permanently selected for sequence sessions",
371 ));
372 }
373 SequenceExecutionAuthoritySource::FailClosed => {
374 return Err(invalid_resource(
375 "logical sequence execution authority selector is fail-closed",
376 ));
377 }
378 };
379 let backing_snapshot = self.backing_snapshot()?;
380 let epoch = match self.next_activation_epoch.fetch_update(
381 Ordering::AcqRel,
382 Ordering::Acquire,
383 |epoch| epoch.checked_add(1).filter(|next| *next <= (u64::MAX >> 2)),
384 ) {
385 Ok(epoch) => epoch,
386 Err(_) => {
387 *authority_source = SequenceExecutionAuthoritySource::FailClosed;
388 return Err(invalid_resource("active sequence epoch space is exhausted"));
389 }
390 };
391 let active_state = sequence_slot_active(epoch);
392 if let Err(actual) =
393 self.state
394 .compare_exchange(0, active_state, Ordering::AcqRel, Ordering::Acquire)
395 {
396 if selecting_legacy {
397 *authority_source = SequenceExecutionAuthoritySource::FailClosed;
398 }
399 return Err(if sequence_slot_is_poisoned(actual) {
400 invalid_resource("logical sequence was abandoned and is poisoned")
401 } else {
402 invalid_resource("logical sequence already owns an active stream")
403 });
404 }
405 let slot = self.sequence_authority().sparse_id();
406 let recovery_metadata = AbandonedSequenceMetadata {
407 plan: self.request.plan.evidence(),
408 sequence_authority: self.sequence_authority(),
409 run_id: self.run_id().clone(),
410 request_id: self.request_id().clone(),
411 slot,
412 activation_epoch: epoch,
413 runtime_implementation_fingerprint: self
414 .request
415 .plan
416 .runtime_implementation_fingerprint()
417 .to_owned(),
418 state: Arc::clone(&self.state),
419 sequence_dispatch_gate: Arc::clone(&self.sequence_dispatch_gate),
420 drained: false,
421 };
422 let recovery_key = recovery_metadata.key();
423 self.sequence_recovery.register(recovery_metadata);
424 stream.abandoned_sequence = Some(recovery_key);
425 stream.state = BoundExecutionStreamState::InUse;
426 *authority_source = SequenceExecutionAuthoritySource::LegacyStream;
427 Ok(ActiveSequencePermit {
428 resources: self,
429 backing_snapshot,
430 epoch,
431 state: Arc::clone(&self.state),
432 stream,
433 runtime_fingerprint: self
434 .request
435 .plan
436 .runtime_implementation_fingerprint()
437 .to_owned(),
438 stream_drained: false,
439 completed: false,
440 })
441 }
442
443 pub fn recover_abandoned_sequence(
444 &self,
445 ) -> Result<ActiveSequenceAbortReceipt, AbandonedSequenceRecoveryError<R::Error>> {
446 self.sequence_recovery.recover(
447 self.request.plan.runtime(),
448 self.sequence_authority().sparse_id(),
449 )
450 }
451}
452
453#[must_use = "an active sequence permit must live until asynchronous work is complete"]
457pub struct ActiveSequencePermit<'resources, 'exec, R>
458where
459 R: DeviceRuntime,
460{
461 resources: &'resources AdmittedSequenceResources<R>,
462 backing_snapshot: Arc<SequenceBackingSnapshot<R>>,
463 epoch: u64,
464 state: Arc<AtomicU64>,
465 stream: &'exec mut BoundExecutionStream<R>,
466 runtime_fingerprint: String,
467 stream_drained: bool,
468 completed: bool,
469}
470
471impl<'resources, 'exec, R> ActiveSequencePermit<'resources, 'exec, R>
472where
473 R: DeviceRuntime,
474{
475 pub fn resources(&self) -> &'resources AdmittedSequenceResources<R> {
476 self.resources
477 }
478
479 pub fn run_id(&self) -> &RunId {
480 self.resources.run_id()
481 }
482
483 pub fn request_id(&self) -> &RequestIdentity {
484 self.resources.request_id()
485 }
486
487 pub fn sequence_authority(&self) -> SequenceAuthorityId {
488 self.resources.sequence_authority()
489 }
490
491 pub fn coordinator_id(&self) -> LogicalAdmissionCoordinatorId {
492 self.resources.coordinator_id()
493 }
494
495 pub fn backing_slices(&self) -> &[LogicalBackingSliceAuthority] {
496 self.backing_snapshot.backing_slices()
497 }
498
499 pub const fn activation_epoch(&self) -> u64 {
500 self.epoch
501 }
502
503 pub fn runtime_implementation_fingerprint(&self) -> &str {
504 &self.runtime_fingerprint
505 }
506
507 pub(crate) fn with_runtime_and_stream<T>(
508 &mut self,
509 action: impl FnOnce(&R, &mut R::Stream) -> T,
510 ) -> Result<T, VNextError> {
511 if self.stream.state != BoundExecutionStreamState::InUse {
512 return Err(invalid_resource(
513 "operation dispatch requires one core-owned in-use stream",
514 ));
515 }
516 let _dispatch_guard = enter_sequence_dispatch(&self.resources.sequence_dispatch_gate)?;
517 Ok(action(
518 self.resources.request.plan.runtime(),
519 self.stream.stream_mut(),
520 ))
521 }
522
523 pub fn synchronize(
527 mut self,
528 ) -> Result<
529 SynchronizedSequencePermit<'resources, 'exec, R>,
530 SequenceSynchronizationFailure<'resources, 'exec, R>,
531 > {
532 let preflight = self
533 .resources
534 .validate_runtime("sequence synchronization preflight")
535 .and_then(|()| {
536 if self
537 .resources
538 .request
539 .plan
540 .runtime()
541 .descriptor()
542 .runtime_implementation_fingerprint
543 == self.runtime_fingerprint
544 {
545 Ok(())
546 } else {
547 Err(invalid_resource(
548 "sequence synchronization runtime differs from its activation snapshot",
549 ))
550 }
551 });
552
553 let runtime_error = match self
557 .resources
558 .request
559 .plan
560 .runtime()
561 .synchronize(self.stream.stream_mut())
562 {
563 Ok(()) => None,
564 Err(error) => Some(error),
565 };
566 let stream_ready = self
567 .resources
568 .request
569 .plan
570 .runtime()
571 .stream_state(self.stream.stream())
572 == StreamState::Ready;
573 self.stream_drained = runtime_error.is_none() && stream_ready;
574 if self.stream_drained {
575 self.stream
576 .sequence_recovery
577 .set_drained((self.sequence_authority().sparse_id(), self.epoch), true);
578 }
579 let completion = self
580 .resources
581 .validate_runtime("sequence synchronization completion")
582 .and_then(|()| {
583 if stream_ready {
584 Ok(())
585 } else {
586 Err(invalid_resource(
587 "sequence synchronization did not return the bound stream to ready",
588 ))
589 }
590 });
591 let error = preflight
592 .err()
593 .map(SequenceSynchronizationError::Contract)
594 .or_else(|| runtime_error.map(SequenceSynchronizationError::Runtime))
595 .or_else(|| completion.err().map(SequenceSynchronizationError::Contract));
596 if let Some(error) = error {
597 return Err(SequenceSynchronizationFailure {
598 permit: Some(self),
599 error,
600 });
601 }
602 self.stream.state = BoundExecutionStreamState::Ready;
603 Ok(SynchronizedSequencePermit { permit: Some(self) })
604 }
605}
606
607#[derive(Debug)]
608pub enum SequenceSynchronizationError<E> {
609 Contract(VNextError),
610 Runtime(E),
611}
612
613#[must_use = "failed sequence synchronization must be retried or retained"]
617pub struct SequenceSynchronizationFailure<'resources, 'exec, R>
618where
619 R: DeviceRuntime,
620{
621 permit: Option<ActiveSequencePermit<'resources, 'exec, R>>,
622 error: SequenceSynchronizationError<R::Error>,
623}
624
625impl<R> fmt::Debug for SequenceSynchronizationFailure<'_, '_, R>
626where
627 R: DeviceRuntime,
628{
629 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
630 formatter
631 .debug_struct("SequenceSynchronizationFailure")
632 .field("error", &self.error)
633 .finish_non_exhaustive()
634 }
635}
636
637impl<'resources, 'exec, R> SequenceSynchronizationFailure<'resources, 'exec, R>
638where
639 R: DeviceRuntime,
640{
641 pub fn error(&self) -> &SequenceSynchronizationError<R::Error> {
642 &self.error
643 }
644
645 pub fn retry(
646 mut self,
647 ) -> Result<
648 SynchronizedSequencePermit<'resources, 'exec, R>,
649 SequenceSynchronizationFailure<'resources, 'exec, R>,
650 > {
651 self.permit
652 .take()
653 .expect("synchronization failure owns its active permit")
654 .synchronize()
655 }
656}
657
658#[must_use = "a synchronized sequence must be completed or aborted"]
661pub struct SynchronizedSequencePermit<'resources, 'exec, R>
662where
663 R: DeviceRuntime,
664{
665 permit: Option<ActiveSequencePermit<'resources, 'exec, R>>,
666}
667
668impl<R> SynchronizedSequencePermit<'_, '_, R>
669where
670 R: DeviceRuntime,
671{
672 pub fn complete(mut self) -> Result<ActiveSequenceCompletionReceipt, VNextError> {
673 let mut permit = self
674 .permit
675 .take()
676 .expect("synchronized sequence owns its active permit");
677 let sequence_poisoned =
678 sequence_dispatch_is_poisoned(&permit.resources.sequence_dispatch_gate);
679 let terminal_state = if sequence_poisoned {
680 sequence_slot_poisoned_drained(permit.epoch)
681 } else {
682 0
683 };
684 permit
685 .state
686 .compare_exchange(
687 sequence_slot_active(permit.epoch),
688 terminal_state,
689 Ordering::AcqRel,
690 Ordering::Acquire,
691 )
692 .map_err(|_| invalid_resource("active sequence epoch is no longer completable"))?;
693 permit
694 .stream
695 .sequence_recovery
696 .clear((permit.sequence_authority().sparse_id(), permit.epoch));
697 permit.stream.abandoned_sequence = None;
698 permit.stream.state = BoundExecutionStreamState::Ready;
699 permit.completed = true;
700 if sequence_poisoned {
701 return Err(invalid_resource(
702 "sequence cannot complete successfully after its dispatch authority was poisoned",
703 ));
704 }
705 Ok(ActiveSequenceCompletionReceipt {
706 plan: permit.resources.request.plan.evidence(),
707 sequence_authority: permit.sequence_authority(),
708 run_id: permit.run_id().clone(),
709 request_id: permit.request_id().clone(),
710 activation_epoch: permit.epoch,
711 runtime_implementation_fingerprint: permit.runtime_fingerprint.clone(),
712 })
713 }
714
715 pub fn abort(mut self) -> Result<ActiveSequenceAbortReceipt, VNextError> {
718 let mut permit = self
719 .permit
720 .take()
721 .expect("synchronized sequence owns its active permit");
722 permit
723 .state
724 .compare_exchange(
725 sequence_slot_active(permit.epoch),
726 sequence_slot_poisoned_drained(permit.epoch),
727 Ordering::AcqRel,
728 Ordering::Acquire,
729 )
730 .map_err(|_| invalid_resource("active sequence epoch is no longer abortable"))?;
731 permit
732 .resources
733 .sequence_dispatch_gate
734 .fetch_or(SEQUENCE_DISPATCH_POISONED_BIT, Ordering::AcqRel);
735 permit
736 .stream
737 .sequence_recovery
738 .clear((permit.sequence_authority().sparse_id(), permit.epoch));
739 permit.stream.abandoned_sequence = None;
740 permit.stream.state = BoundExecutionStreamState::Ready;
741 permit.completed = true;
742 Ok(ActiveSequenceAbortReceipt {
743 plan: permit.resources.request.plan.evidence(),
744 sequence_authority: permit.sequence_authority(),
745 run_id: permit.run_id().clone(),
746 request_id: permit.request_id().clone(),
747 activation_epoch: permit.epoch,
748 runtime_implementation_fingerprint: permit.runtime_fingerprint.clone(),
749 disposition: ActiveSequenceAbortDisposition::SynchronizedAndPoisoned,
750 })
751 }
752}
753
754#[derive(Debug, Serialize)]
758#[must_use = "sequence completion evidence must be recorded by execution"]
759pub struct ActiveSequenceCompletionReceipt {
760 plan: TrustedPlanRuntimeEvidence,
761 sequence_authority: SequenceAuthorityId,
762 run_id: RunId,
763 request_id: RequestIdentity,
764 activation_epoch: u64,
765 runtime_implementation_fingerprint: String,
766}
767
768impl ActiveSequenceCompletionReceipt {
769 pub fn plan(&self) -> &TrustedPlanRuntimeEvidence {
770 &self.plan
771 }
772
773 pub fn run_id(&self) -> &RunId {
774 &self.run_id
775 }
776
777 pub fn request_id(&self) -> &RequestIdentity {
778 &self.request_id
779 }
780
781 pub const fn sequence_authority(&self) -> SequenceAuthorityId {
782 self.sequence_authority
783 }
784
785 pub const fn activation_epoch(&self) -> u64 {
786 self.activation_epoch
787 }
788
789 pub fn runtime_implementation_fingerprint(&self) -> &str {
790 &self.runtime_implementation_fingerprint
791 }
792}
793
794impl<R> Drop for ActiveSequencePermit<'_, '_, R>
795where
796 R: DeviceRuntime,
797{
798 fn drop(&mut self) {
799 if !self.completed {
800 let poisoned_state = if self.stream_drained {
801 sequence_slot_poisoned_drained(self.epoch)
802 } else {
803 sequence_slot_poisoned_undrained(self.epoch)
804 };
805 let result = self.state.compare_exchange(
806 sequence_slot_active(self.epoch),
807 poisoned_state,
808 Ordering::AcqRel,
809 Ordering::Acquire,
810 );
811 debug_assert!(result.is_ok(), "active sequence slot guard lost ownership");
812 if result.is_ok() {
813 self.resources
814 .sequence_dispatch_gate
815 .fetch_or(SEQUENCE_DISPATCH_POISONED_BIT, Ordering::AcqRel);
816 self.stream.state = BoundExecutionStreamState::Poisoned;
817 self.stream.sequence_recovery.set_drained(
818 (self.sequence_authority().sparse_id(), self.epoch),
819 self.stream_drained,
820 );
821 }
822 }
823 }
824}