Skip to main content

bacnet_objects/
durable.rs

1//! Saving object state off the object database lock (#1270).
2//!
3//! The Audit Log, the Notification Forwarder, the Notification Class and the
4//! Access Rights object keep state in storage the application provides
5//! ([`AuditLogPersistence`], [`NotificationForwarderPersistence`],
6//! [`NotificationClassPersistence`] and [`AccessRightsPersistence`]).
7//! A storage call can be slow, since the file backends write, synchronize and
8//! rename, so none of these objects makes one while its caller holds the
9//! database's write guard. They all follow the pattern this module provides.
10//! The Trend Log, Trend Log Multiple and Event Log commit to their
11//! [`LogPersistence`] through the same writer, on a cadence rather than per
12//! request (see [`crate::log_persistence`]), so they never stage.
13//!
14//! - Under the guard, the object copies the state to keep and queues the copy
15//!   on its save writer. The writer's own thread makes the storage calls, one
16//!   at a time and in the order they were queued.
17//! - A save whose outcome decides a request is *staged*. The object sets the
18//!   state the request would leave aside and keeps serving the old one. The
19//!   server drops the guard and waits for the save's [`SaveWait`] on Tokio's
20//!   blocking pool, so a paused test clock does not jump while the writer
21//!   thread works; then, holding
22//!   the guard again, it runs the request as usual, and the object takes the
23//!   saved state, or refuses the request if the save failed. So a forwarder
24//!   list write that cannot be saved is refused and leaves the old list, and an
25//!   Audit notification is stored, and a confirmed one acknowledged, only once
26//!   its commit is durable. An application's Audit Log purge (#1238) stages
27//!   the same way, though no property is written.
28//! - A save no request waits for, such as a forwarder's lapse and minute
29//!   saves, coalesces: a queued save the thread has not started is replaced by
30//!   the newer one, so a burst costs one save of the latest state.
31//!
32//! # Storage leads the served state
33//!
34//! A staged state reaches storage before anyone can read it from the object.
35//! The storage call returns on the writer's thread, and the object serves the
36//! new state only when the request that staged it holds the guard again and
37//! takes it (or a later stage, or the object's operation task, settles it).
38//! Peers never see that gap: the request answers, and a confirmed Audit
39//! notification is acknowledged, only after the object serves the saved
40//! state. Code that watches storage directly does see it. A storage call
41//! that has returned, such as an in-memory [`AuditLogPersistence`] whose
42//! `commit` has stored a record, does not mean a read or an AuditLogQuery
43//! finds the record yet; wait on the served state, such as the log's
44//! Record_Count, instead.
45//!
46//! A request that makes several writes to one object, as a
47//! WritePropertyMultiple can, stages them as one save of the state they
48//! leave together. Each write takes its own step of it as the request makes
49//! it, so the object serves the state the last write leaves only once every
50//! write has been made, and a request that stops part way keeps the steps it
51//! took while storage goes back to that served state (#1423). Under a
52//! mutation authorizer, the server asks it about such writes before staging
53//! them, and stages only the ones it allows (#1321).
54//!
55//! Some paths still wait for a save while the guard is held. They get the
56//! same outcome through the same writer; the object just queues the save and
57//! waits for it where it is:
58//!
59//! - a write nobody staged, such as application code writing through the
60//!   database;
61//! - an in-place change to an Audit Log, such as `add_record`, which first
62//!   lets a staged commit land, waiting for it if it is still running.
63//!
64//! A write nobody staged takes the place of a staged write it doesn't match
65//! only when the object will make it: a write the object refuses leaves the
66//! staged write alone (#1424).
67//!
68//! Dropping an object waits for the saves it has queued. The server's
69//! DeleteObject therefore drops a removed object on a blocking thread after
70//! releasing the guard, and a server dropped without `stop()` in async code
71//! drops its database there too (#1409); application code that removes an
72//! object, or drops the last handle on the database, should do the same.
73//! `bacnet_server::server::drop_database_off_runtime` lets go of a handle on
74//! the server's database that way, as the server's own detached tasks do
75//! (#1513).
76//! An object dropped with a write still staged for a request that never
77//! came back first saves the state it serves, so storage never keeps a
78//! state no client was told about, and waits for that save too (#1363).
79//! The server's `stop()` settles such writes once it has joined its
80//! requests ([`DurableWrites::settle_forgotten_writes`]) and waits for
81//! their saves, so a database dropped after a stop has nothing more to
82//! save. A server dropped without `stop()` returns before those saves land.
83//!
84//! The writer's thread is a plain `std` thread with no Tokio runtime, so a
85//! storage implementation that needs one brings its own handle. A storage
86//! call that panics fails that save. An object starts the thread with its
87//! first save and keeps it, parked while idle, until the object is dropped:
88//! one thread per object that has saved.
89//!
90//! Without std there are no threads: each save runs on the caller's thread
91//! before the call that queued it returns, so every wait is already over
92//! (#1615). The staging above works the same way; only the storage call now
93//! runs under the caller's guard.
94//!
95//! # Adding an object
96//!
97//! Any other object that keeps a written state in storage reuses this module
98//! instead of saving under the guard:
99//!
100//! 1. Own a `staged::StagedSaves` over a `SaveWriter` of a snapshot of the
101//!    state to keep. It stages one save of the state a request's writes
102//!    leave (`staged::steps` works out each write's step), hands each write
103//!    its step or the save's error, and drops a staged write its request
104//!    never made, leaving the object to save its served state at once
105//!    (`StagedSaves::correct`), or as the object drops. A write nobody
106//!    staged saves with `StagedSaves::save_now`, once the object knows it
107//!    will make it, and a save nobody waits for coalesces through
108//!    `StagedSaves::submit_coalescing`.
109//! 2. Implement [`DurableWrites`] for the object, including
110//!    `settle_forgotten_writes` (`StagedSaves::drop_forgotten`), and return
111//!    it from `BACnetObject::durable_writes_internal`.
112//! 3. Add the object type, and the properties it saves, to `may_save` in the
113//!    server's `durable_writes` module. The server stages only the writes
114//!    listed there; any other write saves in place.
115//! 4. Give a file backend a `file::ObjectFile`, which tags the file with
116//!    the format and the object identifier, caps its size, and replaces it
117//!    through a synchronized temporary file, a rename and `sync_parent_dir`.
118//!
119//! The Notification Forwarder's `saving` module is the full example: a list
120//! write stages, and the operation task's saves coalesce. The Notification
121//! Class's is the smallest: a Recipient_List write stages, and nothing else
122//! saves. The Access Rights object's stages indexed array writes too, keyed
123//! by the index as well as the value.
124//!
125//! [`AuditLogPersistence`]: crate::audit::AuditLogPersistence
126//! [`NotificationForwarderPersistence`]: crate::notification_forwarder::NotificationForwarderPersistence
127//! [`NotificationClassPersistence`]: crate::notification_class::NotificationClassPersistence
128//! [`AccessRightsPersistence`]: crate::access_control::AccessRightsPersistence
129//! [`LogPersistence`]: crate::log_persistence::LogPersistence
130
131use alloc::sync::Arc;
132use core::future::Future;
133use core::pin::Pin;
134use core::task::{Context, Poll, Waker};
135#[cfg(feature = "std")]
136use std::path::Path;
137#[cfg(feature = "std")]
138use std::sync::OnceLock;
139#[cfg(feature = "std")]
140use std::time::Instant;
141
142use bacnet_types::enums::{ErrorClass, ErrorCode, PropertyIdentifier};
143use bacnet_types::error::Error;
144use bacnet_types::primitives::PropertyValue;
145
146use crate::prelude::*;
147use crate::sync::Mutex;
148
149#[cfg(feature = "std")]
150pub(crate) mod file;
151// Without std there are no threads, so each save runs inline, on the
152// caller's (#1615).
153#[cfg(not(feature = "std"))]
154mod inline;
155pub(crate) mod staged;
156#[cfg(feature = "std")]
157mod thread;
158
159#[cfg(not(feature = "std"))]
160pub(crate) use inline::SaveWriter;
161#[cfg(feature = "std")]
162pub(crate) use thread::SaveWriter;
163
164/// How long a staged write may wait for its request once its save has run.
165/// It counts from the end of the save, so a slow save never uses it up. Past
166/// it the request is taken to be gone, and the object drops the staged state
167/// at the next stage or operation task call.
168#[cfg(not(test))]
169pub(crate) const STAGED_WRITE_LIFETIME: core::time::Duration = core::time::Duration::from_secs(10);
170/// Short in tests, so one can watch a forgotten staged write go.
171#[cfg(test)]
172pub(crate) const STAGED_WRITE_LIFETIME: core::time::Duration =
173    core::time::Duration::from_millis(300);
174
175/// Something that happens once, which any number of threads and tasks can
176/// wait for.
177#[derive(Default)]
178pub(crate) struct Event {
179    state: Mutex<EventState>,
180    #[cfg(feature = "std")]
181    happened: std::sync::Condvar,
182}
183
184#[derive(Default)]
185struct EventState {
186    done: bool,
187    wakers: Vec<Waker>,
188}
189
190impl Event {
191    /// Mark the event as happened and wake every waiter.
192    pub(crate) fn set(&self) {
193        let wakers = {
194            let mut state = self.state.lock();
195            state.done = true;
196            core::mem::take(&mut state.wakers)
197        };
198        #[cfg(feature = "std")]
199        self.happened.notify_all();
200        for waker in wakers {
201            waker.wake();
202        }
203    }
204
205    pub(crate) fn is_set(&self) -> bool {
206        self.state.lock().done
207    }
208
209    #[cfg(feature = "std")]
210    fn wait_blocking(&self) {
211        let mut state = self.state.lock();
212        while !state.done {
213            state = self
214                .happened
215                .wait(state)
216                .unwrap_or_else(std::sync::PoisonError::into_inner);
217        }
218    }
219
220    /// Without std nothing can park the thread, so this spins. A save runs
221    /// on the caller's thread there, so a save's event is set before anyone
222    /// can wait on it.
223    #[cfg(not(feature = "std"))]
224    fn wait_blocking(&self) {
225        while !self.is_set() {
226            core::hint::spin_loop();
227        }
228    }
229}
230
231/// A future that resolves once a save has run, or once a staged write that
232/// held an object has gone, whichever it was handed out for.
233///
234/// It holds no lock and borrows nothing, so it can be awaited after the
235/// database guard is dropped.
236#[must_use = "a SaveWait does nothing unless awaited"]
237#[derive(Clone)]
238pub struct SaveWait {
239    event: Arc<Event>,
240    /// A second wait this one also ends after, when an object that keeps
241    /// two kinds of state in storage settles both ([`and`](Self::and)).
242    then: Option<Arc<SaveWait>>,
243}
244
245impl SaveWait {
246    pub(crate) fn new(event: Arc<Event>) -> Self {
247        Self { event, then: None }
248    }
249
250    /// A wait that ends once both this wait and `other` have.
251    pub(crate) fn and(self, other: SaveWait) -> SaveWait {
252        let then = match self.then {
253            Some(then) => SaveWait::clone(&then).and(other),
254            None => other,
255        };
256        SaveWait {
257            event: self.event,
258            then: Some(Arc::new(then)),
259        }
260    }
261
262    /// Whether the wait is already over.
263    pub fn is_ready(&self) -> bool {
264        self.event.is_set() && self.then.as_ref().is_none_or(|then| then.is_ready())
265    }
266
267    /// Block this thread until the wait is over, for a caller that waits
268    /// off its async runtime, such as on Tokio's blocking pool.
269    ///
270    /// Without std, await the wait instead; never block on one that isn't
271    /// over. A save's wait is over by the time anyone holds it, since the
272    /// save ran on the caller's thread, but [`StageStep::Busy`] hands out a
273    /// wait for another request's staged write, which only that request can
274    /// end. Nothing can park the thread, so `block` would spin, and on a
275    /// single-threaded executor it would spin forever. A debug build panics
276    /// instead; a release build spins, for firmware that releases the write
277    /// from another core or an interrupt.
278    pub fn block(&self) {
279        #[cfg(not(feature = "std"))]
280        debug_assert!(
281            self.is_ready(),
282            "SaveWait::block without std on a wait that isn't over: await it instead"
283        );
284        self.event.wait_blocking();
285        if let Some(then) = &self.then {
286            then.block();
287        }
288    }
289}
290
291impl core::fmt::Debug for SaveWait {
292    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
293        f.debug_struct("SaveWait")
294            .field("ready", &self.is_ready())
295            .finish()
296    }
297}
298
299impl Future for SaveWait {
300    type Output = ();
301
302    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
303        {
304            let mut state = self.event.state.lock();
305            if !state.done {
306                if !state.wakers.iter().any(|waker| waker.will_wake(cx.waker())) {
307                    state.wakers.push(cx.waker().clone());
308                }
309                return Poll::Pending;
310            }
311        }
312        match &self.then {
313            Some(then) => Pin::new(&mut SaveWait::clone(then)).poll(cx),
314            None => Poll::Ready(()),
315        }
316    }
317}
318
319/// The outcome of one queued save, filled in by the writer's thread.
320#[derive(Clone, Default)]
321pub(crate) struct SaveTicket {
322    slot: Arc<TicketSlot>,
323}
324
325#[derive(Default)]
326struct TicketSlot {
327    outcome: Mutex<Outcome>,
328    /// When the save finished; set before `done`. Only std has a clock for
329    /// it; without std a staged write expires only by the operation task's
330    /// clock ([`staged::StagedSaves::expire`]).
331    #[cfg(feature = "std")]
332    finished_at: OnceLock<Instant>,
333    done: Arc<Event>,
334}
335
336#[derive(Default)]
337enum Outcome {
338    #[default]
339    Pending,
340    Done(Result<(), Error>),
341    /// Someone took the result; whether the save succeeded.
342    Taken(bool),
343}
344
345impl SaveTicket {
346    fn complete(&self, result: Result<(), Error>) {
347        *self.slot.outcome.lock() = Outcome::Done(result);
348        #[cfg(feature = "std")]
349        let _ = self.slot.finished_at.set(Instant::now());
350        self.slot.done.set();
351    }
352
353    /// Whether the save has run.
354    pub(crate) fn is_done(&self) -> bool {
355        self.slot.done.is_set()
356    }
357
358    /// A future that resolves once the save has run.
359    pub(crate) fn wait(&self) -> SaveWait {
360        SaveWait::new(Arc::clone(&self.slot.done))
361    }
362
363    /// Whether `wait` is the one [`wait`](Self::wait) hands out for this
364    /// save, so a request releasing a staged write can show it staged it.
365    pub(crate) fn issued(&self, wait: &SaveWait) -> bool {
366        Arc::ptr_eq(&self.slot.done, &wait.event)
367    }
368
369    /// Whether, at `now`, a staged write waiting on this save has outlived
370    /// [`STAGED_WRITE_LIFETIME`]. The lifetime starts when the save finishes;
371    /// a save still running never outlives it.
372    #[cfg(feature = "std")]
373    pub(crate) fn outlived_at(&self, now: Instant) -> bool {
374        self.slot.finished_at.get().is_some_and(|finished| {
375            now.saturating_duration_since(*finished) >= STAGED_WRITE_LIFETIME
376        })
377    }
378
379    /// Whether a staged write waiting on this save has outlived
380    /// [`STAGED_WRITE_LIFETIME`] by now. Never without std, which has no
381    /// clock to tell.
382    pub(crate) fn outlived(&self) -> bool {
383        #[cfg(feature = "std")]
384        {
385            self.outlived_at(Instant::now())
386        }
387        #[cfg(not(feature = "std"))]
388        {
389            false
390        }
391    }
392
393    /// Whether the save succeeded, once it has run.
394    pub(crate) fn succeeded(&self) -> Option<bool> {
395        match &*self.slot.outcome.lock() {
396            Outcome::Pending => None,
397            Outcome::Done(result) => Some(result.is_ok()),
398            Outcome::Taken(ok) => Some(*ok),
399        }
400    }
401
402    /// Wait for the save, blocking the thread if it has not run yet, and
403    /// take its result. The result goes to one taker; a later call gets an
404    /// error that says so.
405    pub(crate) fn take_outcome(&self) -> Result<(), Error> {
406        self.slot.done.wait_blocking();
407        let mut outcome = self.slot.outcome.lock();
408        match core::mem::take(&mut *outcome) {
409            Outcome::Done(result) => {
410                *outcome = Outcome::Taken(result.is_ok());
411                result
412            }
413            Outcome::Taken(ok) => {
414                *outcome = Outcome::Taken(ok);
415                if ok {
416                    Ok(())
417                } else {
418                    Err(Error::Encoding(
419                        "save failed; its error was reported".into(),
420                    ))
421                }
422            }
423            Outcome::Pending => unreachable!("the event is set only after the outcome"),
424        }
425    }
426}
427
428/// Make a rename or a newly created file in `path`'s directory durable by
429/// synchronizing the directory.
430///
431/// Call it once the file is in place. By then the save has landed: memory
432/// takes the saved state, so storage and memory agree, and this never fails
433/// the save. A filesystem that cannot synchronize a directory (`EINVAL`,
434/// `ENOTSUP`, `EOPNOTSUPP` or `EBADF`, as some network and FUSE filesystems
435/// answer) is passed over quietly, as PostgreSQL does. Any other error is
436/// logged as a warning: the save stands, but a power loss right after it
437/// may bring back the previous file.
438///
439/// On Unix this opens the directory and calls `fsync` on it. Windows cannot
440/// open a directory through `std::fs`, so there the call does nothing: NTFS
441/// journals the rename, but a power loss right after it may still bring back
442/// the previous file.
443#[cfg(feature = "std")]
444pub(crate) fn sync_parent_dir(path: &Path) {
445    #[cfg(unix)]
446    {
447        let parent = match path.parent() {
448            Some(parent) if !parent.as_os_str().is_empty() => parent,
449            _ => Path::new("."),
450        };
451        let result = std::fs::File::open(parent).and_then(|directory| directory.sync_all());
452        if let Err(error) = result {
453            if directory_sync_unsupported(&error) {
454                log_debug!(directory = %parent.display(), %error, "Directory sync not supported here");
455            } else {
456                log_warn!(
457                    directory = %parent.display(),
458                    %error,
459                    "Could not synchronize the directory after a save; the save stands"
460                );
461            }
462        }
463    }
464    #[cfg(not(unix))]
465    let _ = path;
466}
467
468/// The CRC-32 (IEEE 802.3, reflected) of `data`, which the two-slot
469/// snapshot files end with.
470pub(crate) fn crc32(data: &[u8]) -> u32 {
471    let mut crc = !0u32;
472    for byte in data {
473        crc ^= u32::from(*byte);
474        for _ in 0..8 {
475            crc = (crc >> 1) ^ (0xedb8_8320 & (0u32.wrapping_sub(crc & 1)));
476        }
477    }
478    !crc
479}
480
481/// Whether a directory sync failed only because the filesystem cannot
482/// synchronize a directory.
483#[cfg(all(feature = "std", unix))]
484fn directory_sync_unsupported(error: &std::io::Error) -> bool {
485    // ENOTSUP and EOPNOTSUPP are one value on Linux, two on macOS.
486    error.raw_os_error().is_some_and(|code| {
487        [libc::EINVAL, libc::ENOTSUP, libc::EOPNOTSUPP, libc::EBADF].contains(&code)
488    })
489}
490
491/// What [`DurableWrites::stage_write`] gave its caller.
492#[doc(hidden)]
493#[derive(Debug)]
494pub enum StageStep {
495    /// The object queued a save of the state the write would leave, and keeps
496    /// that state aside. Await the wait without the database guard, then make
497    /// the write as usual: the object takes the saved state, or refuses the
498    /// write if the save failed. Keep a clone of the wait to release the
499    /// staged write with.
500    Staged(SaveWait),
501    /// Another request's staged write holds the object. Await the wait, then
502    /// stage again.
503    Busy(SaveWait),
504    /// Nothing to stage: the object saves nothing for this write, or will
505    /// refuse it anyway. Make the write as usual.
506    Skip,
507}
508
509/// One write a request makes to an object, as
510/// [`DurableWrites::stage_writes`] receives it.
511#[doc(hidden)]
512#[derive(Debug, Clone, PartialEq)]
513pub struct PendingWrite {
514    /// The property written.
515    pub property: PropertyIdentifier,
516    /// The array index written, if any.
517    pub array_index: Option<u32>,
518    /// The value written.
519    pub value: PropertyValue,
520}
521
522/// Writes whose new state an object saves before serving it, staged so the
523/// save runs while the database guard is dropped (#1270).
524///
525/// The bundled server stages the writes each request makes to such an
526/// object: it calls [`stage_writes`](Self::stage_writes) under the guard,
527/// awaits the save after dropping it, makes the writes under the guard
528/// again, and in that same critical section calls
529/// [`release_staged_write`](Self::release_staged_write). A write made
530/// without staging still works: the object then saves in place.
531#[doc(hidden)]
532pub trait DurableWrites {
533    /// Stage a write of `value` to `property` (at `array_index`).
534    fn stage_write(
535        &mut self,
536        property: PropertyIdentifier,
537        array_index: Option<u32>,
538        value: &PropertyValue,
539    ) -> StageStep;
540
541    /// Stage the writes one request makes to this object, in the order it
542    /// makes them; a WritePropertyMultiple can make several. The default
543    /// stages the first write the object takes through
544    /// [`stage_write`](Self::stage_write), and the request's later writes to
545    /// the object save in place. Every bundled object overrides it to fold
546    /// the writes into one save (#1423): each write then takes its own step
547    /// of that save as the request makes it.
548    fn stage_writes(&mut self, writes: &[PendingWrite]) -> StageStep {
549        for write in writes {
550            match self.stage_write(write.property, write.array_index, &write.value) {
551                StageStep::Skip => continue,
552                step => return step,
553            }
554        }
555        StageStep::Skip
556    }
557
558    /// Stage a purge of the object's records, which the application asks
559    /// for through the server (an Audit Log's, #1238). The server goes on as
560    /// for a staged write, calling [`commit_purge`](Self::commit_purge) where
561    /// it would make the write. The default has nothing to purge.
562    fn stage_purge(&mut self) -> StageStep {
563        StageStep::Skip
564    }
565
566    /// Purge the object's records: take the purge staged on the state the
567    /// object serves, if there is one, or purge in place. The default
568    /// refuses with OBJECT / OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED.
569    fn commit_purge(&mut self) -> Result<(), Error> {
570        Err(Error::protocol(
571            ErrorClass::OBJECT,
572            ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
573        ))
574    }
575
576    /// The request that staged a write is done, whether or not the write
577    /// reached the object. `staged` is the wait [`StageStep::Staged`] gave
578    /// it; a staged write some other request made since is left alone. A
579    /// staged state the write never took is dropped, and a save of the state
580    /// the object serves is queued at once, so storage follows the object
581    /// again.
582    fn release_staged_write(&mut self, staged: &SaveWait);
583
584    /// Whether the object holds something staged for a request that the
585    /// request has still to take or release. The default stages nothing.
586    fn has_staged_write(&self) -> bool {
587        false
588    }
589
590    /// No request is left to take or release what is staged, as once the
591    /// server's `stop()` has joined its requests (#1363). Settle it: a
592    /// staged write goes as a release would drop it, so storage goes back
593    /// to the served state. Returns a wait that ends once every save the
594    /// object has queued so far has run, to await off the guard, or `None`
595    /// when the object keeps nothing in storage. The default has nothing
596    /// staged.
597    fn settle_forgotten_writes(&mut self) -> Option<SaveWait> {
598        None
599    }
600}
601
602#[cfg(all(test, feature = "std"))]
603#[path = "durable/tests.rs"]
604mod tests;