1mod probe;
4#[cfg(test)]
5mod tests;
6mod transport;
7
8use crate::types::SessionId;
9use serde::{Deserialize, Serialize};
10use std::{
11 collections::{BTreeMap, BTreeSet},
12 path::Path,
13 sync::{Arc, Mutex, OnceLock, Weak},
14 time::Duration,
15};
16use tokio::{
17 io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt},
18 sync::{Notify, broadcast, oneshot},
19 task::JoinSet,
20};
21use tokio_util::sync::CancellationToken;
22use transport::Endpoint;
23
24pub const RECONCILE: Duration = Duration::from_secs(5);
26const RECONNECT: Duration = Duration::from_millis(500);
28const DELIVERY_TIMEOUT: Duration = Duration::from_millis(300);
30const MAX_FRAME_BYTES: usize = 1024;
32const MAX_STATES: usize = 32;
34const MAX_CLIENTS: usize = 64;
35const MAX_BACKLOG: usize = 64;
36const MAX_DIRTY_SESSIONS: usize = 64;
37const VERSION: u32 = 1;
41
42#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
44pub enum Change {
45 StateInvalidated,
46 SessionInvalidated(SessionId),
47 Resync,
49}
50
51enum Dirty {
53 Pending {
54 state: bool,
55 sessions: BTreeSet<SessionId>,
56 },
57 Resync,
58}
59impl Default for Dirty {
60 fn default() -> Self {
61 Self::Pending {
62 state: false,
63 sessions: BTreeSet::new(),
64 }
65 }
66}
67impl Dirty {
68 fn add(&mut self, change: Change) {
69 let Self::Pending { state, sessions } = self else {
70 return;
71 };
72 match change {
73 Change::Resync => *self = Self::Resync,
74 Change::StateInvalidated => *state = true,
75 Change::SessionInvalidated(id) => {
76 sessions.insert(id);
77 if sessions.len() > MAX_DIRTY_SESSIONS {
78 *self = Self::Resync;
79 }
80 }
81 }
82 }
83 fn pop(&mut self) -> Option<Change> {
84 match self {
85 Self::Resync => {
86 *self = Self::default();
87 Some(Change::Resync)
88 }
89 Self::Pending { state, sessions } => {
90 if std::mem::take(state) {
91 Some(Change::StateInvalidated)
92 } else {
93 sessions.pop_first().map(Change::SessionInvalidated)
94 }
95 }
96 }
97 }
98 fn is_empty(&self) -> bool {
99 matches!(self, Self::Pending { state: false, sessions } if sessions.is_empty())
100 }
101}
102struct Inbox {
103 #[cfg(test)]
104 registration: tokio::sync::watch::Sender<Option<crate::config::HubEpoch>>,
105 dirty: Mutex<Dirty>,
106 wake: Notify,
107}
108impl Default for Inbox {
109 fn default() -> Self {
110 Self {
111 #[cfg(test)]
112 registration: tokio::sync::watch::channel(None).0,
113 dirty: Mutex::default(),
114 wake: Notify::new(),
115 }
116 }
117}
118impl Inbox {
119 fn add(&self, change: Change) {
120 self.dirty.lock().unwrap().add(change);
121 self.wake.notify_one();
122 }
123 async fn next(&self) -> Change {
124 loop {
125 let wake = self.wake.notified();
127 if let Some(change) = self.dirty.lock().unwrap().pop() {
128 return change;
129 }
130 wake.await;
131 }
132 }
133}
134
135pub struct Subscription {
138 inbox: Arc<Inbox>,
139 cancel: CancellationToken,
140}
141impl Subscription {
142 pub async fn new(state: &Path) -> Self {
143 let inbox = Arc::new(Inbox::default());
144 let cancel = CancellationToken::new();
145 let (ready, registered) = oneshot::channel();
146 let state = state.to_path_buf();
147 tokio::spawn(watch(state, inbox.clone(), cancel.clone(), ready));
148 let subscription = Self { inbox, cancel };
150 let _ = registered.await;
152 subscription
153 }
154 pub async fn next(&mut self) -> Change {
155 self.inbox.next().await
156 }
157 pub async fn next_state(&mut self) -> Change {
159 loop {
160 let change = self.next().await;
161 if !matches!(change, Change::SessionInvalidated(_)) {
162 return change;
163 }
164 }
165 }
166}
167impl Drop for Subscription {
168 fn drop(&mut self) {
169 self.cancel.cancel();
170 }
171}
172
173#[derive(Default)]
174struct Outbox {
175 dirty: Dirty,
176 submitted: u64,
177 delivered: u64,
178}
179struct Publishing {
180 endpoint: Endpoint,
181 pending: Mutex<Outbox>,
182 wake: Notify,
183 drained: Notify,
184}
185#[derive(Clone, Default)]
187pub(crate) struct Publisher(Option<Arc<Publishing>>);
188static PUBLISHERS: OnceLock<Mutex<BTreeMap<crate::config::EndpointId, Weak<Publishing>>>> =
189 OnceLock::new();
190impl Publisher {
191 pub(crate) fn new(state: &Path) -> Self {
192 let Ok(runtime) = tokio::runtime::Handle::try_current() else {
193 return Self::default();
194 };
195 let Ok(endpoint) = Endpoint::new(state) else {
196 return Self::default();
197 };
198 let mut publishers = PUBLISHERS.get_or_init(Default::default).lock().unwrap();
199 publishers.retain(|_, publisher| publisher.strong_count() != 0);
200 if let Some(publisher) = publishers.get(&endpoint.identity).and_then(Weak::upgrade) {
201 return Self(Some(publisher));
202 }
203 if publishers.len() >= MAX_STATES {
204 return Self::default();
205 }
206 let publisher = Arc::new(Publishing {
207 endpoint,
208 pending: Mutex::new(Outbox::default()),
209 wake: Notify::new(),
210 drained: Notify::new(),
211 });
212 publishers.insert(
213 publisher.endpoint.identity.clone(),
214 Arc::downgrade(&publisher),
215 );
216 runtime.spawn(publish(publisher.clone()));
217 Self(Some(publisher))
218 }
219 pub(crate) fn notify(&self, change: Change) {
220 if let Some(publisher) = &self.0 {
221 let mut pending = publisher.pending.lock().unwrap();
222 pending.dirty.add(change);
223 pending.submitted = pending.submitted.wrapping_add(1);
224 drop(pending);
225 publisher.wake.notify_one();
226 }
227 }
228}
229
230pub async fn drain() {
232 let publishers: Vec<_> = PUBLISHERS
233 .get()
234 .map(|publishers| {
235 publishers
236 .lock()
237 .unwrap()
238 .values()
239 .filter_map(Weak::upgrade)
240 .collect()
241 })
242 .unwrap_or_default();
243 let wait = async {
244 for publisher in publishers {
245 let target = publisher.pending.lock().unwrap().submitted;
246 loop {
247 let drained = publisher.drained.notified();
248 if publisher.pending.lock().unwrap().delivered >= target {
249 break;
250 }
251 drained.await;
252 }
253 }
254 };
255 let _ = tokio::time::timeout(DELIVERY_TIMEOUT, wait).await;
256}
257
258#[derive(Serialize, Deserialize)]
259enum Frame {
260 Hello {
261 version: u32,
262 identity: crate::config::EndpointId,
263 subscriber: bool,
264 },
265 Registered {
266 epoch: crate::config::HubEpoch,
267 },
268 Publish(Change),
269 Delivered,
270 Hint {
271 epoch: crate::config::HubEpoch,
272 sequence: u64,
273 change: Change,
274 },
275}
276async fn read_frame<R: AsyncRead + Unpin>(reader: &mut R) -> std::io::Result<Frame> {
277 let bytes = reader.read_u32().await? as usize;
278 if bytes == 0 || bytes > MAX_FRAME_BYTES {
279 return Err(std::io::ErrorKind::InvalidData.into());
280 }
281 let mut data = vec![0; bytes];
282 reader.read_exact(&mut data).await?;
283 serde_json::from_slice(&data).map_err(std::io::Error::other)
284}
285async fn write_frame<W: AsyncWrite + Unpin>(writer: &mut W, frame: &Frame) -> std::io::Result<()> {
286 let data = serde_json::to_vec(frame).map_err(std::io::Error::other)?;
287 if data.len() > MAX_FRAME_BYTES {
288 return Err(std::io::ErrorKind::InvalidData.into());
289 }
290 writer.write_u32(data.len() as u32).await?;
291 writer.write_all(&data).await?;
292 writer.flush().await
293}
294async fn connect(
295 endpoint: &Endpoint,
296 subscriber: bool,
297) -> std::io::Result<(transport::Stream, crate::config::HubEpoch)> {
298 let mut stream = endpoint.connect().await?;
299 write_frame(
300 &mut stream,
301 &Frame::Hello {
302 version: VERSION,
303 identity: endpoint.identity.clone(),
304 subscriber,
305 },
306 )
307 .await?;
308 match read_frame(&mut stream).await? {
309 Frame::Registered { epoch } if epoch.len() <= crate::types::MAX_ID_BYTES => {
310 Ok((stream, epoch))
311 }
312 _ => Err(std::io::ErrorKind::InvalidData.into()),
313 }
314}
315async fn publish(publisher: Arc<Publishing>) {
316 loop {
317 let wake = publisher.wake.notified();
318 if publisher.pending.lock().unwrap().dirty.is_empty() {
319 tokio::select! {
320 _ = wake => {},
321 _ = tokio::time::sleep(RECONNECT) => {
322 if Arc::strong_count(&publisher) == 1 {
324 break;
325 }
326 continue;
327 }
328 }
329 }
330 let (mut dirty, target) = {
331 let mut pending = publisher.pending.lock().unwrap();
332 (std::mem::take(&mut pending.dirty), pending.submitted)
333 };
334 let delivery = async {
336 let (mut stream, _) = connect(&publisher.endpoint, false).await?;
337 while let Some(change) = dirty.pop() {
338 write_frame(&mut stream, &Frame::Publish(change)).await?;
339 if !matches!(read_frame(&mut stream).await?, Frame::Delivered) {
340 return Err(std::io::ErrorKind::InvalidData.into());
341 }
342 }
343 Ok::<_, std::io::Error>(())
344 };
345 let _ = tokio::time::timeout(DELIVERY_TIMEOUT, delivery).await;
346 publisher.pending.lock().unwrap().delivered = target;
347 publisher.drained.notify_one();
348 }
349}
350
351async fn watch(
352 state: std::path::PathBuf,
353 inbox: Arc<Inbox>,
354 cancel: CancellationToken,
355 ready: oneshot::Sender<()>,
356) {
357 let mut ready = Some(ready);
358 let endpoint = Endpoint::new(&state).ok();
359 let mut hub = None;
360 let mut probe = probe::Probe::new(state);
361 let mut reconcile =
364 tokio::time::interval_at(tokio::time::Instant::now() + RECONCILE, RECONCILE);
365 reconcile.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
366 let _ = probe.changed().await;
368 loop {
369 if cancel.is_cancelled() {
370 break;
371 }
372 if let Some(endpoint) = &endpoint {
373 if hub
374 .as_ref()
375 .is_some_and(|task: &tokio::task::JoinHandle<()>| task.is_finished())
376 {
377 hub = None;
378 }
379 if hub.is_none()
380 && let Ok(Some(owner)) = endpoint.elect()
381 && let Ok(listener) = endpoint.listen()
382 {
383 let endpoint = endpoint.clone();
384 let token = cancel.child_token();
385 hub = Some(tokio::spawn(async move {
386 let _ = serve(endpoint, listener, owner, token).await;
387 }));
388 }
389 let connection = tokio::time::timeout(DELIVERY_TIMEOUT, connect(endpoint, true));
390 tokio::select! {
391 _ = cancel.cancelled() => break,
392 result = connection => {
393 if let Ok(Ok((stream, epoch))) = result {
394 inbox.add(Change::Resync);
395 #[cfg(test)]
396 inbox.registration.send_replace(Some(epoch.clone()));
397 if let Some(ready) = ready.take() {
398 let _ = ready.send(());
399 }
400 let receive = receive(stream, epoch, inbox.clone());
401 tokio::pin!(receive);
402 loop {
403 tokio::select! {
404 _ = cancel.cancelled() => break,
405 _ = &mut receive => {
406 #[cfg(test)]
407 inbox.registration.send_replace(None);
408 inbox.add(Change::Resync);
409 break;
410 }
411 _ = reconcile.tick() => {
412 if probe.changed().await {
413 inbox.add(Change::Resync);
414 }
415 }
416 }
417 }
418 }
419 }
420 }
421 }
422 if let Some(ready) = ready.take() {
423 inbox.add(Change::Resync);
424 let _ = ready.send(());
425 }
426 tokio::select! {
427 _ = cancel.cancelled() => break,
428 _ = reconcile.tick() => {
429 let _ = probe.changed().await;
430 inbox.add(Change::Resync);
433 },
434 _ = tokio::time::sleep(RECONNECT) => {},
435 }
436 }
437 if let Some(task) = hub {
438 task.abort();
439 let _ = crate::task::joined(task.await);
440 }
441}
442async fn receive(
443 mut stream: transport::Stream,
444 epoch: crate::config::HubEpoch,
445 inbox: Arc<Inbox>,
446) -> std::io::Result<()> {
447 let mut previous = None;
448 loop {
449 match read_frame(&mut stream).await? {
450 Frame::Hint {
451 epoch: incoming,
452 sequence,
453 change,
454 } if incoming == epoch => {
455 if previous.is_some_and(|previous: u64| sequence != previous.wrapping_add(1)) {
456 inbox.add(Change::Resync);
457 }
458 previous = Some(sequence);
459 inbox.add(change);
460 }
461 _ => return Err(std::io::ErrorKind::InvalidData.into()),
462 }
463 }
464}
465
466#[derive(Clone)]
467struct Hint {
468 sequence: u64,
469 change: Change,
470}
471async fn serve(
472 endpoint: Endpoint,
473 mut listener: transport::Listener,
474 _owner: std::fs::File,
475 cancel: CancellationToken,
476) -> std::io::Result<()> {
477 let epoch: crate::config::HubEpoch = crate::agent::uuid()
478 .map_err(std::io::Error::other)?
479 .parse()
480 .expect("UUID hub epoch");
481 let (sender, _) = broadcast::channel(MAX_BACKLOG);
482 let sequence = Arc::new(Mutex::new(0_u64));
483 let mut clients = JoinSet::new();
484 loop {
485 tokio::select! {
486 _ = cancel.cancelled() => break,
487 Some(result) = clients.join_next(), if !clients.is_empty() => {
488 let _ = crate::task::joined(result);
489 },
490 result = listener.accept(), if clients.len() < MAX_CLIENTS => {
491 let stream = result?;
492 let (identity, epoch, sender, sequence) = (
493 endpoint.identity.clone(),
494 epoch.clone(),
495 sender.clone(),
496 sequence.clone(),
497 );
498 clients.spawn(async move {
499 let _ = client(stream, identity, epoch, sender, sequence).await;
500 });
501 }
502 }
503 }
504 clients.abort_all();
505 while let Some(result) = clients.join_next().await {
506 let _ = crate::task::joined(result);
507 }
508 Ok(())
509}
510async fn client(
511 mut stream: transport::Stream,
512 identity: crate::config::EndpointId,
513 epoch: crate::config::HubEpoch,
514 sender: broadcast::Sender<Hint>,
515 sequence: Arc<Mutex<u64>>,
516) -> std::io::Result<()> {
517 transport::validate_peer(&stream)?;
520 let hello = tokio::time::timeout(DELIVERY_TIMEOUT, read_frame(&mut stream)).await??;
521 let Frame::Hello {
522 version: VERSION,
523 identity: incoming,
524 subscriber,
525 } = hello
526 else {
527 return Err(std::io::ErrorKind::InvalidData.into());
528 };
529 if incoming != identity {
530 return Err(std::io::ErrorKind::PermissionDenied.into());
531 }
532 let mut receiver = sender.subscribe();
534 tokio::time::timeout(
535 DELIVERY_TIMEOUT,
536 write_frame(
537 &mut stream,
538 &Frame::Registered {
539 epoch: epoch.clone(),
540 },
541 ),
542 )
543 .await??;
544 if subscriber {
545 let (mut reader, mut writer) = tokio::io::split(stream);
546 let outgoing = async {
547 loop {
548 let hint = match receiver.recv().await {
549 Ok(hint) => hint,
550 Err(broadcast::error::RecvError::Lagged(_)) => Hint {
551 sequence: *sequence.lock().unwrap(),
552 change: Change::Resync,
553 },
554 Err(_) => return Ok::<_, std::io::Error>(()),
555 };
556 tokio::time::timeout(
558 DELIVERY_TIMEOUT,
559 write_frame(
560 &mut writer,
561 &Frame::Hint {
562 epoch: epoch.clone(),
563 sequence: hint.sequence,
564 change: hint.change,
565 },
566 ),
567 )
568 .await??;
569 }
570 };
571 tokio::select! { result = outgoing => result, _ = reader.read_u8() => Ok(()) }
572 } else {
573 loop {
574 let frame = tokio::time::timeout(DELIVERY_TIMEOUT, read_frame(&mut stream)).await??;
575 let Frame::Publish(change) = frame else {
576 return Err(std::io::ErrorKind::InvalidData.into());
577 };
578 {
579 let mut sequence = sequence.lock().unwrap();
581 *sequence = sequence.wrapping_add(1);
582 let _ = sender.send(Hint {
583 sequence: *sequence,
584 change,
585 });
586 }
587 tokio::time::timeout(
588 DELIVERY_TIMEOUT,
589 write_frame(&mut stream, &Frame::Delivered),
590 )
591 .await??;
592 }
593 }
594}