1use std::collections::{HashMap, HashSet, VecDeque};
14use std::sync::{Arc, Mutex as StdMutex};
15use std::time::{Duration, Instant};
16
17use lsp_types::{LogMessageParams, PublishDiagnosticsParams, ShowMessageParams, Uri};
18use tokio::sync::{Notify, mpsc};
19use tracing::{debug, warn};
20
21use crate::bridge::{BoundedDiagnostics, DiagnosticsKey, MAX_DIAGNOSTIC_ENTRIES};
22use crate::util::{WarnLimiter, lock_std};
23
24pub const MAX_PENDING_PUBLISHES: usize = MAX_DIAGNOSTIC_ENTRIES;
27
28pub const MAILBOX_MAX_BYTES: usize = 64 * 1024 * 1024;
30
31const MAX_LOST_FILES: usize = MAX_DIAGNOSTIC_ENTRIES;
33
34pub const DROP_WARN_PERIOD: Duration = WarnLimiter::DEFAULT_PERIOD;
36
37#[derive(Debug, Clone, Copy)]
39pub struct MailboxLimits {
40 pub max_pending: usize,
42 pub max_bytes: usize,
44}
45
46impl Default for MailboxLimits {
47 fn default() -> Self {
48 Self {
49 max_pending: MAX_PENDING_PUBLISHES,
50 max_bytes: MAILBOX_MAX_BYTES,
51 }
52 }
53}
54
55#[derive(Debug, Default)]
58pub struct DropCounter {
59 warn: WarnLimiter,
60 suppressed: usize,
61}
62
63impl DropCounter {
64 pub const fn new() -> Self {
66 Self {
67 warn: WarnLimiter::new(),
68 suppressed: 0,
69 }
70 }
71
72 pub fn record(&mut self, now: Instant) -> Option<usize> {
76 if self.warn.due(now, DROP_WARN_PERIOD) {
77 Some(std::mem::take(&mut self.suppressed))
78 } else {
79 self.suppressed = self.suppressed.saturating_add(1);
80 None
81 }
82 }
83}
84
85#[derive(Debug)]
88pub struct BoundedPublish {
89 uri: Uri,
90 version: Option<i32>,
91 diagnostics: BoundedDiagnostics,
92}
93
94impl BoundedPublish {
95 fn new(params: PublishDiagnosticsParams) -> (Self, usize) {
96 let PublishDiagnosticsParams {
97 uri,
98 diagnostics,
99 version,
100 } = params;
101 let diagnostics = BoundedDiagnostics::new(&uri, diagnostics);
102 let bytes = diagnostics.buffered_bytes();
103 (
104 Self {
105 uri,
106 version,
107 diagnostics,
108 },
109 bytes,
110 )
111 }
112
113 #[must_use]
115 pub const fn uri(&self) -> &Uri {
116 &self.uri
117 }
118
119 #[must_use]
121 pub const fn version(&self) -> Option<i32> {
122 self.version
123 }
124
125 #[must_use]
127 pub const fn diagnostics(&self) -> &BoundedDiagnostics {
128 &self.diagnostics
129 }
130
131 #[must_use]
133 pub fn into_parts(self) -> (Uri, Option<i32>, BoundedDiagnostics) {
134 (self.uri, self.version, self.diagnostics)
135 }
136}
137
138#[derive(Debug, PartialEq, Eq)]
141pub struct LostFiles(Vec<Uri>);
142
143impl LostFiles {
144 fn new(uris: Vec<Uri>) -> Option<Self> {
145 (!uris.is_empty()).then_some(Self(uris))
146 }
147
148 #[must_use]
150 pub fn uris(&self) -> &[Uri] {
151 &self.0
152 }
153}
154
155#[derive(Debug)]
157pub enum PublishDelivery {
158 Publish(BoundedPublish),
160 Lost(LostFiles),
162 LostUnnamed,
165}
166
167#[derive(Debug)]
169pub enum ServerMessage {
170 Log(LogMessageParams),
172 Show(ShowMessageParams),
174}
175
176#[derive(Debug)]
177struct Pending {
178 publish: BoundedPublish,
179 bytes: usize,
180}
181
182#[derive(Debug, Default)]
183struct State {
184 pending: HashMap<DiagnosticsKey, Pending>,
185 order: VecDeque<DiagnosticsKey>,
188 bytes: usize,
189 lost: Vec<Uri>,
190 lost_keys: HashSet<DiagnosticsKey>,
191 lost_overflowed: bool,
192 drops: DropCounter,
193 writer_gone: bool,
194 reader_gone: bool,
195}
196
197#[derive(Debug)]
198struct Shared {
199 state: StdMutex<State>,
200 wake: Notify,
201 limits: MailboxLimits,
202}
203
204impl State {
205 fn withdraw(&mut self, key: &DiagnosticsKey) {
206 if let Some(pending) = self.pending.remove(key) {
207 self.bytes = self.bytes.saturating_sub(pending.bytes);
208 }
209 }
210
211 fn compact_order(&mut self, max_pending: usize) {
215 if self.order.len() > max_pending.saturating_mul(2).max(16) {
216 let pending = &self.pending;
217 self.order.retain(|key| pending.contains_key(key));
218 }
219 }
220
221 fn next_ready(&mut self) -> Option<PublishDelivery> {
224 if let Some(lost) = self.take_lost_files() {
225 return Some(PublishDelivery::Lost(lost));
226 }
227 while let Some(key) = self.order.pop_front() {
228 if let Some(pending) = self.pending.remove(&key) {
229 self.bytes = self.bytes.saturating_sub(pending.bytes);
230 return Some(PublishDelivery::Publish(pending.publish));
231 }
232 }
233 std::mem::take(&mut self.lost_overflowed).then_some(PublishDelivery::LostUnnamed)
234 }
235
236 fn mark_lost(&mut self, key: DiagnosticsKey, uri: Uri) {
237 if self.lost_keys.contains(&key) {
238 return;
239 }
240 if self.lost.len() >= MAX_LOST_FILES {
241 self.lost_overflowed = true;
242 return;
243 }
244 self.lost_keys.insert(key);
245 self.lost.push(uri);
246 }
247
248 fn take_lost_files(&mut self) -> Option<LostFiles> {
249 let lost = LostFiles::new(std::mem::take(&mut self.lost))?;
250 self.lost_keys.clear();
251 Some(lost)
252 }
253
254 fn count_drop(&mut self) -> Option<usize> {
256 self.drops.record(Instant::now())
257 }
258}
259
260#[derive(Debug)]
263pub struct PublishWriter {
264 shared: Arc<Shared>,
265}
266
267#[derive(Debug)]
270pub struct PublishReader {
271 shared: Arc<Shared>,
272}
273
274pub fn mailbox(limits: MailboxLimits) -> (PublishWriter, PublishReader) {
277 let shared = Arc::new(Shared {
278 state: StdMutex::new(State::default()),
279 wake: Notify::new(),
280 limits,
281 });
282 (
283 PublishWriter {
284 shared: Arc::clone(&shared),
285 },
286 PublishReader { shared },
287 )
288}
289
290impl PublishWriter {
291 pub fn publish(&self, params: PublishDiagnosticsParams) {
299 let (publish, bytes) = BoundedPublish::new(params);
300 let key = DiagnosticsKey::of(publish.uri());
301 let limits = self.shared.limits;
302 let mut state = lock_std(&self.shared.state);
303 if state.reader_gone {
304 debug!(
305 "dropping a publish for {}: nobody reads the mailbox",
306 publish.uri().as_ref()
307 );
308 return;
309 }
310 let replaced_bytes = state.pending.get(&key).map(|pending| pending.bytes);
311 let is_new = replaced_bytes.is_none();
312 let fits = state
313 .bytes
314 .saturating_sub(replaced_bytes.unwrap_or(0))
315 .saturating_add(bytes)
316 <= limits.max_bytes
317 && (!is_new || state.pending.len() < limits.max_pending);
318 state.compact_order(limits.max_pending);
319 state.withdraw(&key);
320 let warn_due = if fits {
321 state.bytes = state.bytes.saturating_add(bytes);
322 if is_new {
323 state.order.push_back(key.clone());
324 }
325 state.pending.insert(key, Pending { publish, bytes });
326 None
327 } else {
328 let (uri, ..) = publish.into_parts();
329 state.mark_lost(key, uri);
330 state.count_drop()
331 };
332 drop(state);
333 if let Some(suppressed) = warn_due {
334 warn!(
335 "publishDiagnostics mailbox is full: dropped a publish ({suppressed} others \
336 since the last warning); the files read as evicted until the server publishes \
337 again"
338 );
339 }
340 self.shared.wake.notify_one();
341 }
342}
343
344impl Drop for PublishWriter {
345 fn drop(&mut self) {
346 lock_std(&self.shared.state).writer_gone = true;
347 self.shared.wake.notify_one();
348 }
349}
350
351impl PublishReader {
352 #[must_use]
354 pub fn closed() -> Self {
355 mailbox(MailboxLimits::default()).1
356 }
357
358 pub fn try_recv(&mut self) -> Option<PublishDelivery> {
360 lock_std(&self.shared.state).next_ready()
361 }
362
363 pub async fn recv(&mut self) -> Option<PublishDelivery> {
366 loop {
367 {
368 let mut state = lock_std(&self.shared.state);
369 if let Some(delivery) = state.next_ready() {
370 return Some(delivery);
371 }
372 if state.writer_gone {
373 return None;
374 }
375 }
376 self.shared.wake.notified().await;
377 }
378 }
379}
380
381impl Drop for PublishReader {
382 fn drop(&mut self) {
383 let mut state = lock_std(&self.shared.state);
384 state.reader_gone = true;
385 state.order.clear();
386 state.bytes = 0;
387 let pending = std::mem::take(&mut state.pending);
388 drop(state);
389 drop(pending);
390 }
391}
392
393#[derive(Debug)]
398pub struct NotificationSink {
399 messages: mpsc::Sender<ServerMessage>,
400 publishes: PublishWriter,
401}
402
403impl NotificationSink {
404 pub fn new(messages: mpsc::Sender<ServerMessage>) -> (Self, PublishReader) {
406 Self::with_limits(messages, MailboxLimits::default())
407 }
408
409 pub fn with_limits(
411 messages: mpsc::Sender<ServerMessage>,
412 limits: MailboxLimits,
413 ) -> (Self, PublishReader) {
414 let (publishes, reader) = mailbox(limits);
415 (
416 Self {
417 messages,
418 publishes,
419 },
420 reader,
421 )
422 }
423
424 pub const fn messages(&self) -> &mpsc::Sender<ServerMessage> {
426 &self.messages
427 }
428
429 pub const fn publishes(&self) -> &PublishWriter {
431 &self.publishes
432 }
433}
434
435#[derive(Debug)]
438pub struct NotificationInbox {
439 pub messages: mpsc::Receiver<ServerMessage>,
441 pub publishes: PublishReader,
443}
444
445impl NotificationInbox {
446 #[must_use]
448 pub const fn new(messages: mpsc::Receiver<ServerMessage>, publishes: PublishReader) -> Self {
449 Self {
450 messages,
451 publishes,
452 }
453 }
454}
455
456#[cfg(test)]
457impl From<mpsc::Receiver<ServerMessage>> for NotificationInbox {
458 fn from(messages: mpsc::Receiver<ServerMessage>) -> Self {
459 Self::new(messages, PublishReader::closed())
460 }
461}
462
463#[derive(Debug, Clone, Copy, PartialEq, Eq)]
465pub enum Lane {
466 Notification,
468 Lifecycle,
470}
471
472impl Lane {
473 const fn name(self) -> &'static str {
474 match self {
475 Self::Notification => "notification",
476 Self::Lifecycle => "lifecycle",
477 }
478 }
479}
480
481#[derive(Debug)]
484pub struct DropLog {
485 lane: Lane,
486 drops: StdMutex<DropCounter>,
487}
488
489impl DropLog {
490 const fn new(lane: Lane) -> Self {
491 Self {
492 lane,
493 drops: StdMutex::new(DropCounter::new()),
494 }
495 }
496
497 pub fn of(lane: Lane) -> &'static Self {
500 static NOTIFICATION: DropLog = DropLog::new(Lane::Notification);
501 static LIFECYCLE: DropLog = DropLog::new(Lane::Lifecycle);
502 match lane {
503 Lane::Notification => &NOTIFICATION,
504 Lane::Lifecycle => &LIFECYCLE,
505 }
506 }
507
508 pub fn record(&self, method: &str) {
511 let lane = self.lane.name();
512 debug!("dropping notification: lane={lane}, method={method}");
513 let warn_due = lock_std(&self.drops).record(Instant::now());
514 if let Some(suppressed) = warn_due {
515 warn!(
516 "dropped a notification on the {lane} lane ({suppressed} others since the last \
517 warning; channel full or closed)"
518 );
519 }
520 }
521}
522
523#[cfg(test)]
524mod tests {
525 use std::assert_matches;
526
527 use lsp_types::Diagnostic;
528
529 use super::*;
530
531 fn uri(n: usize) -> Uri {
532 Uri::from(format!("file:///ws/f{n}.rs"))
533 }
534
535 fn publish_of(n: usize, message: &str) -> PublishDiagnosticsParams {
536 PublishDiagnosticsParams {
537 uri: uri(n),
538 diagnostics: vec![Diagnostic {
539 message: message.to_owned().into(),
540 ..Diagnostic::default()
541 }],
542 version: None,
543 }
544 }
545
546 fn bytes_of(n: usize, message: &str) -> usize {
547 let params = publish_of(n, message);
548 BoundedDiagnostics::new(¶ms.uri, params.diagnostics).buffered_bytes()
549 }
550
551 fn small(max_pending: usize, max_bytes: usize) -> MailboxLimits {
552 MailboxLimits {
553 max_pending,
554 max_bytes,
555 }
556 }
557
558 async fn next_publish(reader: &mut PublishReader) -> BoundedPublish {
559 match reader.recv().await {
560 Some(PublishDelivery::Publish(publish)) => publish,
561 other => panic!("expected a publish, got {other:?}"),
562 }
563 }
564
565 #[tokio::test]
566 async fn burst_below_the_cap_is_delivered_whole_in_order() {
567 let (writer, mut reader) = mailbox(small(10, usize::MAX));
568 for n in 0..10 {
569 writer.publish(publish_of(n, "e"));
570 }
571 drop(writer);
572
573 for n in 0..10 {
574 assert_eq!(next_publish(&mut reader).await.uri(), &uri(n));
575 }
576 assert!(reader.recv().await.is_none());
577 }
578
579 #[tokio::test]
580 async fn later_publish_replaces_the_pending_one_of_the_same_file() {
581 let (writer, mut reader) = mailbox(small(10, usize::MAX));
582 writer.publish(publish_of(1, "old"));
583 writer.publish(publish_of(1, "new"));
584 drop(writer);
585
586 let delivered = next_publish(&mut reader).await;
587 assert_eq!(delivered.diagnostics().as_slice().len(), 1);
588 assert_eq!(
589 delivered
590 .diagnostics()
591 .as_slice()
592 .first()
593 .map(|d| d.message.clone()),
594 Some("new".to_owned().into())
595 );
596 assert!(reader.recv().await.is_none());
597 }
598
599 #[tokio::test]
600 async fn publish_over_the_file_cap_is_reported_lost_not_dropped_quietly() {
601 let (writer, mut reader) = mailbox(small(2, usize::MAX));
602 for n in 0..4 {
603 writer.publish(publish_of(n, "e"));
604 }
605
606 let Some(PublishDelivery::Lost(lost)) = reader.recv().await else {
607 panic!("the loss must be handed over first");
608 };
609 assert_eq!(lost.uris(), [uri(2), uri(3)]);
610 assert_eq!(next_publish(&mut reader).await.uri(), &uri(0));
611 assert_eq!(next_publish(&mut reader).await.uri(), &uri(1));
612 }
613
614 #[tokio::test]
617 async fn oversized_replacement_withdraws_the_older_publish_and_marks_the_file_lost() {
618 let one = bytes_of(1, "x");
619 let (writer, mut reader) = mailbox(small(10, one + one / 2));
620 writer.publish(publish_of(1, "x"));
621 writer.publish(publish_of(1, &"y".repeat(one * 2)));
622 drop(writer);
623
624 let Some(PublishDelivery::Lost(lost)) = reader.recv().await else {
625 panic!("expected the lost file");
626 };
627 assert_eq!(lost.uris(), [uri(1)]);
628 assert!(reader.recv().await.is_none());
629 }
630
631 #[tokio::test]
633 async fn arrival_queue_stays_bounded_under_withdrawals() {
634 let one = bytes_of(0, "x");
635 let (writer, _reader) = mailbox(small(4, one * 2));
636 for n in 0..1000 {
637 writer.publish(publish_of(n, "x"));
638 writer.publish(publish_of(n, &"y".repeat(one * 4)));
639 }
640
641 assert!(lock_std(&writer.shared.state).order.len() <= 16);
642 }
643
644 #[tokio::test]
645 async fn replacement_after_order_compaction_is_still_delivered() {
646 let one = bytes_of(0, "x");
647 let (writer, mut reader) = mailbox(small(4, one * 2));
648 for n in 0..16 {
649 writer.publish(publish_of(n, "x"));
650 writer.publish(publish_of(n, &"y".repeat(one * 4)));
651 }
652 writer.publish(publish_of(100, "x"));
653 writer.publish(publish_of(100, "z"));
654 drop(writer);
655
656 let mut delivered = None;
657 while let Some(delivery) = reader.recv().await {
658 if let PublishDelivery::Publish(params) = delivery {
659 delivered = Some(params);
660 }
661 }
662 let delivered = delivered.expect("the replaced publish must reach the reader");
663 assert_eq!(delivered.uri(), &uri(100));
664 assert_eq!(
665 delivered
666 .diagnostics()
667 .as_slice()
668 .first()
669 .map(|d| d.message.clone()),
670 Some("z".to_owned().into())
671 );
672 }
673
674 #[tokio::test]
675 async fn delivered_publish_carries_bounded_diagnostics() {
676 let (writer, mut reader) = mailbox(small(10, usize::MAX));
677 let oversized = 512 * 1024;
678 writer.publish(publish_of(1, &"m".repeat(oversized)));
679 drop(writer);
680
681 let delivered = next_publish(&mut reader).await;
682
683 let message = delivered
684 .diagnostics()
685 .as_slice()
686 .first()
687 .map(|d| d.message.clone());
688 assert_matches!(
689 message,
690 Some(lsp_types::Message::String(text)) if text.len() < oversized
691 );
692 }
693
694 #[test]
695 fn drop_counter_warns_once_per_period_and_counts_the_silenced() {
696 let mut counter = DropCounter::new();
697 let start = Instant::now();
698 let at = |secs| start + Duration::from_secs(secs);
699
700 assert_eq!(counter.record(at(0)), Some(0));
701 assert_eq!(counter.record(at(1)), None);
702 assert_eq!(counter.record(at(2)), None);
703 assert_eq!(counter.record(at(61)), Some(2));
704 }
705
706 #[test]
707 fn each_lane_has_its_own_drop_log() {
708 for lane in [Lane::Notification, Lane::Lifecycle] {
709 assert_eq!(DropLog::of(lane).lane, lane);
710 }
711 }
712
713 #[tokio::test]
714 async fn byte_cap_bounds_what_is_buffered() {
715 let one = bytes_of(0, "e");
716 let (writer, mut reader) = mailbox(small(100, one * 3));
717 for n in 0..10 {
718 writer.publish(publish_of(n, "e"));
719 }
720 drop(writer);
721
722 let mut delivered = 0;
723 let mut lost = 0;
724 while let Some(item) = reader.recv().await {
725 match item {
726 PublishDelivery::Publish(_) => delivered += 1,
727 PublishDelivery::Lost(files) => lost += files.uris().len(),
728 PublishDelivery::LostUnnamed => panic!("no unnamed losses expected"),
729 }
730 }
731 assert_eq!((delivered, lost), (3, 7));
732 }
733
734 #[tokio::test]
735 async fn lost_list_is_capped_and_then_only_the_fact_is_kept() {
736 let (writer, mut reader) = mailbox(small(0, usize::MAX));
737 for n in 0..=MAX_LOST_FILES {
738 writer.publish(publish_of(n, "e"));
739 }
740 drop(writer);
741
742 let Some(PublishDelivery::Lost(lost)) = reader.recv().await else {
743 panic!("expected losses");
744 };
745 assert_eq!(lost.uris().len(), MAX_LOST_FILES);
746 assert_matches!(reader.recv().await, Some(PublishDelivery::LostUnnamed));
747 assert!(reader.recv().await.is_none());
748 }
749
750 #[tokio::test]
751 async fn dropping_the_writer_ends_the_reader_and_dropping_the_reader_stops_buffering() {
752 let (writer, reader) = mailbox(small(10, usize::MAX));
753 writer.publish(publish_of(1, "e"));
754 drop(reader);
755 writer.publish(publish_of(2, "e"));
756 assert_eq!(lock_std(&writer.shared.state).bytes, 0);
757 assert!(lock_std(&writer.shared.state).pending.is_empty());
758
759 let (writer, mut reader) = mailbox(small(10, usize::MAX));
760 drop(writer);
761 assert!(reader.recv().await.is_none());
762 }
763
764 #[tokio::test]
765 async fn a_waiting_reader_wakes_for_a_new_publish() {
766 let (writer, mut reader) = mailbox(small(10, usize::MAX));
767 let waiting = tokio::spawn(async move { next_publish(&mut reader).await.uri });
768 tokio::task::yield_now().await;
769
770 writer.publish(publish_of(7, "e"));
771
772 assert_eq!(waiting.await.unwrap(), uri(7));
773 }
774}