use crate::storage::manager::RecordWriteBytes;
use std::sync::Arc;
use super::{RecoveryEntry, RecoverySnapshot};
use crate::storage::manager::{
ENTRY_OVERHEAD, Inner, Operation, Pending, Target, encoded_size, recovery_preparation_bytes, recovery_resident_bytes,
};
use crate::storage::{ReadLimits, RecordWrite, StorageCursor, StorageManager, StoredRecord};
use crate::{Error, Result};
use harmos_domain::state::Position;
pub struct Permit {
inner: Arc<Inner>,
bytes: usize,
target: Target,
first_record: StorageCursor,
records: Vec<StoredRecord>,
record_bytes: usize,
capture_bytes: usize,
payload: Vec<u8>,
active: bool,
snapshot: bool,
}
impl<C> StorageManager<C> {
pub async fn reserve_transition(&self, payload_bytes: usize, records: Option<ReadLimits>) -> Result<Permit> {
self.reserve_recovery(payload_bytes, records, false).await
}
pub async fn reserve_snapshot(&self, payload_bytes: usize) -> Result<Permit> {
self.reserve_recovery(payload_bytes, None, true).await
}
async fn reserve_recovery(&self, payload_bytes: usize, records: Option<ReadLimits>, snapshot: bool) -> Result<Permit> {
let recovery_bytes = recovery_preparation_bytes(payload_bytes, self.inner.recovery.history(), self.inner.primary.history())?;
let bytes = recovery_bytes;
let batches = 1 + usize::from(records.is_some());
if batches > self.inner.settings.request.max_pending_write_batches.get() {
return Err(Error::InvalidInput { field: "managed capture", reason: "requires two queue slots" });
}
if bytes > self.inner.settings.request.max_pending_write_bytes.get() {
return Err(Error::InvalidInput { field: "managed recovery", reason: "group exceeds memory budget" });
}
let mut changed = self.inner.changed.subscribe();
loop {
{
let mut state = self.inner.state.lock().unwrap();
if !snapshot && recovery_bytes > state.journal_limit {
return Err(Error::InvalidInput { field: "managed recovery", reason: "group exceeds recovery read bound" });
}
if state.status.closing {
return Err(Error::Closing { domain: "storage" });
}
if !state.ready || !state.status.running {
return Err(Error::NotReady { domain: "storage" });
}
if let Some(source) = &state.status.failure {
return Err(Error::Persistence { source: source.clone() });
}
state.collect();
if state.reserved == 0
&& state.finishing.is_none()
&& state.status.pending_bytes <= self.inner.settings.request.max_pending_write_bytes.get() - bytes
&& state.status.pending_batches <= self.inner.settings.request.max_pending_write_batches.get() - batches
{
state
.next
.checked_add(batches as u64)
.ok_or(Error::InvalidInput { field: "managed capture", reason: "queue order overflow" })?;
let mut first_record = state.status.accepted.clone();
if let Some(limits) = records {
first_record
.sequence
.checked_add(limits.rows.get() as u64)
.ok_or(Error::InvalidInput { field: "managed capture", reason: "position overflow" })?;
first_record.sequence += 1;
}
let target = Target {
index: state
.next
.checked_add(1)
.ok_or(Error::InvalidInput { field: "managed recovery", reason: "queue order overflow" })?,
sequence: state
.status
.recovery_accepted
.sequence
.checked_add(u64::from(!snapshot))
.ok_or(Error::InvalidInput { field: "managed recovery", reason: "position overflow" })?,
};
state.reserved = bytes;
state.reserved_batches = batches;
state.collect();
return Ok(Permit {
inner: self.inner.clone(),
bytes: recovery_bytes,
target,
first_record,
records: Vec::new(),
record_bytes: 0,
capture_bytes: 0,
payload: Vec::new(),
active: true,
snapshot,
});
}
self.inner.relieve_pressure(&mut state, bytes, batches);
}
changed.changed().await.map_err(|_| Error::NotReady { domain: "storage" })?;
}
}
pub async fn settle_transition(&self) {
let mut changed = self.inner.changed.subscribe();
loop {
if self.inner.state.lock().unwrap().reserved == 0 {
return;
}
if changed.changed().await.is_err() {
return;
}
}
}
}
impl Permit {
pub fn snapshot_payload(&mut self, payload: Vec<u8>) -> Result<()> {
debug_assert!(self.snapshot);
let bytes = recovery_resident_bytes(payload.capacity(), self.inner.recovery.history(), self.inner.primary.history())?;
if bytes > self.bytes {
return Err(Error::InvalidInput { field: "state snapshot", reason: "payload exceeds reservation" });
}
self.payload = payload;
self.bytes = bytes;
let mut state = self.inner.state.lock().unwrap();
state.reserved = bytes;
state.collect();
self.inner.notify();
Ok(())
}
pub fn projected(&self) -> &[StoredRecord] {
&self.records
}
pub async fn push(&mut self, record: RecordWrite<'_>, limits: ReadLimits) -> Result<()> {
let invalid = || Error::InvalidInput { field: "record projection", reason: "capture limit exceeded" };
if self.records.len() >= limits.rows.get() {
return Err(invalid());
}
let capture = encoded_size(&record)?
.checked_add(encoded_size(&self.first_record.history)?)
.and_then(|bytes| bytes.checked_add(ENTRY_OVERHEAD))
.ok_or_else(invalid)?;
let capture_total = self.capture_bytes.checked_add(capture).ok_or_else(invalid)?;
let bytes =
record.resident_bytes(&self.first_record.history)?.checked_add(std::mem::size_of::<StoredRecord>() * 3).ok_or_else(invalid)?;
let total = self.record_bytes.checked_add(bytes).ok_or_else(invalid)?;
if capture_total > limits.bytes.get()
|| self.bytes.checked_add(total).is_none_or(|total| total > self.inner.settings.request.max_pending_write_bytes.get())
{
return Err(invalid());
}
let mut position = self.first_record.clone();
position.sequence = position.sequence.checked_add(self.records.len() as u64).ok_or_else(invalid)?;
let capacity = self.inner.settings.request.max_pending_write_bytes.get();
self.inner
.admit_when(|state| {
super::super::capacity::admitting(state, super::super::capacity::Pool::Write)?;
state.collect();
if state.status.pending_bytes <= capacity - bytes {
state.reserved += bytes;
state.collect();
return Ok(Some(()));
}
let held = state.status.pending_bytes.saturating_sub(state.queue.bytes()).saturating_sub(state.scratch_bytes);
if held > capacity - bytes {
return Err(invalid());
}
self.inner.relieve_pressure(state, bytes, 0);
Ok(None)
})
.await?;
self.records.push(record.stored(position));
self.record_bytes = total;
self.capture_bytes = capture_total;
Ok(())
}
pub fn encode(&mut self, value: &impl serde::Serialize) -> Result<()> {
self.payload =
serde_json::to_vec(value).map_err(|_| Error::InvalidInput { field: "managed recovery", reason: "cannot encode group" })?;
let bytes = recovery_resident_bytes(self.payload.capacity(), self.inner.recovery.history(), self.inner.primary.history())?;
if bytes > self.bytes {
return Err(Error::InvalidInput { field: "managed recovery", reason: "encoded allocation exceeds reservation" });
}
self.bytes = bytes;
let mut state = self.inner.state.lock().unwrap();
state.reserved =
self.bytes.checked_add(self.record_bytes).ok_or(Error::InvalidInput { field: "managed recovery", reason: "size overflow" })?;
state.reserved_batches = 1 + usize::from(!self.records.is_empty());
state.collect();
self.inner.notify();
Ok(())
}
pub fn commit(mut self, frontier: Position) {
let mut state = self.inner.state.lock().unwrap();
let required_data = if self.snapshot {
state.journal_required_data.clone()
} else {
self.records.last().map_or_else(|| state.status.saved.clone(), |record| record.position.clone())
};
if required_data.sequence > state.journal_required_data.sequence {
state.journal_required_data = required_data.clone();
}
if let Some(last) = self.records.last() {
state.status.accepted = last.position.clone();
state.data_accepted = Target { index: self.target.index, sequence: last.position.sequence };
state.queue.push_back(Arc::new(Pending {
index: self.target.index,
bytes: self.record_bytes,
journal: None,
operation: Operation::Records(std::mem::take(&mut self.records)),
}));
self.target.index += 1; }
let position =
crate::storage::RecoveryPosition { history: state.status.recovery_accepted.history.clone(), sequence: self.target.sequence };
state.status.recovery_accepted = position.clone();
state.recovery_accepted = self.target;
state.next = self.target.index;
state.queue.push_back(Arc::new(Pending {
index: self.target.index,
bytes: self.bytes,
journal: (!self.snapshot).then_some(frontier),
operation: if self.snapshot {
Operation::Snapshot(RecoverySnapshot {
required_data: required_data.clone(),
through: position,
payload: std::mem::take(&mut self.payload),
})
} else {
Operation::Recovery(vec![RecoveryEntry { required_data, position, payload: std::mem::take(&mut self.payload) }])
},
}));
state.reserved = 0;
state.reserved_batches = 0;
self.active = false;
state.collect();
self.inner.notify();
}
}
impl Drop for Permit {
fn drop(&mut self) {
if self.active {
let mut state = self.inner.state.lock().unwrap();
state.reserved = 0;
state.reserved_batches = 0;
state.collect();
self.inner.notify();
}
}
}