use super::{
invalid_resource, ActiveSequenceSessionState, Arc, DeviceRuntime, NonZeroU64,
SequenceBackingGeneration, SequenceBackingSnapshot, SequenceSession, SequenceSessionEpoch,
SequenceSessionFingerprint, SequenceSessionPhase, SequenceSessionSlot,
SequenceSessionSlotState, VNextError,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum SequenceStateTransferKind {
CaptureRead,
RestoreWrite,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct StateTransferReservation {
serial: NonZeroU64,
kind: SequenceStateTransferKind,
generation: SequenceBackingGeneration,
}
#[derive(Debug, Clone)]
pub(crate) struct SequenceStateTransferSlot {
next_serial: Option<NonZeroU64>,
active: Option<StateTransferReservation>,
}
impl Default for SequenceStateTransferSlot {
fn default() -> Self {
Self {
next_serial: Some(NonZeroU64::MIN),
active: None,
}
}
}
impl SequenceStateTransferSlot {
pub(crate) fn is_reserved(&self) -> bool {
self.active.is_some()
}
fn reserve(
&mut self,
kind: SequenceStateTransferKind,
generation: SequenceBackingGeneration,
) -> Result<StateTransferReservation, VNextError> {
if self.is_reserved() {
return Err(invalid_resource(
"sequence already owns a state transfer reservation",
));
}
let serial = self.next_serial.ok_or_else(|| {
invalid_resource("sequence state transfer reservation identities are exhausted")
})?;
let reservation = StateTransferReservation {
serial,
kind,
generation,
};
self.next_serial = serial.get().checked_add(1).and_then(NonZeroU64::new);
self.active = Some(reservation);
Ok(reservation)
}
}
pub(crate) enum SequenceStateTransferPreparation<R: DeviceRuntime> {
Prepared(PreparedSequenceStateTransfer<R>),
Busy,
StaleBacking,
}
#[must_use = "the preparation owns exclusive sequence state access"]
pub(crate) struct PreparedSequenceStateTransfer<R: DeviceRuntime> {
reservation: PreparedStateTransferHold,
backing: Arc<SequenceBackingSnapshot<R>>,
session: Arc<SequenceSession<R>>,
}
impl<R: DeviceRuntime> PreparedSequenceStateTransfer<R> {
pub(crate) fn backing(&self) -> &Arc<SequenceBackingSnapshot<R>> {
&self.backing
}
pub(crate) fn session(&self) -> &Arc<SequenceSession<R>> {
&self.session
}
pub(crate) fn kind(&self) -> SequenceStateTransferKind {
self.reservation.reservation.kind
}
pub(crate) fn reservation_serial(&self) -> NonZeroU64 {
self.reservation.reservation.serial
}
pub(crate) fn runtime_arc(&self) -> &Arc<R> {
&self.session.resources.request.plan.resources.runtime
}
pub(crate) fn deferred_cleanup_domain(&self) -> crate::vnext::DeferredDeviceCleanupDomainId {
self.session
.resources
.request
.plan
.resources
.deferred_cleanup_domain
}
pub(crate) fn backing_view(
&self,
resource: &crate::vnext::ResourceId,
) -> Result<crate::vnext::LogicalBackingBufferView<'_, R::Buffer>, VNextError> {
self.session
.resources
.request
.plan
.dynamic_pools()
.view_many(self.backing.backing_slices_for(resource))
}
pub(super) fn ensure_active_reservation(
&self,
active: &ActiveSequenceSessionState,
) -> Result<(), VNextError> {
if active.epoch != self.reservation.epoch
|| active.fingerprint != self.reservation.fingerprint
|| active.state_transfer.active != Some(self.reservation.reservation)
|| self.reservation.reservation.generation != self.backing.generation()
{
return Err(invalid_resource(
"state transfer reservation does not own this exact session and backing",
));
}
Ok(())
}
pub(super) fn release_active_reservation(
&self,
active: &mut ActiveSequenceSessionState,
) -> Result<(), VNextError> {
self.ensure_active_reservation(active)?;
self.reservation
.released
.store(true, std::sync::atomic::Ordering::Release);
active.state_transfer.active = None;
Ok(())
}
}
struct PreparedStateTransferHold {
slot: Arc<SequenceSessionSlot>,
epoch: SequenceSessionEpoch,
fingerprint: SequenceSessionFingerprint,
reservation: StateTransferReservation,
released: std::sync::atomic::AtomicBool,
}
impl Drop for PreparedStateTransferHold {
fn drop(&mut self) {
if self.released.load(std::sync::atomic::Ordering::Acquire) {
return;
}
let mut state = match self.slot.state.lock() {
Ok(state) => state,
Err(poisoned) => {
*poisoned.into_inner() = SequenceSessionSlotState::FailClosed;
return;
}
};
match &mut *state {
SequenceSessionSlotState::Active(active)
if active.epoch == self.epoch && active.fingerprint == self.fingerprint =>
{
if active.state_transfer.active == Some(self.reservation) {
active.state_transfer.active = None;
} else {
active.phase = SequenceSessionPhase::Poisoned;
}
}
_ => {}
}
}
}
fn ensure_transfer_candidate(active: &ActiveSequenceSessionState) -> Result<bool, VNextError> {
if active.phase != SequenceSessionPhase::Open {
return Err(invalid_resource(
"state transfer requires an open sequence session",
));
}
Ok(active.active_frame.is_none()
&& !active.has_participant_flights()
&& !active.state_transfer.is_reserved())
}
impl<R: DeviceRuntime> SequenceSession<R> {
pub(crate) fn try_prepare_state_transfer(
self: &Arc<Self>,
kind: SequenceStateTransferKind,
expected_generation: SequenceBackingGeneration,
) -> Result<SequenceStateTransferPreparation<R>, VNextError> {
let _lifecycle = self
.resources
.request
.plan
.resources
.read_lifecycle("prepare sequence state transfer")?;
if self.resources.is_poisoned() {
return Err(invalid_resource(
"poisoned sequence cannot prepare state transfer",
));
}
let fingerprint = self.fingerprint.clone();
let mut state = self
.slot
.state
.lock()
.map_err(|_| invalid_resource("sequence session state mutex is poisoned"))?;
let active = match &mut *state {
SequenceSessionSlotState::Active(active)
if active.epoch == self.epoch && active.fingerprint == self.fingerprint =>
{
active
}
_ => {
return Err(invalid_resource(
"state transfer sequence session is stale or inactive",
))
}
};
if !ensure_transfer_candidate(active)? {
return Ok(SequenceStateTransferPreparation::Busy);
}
let backing = self.resources.lock_backing_state()?;
if backing.current.generation() != expected_generation {
return Ok(SequenceStateTransferPreparation::StaleBacking);
}
let reservation = active.state_transfer.reserve(kind, expected_generation)?;
Ok(SequenceStateTransferPreparation::Prepared(
PreparedSequenceStateTransfer {
reservation: PreparedStateTransferHold {
slot: Arc::clone(&self.slot),
epoch: self.epoch,
fingerprint,
reservation,
released: std::sync::atomic::AtomicBool::new(false),
},
backing: Arc::clone(&backing.current),
session: Arc::clone(self),
},
))
}
}
#[cfg(test)]
mod tests;