tuwunel_service/sending/sender/
mod.rs1mod dispatch;
2mod netburst;
3mod response;
4mod select;
5mod split;
6#[cfg(test)]
7mod tests;
8mod wake;
9
10use std::{
11 cmp::Reverse,
12 collections::{BinaryHeap, HashMap},
13 sync::Arc,
14 time::{Duration, Instant},
15};
16
17use futures::{FutureExt, StreamExt, stream::FuturesUnordered};
18use tokio::{
19 select,
20 time::{Instant as TokioInstant, sleep_until},
21};
22use tuwunel_core::{
23 Result, implement,
24 smallvec::{SmallVec, smallvec},
25 trace,
26 utils::BoolExt,
27};
28
29use self::{
30 dispatch::{Completion, SendingFuture},
31 select::Selection,
32 split::Split,
33 wake::{arm_park_wake, arm_wake, is_armed},
34};
35use super::{Destination, Msg, SendingEvent, Service, data::QueueItem};
36
37#[derive(Debug)]
42enum TransactionStatus {
43 Pending,
45
46 Running {
48 tries: u32,
49 },
50
51 RunningForceRetry {
53 tries: u32,
54 },
55
56 Failed {
58 tries: u32,
59 last: Instant,
60 },
61
62 Retrying {
67 tries: u32,
68 },
69
70 Splitting {
72 tries: u32,
73 split: Split,
74 },
75}
76
77#[derive(Clone, Copy)]
78enum RetryAction {
79 None,
80 Force,
81}
82
83type SendingFutures<'a> = FuturesUnordered<SendingFuture<'a>>;
84type TransactionStatuses = HashMap<Destination, TransactionStatus>;
85
86type NewEvents = SmallVec<[QueueItem; 1]>;
90
91type WakeQueue = BinaryHeap<Reverse<(TokioInstant, Destination)>>;
97
98const DEQUEUE_LIMIT: usize = 48;
99
100pub const PDU_LIMIT: usize = 50;
105
106pub const EDU_LIMIT: usize = 100;
111
112#[implement(Service)]
113#[tracing::instrument(skip(self), level = "debug")]
114pub(super) async fn sender(self: Arc<Self>, id: usize) -> Result {
115 let mut statuses = TransactionStatuses::new();
117 let mut futures = SendingFutures::new();
118 let mut wakes = WakeQueue::new();
119
120 self.startup_netburst(id, &mut futures, &mut statuses, &mut wakes)
121 .boxed() .await;
123
124 self.work_loop(id, &mut futures, &mut statuses, &mut wakes)
125 .await;
126
127 if !futures.is_empty() {
128 self.finish_responses(&mut futures)
129 .boxed() .await;
131 }
132
133 Ok(())
134}
135
136#[implement(Service)]
137#[tracing::instrument(
138 name = "work",
139 level = "trace",
140 skip_all,
141 fields(
142 futures = %futures.len(),
143 statuses = %statuses.len(),
144 ),
145)]
146async fn work_loop<'a>(
147 &'a self,
148 id: usize,
149 futures: &mut SendingFutures<'a>,
150 statuses: &mut TransactionStatuses,
151 wakes: &mut WakeQueue,
152) {
153 let receiver = &self
154 .channels
155 .get(id)
156 .expect("Missing channel for sender worker")
157 .1;
158
159 while !receiver.is_closed() {
160 let next_due = wakes
161 .peek()
162 .map_or_else(TokioInstant::now, |Reverse((instant, _))| *instant);
163
164 select! {
165 Some(response) = futures.next() => {
166 self.handle_response(response, futures, statuses, wakes).await;
167 },
168 request = receiver.recv_async() => match request {
169 Ok(request) => self.handle_request(request, futures, statuses, wakes).await,
170 Err(_) => return,
171 },
172 () = sleep_until(next_due), if !wakes.is_empty() => {
173 self.drain_due_wakes(futures, statuses, wakes).await;
174 },
175 }
176 }
177}
178
179#[implement(Service)]
180#[tracing::instrument(name = "request", level = "debug", skip_all)]
181async fn handle_request<'a>(
182 &'a self,
183 msg: Msg,
184 futures: &mut SendingFutures<'a>,
185 statuses: &mut TransactionStatuses,
186 wakes: &mut WakeQueue,
187) {
188 let synthetic_badge =
189 msg.queue_id.is_empty() && matches!(&msg.event, SendingEvent::BadgeRefresh);
190
191 let new_events = match (synthetic_badge, statuses.contains_key(&msg.dest)) {
192 | (false, _) => smallvec![(msg.queue_id, msg.event)],
193 | (true, true) => NewEvents::new(),
194 | (true, false) =>
195 self.db
196 .queued_requests(&msg.dest)
197 .take(DEQUEUE_LIMIT)
198 .collect()
199 .await,
200 };
201
202 if let Ok(selection) = self
203 .select_events(&msg.dest, new_events, statuses)
204 .await
205 {
206 self.schedule_events(msg.dest, selection, futures, statuses, wakes);
207 }
208}
209
210#[implement(Service)]
211#[expect(
212 clippy::needless_pass_by_ref_mut,
213 reason = "mutable reference avoids requiring SendingFutures to be Sync"
214)]
215fn schedule_events<'a>(
216 &'a self,
217 dest: Destination,
218 selection: Selection,
219 futures: &mut SendingFutures<'a>,
220 statuses: &mut TransactionStatuses,
221 wakes: &mut WakeQueue,
222) {
223 match selection {
224 | Selection::Events(items) if items.is_empty() => {
225 statuses.remove(&dest);
226 },
227 | Selection::Events(items) => futures.push(self.send_events(dest, items, None)),
228 | Selection::Slice(items, split) =>
229 futures.push(self.send_events(dest, items, Some(split))),
230 | Selection::Parked { until } => {
231 statuses.remove(&dest);
232 arm_park_wake(wakes, dest, until);
233 },
234 | Selection::Refused { earliest_retry } if is_armed(wakes, &dest).is_false() =>
235 arm_wake(wakes, dest, earliest_retry),
236 | Selection::Refused { .. } | Selection::Busy => {},
237 }
238}
239
240#[implement(Service)]
241#[tracing::instrument(
242 name = "finish",
243 level = "info",
244 skip_all,
245 fields(
246 futures = %futures.len(),
247 ),
248)]
249async fn finish_responses<'a>(&'a self, futures: &mut SendingFutures<'a>) {
250 let timeout = Duration::from_secs(self.server.config.sender_shutdown_timeout);
251 let now = TokioInstant::now();
252 let deadline = now.checked_add(timeout).unwrap_or(now);
253
254 loop {
255 trace!(remaining = futures.len(), "Waiting for requests to complete");
256 select! {
257 () = sleep_until(deadline) => return,
258 response = futures.next() => match response {
259 Some(Completion { result: Ok(_), keys, .. }) =>
260 self.db.delete_active_requests(&keys),
261 Some(_) => {},
262 None => return,
263 },
264 }
265 }
266}