1use crate::config::{OverflowPolicy, PendingQueueConfig};
6use offline_protocol_core::Message;
7use offline_protocol_transport::TransportType;
8use std::collections::{HashMap, HashSet, VecDeque};
9use std::time::{Duration as StdDuration, Instant};
10use tracing::{debug, warn};
11
12const TTL_SPIKE_WARN_THRESHOLD: usize = 25;
13const PEER_PRESSURE_WARN_EVERY: u32 = 10;
14const DROP_WARN_EVERY: u64 = 100;
15const EVICTION_FAILURE_WARN_EVERY: u64 = 10;
16
17#[derive(Clone)]
18pub(crate) struct PendingDecryptMessage {
19 pub(crate) peer_id: String,
20 pub(crate) message_id: String,
21 pub(crate) received_at: Instant,
22 pub(crate) sequence: u64,
23 pub(crate) message: Message,
24 pub(crate) received_via: Option<TransportType>,
34}
35
36#[derive(Clone)]
37struct EntryRef {
38 peer_id: String,
39 message_id: String,
40 sequence: u64,
41}
42
43pub(crate) struct DroppedPendingMessage {
49 pub(crate) message: Message,
50 pub(crate) reason: &'static str,
51 pub(crate) received_via: Option<TransportType>,
54}
55
56pub(crate) struct EnqueueOutcome {
72 pub(crate) admitted: bool,
73 pub(crate) dropped: Vec<DroppedPendingMessage>,
74 pub(crate) refused: Option<DroppedPendingMessage>,
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79enum Admission {
80 Admitted,
82 AlreadyQueued,
84 Refused,
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89enum QueueLimit {
90 PerPeer,
91 Global,
92 PerPeerBytes,
93 GlobalBytes,
94}
95
96impl QueueLimit {
97 fn as_str(self) -> &'static str {
98 match self {
99 Self::PerPeer => "per_peer",
100 Self::Global => "global",
101 Self::PerPeerBytes => "per_peer_bytes",
102 Self::GlobalBytes => "global_bytes",
103 }
104 }
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108enum DropReason {
109 OverflowDropOldest,
110 OverflowDropNewest,
111 TtlExpired,
112}
113
114impl DropReason {
115 fn as_str(self) -> &'static str {
116 match self {
117 Self::OverflowDropOldest => "overflow_drop_oldest",
118 Self::OverflowDropNewest => "overflow_drop_newest",
119 Self::TtlExpired => "ttl_expired",
120 }
121 }
122}
123
124#[derive(Debug, Clone, Default)]
126pub struct PendingQueueMetrics {
127 pub pending_messages_received_total: u64,
129 pub pending_messages_evicted_total: u64,
131 pub pending_messages_dropped_overflow_total: u64,
133 pub pending_messages_expired_total: u64,
135 pub pending_messages_eviction_failures_total: u64,
137 pub pending_queue_invariant_violations_total: u64,
139 pub pending_messages_current: usize,
141 pub pending_bytes_current: usize,
143 pub pending_messages_per_peer: HashMap<String, usize>,
145}
146
147#[derive(Clone, Default)]
151pub(crate) struct PendingDecryptionQueue {
152 queues: HashMap<String, VecDeque<PendingDecryptMessage>>,
154 global_order: VecDeque<EntryRef>,
156 live_sequences: HashSet<u64>,
158 total: usize,
160 total_bytes: usize,
162 peer_bytes: HashMap<String, usize>,
164 next_seq: u64,
166 metrics: PendingQueueMetrics,
168 peer_overflow_hits: HashMap<String, u32>,
170 drop_warning_counters: HashMap<String, u64>,
172}
173
174impl PendingDecryptionQueue {
175 pub(crate) fn metrics(&self) -> &PendingQueueMetrics {
179 &self.metrics
180 }
181
182 #[cfg(test)]
184 pub(crate) fn is_empty(&self) -> bool {
185 self.queues.is_empty()
186 }
187
188 #[cfg(test)]
190 pub(crate) fn contains_peer(&self, peer_id: &str) -> bool {
191 self.queues.contains_key(peer_id)
192 }
193
194 #[cfg(test)]
196 pub(crate) fn peer_queue_len(&self, peer_id: &str) -> usize {
197 self.queues.get(peer_id).map(VecDeque::len).unwrap_or(0)
198 }
199
200 #[cfg(test)]
202 pub(crate) fn total(&self) -> usize {
203 self.total
204 }
205
206 #[cfg(test)]
208 pub(crate) fn total_bytes(&self) -> usize {
209 self.total_bytes
210 }
211
212 #[cfg(test)]
214 pub(crate) fn peek_entry(&self, peer_id: &str, index: usize) -> Option<&PendingDecryptMessage> {
215 self.queues.get(peer_id).and_then(|q| q.get(index))
216 }
217
218 #[cfg(test)]
220 pub(crate) fn max_peer_queue_len(&self) -> usize {
221 self.queues.values().map(VecDeque::len).max().unwrap_or(0)
222 }
223
224 #[cfg(test)]
226 pub(crate) fn has_overflow_hits(&self, peer_id: &str) -> bool {
227 self.peer_overflow_hits.contains_key(peer_id)
228 }
229
230 #[cfg(test)]
232 pub(crate) fn overflow_hits_tracked(&self) -> usize {
233 self.peer_overflow_hits.len()
234 }
235
236 #[cfg(test)]
238 pub(crate) fn message_footprint(message: &Message) -> usize {
239 Self::message_bytes(message)
240 }
241
242 #[cfg(test)]
244 pub(crate) fn set_front_received_at(&mut self, peer_id: &str, at: Instant) {
245 if let Some(queue) = self.queues.get_mut(peer_id) {
246 if let Some(front) = queue.front_mut() {
247 front.received_at = at;
248 }
249 }
250 }
251
252 #[cfg(test)]
254 pub(crate) fn corrupt_clear_global_order(&mut self) {
255 self.global_order.clear();
256 }
257
258 fn ttl(config: &PendingQueueConfig) -> StdDuration {
261 StdDuration::from_millis(config.pending_ttl_ms)
262 }
263
264 fn update_peer_gauge(&mut self, peer_id: &str) {
265 let peer_len = self.queues.get(peer_id).map(VecDeque::len).unwrap_or(0);
266 if peer_len == 0 {
267 self.metrics.pending_messages_per_peer.remove(peer_id);
268 } else {
269 self.metrics
270 .pending_messages_per_peer
271 .insert(peer_id.to_string(), peer_len);
272 }
273 }
274
275 fn update_current_gauge(&mut self) {
276 self.metrics.pending_messages_current = self.total;
277 self.metrics.pending_bytes_current = self.total_bytes;
278 }
279
280 fn message_bytes(message: &Message) -> usize {
297 let metadata_bytes: usize = message
298 .metadata
299 .iter()
300 .map(|(k, v)| k.len() + v.len())
301 .sum();
302 let media_bytes = message
303 .media_metadata
304 .as_ref()
305 .map(|m| {
306 m.mime_type.len()
307 + m.file_name.len()
308 + m.thumbnail_base64.as_ref().map(String::len).unwrap_or(0)
309 })
310 .unwrap_or(0);
311 message.content.len()
312 + message
313 .binary_content
314 .as_ref()
315 .map(|binary| binary.len())
316 .unwrap_or(0)
317 + message.sender.as_str().len()
318 + message.recipient.as_str().len()
319 + message.app_id.as_str().len()
320 + metadata_bytes
321 + media_bytes
322 }
323
324 fn peer_bytes_for(&self, peer_id: &str) -> usize {
325 self.peer_bytes.get(peer_id).copied().unwrap_or(0)
326 }
327
328 fn next_sequence(&mut self) -> u64 {
329 let seq = self.next_seq;
330 self.next_seq = self.next_seq.wrapping_add(1);
331 seq
332 }
333
334 fn is_entry_expired(
335 config: &PendingQueueConfig,
336 entry: &PendingDecryptMessage,
337 now: Instant,
338 ) -> bool {
339 now.saturating_duration_since(entry.received_at) >= Self::ttl(config)
340 }
341
342 fn cleanup_global_order_front(&mut self) {
343 while let Some(front) = self.global_order.front() {
344 if self.live_sequences.contains(&front.sequence) {
345 break;
346 }
347 self.global_order.pop_front();
348 }
349 }
350
351 fn remove_entry_by_sequence(
352 &mut self,
353 peer_id: &str,
354 sequence: u64,
355 ) -> Option<PendingDecryptMessage> {
356 let (removed, queue_empty) = {
357 let queue = self.queues.get_mut(peer_id)?;
358 let position = queue.iter().position(|entry| entry.sequence == sequence)?;
359 let removed = queue.remove(position)?;
360 (removed, queue.is_empty())
361 };
362
363 self.live_sequences.remove(&sequence);
364 self.total = self.total.saturating_sub(1);
365 let removed_bytes = Self::message_bytes(&removed.message);
366 self.total_bytes = self.total_bytes.saturating_sub(removed_bytes);
367 if let Some(bytes) = self.peer_bytes.get_mut(peer_id) {
368 *bytes = bytes.saturating_sub(removed_bytes);
369 }
370 self.update_current_gauge();
371
372 if queue_empty {
373 self.queues.remove(peer_id);
374 self.peer_bytes.remove(peer_id);
375 self.peer_overflow_hits.remove(peer_id);
381 }
382 self.update_peer_gauge(peer_id);
383 Some(removed)
384 }
385
386 fn record_drop(
387 &mut self,
388 reason: DropReason,
389 limit_triggered: Option<QueueLimit>,
390 peer_id: &str,
391 message_id: &str,
392 overflow_policy: OverflowPolicy,
393 ) {
394 if matches!(
395 reason,
396 DropReason::OverflowDropOldest | DropReason::TtlExpired
397 ) {
398 self.metrics.pending_messages_evicted_total = self
399 .metrics
400 .pending_messages_evicted_total
401 .saturating_add(1);
402 }
403
404 if matches!(
405 reason,
406 DropReason::OverflowDropOldest | DropReason::OverflowDropNewest
407 ) {
408 self.metrics.pending_messages_dropped_overflow_total = self
409 .metrics
410 .pending_messages_dropped_overflow_total
411 .saturating_add(1);
412 }
413
414 if reason == DropReason::TtlExpired {
415 self.metrics.pending_messages_expired_total = self
416 .metrics
417 .pending_messages_expired_total
418 .saturating_add(1);
419 }
420
421 let limit_label = limit_triggered.map(QueueLimit::as_str).unwrap_or("ttl");
422 let counter_key = format!("{}:{}", reason.as_str(), limit_label);
423 let drop_count = {
424 let counter = self.drop_warning_counters.entry(counter_key).or_insert(0);
425 *counter = counter.saturating_add(1);
426 *counter
427 };
428
429 debug!(
430 reason = reason.as_str(),
431 peer_id = %peer_id,
432 message_id = %message_id,
433 queue_size = self.total,
434 limit_triggered = limit_label,
435 overflow_policy = ?overflow_policy,
436 "Dropped pending encrypted message"
437 );
438 if drop_count == 1 || drop_count % DROP_WARN_EVERY == 0 {
439 warn!(
440 reason = reason.as_str(),
441 limit_triggered = limit_label,
442 drops = drop_count,
443 queue_size = self.total,
444 "Pending encrypted message drops continuing"
445 );
446 }
447 }
448
449 fn record_eviction_failure(
450 &mut self,
451 limit_triggered: QueueLimit,
452 peer_id: &str,
453 message_id: &str,
454 detail: &str,
455 ) {
456 self.metrics.pending_messages_eviction_failures_total = self
457 .metrics
458 .pending_messages_eviction_failures_total
459 .saturating_add(1);
460
461 let count = self.metrics.pending_messages_eviction_failures_total;
462 if count == 1 || count.is_multiple_of(EVICTION_FAILURE_WARN_EVERY) {
463 warn!(
464 limit_triggered = limit_triggered.as_str(),
465 peer_id = %peer_id,
466 message_id = %message_id,
467 failures = count,
468 detail = detail,
469 queue_size = self.total,
470 "Pending queue eviction failure detected"
471 );
472 } else {
473 debug!(
474 limit_triggered = limit_triggered.as_str(),
475 peer_id = %peer_id,
476 message_id = %message_id,
477 detail = detail,
478 "Pending queue eviction failure detected"
479 );
480 }
481 }
482
483 fn verify_invariants(&mut self, context: &str) {
484 let per_peer_sum: usize = self.queues.values().map(VecDeque::len).sum();
485 let live_count = self.live_sequences.len();
486 let current_gauge = self.metrics.pending_messages_current;
487 let total = self.total;
488 let peer_bytes_sum: usize = self.peer_bytes.values().sum();
489 let valid = per_peer_sum == total
490 && live_count == total
491 && current_gauge == total
492 && peer_bytes_sum == self.total_bytes;
493 if valid {
494 return;
495 }
496
497 self.metrics.pending_queue_invariant_violations_total = self
498 .metrics
499 .pending_queue_invariant_violations_total
500 .saturating_add(1);
501 warn!(
502 context = context,
503 per_peer_sum,
504 live_count,
505 current_gauge,
506 total,
507 violations = self.metrics.pending_queue_invariant_violations_total,
508 "Pending queue invariant violation detected"
509 );
510 }
511
512 fn record_peer_overflow_pressure(&mut self, peer_id: &str, per_peer_limit: usize) {
513 let hits = self
514 .peer_overflow_hits
515 .entry(peer_id.to_string())
516 .or_insert(0);
517 *hits = hits.saturating_add(1);
518 if hits.is_multiple_of(PEER_PRESSURE_WARN_EVERY) {
519 warn!(
520 peer_id = %peer_id,
521 overflow_hits = *hits,
522 per_peer_limit,
523 "Peer repeatedly hitting pending queue limits"
524 );
525 }
526 }
527
528 fn evict_global_oldest(
529 &mut self,
530 reason: DropReason,
531 limit_triggered: QueueLimit,
532 overflow_policy: OverflowPolicy,
533 ) -> Option<PendingDecryptMessage> {
534 self.cleanup_global_order_front();
535 let entry_ref = self.global_order.pop_front()?;
536 if !self.live_sequences.contains(&entry_ref.sequence) {
537 return None;
538 }
539 if let Some(evicted) = self.remove_entry_by_sequence(&entry_ref.peer_id, entry_ref.sequence)
540 {
541 self.record_drop(
542 reason,
543 Some(limit_triggered),
544 &evicted.peer_id,
545 &evicted.message_id,
546 overflow_policy,
547 );
548 return Some(evicted);
549 }
550 warn!(
551 peer_id = %entry_ref.peer_id,
552 message_id = %entry_ref.message_id,
553 sequence = entry_ref.sequence,
554 "Failed to evict pending message by global order reference"
555 );
556 None
557 }
558
559 pub(crate) fn prune_expired_for_peer(
563 &mut self,
564 config: &PendingQueueConfig,
565 peer_id: &str,
566 now: Instant,
567 ) -> Vec<DroppedPendingMessage> {
568 let mut expired_entries = Vec::new();
569 let mut expired_sequences = Vec::new();
570 let mut expired_ids = Vec::new();
571 if let Some(queue) = self.queues.get(peer_id) {
572 for entry in queue {
573 if Self::is_entry_expired(config, entry, now) {
574 expired_sequences.push(entry.sequence);
575 expired_ids.push(entry.message_id.clone());
576 } else {
577 break;
578 }
579 }
580 }
581
582 let overflow_policy = config.overflow_policy;
583 for (sequence, message_id) in expired_sequences.into_iter().zip(expired_ids) {
584 if let Some(expired) = self.remove_entry_by_sequence(peer_id, sequence) {
585 self.record_drop(
586 DropReason::TtlExpired,
587 None,
588 peer_id,
589 &message_id,
590 overflow_policy,
591 );
592 expired_entries.push(DroppedPendingMessage {
593 message: expired.message,
594 reason: DropReason::TtlExpired.as_str(),
595 received_via: expired.received_via,
596 });
597 }
598 }
599
600 if expired_entries.len() >= TTL_SPIKE_WARN_THRESHOLD {
601 warn!(
602 peer_id = %peer_id,
603 expired = expired_entries.len(),
604 ttl_ms = config.pending_ttl_ms,
605 "Pending encrypted message TTL eviction spike"
606 );
607 }
608
609 self.cleanup_global_order_front();
610 expired_entries
611 }
612
613 pub(crate) fn prune_expired_global_front(
615 &mut self,
616 config: &PendingQueueConfig,
617 now: Instant,
618 max_evictions: usize,
619 ) -> Vec<DroppedPendingMessage> {
620 let mut evicted = Vec::new();
621 let overflow_policy = config.overflow_policy;
622 while evicted.len() < max_evictions {
623 self.cleanup_global_order_front();
624 let Some(front) = self.global_order.front().cloned() else {
625 break;
626 };
627 if !self.live_sequences.contains(&front.sequence) {
628 self.global_order.pop_front();
629 continue;
630 }
631
632 let Some(queue) = self.queues.get(&front.peer_id) else {
633 self.global_order.pop_front();
634 continue;
635 };
636 let Some(entry) = queue.iter().find(|entry| entry.sequence == front.sequence) else {
637 self.global_order.pop_front();
638 continue;
639 };
640
641 if !Self::is_entry_expired(config, entry, now) {
642 break;
643 }
644
645 if let Some(expired) = self.remove_entry_by_sequence(&front.peer_id, front.sequence) {
646 self.global_order.pop_front();
647 self.record_drop(
648 DropReason::TtlExpired,
649 None,
650 &expired.peer_id,
651 &expired.message_id,
652 overflow_policy,
653 );
654 evicted.push(DroppedPendingMessage {
655 message: expired.message,
656 reason: DropReason::TtlExpired.as_str(),
657 received_via: expired.received_via,
658 });
659 } else {
660 self.global_order.pop_front();
661 }
662 }
663
664 if evicted.len() >= TTL_SPIKE_WARN_THRESHOLD {
665 warn!(
666 expired = evicted.len(),
667 ttl_ms = config.pending_ttl_ms,
668 "Pending encrypted message TTL eviction spike"
669 );
670 }
671
672 evicted
673 }
674
675 #[cfg(test)]
680 pub(crate) fn enqueue(
681 &mut self,
682 config: &PendingQueueConfig,
683 sender: &str,
684 message: &Message,
685 ) -> Vec<DroppedPendingMessage> {
686 let outcome = self.enqueue_via(config, sender, message, None);
687 let mut dropped = outcome.dropped;
688 dropped.extend(outcome.refused);
689 dropped
690 }
691
692 pub(crate) fn enqueue_via(
702 &mut self,
703 config: &PendingQueueConfig,
704 sender: &str,
705 message: &Message,
706 arrival_transport: Option<TransportType>,
707 ) -> EnqueueOutcome {
708 let mut dropped = Vec::new();
709 let admission =
710 self.enqueue_via_inner(config, sender, message, arrival_transport, &mut dropped);
711 EnqueueOutcome {
712 admitted: admission == Admission::Admitted,
713 dropped,
714 refused: (admission == Admission::Refused).then(|| DroppedPendingMessage {
715 message: message.clone(),
716 reason: DropReason::OverflowDropNewest.as_str(),
717 received_via: arrival_transport,
718 }),
719 }
720 }
721
722 fn enqueue_via_inner(
725 &mut self,
726 config: &PendingQueueConfig,
727 sender: &str,
728 message: &Message,
729 arrival_transport: Option<TransportType>,
730 dropped: &mut Vec<DroppedPendingMessage>,
731 ) -> Admission {
732 self.metrics.pending_messages_received_total = self
733 .metrics
734 .pending_messages_received_total
735 .saturating_add(1);
736 let incoming_message_id = message.id.as_str();
737
738 let now = Instant::now();
739 dropped.extend(self.prune_expired_for_peer(config, sender, now));
740 dropped.extend(self.prune_expired_global_front(config, now, 64));
741
742 if self.queues.get(sender).is_some_and(|queue| {
750 queue
751 .iter()
752 .any(|entry| entry.message_id == incoming_message_id)
753 }) {
754 return Admission::AlreadyQueued;
755 }
756
757 let per_peer_limit = config.max_pending_per_peer;
758 let global_limit = config.max_pending_global;
759 let per_peer_bytes_limit = config.max_pending_bytes_per_peer;
760 let global_bytes_limit = config.max_pending_bytes_global;
761 let overflow_policy = config.overflow_policy;
762 let incoming_bytes = Self::message_bytes(message);
763
764 if incoming_bytes > per_peer_bytes_limit || incoming_bytes > global_bytes_limit {
768 let limit = if incoming_bytes > per_peer_bytes_limit {
769 QueueLimit::PerPeerBytes
770 } else {
771 QueueLimit::GlobalBytes
772 };
773 self.record_drop(
774 DropReason::OverflowDropNewest,
775 Some(limit),
776 sender,
777 &incoming_message_id,
778 overflow_policy,
779 );
780 return Admission::Refused;
781 }
782
783 let peer_len = self.queues.get(sender).map(VecDeque::len).unwrap_or(0);
784 if peer_len >= per_peer_limit {
785 self.record_peer_overflow_pressure(sender, per_peer_limit);
786 match overflow_policy {
787 OverflowPolicy::DropNewest => {
788 self.record_drop(
789 DropReason::OverflowDropNewest,
790 Some(QueueLimit::PerPeer),
791 sender,
792 &incoming_message_id,
793 overflow_policy,
794 );
795 return Admission::Refused;
796 }
797 OverflowPolicy::DropOldest => {
798 let evicted_sequence = self
799 .queues
800 .get(sender)
801 .and_then(|queue| queue.front().map(|entry| entry.sequence));
802 let mut evicted_any = false;
803 if let Some(sequence) = evicted_sequence {
804 if let Some(evicted) = self.remove_entry_by_sequence(sender, sequence) {
805 self.record_drop(
806 DropReason::OverflowDropOldest,
807 Some(QueueLimit::PerPeer),
808 &evicted.peer_id,
809 &evicted.message_id,
810 overflow_policy,
811 );
812 dropped.push(DroppedPendingMessage {
813 message: evicted.message,
814 reason: DropReason::OverflowDropOldest.as_str(),
815 received_via: evicted.received_via,
816 });
817 evicted_any = true;
818 }
819 }
820 if !evicted_any {
821 self.record_eviction_failure(
822 QueueLimit::PerPeer,
823 sender,
824 &incoming_message_id,
825 "drop_oldest failed to evict per-peer oldest",
826 );
827 self.record_drop(
828 DropReason::OverflowDropNewest,
829 Some(QueueLimit::PerPeer),
830 sender,
831 &incoming_message_id,
832 overflow_policy,
833 );
834 return Admission::Refused;
835 }
836 }
837 }
838 }
839 let peer_len_after = self.queues.get(sender).map(VecDeque::len).unwrap_or(0);
840 if peer_len_after >= per_peer_limit {
841 self.record_eviction_failure(
842 QueueLimit::PerPeer,
843 sender,
844 &incoming_message_id,
845 "per-peer limit still saturated after eviction",
846 );
847 self.record_drop(
848 DropReason::OverflowDropNewest,
849 Some(QueueLimit::PerPeer),
850 sender,
851 &incoming_message_id,
852 overflow_policy,
853 );
854 return Admission::Refused;
855 }
856
857 if self.peer_bytes_for(sender) + incoming_bytes > per_peer_bytes_limit {
858 self.record_peer_overflow_pressure(sender, per_peer_limit);
859 if overflow_policy == OverflowPolicy::DropNewest {
860 self.record_drop(
861 DropReason::OverflowDropNewest,
862 Some(QueueLimit::PerPeerBytes),
863 sender,
864 &incoming_message_id,
865 overflow_policy,
866 );
867 return Admission::Refused;
868 }
869 while self.peer_bytes_for(sender) + incoming_bytes > per_peer_bytes_limit {
874 let evicted_sequence = self
875 .queues
876 .get(sender)
877 .and_then(|queue| queue.front().map(|entry| entry.sequence));
878 let evicted = evicted_sequence
879 .and_then(|sequence| self.remove_entry_by_sequence(sender, sequence));
880 match evicted {
881 Some(evicted) => {
882 self.record_drop(
883 DropReason::OverflowDropOldest,
884 Some(QueueLimit::PerPeerBytes),
885 &evicted.peer_id,
886 &evicted.message_id,
887 overflow_policy,
888 );
889 dropped.push(DroppedPendingMessage {
890 message: evicted.message,
891 reason: DropReason::OverflowDropOldest.as_str(),
892 received_via: evicted.received_via,
893 });
894 }
895 None => {
896 self.record_eviction_failure(
897 QueueLimit::PerPeerBytes,
898 sender,
899 &incoming_message_id,
900 "drop_oldest failed to evict per-peer oldest for byte budget",
901 );
902 self.record_drop(
903 DropReason::OverflowDropNewest,
904 Some(QueueLimit::PerPeerBytes),
905 sender,
906 &incoming_message_id,
907 overflow_policy,
908 );
909 return Admission::Refused;
910 }
911 }
912 }
913 }
914
915 if self.total >= global_limit {
916 warn!(
917 queue_size = self.total,
918 global_limit, "Pending encrypted queue at global pressure limit"
919 );
920 match overflow_policy {
921 OverflowPolicy::DropNewest => {
922 self.record_drop(
923 DropReason::OverflowDropNewest,
924 Some(QueueLimit::Global),
925 sender,
926 &incoming_message_id,
927 overflow_policy,
928 );
929 return Admission::Refused;
930 }
931 OverflowPolicy::DropOldest => {
932 while self.total >= global_limit {
933 match self.evict_global_oldest(
934 DropReason::OverflowDropOldest,
935 QueueLimit::Global,
936 overflow_policy,
937 ) {
938 Some(evicted) => dropped.push(DroppedPendingMessage {
939 message: evicted.message,
940 reason: DropReason::OverflowDropOldest.as_str(),
941 received_via: evicted.received_via,
942 }),
943 None => {
944 self.record_eviction_failure(
945 QueueLimit::Global,
946 sender,
947 &incoming_message_id,
948 "drop_oldest failed to evict global oldest",
949 );
950 break;
951 }
952 }
953 }
954 }
955 }
956 }
957 if self.total >= global_limit {
958 self.record_drop(
959 DropReason::OverflowDropNewest,
960 Some(QueueLimit::Global),
961 sender,
962 &incoming_message_id,
963 overflow_policy,
964 );
965 return Admission::Refused;
966 }
967
968 if self.total_bytes + incoming_bytes > global_bytes_limit {
969 warn!(
970 queue_bytes = self.total_bytes,
971 global_bytes_limit, "Pending encrypted queue at global byte budget"
972 );
973 if overflow_policy == OverflowPolicy::DropNewest {
974 self.record_drop(
975 DropReason::OverflowDropNewest,
976 Some(QueueLimit::GlobalBytes),
977 sender,
978 &incoming_message_id,
979 overflow_policy,
980 );
981 return Admission::Refused;
982 }
983 while self.total_bytes + incoming_bytes > global_bytes_limit {
984 match self.evict_global_oldest(
985 DropReason::OverflowDropOldest,
986 QueueLimit::GlobalBytes,
987 overflow_policy,
988 ) {
989 Some(evicted) => dropped.push(DroppedPendingMessage {
990 message: evicted.message,
991 reason: DropReason::OverflowDropOldest.as_str(),
992 received_via: evicted.received_via,
993 }),
994 None => {
995 self.record_eviction_failure(
996 QueueLimit::GlobalBytes,
997 sender,
998 &incoming_message_id,
999 "drop_oldest failed to evict global oldest for byte budget",
1000 );
1001 break;
1002 }
1003 }
1004 }
1005 if self.total_bytes + incoming_bytes > global_bytes_limit {
1006 self.record_drop(
1007 DropReason::OverflowDropNewest,
1008 Some(QueueLimit::GlobalBytes),
1009 sender,
1010 &incoming_message_id,
1011 overflow_policy,
1012 );
1013 return Admission::Refused;
1014 }
1015 }
1016
1017 let sequence = self.next_sequence();
1018 let entry = PendingDecryptMessage {
1019 peer_id: sender.to_string(),
1020 message_id: incoming_message_id.clone(),
1021 received_at: now,
1022 sequence,
1023 message: message.clone(),
1024 received_via: arrival_transport,
1025 };
1026
1027 self.queues
1028 .entry(sender.to_string())
1029 .or_default()
1030 .push_back(entry.clone());
1031 self.global_order.push_back(EntryRef {
1032 peer_id: sender.to_string(),
1033 message_id: entry.message_id.clone(),
1034 sequence,
1035 });
1036 self.live_sequences.insert(sequence);
1037 self.total = self.total.saturating_add(1);
1038 self.total_bytes = self.total_bytes.saturating_add(incoming_bytes);
1039 let peer_bytes = self.peer_bytes.entry(sender.to_string()).or_insert(0);
1040 *peer_bytes = peer_bytes.saturating_add(incoming_bytes);
1041 self.update_peer_gauge(sender);
1042 self.update_current_gauge();
1043 self.cleanup_global_order_front();
1044 self.verify_invariants("enqueue");
1045
1046 let peer_len_post_insert = self.queues.get(sender).map(VecDeque::len).unwrap_or(0);
1047 let over_global = self.total > global_limit || self.total_bytes > global_bytes_limit;
1048 let over_peer = peer_len_post_insert > per_peer_limit
1049 || self.peer_bytes_for(sender) > per_peer_bytes_limit;
1050 if over_global || over_peer {
1051 let _ = self.remove_entry_by_sequence(sender, sequence);
1052 let limit = if over_global {
1053 QueueLimit::Global
1054 } else {
1055 QueueLimit::PerPeer
1056 };
1057 self.record_eviction_failure(
1058 limit,
1059 sender,
1060 &incoming_message_id,
1061 "post-insert hard-bound check failed; rolled back enqueue",
1062 );
1063 self.record_drop(
1064 DropReason::OverflowDropNewest,
1065 Some(limit),
1066 sender,
1067 &incoming_message_id,
1068 overflow_policy,
1069 );
1070 return Admission::Refused;
1071 }
1072
1073 Admission::Admitted
1074 }
1075
1076 pub(crate) fn drain_for_peer(
1079 &mut self,
1080 config: &PendingQueueConfig,
1081 sender: &str,
1082 ) -> Vec<PendingDecryptMessage> {
1083 let now = Instant::now();
1084 let _ = self.prune_expired_for_peer(config, sender, now);
1085
1086 let messages = match self.queues.remove(sender) {
1087 Some(msgs) => msgs,
1088 None => return Vec::new(),
1089 };
1090
1091 if messages.is_empty() {
1092 return Vec::new();
1093 }
1094
1095 let drained: Vec<PendingDecryptMessage> = messages.into_iter().collect();
1096 for entry in &drained {
1097 self.live_sequences.remove(&entry.sequence);
1098 self.total = self.total.saturating_sub(1);
1099 self.total_bytes = self
1100 .total_bytes
1101 .saturating_sub(Self::message_bytes(&entry.message));
1102 }
1103 self.peer_bytes.remove(sender);
1104 self.peer_overflow_hits.remove(sender);
1108 self.update_peer_gauge(sender);
1109 self.update_current_gauge();
1110 self.cleanup_global_order_front();
1111 self.verify_invariants("drain_for_peer");
1112
1113 drained
1114 }
1115}