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
//! One port's bounded backlog. All four priority lanes share one capacity;
//! taking a send releases its slot before the transport starts that send.
//! Higher network priority wins, with arrival order within each lane.
//!
//! Admission, selection and closure share a short synchronous lock. No lock
//! crosses an await, and no queued send is evicted. The sole receiver uses
//! `Notify::notify_one`'s retained permit, so admission or the last sender's
//! drop between checking the queue and awaiting cannot lose a wakeup.
use bacnet_npdu::NetworkPriority;
use std::collections::VecDeque;
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
use tokio::sync::Notify;
use tokio::sync::mpsc::error::{TryRecvError, TrySendError};
use bacnet_router_core::router::Backlog;
use super::SendRequest;
use super::engine::EngineGuard;
pub(super) const CAPACITY: usize = 256;
struct State {
lanes: [VecDeque<SendRequest>; 4],
len: usize,
capacity: usize,
senders: usize,
receiver: bool,
/// The engine counts the port as congested, so it wants to hear the
/// backlog as it drains too (#1697).
watched: bool,
}
struct Shared {
state: Mutex<State>,
ready: Notify,
}
impl Shared {
fn lock(&self) -> MutexGuard<'_, State> {
self.state.lock().unwrap_or_else(PoisonError::into_inner)
}
fn backlog(&self) -> Backlog {
let state = self.lock();
backlog(&state)
}
fn set_watched(&self, watched: bool) {
self.lock().watched = watched;
}
}
/// The queue's backlog as the engine counts it. A queue holds far fewer
/// than `u32::MAX` sends.
fn backlog(state: &State) -> Backlog {
let count = |n: usize| u32::try_from(n).unwrap_or(u32::MAX);
Backlog::new(count(state.len), count(state.capacity))
}
pub(super) struct Sender(Arc<Shared>);
pub(super) struct Receiver(Arc<Shared>);
pub(super) fn channel(capacity: usize) -> (Sender, Receiver) {
assert!(capacity > 0);
let shared = Arc::new(Shared {
state: Mutex::new(State {
lanes: std::array::from_fn(|_| VecDeque::new()),
len: 0,
capacity,
senders: 1,
receiver: true,
watched: false,
}),
ready: Notify::new(),
});
(Sender(Arc::clone(&shared)), Receiver(shared))
}
impl Sender {
/// Admit at the engine's network priority.
/// Closure wins over fullness, as for the previous bounded channel.
/// Refused work is dropped; callers need only the refusal kind.
pub(super) fn try_send(
&self,
priority: NetworkPriority,
request: SendRequest,
) -> Result<(), TrySendError<()>> {
{
let mut state = self.0.lock();
if !state.receiver {
return Err(TrySendError::Closed(()));
}
if state.len == state.capacity {
return Err(TrySendError::Full(()));
}
state.lanes[usize::from(priority.to_raw())].push_back(request);
state.len += 1;
}
self.0.ready.notify_one();
Ok(())
}
/// The backlog now, to report to the engine.
pub(super) fn backlog(&self) -> Backlog {
self.0.backlog()
}
/// Whether the receiver reports the backlog as it drains.
pub(super) fn set_watched(&self, watched: bool) {
self.0.set_watched(watched);
}
}
impl Clone for Sender {
fn clone(&self) -> Self {
self.0.lock().senders += 1;
Self(Arc::clone(&self.0))
}
}
impl Drop for Sender {
fn drop(&mut self) {
let last = {
let mut state = self.0.lock();
state.senders -= 1;
state.senders == 0
};
if last {
self.0.ready.notify_one();
}
}
}
impl Receiver {
pub(super) fn try_recv(&mut self) -> Result<SendRequest, TryRecvError> {
let mut state = self.0.lock();
for lane in state.lanes.iter_mut().rev() {
if let Some(request) = lane.pop_front() {
state.len -= 1;
return Ok(request);
}
}
Err(if state.senders == 0 {
TryRecvError::Disconnected
} else {
TryRecvError::Empty
})
}
/// Whether the engine wants to hear the backlog as the queue drains:
/// a cheap look, before the engine's lock is worth taking (#1697).
pub(super) fn is_watched(&self) -> bool {
self.0.lock().watched
}
/// The backlog now, when the engine still wants to hear it. It takes
/// the engine's guard so it can only be read under the engine's lock:
/// read before it, a drain on another task could refill the queue
/// between the read and the report, and the engine would hear a stale
/// low backlog, announcing a full port available. Under the lock, every
/// admission is reported after this one, so the engine always hears
/// the latest. Engine, then queue: the order `drain` takes them in.
pub(super) fn watched_backlog(&self, _engine: &EngineGuard<'_>) -> Option<Backlog> {
let state = self.0.lock();
state.watched.then(|| backlog(&state))
}
/// Whether to keep reporting the backlog as it drains.
pub(super) fn set_watched(&self, watched: bool) {
self.0.set_watched(watched);
}
/// Cancellation while waiting consumes no send. Only this receiver can
/// wait, and its next call checks the queue even after a notification.
pub(super) async fn recv(&mut self) -> Option<SendRequest> {
// Like Tokio's bounded receiver, cooperate even if the queue and
// transport stay ready. Yield before selecting a send, not after
// removing one that a higher-priority arrival could overtake.
tokio::task::consume_budget().await;
loop {
match self.try_recv() {
Ok(request) => return Some(request),
Err(TryRecvError::Disconnected) => return None,
Err(TryRecvError::Empty) => self.0.ready.notified().await,
}
}
}
}
impl Drop for Receiver {
fn drop(&mut self) {
let discarded = {
let mut state = self.0.lock();
state.receiver = false;
state.len = 0;
std::mem::take(&mut state.lanes)
};
// Release payloads even while senders still own the shared state.
drop(discarded);
}
}
#[cfg(test)]
#[path = "send_queue_tests.rs"]
mod tests;