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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
//! Running the writes a Command or Channel object's Present_Value write
//! queues (Clauses 12.10 and 12.53, #1150, #1151).
//!
//! The write only queues the run: under the guard that commits it, the object
//! checks the value, marks itself busy and leaves a [`CommandRun`], which the
//! server takes there and owns from then on (`crate::command_lists`). Each
//! run then goes into the server's request task set as its own task, so
//! neither the request that wrote Present_Value nor `write_local` waits for
//! it, delays included, and `stop` cancels it with the other request work.
//! Until [`CommandRunner::start`] queues that task, the writer holds the run
//! in a [`TakenRuns`], so a writer dropped after its commit ends the run
//! instead of leaving its object busy (#1324). `write_local` hands what a
//! committed write owes, its runs included, to a request task, so its
//! caller's timeout no longer drops them (#1367); `stop()` aborting that
//! task does.
//!
//! A Command's writes are made one at a time, and a Channel's local members
//! in delay order beside its remote ones (`crate::command_lists`), each local
//! write through the same [`LocalWriter`] path as `write_local`: priorities,
//! command-source tracking, audit, COV and the post-write event pass all
//! apply as for any other local write. A command or member naming another
//! device goes out as a confirmed WriteProperty through [`RemoteWriter`]
//! (#1180), which also makes the ReadProperty a Channel learns a remote
//! member's datatype with (#1342). Every run on the server shares one set of
//! queues for those requests, kept in [`NotificationTransactions`] beside the
//! invoke IDs they lease: one at a time per device, a bounded number in all
//! (`crate::command_lists::RemoteSlots`). No database guard is held across a write's
//! notifications, an outstanding remote request or a delay. The
//! object's generation guards every report back, so a run whose object was
//! replaced or reconfigured stops.
//!
//! A run let go of before it ends (cancelled by `stop()`, refused by a closed
//! task set, or unwound by a panic) ends where it stood at once when the
//! database is free: the commands it hadn't made read unsuccessful, and it
//! counts as successful only if every write was made and succeeded. Otherwise
//! it waits in the request task set, and `stop()` ends it once those tasks are
//! joined and the database is free (#1252); a panic ends it straight away.
use super::local_writes::{LocalWrite, LocalWriter};
use super::remote_writes::{RemoteWrite, RemoteWriter};
use super::request_tasks::RequestTaskSpawner;
use super::*;
use crate::command_lists::{RunHost, TakenRuns, Unfinished};
use bacnet_objects::command::{CommandRun, WriteFailure};
use bacnet_services::read_property::ReadPropertyRequest;
use bacnet_types::constructed::{BACnetActionCommand, BACnetDeviceObjectPropertyReference};
/// The server handles a run owns while it waits out post delays.
pub(super) struct CommandRunner<T: TransportPort + 'static> {
db: Arc<RwLock<ObjectDatabase>>,
network: Arc<NetworkLayer<T>>,
cov_table: Arc<RwLock<CovSubscriptionTable>>,
cov_in_flight: Arc<Semaphore>,
notification_transactions: Arc<NotificationTransactions>,
comm_state: Arc<CommState>,
learned_routers: Arc<Mutex<LearnedRouterCache>>,
device_bindings: Arc<RwLock<DeviceBindingTable>>,
event_suppressions: Arc<super::event_suppression::EventSuppressions>,
config: Arc<ServerConfig>,
tasks: RequestTaskSpawner,
}
impl<T: TransportPort + 'static> Clone for CommandRunner<T> {
fn clone(&self) -> Self {
Self {
db: Arc::clone(&self.db),
network: Arc::clone(&self.network),
cov_table: Arc::clone(&self.cov_table),
cov_in_flight: Arc::clone(&self.cov_in_flight),
notification_transactions: Arc::clone(&self.notification_transactions),
comm_state: Arc::clone(&self.comm_state),
learned_routers: Arc::clone(&self.learned_routers),
device_bindings: Arc::clone(&self.device_bindings),
event_suppressions: Arc::clone(&self.event_suppressions),
config: Arc::clone(&self.config),
tasks: self.tasks.clone(),
}
}
}
impl<T: TransportPort + 'static> CommandRunner<T> {
/// A runner over a request's handles, starting runs beside its task.
pub(super) fn new(services: &RequestServices<T>, tasks: &RequestTaskSpawner) -> Self {
Self {
db: Arc::clone(&services.db),
network: Arc::clone(&services.network),
cov_table: Arc::clone(&services.cov_table),
cov_in_flight: Arc::clone(&services.cov_in_flight),
notification_transactions: Arc::clone(&services.notification_transactions),
comm_state: Arc::clone(&services.comm_state),
learned_routers: Arc::clone(&services.learned_routers),
device_bindings: Arc::clone(&services.device_bindings),
event_suppressions: Arc::clone(&services.event_suppressions),
config: Arc::clone(&services.config),
tasks: tasks.clone(),
}
}
/// A runner over an unconfirmed request's handles, for WriteGroup.
pub(super) fn for_unconfirmed(services: &UnconfirmedServices<T>) -> Self {
Self {
db: Arc::clone(&services.db),
network: Arc::clone(&services.network),
cov_table: Arc::clone(&services.cov_table),
cov_in_flight: Arc::clone(&services.cov_in_flight),
notification_transactions: Arc::clone(&services.notification_transactions),
comm_state: Arc::clone(&services.comm_state),
learned_routers: Arc::clone(&services.learned_routers),
device_bindings: Arc::clone(&services.device_bindings),
event_suppressions: Arc::clone(&services.event_suppressions),
config: Arc::clone(&services.config),
tasks: services.tasks.clone(),
}
}
/// A runner over a running server's own handles, for `write_local`.
pub(super) fn for_server(server: &BACnetServer<T>) -> Self {
let writer = server.local_writer();
Self {
db: Arc::clone(writer.db),
network: Arc::clone(writer.network),
cov_table: Arc::clone(writer.cov_table),
cov_in_flight: Arc::clone(writer.cov_in_flight),
notification_transactions: Arc::clone(writer.notification_transactions),
comm_state: Arc::clone(writer.comm_state),
learned_routers: Arc::clone(writer.learned_routers),
device_bindings: Arc::clone(writer.device_bindings),
event_suppressions: Arc::clone(writer.event_suppressions),
config: Arc::clone(writer.config),
tasks: server.request_tasks.spawner(),
}
}
/// Start each run as its own task. A panic in a target's write path
/// ends that run as failed rather than leaving its object busy.
pub(super) fn start(&self, runs: TakenRuns) {
use futures_util::FutureExt;
runs.hand_over(|run| {
let mut queued = Queued {
runner: self.clone(),
left: Some(Unfinished::start(&run)),
};
self.tasks.spawn(async move {
let (source, generation) = (run.source, run.generation);
let execution = std::panic::AssertUnwindSafe(crate::command_lists::execute(
&queued.runner,
run,
));
// The run's own owner holds it from here.
queued.left = None;
if execution.catch_unwind().await.is_err() {
warn!(source = %source, "run panicked; ending it as failed");
queued.runner.end_panicked(source, generation).await;
}
});
});
}
/// End a run whose write panicked. Unwinding handed it to `abandoned`,
/// which ended it if the database was free and stranded it otherwise; a
/// stranded one is taken back and ended where it stood. Either way its
/// subscribers hear of the end.
async fn end_panicked(&self, source: ObjectIdentifier, generation: u64) {
if let Some(left) = self.tasks.unstrand(source, generation) {
let mut db = self.db.write().await;
if left.end(&mut db) {
self.committed(&db, source).await;
}
} else if crate::command_lists::complete(
self,
source,
generation,
Err(WriteFailure::Process),
)
.await
{
// Nothing had ended it, so `complete` did and reported it.
return;
}
self.report(source).await;
}
fn remote_writer(&self) -> RemoteWriter<'_, T> {
RemoteWriter {
network: &self.network,
transactions: &self.notification_transactions,
bindings: &self.device_bindings,
comm_state: &self.comm_state,
timeout: Duration::from_millis(self.config.cov_retry_timeout_ms),
retries: DEFAULT_APDU_RETRIES,
max_apdu: u32::from(self.config.device.max_apdu_length()),
}
}
/// The local write path over these handles.
pub(super) fn writer(&self) -> LocalWriter<'_, T> {
LocalWriter {
db: &self.db,
network: &self.network,
cov_table: &self.cov_table,
cov_in_flight: &self.cov_in_flight,
notification_transactions: &self.notification_transactions,
comm_state: &self.comm_state,
learned_routers: &self.learned_routers,
device_bindings: &self.device_bindings,
event_suppressions: &self.event_suppressions,
config: &self.config,
}
}
}
impl<T: TransportPort + 'static> RunHost for CommandRunner<T> {
fn database(&self) -> &Arc<RwLock<ObjectDatabase>> {
&self.db
}
/// Write one value through the local write path, with `run`'s object as
/// the initiating object.
async fn write(&self, run: &CommandRun, command: &BACnetActionCommand) -> Result<(), Error> {
// The value reaches the target as a WriteProperty carrying the same
// octets would, so constructed values take the shape the object
// expects.
let mut encoded = BytesMut::new();
encode_property_value(&mut encoded, &command.property_value)?;
// A read guard of its own: an object's array and list classification
// is fixed, so it still holds when the write takes its guard, which
// checks the index again (#1426).
let value = {
let db = self.db.read().await;
let object = db.get(&command.object_identifier).ok_or(Error::protocol(
ErrorClass::OBJECT,
ErrorCode::UNKNOWN_OBJECT,
))?;
handlers::gate_and_decode_write(
object,
command.property_identifier,
command.property_array_index,
&encoded,
)?
};
let runs = self
.writer()
.write(
&command.object_identifier,
LocalWrite::Property {
property: command.property_identifier,
array_index: command.property_array_index,
priority: command.priority,
},
value,
Some(crate::LocalCommandSource::Object(run.source)),
)
.await?;
// A write that starts another Command's or Channel's run starts that
// run too, unless it would close a loop.
crate::command_lists::admit(self, run, runs, |runs| self.start(runs)).await
}
/// Write one value in another device as a confirmed WriteProperty.
async fn write_remote(
&self,
device: ObjectIdentifier,
command: &BACnetActionCommand,
) -> Result<(), RemoteRequestError> {
let write = RemoteWrite::for_command(device, command)?;
self.remote_writer().write(&write).await
}
/// Read one property in another device as a confirmed ReadProperty.
async fn read_remote(
&self,
device: ObjectIdentifier,
reference: &BACnetDeviceObjectPropertyReference,
) -> Result<PropertyValue, RemoteRequestError> {
let request = ReadPropertyRequest {
object_identifier: reference.object_identifier,
property_identifier: PropertyIdentifier::from_raw(reference.property_identifier),
property_array_index: reference.property_array_index,
};
self.remote_writer().read(device, &request).await
}
/// The server's queues, shared by every run on it.
fn remote_slots(&self) -> Option<&crate::command_lists::RemoteSlots> {
Some(self.notification_transactions.run_slots())
}
/// Timestamped references capture the change under its guard (#856).
async fn committed(&self, db: &ObjectDatabase, source: ObjectIdentifier) {
let capture = self.cov_table.read().await.timed_capture(source);
capture.run(db);
}
/// Property subscribers hear of In_Process, All_Writes_Successful and
/// Write_Status.
async fn report(&self, source: ObjectIdentifier) {
BACnetServer::<T>::fire_cov_notifications(&self.writer().cov_context(), &source).await;
}
/// End the run where it stood if the database is free. Otherwise leave it
/// with the request task set for `stop()`, or, once the server is gone,
/// end it when the database frees up. Nothing is reported from here:
/// `stop()` is the usual caller, and a panic reports in [`Self::start`].
fn abandoned(&self, left: Unfinished) {
let source = left.source();
match self.db.try_write() {
Ok(mut db) => {
// The COV table comes after the database in lock order, so a
// busy one only costs the timestamped capture.
if left.end(&mut db)
&& let Ok(table) = self.cov_table.try_read()
{
table.timed_capture(source).run(&db);
}
}
Err(_) => {
if let Err(left) = self.tasks.strand(left) {
crate::command_lists::end_when_free(&self.db, left);
}
}
}
}
}
/// A run taken for a task that hasn't started it yet. Dropping it first (a
/// task set that refused the task, or an abort before its first poll) hands
/// the run to [`RunHost::abandoned`], so its object isn't left busy.
struct Queued<T: TransportPort + 'static> {
runner: CommandRunner<T>,
left: Option<Unfinished>,
}
impl<T: TransportPort + 'static> Drop for Queued<T> {
fn drop(&mut self) {
if let Some(left) = self.left.take() {
self.runner.abandoned(left);
}
}
}