Skip to main content

bacnet_objects/notification_class/
saving.rs

1//! When a Notification Class saves its Recipient_List (Clause 12.21.8,
2//! #1315).
3//!
4//! A class built with [`NotificationClass::with_persistence`] saves a written
5//! Recipient_List before serving it, and refuses a write it cannot save with
6//! DEVICE / OPERATIONAL_PROBLEM, keeping the old list. This is the
7//! Notification Forwarder's scheme ([`crate::durable`]), shared with it
8//! through `StagedSaves`: the bundled server stages the write, the save runs
9//! on the class's writer thread while the database guard is dropped, and the
10//! write then takes the saved list without saving again. A
11//! WritePropertyMultiple that writes the list more than once stages one
12//! save of the last (#1423). A write that was not staged queues its save and
13//! waits for it where it is.
14//!
15//! A staged write its request releases without making is dropped, and the
16//! class at once queues a save of the list it serves, so storage goes back to
17//! that list. A staged write whose request vanished without releasing it, as
18//! when `stop()` aborts a request or an application drops a local write's
19//! future, is dropped the same way once
20//! [`STAGED_WRITE_LIFETIME`](crate::durable::STAGED_WRITE_LIFETIME) has
21//! passed since its save finished: by the next write that stages, or by the
22//! server's once-a-second operation task, which calls
23//! `advance_monotonic_time_internal`. The class saves nothing else between
24//! writes.
25//!
26//! Neither runs once the server has stopped. So `stop()`, once it has
27//! joined its requests, drops a staged write that is still held and waits
28//! for the save of the served list, and a class dropped with one still
29//! held saves the served list as it goes, unless the staged save failed
30//! (#1363). Either way a restart serves the list the class served.
31//!
32//! A written list wins over the destinations the application configures:
33//! once a write has set the list and it was saved, a rebuilt class serves the
34//! saved list, and [`add_destination`](NotificationClass::add_destination)
35//! checks a destination without adding it. The configured destinations are
36//! never saved.
37
38use crate::prelude::*;
39use alloc::sync::Arc;
40use core::time::Duration;
41
42use bacnet_types::constructed::BACnetDestination;
43use bacnet_types::enums::{ErrorClass, ErrorCode, PropertyIdentifier};
44use bacnet_types::error::Error;
45use bacnet_types::primitives::PropertyValue;
46
47use super::persistence::{NotificationClassPersistence, NotificationClassSnapshot};
48use super::{MAX_RECIPIENT_LIST_DESTINATIONS, NotificationClass, recipient_list};
49use crate::common;
50use crate::durable::staged::{self, StagedSaves};
51use crate::durable::{DurableWrites, PendingWrite, SaveWait, SaveWriter, StageStep};
52
53/// A class's writer and the Recipient_List write staged on it.
54pub(super) type Storage = StagedSaves<NotificationClassSnapshot, Vec<BACnetDestination>>;
55
56impl NotificationClass {
57    /// A Notification Class that keeps a written Recipient_List in
58    /// `persistence`, starting from the list saved there for this object, if
59    /// any. Its Notification_Class number defaults to the instance number,
60    /// as with [`new`](Self::new).
61    ///
62    /// Fails when the saved list cannot be loaded, or holds what a write
63    /// would refuse: more than [`MAX_RECIPIENT_LIST_DESTINATIONS`]
64    /// destinations, or an address recipient whose MAC is too long.
65    pub fn with_persistence(
66        instance: u32,
67        name: impl Into<String>,
68        persistence: Arc<dyn NotificationClassPersistence>,
69    ) -> Result<Self, Error> {
70        let mut class = Self::new(instance, name)?;
71        let oid = class.oid;
72        if let Some(list) = persistence
73            .load(oid)?
74            .and_then(|saved| saved.recipient_list)
75        {
76            if list.len() > MAX_RECIPIENT_LIST_DESTINATIONS {
77                return Err(recipient_list::no_space_error());
78            }
79            for destination in &list {
80                recipient_list::check_added(destination)?;
81            }
82            class.recipient_list = list;
83            class.recipient_list_written = true;
84        }
85        let writer = SaveWriter::new(
86            format!("bacnet-nc-{instance}-save"),
87            move |snapshot: &NotificationClassSnapshot| persistence.save(oid, snapshot),
88            move |_, result| {
89                if let Err(error) = result {
90                    log_warn!(
91                        class = %oid,
92                        %error,
93                        "Failed to save a Notification Class Recipient_List"
94                    );
95                }
96            },
97        );
98        class.storage = Some(StagedSaves::new(writer));
99        Ok(class)
100    }
101
102    /// Whether storage holds a Recipient_List a write set, now or before a
103    /// restart. [`add_destination`](Self::add_destination) then leaves the
104    /// list alone. Always false without persistence.
105    pub fn recipient_list_saved(&self) -> bool {
106        self.storage.is_some() && self.recipient_list_written
107    }
108
109    /// Block until the saves queued so far have run. Dropping the class
110    /// waits for them too.
111    pub fn wait_for_saves(&self) {
112        if let Some(storage) = &self.storage {
113            storage.wait_idle();
114        }
115    }
116
117    /// What storage holds for the list the class serves.
118    fn snapshot(&self) -> NotificationClassSnapshot {
119        NotificationClassSnapshot {
120            recipient_list: self
121                .recipient_list_written
122                .then(|| self.recipient_list.clone()),
123        }
124    }
125
126    fn install(&mut self, list: Vec<BACnetDestination>) {
127        self.recipient_list = list;
128        self.recipient_list_written = true;
129        self.list_writes = self.list_writes.wrapping_add(1);
130    }
131
132    /// Replace Recipient_List with a written list, saving it before the class
133    /// serves it. A staged write takes the list already saved; any other
134    /// saves now. A list that cannot be saved is refused with DEVICE /
135    /// OPERATIONAL_PROBLEM and the old list stays.
136    pub(super) fn write_recipient_list(&mut self, value: PropertyValue) -> Result<(), Error> {
137        let refused =
138            |_| common::protocol_error(ErrorClass::DEVICE, ErrorCode::OPERATIONAL_PROBLEM);
139        if let Some(taken) = self.take_staged(&value) {
140            return taken.map_err(refused);
141        }
142        // A list the class refuses leaves a write staged for another
143        // request alone (#1424).
144        let next = recipient_list::decode_write(value)?;
145        if self.storage.is_some() {
146            let mut snapshot = self.snapshot();
147            apply(&mut snapshot, &next);
148            // This write will be made, so it supersedes a staged one: storage
149            // goes back to the served list ahead of its save.
150            self.with_storage(Storage::drop_staged);
151            let storage = self.storage.as_mut().expect("checked above");
152            storage.save_now(snapshot).map_err(refused)?;
153        }
154        self.install(next);
155        Ok(())
156    }
157
158    /// Take the staged step for a Recipient_List write of `value`, if it is
159    /// the next one: the class then serves its list, or the save's error
160    /// comes back. `None` when nothing staged is this write.
161    fn take_staged(&mut self, value: &PropertyValue) -> Option<Result<(), Error>> {
162        let mut storage = self.storage.take()?;
163        let base = self.list_writes;
164        let taken = storage
165            .claim(PropertyIdentifier::RECIPIENT_LIST, None, value, base)
166            .map(|claimed| claimed.map(|list| self.install(list)));
167        // Steps the request has still to take keep the list served now.
168        storage.correct(|| self.snapshot());
169        self.storage = Some(storage);
170        taken
171    }
172
173    /// From the server's operation task at monotonic `now`: drop a staged
174    /// write whose request is gone and queue the save of the served list
175    /// (see the module docs). The save runs on the writer thread.
176    pub(super) fn expire_staged_write(&mut self, now: Duration) {
177        self.with_storage(|storage| storage.expire(now));
178    }
179
180    /// Run `f` on storage; then, if `f` dropped a staged write, queue the
181    /// save that puts storage back to the served list. `None` without
182    /// persistence.
183    fn with_storage<R>(&mut self, f: impl FnOnce(&mut Storage) -> R) -> Option<R> {
184        let mut storage = self.storage.take()?;
185        let result = f(&mut storage);
186        storage.correct(|| self.snapshot());
187        self.storage = Some(storage);
188        Some(result)
189    }
190}
191
192/// Put a written Recipient_List into `snapshot`.
193fn apply(snapshot: &mut NotificationClassSnapshot, list: &[BACnetDestination]) {
194    snapshot.recipient_list = Some(list.to_vec());
195}
196
197impl DurableWrites for NotificationClass {
198    fn stage_write(
199        &mut self,
200        property: PropertyIdentifier,
201        array_index: Option<u32>,
202        value: &PropertyValue,
203    ) -> StageStep {
204        self.stage_writes(&[PendingWrite {
205            property,
206            array_index,
207            value: value.clone(),
208        }])
209    }
210
211    /// Fold the request's Recipient_List writes into one staged save
212    /// (#1423); the list each leaves replaces the one before it.
213    fn stage_writes(&mut self, writes: &[PendingWrite]) -> StageStep {
214        let listed = |write: &PendingWrite| write.property == PropertyIdentifier::RECIPIENT_LIST;
215        if self.storage.is_none() || !writes.iter().any(listed) {
216            return StageStep::Skip;
217        }
218        if let Some(wait) = self.with_storage(Storage::busy).flatten() {
219            return StageStep::Busy(wait);
220        }
221        let steps = staged::steps(writes, |_, write| {
222            if !listed(write) {
223                return Ok(None);
224            }
225            if write.array_index.is_some() {
226                return Err(common::property_is_not_an_array_error());
227            }
228            recipient_list::decode_write(write.value.clone()).map(Some)
229        });
230        let served = self.snapshot();
231        let base = self.list_writes;
232        let storage = self.storage.as_mut().expect("checked above");
233        storage.stage(steps, base, served, |snapshot, list| apply(snapshot, list))
234    }
235
236    fn release_staged_write(&mut self, staged: &SaveWait) {
237        self.with_storage(|storage| storage.release(staged));
238    }
239
240    fn has_staged_write(&self) -> bool {
241        self.storage.as_ref().is_some_and(Storage::is_staged)
242    }
243
244    fn settle_forgotten_writes(&mut self) -> Option<SaveWait> {
245        let mut storage = self.storage.take()?;
246        let wait = storage.drop_forgotten(|| self.snapshot());
247        self.storage = Some(storage);
248        Some(wait)
249    }
250}