1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
//! Durable storage for the Trend Log, Trend Log Multiple and Event Log
//! (#1541).
//!
//! A log built with `with_persistence` (for example
//! [`TrendLogObject::with_persistence`]) reopens from the newest snapshot its
//! [`LogPersistence`] holds and then commits a fresh snapshot of itself on a
//! cadence ([`LogCommitCadence`]):
//!
//! - once Total_Record_Count has moved [`records`](LogCommitCadence::records)
//! past the count of the last commit, as a record lands;
//! - once [`interval`](LogCommitCadence::interval) has passed since the last
//! commit and anything the snapshot holds has changed, as the server's
//! once-a-second operation pass looks at the log. After a quiet spell
//! longer than the interval, the first change commits at the next pass:
//! the interval has already run out, whichever trigger comes first;
//! - once more when the server stops, and when the log is dropped, if
//! anything changed since the last commit.
//!
//! The defaults, 100 records or 60 seconds, bound what a power loss costs to
//! about one interval of records, while a log polling every second writes to
//! disk about once a minute instead of on every sample. A log that only an
//! application drives, with no server, commits by count and when dropped.
//!
//! The snapshot is taken under the database guard, which is only a copy of
//! the records and settings; the storage call runs on the log's own writer
//! thread ([`crate::durable`]), so no file I/O happens under the guard. A
//! commit queued while an earlier one is still waiting to start replaces it,
//! so a slow disk costs one write of the latest state.
//!
//! # Reopening
//!
//! The snapshot gives the log back its Buffer_Size, Enable, Stop_When_Full,
//! records and Total_Record_Count, its BUFFER_READY counting
//! (Last_Notify_Record and where Records_Since_Notification counts from), and
//! the settings a peer can write: Start_Time and Stop_Time, and on the trend
//! logs Logging_Type, Log_Interval, Align_Intervals, Interval_Offset and
//! Log_DeviceObjectProperty. Buffer_Size is the application's (peers can't
//! write it), so the one the constructor is given wins over the saved one:
//! a larger size keeps every record, and a smaller one keeps the newest
//! records that fit and logs a warning naming both sizes. A Buffer_Size
//! whose full buffer of the family's worst-case records couldn't fit a
//! slot's 256 MiB is refused.
//! Settings the application applies after opening replace the stored ones,
//! so an application that configures its logs on every start should skip
//! that for a log that [reopened](crate::trend::TrendLogObject::reopened).
//!
//! A reopened log then appends one LOG_INTERRUPTED status record, which
//! tells readers samples may be missing across the restart (Clauses
//! 12.25.14, 12.27.13 and 12.30.19). It is stamped with the device clock the
//! first time the log can read a valid one: as it joins an `ObjectDatabase`
//! with a clock, or at the next record or window look after that. A record
//! that arrives while no valid clock is bound lends the interruption its own
//! timestamp, so the record still lands after it. Calling
//! `record_interruption` first stands in for it. A log that reopens full
//! and stopped (Enable FALSE, as Stop_When_Full leaves it) skips the marker
//! rather than push out its oldest record; a stopped log with room takes it,
//! marked LOG_DISABLED too.
//!
//! Notification_Threshold comes back with the counts, since BUFFER_READY
//! resumes from them.
//!
//! # Failed commits
//!
//! A commit that fails is logged and leaves the log counted as changed, so
//! the next interval pass, or the final commit at stop or drop, tries again
//! even if nothing else changed.
//!
//! # Deleting a log
//!
//! Storage outlives the object: a log removed from the database leaves its
//! slots on disk, and one built again on the same storage reopens from them.
//! Delete the slot files to start afresh.
//!
//! [`TrendLogObject::with_persistence`]: crate::trend::TrendLogObject::with_persistence
use crate*;
use Duration;
use BACnetDeviceObjectPropertyReference;
use LoggingType;
use Error;
use ;
// The snapshot format: only the file backend reads and writes it whole.
pub
pub use FileLogPersistence;
/// The settings that say how a Trend Log or Trend Log Multiple acquires its
/// records, as a snapshot keeps them.
/// The durable state of one Trend Log (`R` = `BACnetLogRecord`), Trend Log
/// Multiple (`BACnetLogMultipleRecord`) or Event Log (`BACnetEventLogRecord`).
/// Application-owned storage for one log's [`LogSnapshot`].
///
/// The log calls `load` once, as it opens, and `commit` from its writer
/// thread, one call at a time.
/// When a durable log commits its snapshot: after `records` new records or
/// `interval` of time with a change, whichever comes first, and once more
/// as the server stops or the log is dropped.