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