liminal_server/server/participant/
publication.rs1use std::collections::{BTreeMap, BTreeSet};
10use std::sync::{Arc, Mutex, Weak};
11
12use liminal_protocol::wire::{
13 BindingEpoch, ConnectionIncarnation, ConversationId, ParticipantDelivery, ParticipantId,
14 ServerPush,
15};
16
17use crate::server::connection::ReadyWaker;
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub struct ParticipantOfferedProgress {
24 pub(crate) binding_epoch: BindingEpoch,
25 pub(crate) through_seq: u64,
26}
27
28#[derive(Clone, Debug, PartialEq, Eq)]
30pub struct ParticipantPublication {
31 pub(crate) participant_id: ParticipantId,
32 pub(crate) binding_epoch: BindingEpoch,
33 pub(crate) delivery: ParticipantDelivery,
34}
35
36impl ParticipantPublication {
37 #[must_use]
38 pub(crate) const fn conversation_id(&self) -> ConversationId {
39 self.delivery.conversation_id
40 }
41
42 #[must_use]
43 pub(crate) const fn delivery_seq(&self) -> u64 {
44 self.delivery.delivery_seq
45 }
46}
47
48#[derive(Clone, Copy, Debug, PartialEq, Eq)]
53pub struct ObserverPublication {
54 pub(crate) conversation_id: ConversationId,
55 pub(crate) refused_epoch: u64,
56 pub(crate) observer_progress: u64,
57}
58
59impl ObserverPublication {
60 #[must_use]
61 pub(crate) const fn into_server_push(self) -> ServerPush {
62 ServerPush::ObserverProgressed {
63 conversation_id: self.conversation_id,
64 refused_epoch: self.refused_epoch,
65 observer_progress: self.observer_progress,
66 }
67 }
68}
69
70#[derive(Clone, Debug)]
75pub struct ObserverPublicationTarget {
76 inbox: Weak<Mutex<ReadyPublications>>,
77 waker: ReadyWaker,
78}
79
80impl ObserverPublicationTarget {
81 pub(crate) fn publish(
86 &self,
87 publication: ObserverPublication,
88 ) -> Result<bool, ParticipantPublicationError> {
89 let Some(inbox) = self.inbox.upgrade() else {
90 return Ok(false);
91 };
92 let should_wake = {
93 let mut inbox = inbox
94 .lock()
95 .map_err(|_| ParticipantPublicationError::InboxPoisoned)?;
96 let replacing = inbox
97 .observer_progressed
98 .contains_key(&publication.conversation_id);
99 if !replacing {
100 let occupied = u64::try_from(inbox.observer_progressed.len()).unwrap_or(u64::MAX);
101 if occupied >= inbox.limit {
102 return Err(ParticipantPublicationError::InboxCapacity { limit: inbox.limit });
103 }
104 }
105 let was_empty = inbox.is_empty();
106 inbox
107 .observer_progressed
108 .insert(publication.conversation_id, publication);
109 was_empty
110 };
111 if should_wake {
112 self.waker.fire();
113 }
114 Ok(true)
115 }
116}
117
118#[derive(Clone, Copy, Debug, thiserror::Error)]
119pub enum ParticipantPublicationError {
120 #[error("participant publication incarnation {incarnation:?} is already registered")]
122 DuplicateRegistration {
123 incarnation: ConnectionIncarnation,
125 },
126 #[error("participant publication inbox is poisoned")]
128 InboxPoisoned,
129 #[error("participant publication inbox exceeds its signed conversation bound {limit}")]
131 InboxCapacity {
132 limit: u64,
134 },
135}
136
137#[derive(Debug)]
138struct ReadyPublications {
139 limit: u64,
140 conversations: BTreeSet<ConversationId>,
141 observer_progressed: BTreeMap<ConversationId, ObserverPublication>,
142}
143
144impl ReadyPublications {
145 fn is_empty(&self) -> bool {
146 self.conversations.is_empty() && self.observer_progressed.is_empty()
147 }
148}
149
150#[derive(Debug)]
154pub struct ReadyPublicationBatch {
155 pub(crate) conversations: Vec<ConversationId>,
156 pub(crate) observer_progressed: Vec<ObserverPublication>,
157}
158
159#[derive(Debug)]
166pub struct ParticipantPublicationInbox {
167 inner: Arc<Mutex<ReadyPublications>>,
168}
169
170impl ParticipantPublicationInbox {
171 #[must_use]
174 pub(crate) fn new(limit: u64) -> Self {
175 Self {
176 inner: Arc::new(Mutex::new(ReadyPublications {
177 limit,
178 conversations: BTreeSet::new(),
179 observer_progressed: BTreeMap::new(),
180 })),
181 }
182 }
183
184 fn weak(&self) -> Weak<Mutex<ReadyPublications>> {
185 Arc::downgrade(&self.inner)
186 }
187
188 pub(crate) fn take_ready(&self) -> Result<ReadyPublicationBatch, ParticipantPublicationError> {
190 let mut inbox = self
191 .inner
192 .lock()
193 .map_err(|_| ParticipantPublicationError::InboxPoisoned)?;
194 Ok(ReadyPublicationBatch {
195 conversations: std::mem::take(&mut inbox.conversations)
196 .into_iter()
197 .collect(),
198 observer_progressed: std::mem::take(&mut inbox.observer_progressed)
199 .into_values()
200 .collect(),
201 })
202 }
203
204 pub(crate) fn requeue(
207 &self,
208 conversations: impl IntoIterator<Item = ConversationId>,
209 ) -> Result<(), ParticipantPublicationError> {
210 let mut inbox = self
211 .inner
212 .lock()
213 .map_err(|_| ParticipantPublicationError::InboxPoisoned)?;
214 for conversation_id in conversations {
215 if inbox.conversations.contains(&conversation_id) {
216 continue;
217 }
218 let occupied = u64::try_from(inbox.conversations.len()).unwrap_or(u64::MAX);
219 if occupied >= inbox.limit {
220 return Err(ParticipantPublicationError::InboxCapacity { limit: inbox.limit });
221 }
222 inbox.conversations.insert(conversation_id);
223 }
224 drop(inbox);
225 Ok(())
226 }
227
228 pub(crate) fn requeue_observers(
232 &self,
233 publications: impl IntoIterator<Item = ObserverPublication>,
234 ) -> Result<(), ParticipantPublicationError> {
235 let mut inbox = self
236 .inner
237 .lock()
238 .map_err(|_| ParticipantPublicationError::InboxPoisoned)?;
239 for publication in publications {
240 if inbox
241 .observer_progressed
242 .contains_key(&publication.conversation_id)
243 {
244 continue;
245 }
246 let occupied = u64::try_from(inbox.observer_progressed.len()).unwrap_or(u64::MAX);
247 if occupied >= inbox.limit {
248 return Err(ParticipantPublicationError::InboxCapacity { limit: inbox.limit });
249 }
250 inbox
251 .observer_progressed
252 .insert(publication.conversation_id, publication);
253 }
254 drop(inbox);
255 Ok(())
256 }
257
258 pub(crate) fn has_pending(&self) -> Result<bool, ParticipantPublicationError> {
260 self.inner
261 .lock()
262 .map(|inbox| !inbox.is_empty())
263 .map_err(|_| ParticipantPublicationError::InboxPoisoned)
264 }
265}
266
267#[derive(Debug)]
268struct ParticipantPublicationHandle {
269 inbox: Weak<Mutex<ReadyPublications>>,
270 waker: ReadyWaker,
271}
272
273#[derive(Debug, Default)]
275pub struct ParticipantPublicationRegistry {
276 registrations: Mutex<BTreeMap<ConnectionIncarnation, ParticipantPublicationHandle>>,
277}
278
279impl ParticipantPublicationRegistry {
280 pub(crate) fn register(
283 &self,
284 incarnation: ConnectionIncarnation,
285 inbox: &ParticipantPublicationInbox,
286 waker: ReadyWaker,
287 ) -> Result<(), ParticipantPublicationError> {
288 let mut registrations = self
289 .registrations
290 .lock()
291 .map_err(|_| ParticipantPublicationError::InboxPoisoned)?;
292 if registrations
293 .get(&incarnation)
294 .is_some_and(|existing| existing.inbox.strong_count() > 0)
295 {
296 return Err(ParticipantPublicationError::DuplicateRegistration { incarnation });
297 }
298 registrations.insert(
299 incarnation,
300 ParticipantPublicationHandle {
301 inbox: inbox.weak(),
302 waker,
303 },
304 );
305 drop(registrations);
306 Ok(())
307 }
308
309 pub(crate) fn deregister(&self, incarnation: ConnectionIncarnation) {
312 if let Ok(mut registrations) = self.registrations.lock() {
313 registrations.remove(&incarnation);
314 }
315 }
316
317 pub(crate) fn observer_target(
321 &self,
322 incarnation: ConnectionIncarnation,
323 ) -> Result<Option<ObserverPublicationTarget>, ParticipantPublicationError> {
324 let registrations = self
325 .registrations
326 .lock()
327 .map_err(|_| ParticipantPublicationError::InboxPoisoned)?;
328 let target = registrations.get(&incarnation).and_then(|handle| {
329 (handle.inbox.strong_count() > 0).then(|| ObserverPublicationTarget {
330 inbox: Weak::clone(&handle.inbox),
331 waker: handle.waker.clone(),
332 })
333 });
334 drop(registrations);
335 Ok(target)
336 }
337
338 pub(crate) fn notify(
343 &self,
344 incarnation: ConnectionIncarnation,
345 conversation_id: ConversationId,
346 ) -> Result<bool, ParticipantPublicationError> {
347 let (weak_inbox, waker) = {
348 let registrations = self
349 .registrations
350 .lock()
351 .map_err(|_| ParticipantPublicationError::InboxPoisoned)?;
352 let Some(handle) = registrations.get(&incarnation) else {
353 return Ok(false);
354 };
355 let weak_inbox = Weak::clone(&handle.inbox);
356 let waker = handle.waker.clone();
357 drop(registrations);
358 (weak_inbox, waker)
359 };
360 let Some(inbox) = weak_inbox.upgrade() else {
361 self.deregister(incarnation);
362 return Ok(false);
363 };
364 let should_wake = {
365 let mut inbox = inbox
366 .lock()
367 .map_err(|_| ParticipantPublicationError::InboxPoisoned)?;
368 if inbox.conversations.contains(&conversation_id) {
369 return Ok(true);
370 }
371 let occupied = u64::try_from(inbox.conversations.len()).unwrap_or(u64::MAX);
372 if occupied >= inbox.limit {
373 return Err(ParticipantPublicationError::InboxCapacity { limit: inbox.limit });
374 }
375 let was_empty = inbox.is_empty();
376 inbox.conversations.insert(conversation_id);
377 was_empty
378 };
379 if should_wake {
380 waker.fire();
381 }
382 Ok(true)
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use std::sync::Arc;
389 use std::sync::atomic::{AtomicU64, Ordering};
390
391 use liminal_protocol::wire::ConnectionIncarnation;
392
393 use super::{ParticipantPublicationInbox, ParticipantPublicationRegistry};
394 use crate::server::connection::ReadyWaker;
395
396 #[test]
397 fn parked_connection_wakes_on_outbox_and_no_polling_occurs()
398 -> Result<(), Box<dyn std::error::Error>> {
399 let incarnation = ConnectionIncarnation::new(12, 34);
400 let wake_count = Arc::new(AtomicU64::new(0));
401 let registry = ParticipantPublicationRegistry::default();
402 let inbox = ParticipantPublicationInbox::new(3);
403 registry.register(
404 incarnation,
405 &inbox,
406 ReadyWaker::for_test(Arc::clone(&wake_count)),
407 )?;
408
409 assert!(!inbox.has_pending()?);
410 assert!(registry.notify(incarnation, 7)?);
411 assert_eq!(wake_count.load(Ordering::SeqCst), 1);
412 assert!(inbox.has_pending()?);
413
414 assert!(registry.notify(incarnation, 7)?);
417 assert!(registry.notify(incarnation, 8)?);
418 assert_eq!(wake_count.load(Ordering::SeqCst), 1);
419 let ready = inbox.take_ready()?;
420 assert_eq!(ready.conversations, vec![7, 8]);
421 assert!(ready.observer_progressed.is_empty());
422 assert!(!inbox.has_pending()?);
423
424 assert!(registry.notify(incarnation, 9)?);
428 assert!(inbox.has_pending()?);
429 assert_eq!(wake_count.load(Ordering::SeqCst), 2);
430 let ready = inbox.take_ready()?;
431 assert_eq!(ready.conversations, vec![9]);
432 assert!(ready.observer_progressed.is_empty());
433 assert!(!inbox.has_pending()?);
434
435 let idle_count = wake_count.load(Ordering::SeqCst);
436 assert_eq!(wake_count.load(Ordering::SeqCst), idle_count);
437 registry.deregister(incarnation);
438 assert!(!registry.notify(incarnation, 10)?);
439 assert_eq!(wake_count.load(Ordering::SeqCst), idle_count);
440 Ok(())
441 }
442}