use alloc::sync::Arc;
use core::future::Future;
use core::pin::Pin;
use core::task::{Context, Poll, Waker};
#[cfg(feature = "std")]
use std::path::Path;
#[cfg(feature = "std")]
use std::sync::OnceLock;
#[cfg(feature = "std")]
use std::time::Instant;
use bacnet_types::enums::{ErrorClass, ErrorCode, PropertyIdentifier};
use bacnet_types::error::Error;
use bacnet_types::primitives::PropertyValue;
use crate::prelude::*;
use crate::sync::Mutex;
#[cfg(feature = "std")]
pub(crate) mod file;
#[cfg(not(feature = "std"))]
mod inline;
pub(crate) mod staged;
#[cfg(feature = "std")]
mod thread;
#[cfg(not(feature = "std"))]
pub(crate) use inline::SaveWriter;
#[cfg(feature = "std")]
pub(crate) use thread::SaveWriter;
#[cfg(not(test))]
pub(crate) const STAGED_WRITE_LIFETIME: core::time::Duration = core::time::Duration::from_secs(10);
#[cfg(test)]
pub(crate) const STAGED_WRITE_LIFETIME: core::time::Duration =
core::time::Duration::from_millis(300);
#[derive(Default)]
pub(crate) struct Event {
state: Mutex<EventState>,
#[cfg(feature = "std")]
happened: std::sync::Condvar,
}
#[derive(Default)]
struct EventState {
done: bool,
wakers: Vec<Waker>,
}
impl Event {
pub(crate) fn set(&self) {
let wakers = {
let mut state = self.state.lock();
state.done = true;
core::mem::take(&mut state.wakers)
};
#[cfg(feature = "std")]
self.happened.notify_all();
for waker in wakers {
waker.wake();
}
}
pub(crate) fn is_set(&self) -> bool {
self.state.lock().done
}
#[cfg(feature = "std")]
fn wait_blocking(&self) {
let mut state = self.state.lock();
while !state.done {
state = self
.happened
.wait(state)
.unwrap_or_else(std::sync::PoisonError::into_inner);
}
}
#[cfg(not(feature = "std"))]
fn wait_blocking(&self) {
while !self.is_set() {
core::hint::spin_loop();
}
}
}
#[must_use = "a SaveWait does nothing unless awaited"]
#[derive(Clone)]
pub struct SaveWait {
event: Arc<Event>,
then: Option<Arc<SaveWait>>,
}
impl SaveWait {
pub(crate) fn new(event: Arc<Event>) -> Self {
Self { event, then: None }
}
pub(crate) fn and(self, other: SaveWait) -> SaveWait {
let then = match self.then {
Some(then) => SaveWait::clone(&then).and(other),
None => other,
};
SaveWait {
event: self.event,
then: Some(Arc::new(then)),
}
}
pub fn is_ready(&self) -> bool {
self.event.is_set() && self.then.as_ref().is_none_or(|then| then.is_ready())
}
pub fn block(&self) {
#[cfg(not(feature = "std"))]
debug_assert!(
self.is_ready(),
"SaveWait::block without std on a wait that isn't over: await it instead"
);
self.event.wait_blocking();
if let Some(then) = &self.then {
then.block();
}
}
}
impl core::fmt::Debug for SaveWait {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("SaveWait")
.field("ready", &self.is_ready())
.finish()
}
}
impl Future for SaveWait {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
{
let mut state = self.event.state.lock();
if !state.done {
if !state.wakers.iter().any(|waker| waker.will_wake(cx.waker())) {
state.wakers.push(cx.waker().clone());
}
return Poll::Pending;
}
}
match &self.then {
Some(then) => Pin::new(&mut SaveWait::clone(then)).poll(cx),
None => Poll::Ready(()),
}
}
}
#[derive(Clone, Default)]
pub(crate) struct SaveTicket {
slot: Arc<TicketSlot>,
}
#[derive(Default)]
struct TicketSlot {
outcome: Mutex<Outcome>,
#[cfg(feature = "std")]
finished_at: OnceLock<Instant>,
done: Arc<Event>,
}
#[derive(Default)]
enum Outcome {
#[default]
Pending,
Done(Result<(), Error>),
Taken(bool),
}
impl SaveTicket {
fn complete(&self, result: Result<(), Error>) {
*self.slot.outcome.lock() = Outcome::Done(result);
#[cfg(feature = "std")]
let _ = self.slot.finished_at.set(Instant::now());
self.slot.done.set();
}
pub(crate) fn is_done(&self) -> bool {
self.slot.done.is_set()
}
pub(crate) fn wait(&self) -> SaveWait {
SaveWait::new(Arc::clone(&self.slot.done))
}
pub(crate) fn issued(&self, wait: &SaveWait) -> bool {
Arc::ptr_eq(&self.slot.done, &wait.event)
}
#[cfg(feature = "std")]
pub(crate) fn outlived_at(&self, now: Instant) -> bool {
self.slot.finished_at.get().is_some_and(|finished| {
now.saturating_duration_since(*finished) >= STAGED_WRITE_LIFETIME
})
}
pub(crate) fn outlived(&self) -> bool {
#[cfg(feature = "std")]
{
self.outlived_at(Instant::now())
}
#[cfg(not(feature = "std"))]
{
false
}
}
pub(crate) fn succeeded(&self) -> Option<bool> {
match &*self.slot.outcome.lock() {
Outcome::Pending => None,
Outcome::Done(result) => Some(result.is_ok()),
Outcome::Taken(ok) => Some(*ok),
}
}
pub(crate) fn take_outcome(&self) -> Result<(), Error> {
self.slot.done.wait_blocking();
let mut outcome = self.slot.outcome.lock();
match core::mem::take(&mut *outcome) {
Outcome::Done(result) => {
*outcome = Outcome::Taken(result.is_ok());
result
}
Outcome::Taken(ok) => {
*outcome = Outcome::Taken(ok);
if ok {
Ok(())
} else {
Err(Error::Encoding(
"save failed; its error was reported".into(),
))
}
}
Outcome::Pending => unreachable!("the event is set only after the outcome"),
}
}
}
#[cfg(feature = "std")]
pub(crate) fn sync_parent_dir(path: &Path) {
#[cfg(unix)]
{
let parent = match path.parent() {
Some(parent) if !parent.as_os_str().is_empty() => parent,
_ => Path::new("."),
};
let result = std::fs::File::open(parent).and_then(|directory| directory.sync_all());
if let Err(error) = result {
if directory_sync_unsupported(&error) {
log_debug!(directory = %parent.display(), %error, "Directory sync not supported here");
} else {
log_warn!(
directory = %parent.display(),
%error,
"Could not synchronize the directory after a save; the save stands"
);
}
}
}
#[cfg(not(unix))]
let _ = path;
}
pub(crate) fn crc32(data: &[u8]) -> u32 {
let mut crc = !0u32;
for byte in data {
crc ^= u32::from(*byte);
for _ in 0..8 {
crc = (crc >> 1) ^ (0xedb8_8320 & (0u32.wrapping_sub(crc & 1)));
}
}
!crc
}
#[cfg(all(feature = "std", unix))]
fn directory_sync_unsupported(error: &std::io::Error) -> bool {
error.raw_os_error().is_some_and(|code| {
[libc::EINVAL, libc::ENOTSUP, libc::EOPNOTSUPP, libc::EBADF].contains(&code)
})
}
#[doc(hidden)]
#[derive(Debug)]
pub enum StageStep {
Staged(SaveWait),
Busy(SaveWait),
Skip,
}
#[doc(hidden)]
#[derive(Debug, Clone, PartialEq)]
pub struct PendingWrite {
pub property: PropertyIdentifier,
pub array_index: Option<u32>,
pub value: PropertyValue,
}
#[doc(hidden)]
pub trait DurableWrites {
fn stage_write(
&mut self,
property: PropertyIdentifier,
array_index: Option<u32>,
value: &PropertyValue,
) -> StageStep;
fn stage_writes(&mut self, writes: &[PendingWrite]) -> StageStep {
for write in writes {
match self.stage_write(write.property, write.array_index, &write.value) {
StageStep::Skip => continue,
step => return step,
}
}
StageStep::Skip
}
fn stage_purge(&mut self) -> StageStep {
StageStep::Skip
}
fn commit_purge(&mut self) -> Result<(), Error> {
Err(Error::protocol(
ErrorClass::OBJECT,
ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
))
}
fn release_staged_write(&mut self, staged: &SaveWait);
fn has_staged_write(&self) -> bool {
false
}
fn settle_forgotten_writes(&mut self) -> Option<SaveWait> {
None
}
}
#[cfg(all(test, feature = "std"))]
#[path = "durable/tests.rs"]
mod tests;