1use std::cmp::Ordering;
2use std::collections::BTreeSet;
3use std::convert::Infallible;
4use std::error::Error;
5use std::mem;
6use std::time::Duration;
7
8use chrono::{DateTime, Utc};
9use hashbrown::HashSet;
10use relay_base_schema::project::ProjectKey;
11use relay_config::ConfigSnapshot;
12use tokio::time::{Instant, timeout};
13
14use crate::envelope::Envelope;
15use crate::envelope::Item;
16use crate::services::buffer::common::ProjectKeyPair;
17use crate::services::buffer::envelope_stack::EnvelopeStack;
18use crate::services::buffer::envelope_stack::sqlite::SqliteEnvelopeStackError;
19use crate::services::buffer::envelope_store::sqlite::SqliteEnvelopeStoreError;
20use crate::services::buffer::stack_provider::memory::MemoryStackProvider;
21use crate::services::buffer::stack_provider::sqlite::SqliteStackProvider;
22use crate::services::buffer::stack_provider::{StackCreationType, StackProvider};
23use crate::statsd::{RelayDistributions, RelayGauges, RelayTimers};
24use crate::utils::MemoryChecker;
25
26#[derive(Debug)]
34#[allow(private_interfaces)]
35pub enum PolymorphicEnvelopeBuffer {
36 InMemory(EnvelopeBuffer<MemoryStackProvider>),
38 Sqlite(EnvelopeBuffer<SqliteStackProvider>),
40}
41
42impl PolymorphicEnvelopeBuffer {
43 pub fn is_memory(&self) -> bool {
45 match self {
46 Self::InMemory(_) => true,
47 Self::Sqlite(_) => false,
48 }
49 }
50
51 pub async fn from_config(
54 partition_id: u8,
55 config: &ConfigSnapshot,
56 memory_checker: MemoryChecker,
57 ) -> Result<Self, EnvelopeBufferError> {
58 let buffer = if config.spool_envelopes_path(partition_id).is_some() {
59 relay_log::trace!("PolymorphicEnvelopeBuffer: initializing sqlite envelope buffer");
60 let buffer = EnvelopeBuffer::<SqliteStackProvider>::new(partition_id, config).await?;
61 Self::Sqlite(buffer)
62 } else {
63 relay_log::trace!("PolymorphicEnvelopeBuffer: initializing memory envelope buffer");
64 let buffer = EnvelopeBuffer::<MemoryStackProvider>::new(partition_id, memory_checker);
65 Self::InMemory(buffer)
66 };
67
68 Ok(buffer)
69 }
70
71 pub async fn initialize(&mut self) {
73 match self {
74 PolymorphicEnvelopeBuffer::InMemory(buffer) => buffer.initialize().await,
75 PolymorphicEnvelopeBuffer::Sqlite(buffer) => buffer.initialize().await,
76 }
77 }
78
79 pub async fn push(&mut self, envelope: Box<Envelope>) -> Result<(), EnvelopeBufferError> {
81 relay_statsd::metric!(
82 distribution(RelayDistributions::BufferEnvelopeBodySize) =
83 envelope.items().map(Item::len).sum::<usize>() as u64,
84 partition_id = self.partition_tag()
85 );
86
87 relay_statsd::metric!(
88 timer(RelayTimers::BufferPush),
89 partition_id = self.partition_tag(),
90 {
91 match self {
92 Self::Sqlite(buffer) => buffer.push(envelope).await,
93 Self::InMemory(buffer) => buffer.push(envelope).await,
94 }
95 }
96 )
97 }
98
99 pub async fn peek(&mut self) -> Result<Peek, EnvelopeBufferError> {
101 relay_statsd::metric!(
102 timer(RelayTimers::BufferPeek),
103 partition_id = self.partition_tag(),
104 {
105 match self {
106 Self::Sqlite(buffer) => buffer.peek().await,
107 Self::InMemory(buffer) => buffer.peek().await,
108 }
109 }
110 )
111 }
112
113 pub async fn pop(&mut self) -> Result<Option<Box<Envelope>>, EnvelopeBufferError> {
115 relay_statsd::metric!(
116 timer(RelayTimers::BufferPop),
117 partition_id = self.partition_tag(),
118 {
119 match self {
120 Self::Sqlite(buffer) => buffer.pop().await,
121 Self::InMemory(buffer) => buffer.pop().await,
122 }
123 }
124 )
125 }
126
127 pub fn mark_ready(&mut self, project: &ProjectKey, is_ready: bool) -> bool {
132 relay_log::trace!(
133 project_key = project.as_str(),
134 "buffer marked {}",
135 if is_ready { "ready" } else { "not ready" }
136 );
137 match self {
138 Self::Sqlite(buffer) => buffer.mark_ready(project, is_ready),
139 Self::InMemory(buffer) => buffer.mark_ready(project, is_ready),
140 }
141 }
142
143 pub fn mark_seen(&mut self, project_key_pair: &ProjectKeyPair, next_fetch: Duration) {
149 match self {
150 Self::Sqlite(buffer) => buffer.mark_seen(project_key_pair, next_fetch),
151 Self::InMemory(buffer) => buffer.mark_seen(project_key_pair, next_fetch),
152 }
153 }
154
155 pub fn has_capacity(&self) -> bool {
157 match self {
158 Self::Sqlite(buffer) => buffer.has_capacity(),
159 Self::InMemory(buffer) => buffer.has_capacity(),
160 }
161 }
162
163 pub fn item_count(&self) -> u64 {
166 match self {
167 Self::Sqlite(buffer) => buffer.tracked_count,
168 Self::InMemory(buffer) => buffer.tracked_count,
169 }
170 }
171
172 pub fn total_size(&self) -> Option<u64> {
175 match self {
176 Self::Sqlite(buffer) => buffer.stack_provider.total_size(),
177 Self::InMemory(buffer) => buffer.stack_provider.total_size(),
178 }
179 }
180
181 pub async fn shutdown(&mut self) -> bool {
183 match self {
187 Self::Sqlite(buffer) if !buffer.stack_provider.ephemeral() => {
188 buffer.flush().await;
189 true
190 }
191 _ => {
192 relay_log::trace!("shutdown procedure not needed");
193 false
194 }
195 }
196 }
197
198 fn partition_tag(&self) -> &str {
200 match self {
201 PolymorphicEnvelopeBuffer::InMemory(buffer) => &buffer.partition_tag,
202 PolymorphicEnvelopeBuffer::Sqlite(buffer) => &buffer.partition_tag,
203 }
204 }
205}
206
207#[derive(Debug, thiserror::Error)]
209pub enum EnvelopeBufferError {
210 #[error("sqlite")]
211 SqliteStore(#[from] SqliteEnvelopeStoreError),
212
213 #[error("sqlite")]
214 SqliteStack(#[from] SqliteEnvelopeStackError),
215
216 #[error("failed to push envelope to the buffer")]
217 PushFailed,
218}
219
220impl From<Infallible> for EnvelopeBufferError {
221 fn from(value: Infallible) -> Self {
222 match value {}
223 }
224}
225
226#[derive(Debug)]
231struct EnvelopeBuffer<P: StackProvider> {
232 priority_queue: priority_queue::PriorityQueue<QueueItem<ProjectKeyPair, P::Stack>, Priority>,
234 stacks_by_project: hashbrown::HashMap<ProjectKey, BTreeSet<ProjectKeyPair>>,
236 stack_provider: P,
241 total_count: i64,
248 tracked_count: u64,
254 total_count_initialized: bool,
259 partition_tag: String,
261}
262
263impl EnvelopeBuffer<MemoryStackProvider> {
264 pub fn new(partition_id: u8, memory_checker: MemoryChecker) -> Self {
266 Self {
267 stacks_by_project: Default::default(),
268 priority_queue: Default::default(),
269 stack_provider: MemoryStackProvider::new(memory_checker),
270 total_count: 0,
271 tracked_count: 0,
272 total_count_initialized: false,
273 partition_tag: partition_id.to_string(),
274 }
275 }
276}
277
278#[allow(dead_code)]
279impl EnvelopeBuffer<SqliteStackProvider> {
280 pub async fn new(
282 partition_id: u8,
283 config: &ConfigSnapshot,
284 ) -> Result<Self, EnvelopeBufferError> {
285 Ok(Self {
286 stacks_by_project: Default::default(),
287 priority_queue: Default::default(),
288 stack_provider: SqliteStackProvider::new(partition_id, config).await?,
289 total_count: 0,
290 tracked_count: 0,
291 total_count_initialized: false,
292 partition_tag: partition_id.to_string(),
293 })
294 }
295}
296
297impl<P: StackProvider> EnvelopeBuffer<P>
298where
299 EnvelopeBufferError: From<<P::Stack as EnvelopeStack>::Error>,
300{
301 pub async fn initialize(&mut self) {
304 relay_statsd::metric!(
305 timer(RelayTimers::BufferInitialization),
306 partition_id = &self.partition_tag,
307 {
308 let initialization_state = self.stack_provider.initialize().await;
309 self.load_stacks(initialization_state.project_key_pairs)
310 .await;
311 self.load_store_total_count().await;
312 }
313 );
314 }
315
316 pub async fn push(&mut self, envelope: Box<Envelope>) -> Result<(), EnvelopeBufferError> {
321 let received_at = envelope.received_at();
322
323 let project_key_pair = ProjectKeyPair::from_envelope(&envelope);
324 if let Some((
325 QueueItem {
326 key: _,
327 value: stack,
328 },
329 _,
330 )) = self.priority_queue.get_mut(&project_key_pair)
331 {
332 stack.push(envelope).await?;
333 } else {
334 self.push_stack(
337 StackCreationType::New,
338 ProjectKeyPair::from_envelope(&envelope),
339 Some(envelope),
340 )
341 .await?;
342 }
343 self.priority_queue
344 .change_priority_by(&project_key_pair, |prio| {
345 prio.received_at = received_at;
346 });
347
348 self.total_count += 1;
349 self.tracked_count += 1;
350 self.track_total_count();
351
352 Ok(())
353 }
354
355 pub async fn peek(&mut self) -> Result<Peek, EnvelopeBufferError> {
357 let Some((
358 QueueItem {
359 key: project_key_pair,
360 value: stack,
361 },
362 Priority {
363 readiness,
364 next_project_fetch,
365 ..
366 },
367 )) = self.priority_queue.peek_mut()
368 else {
369 return Ok(Peek::Empty);
370 };
371
372 let ready = readiness.ready();
373
374 Ok(match (stack.peek().await?, ready) {
375 (None, _) => Peek::Empty,
376 (Some(last_received_at), true) => Peek::Ready {
377 project_key_pair: *project_key_pair,
378 last_received_at,
379 },
380 (Some(last_received_at), false) => Peek::NotReady {
381 project_key_pair: *project_key_pair,
382 next_project_fetch: *next_project_fetch,
383 last_received_at,
384 },
385 })
386 }
387
388 pub async fn pop(&mut self) -> Result<Option<Box<Envelope>>, EnvelopeBufferError> {
393 let Some((QueueItem { key, value: stack }, _)) = self.priority_queue.peek_mut() else {
394 return Ok(None);
395 };
396 let project_key_pair = *key;
397 let envelope = stack.pop().await?.expect("found an empty stack");
398
399 let last_received_at = stack.peek().await?;
400
401 match last_received_at {
402 None => {
403 self.pop_stack(project_key_pair);
404 }
405 Some(last_received_at) => {
406 self.priority_queue
407 .change_priority_by(&project_key_pair, |prio| {
408 prio.received_at = last_received_at;
409 });
410 }
411 }
412
413 self.total_count -= 1;
417 self.tracked_count = self.tracked_count.saturating_sub(1);
418 self.track_total_count();
419
420 Ok(Some(envelope))
421 }
422
423 pub fn mark_ready(&mut self, project: &ProjectKey, is_ready: bool) -> bool {
427 let mut changed = false;
428 if let Some(project_key_pairs) = self.stacks_by_project.get(project) {
429 for project_key_pair in project_key_pairs {
430 self.priority_queue
431 .change_priority_by(project_key_pair, |stack| {
432 let mut found = false;
433 for (subkey, readiness) in [
434 (
435 project_key_pair.own_key,
436 &mut stack.readiness.own_project_ready,
437 ),
438 (
439 project_key_pair.sampling_key,
440 &mut stack.readiness.sampling_project_ready,
441 ),
442 ] {
443 if subkey == *project {
444 found = true;
445 if *readiness != is_ready {
446 changed = true;
447 *readiness = is_ready;
448 }
449 }
450 }
451 debug_assert!(found);
452 });
453 }
454 }
455
456 changed
457 }
458
459 pub fn mark_seen(&mut self, project_key_pair: &ProjectKeyPair, next_fetch: Duration) {
465 self.priority_queue
466 .change_priority_by(project_key_pair, |stack| {
467 stack.next_project_fetch = Instant::now() + next_fetch;
470 });
471 }
472
473 pub fn has_capacity(&self) -> bool {
475 self.stack_provider.has_store_capacity()
476 }
477
478 pub async fn flush(&mut self) {
480 let priority_queue = mem::take(&mut self.priority_queue);
481 self.stack_provider
482 .flush(priority_queue.into_iter().map(|(q, _)| q.value))
483 .await;
484 }
485
486 async fn push_stack(
488 &mut self,
489 stack_creation_type: StackCreationType,
490 project_key_pair: ProjectKeyPair,
491 envelope: Option<Box<Envelope>>,
492 ) -> Result<(), EnvelopeBufferError> {
493 let received_at = envelope.as_ref().map_or(Utc::now(), |e| e.received_at());
494
495 let mut stack = self
496 .stack_provider
497 .create_stack(stack_creation_type, project_key_pair);
498 if let Some(envelope) = envelope {
499 stack.push(envelope).await?;
500 }
501
502 let previous_entry = self.priority_queue.push(
503 QueueItem {
504 key: project_key_pair,
505 value: stack,
506 },
507 Priority::new(received_at),
508 );
509 debug_assert!(previous_entry.is_none());
510 for project_key in project_key_pair.iter() {
511 self.stacks_by_project
512 .entry(project_key)
513 .or_default()
514 .insert(project_key_pair);
515 }
516 relay_statsd::metric!(
517 gauge(RelayGauges::BufferStackCount) = self.priority_queue.len() as u64,
518 partition_id = &self.partition_tag
519 );
520
521 Ok(())
522 }
523
524 fn pop_stack(&mut self, project_key_pair: ProjectKeyPair) {
526 for project_key in project_key_pair.iter() {
527 self.stacks_by_project
528 .get_mut(&project_key)
529 .expect("project_key is missing from lookup")
530 .remove(&project_key_pair);
531 }
532 self.priority_queue.remove(&project_key_pair);
533
534 relay_statsd::metric!(
535 gauge(RelayGauges::BufferStackCount) = self.priority_queue.len() as u64,
536 partition_id = &self.partition_tag
537 );
538 }
539
540 async fn load_stacks(&mut self, project_key_pairs: HashSet<ProjectKeyPair>) {
542 for project_key_pair in project_key_pairs {
543 self.push_stack(StackCreationType::Initialization, project_key_pair, None)
544 .await
545 .expect("Pushing an empty stack raised an error");
546 }
547 }
548
549 async fn load_store_total_count(&mut self) {
555 let total_count = timeout(Duration::from_secs(1), async {
556 self.stack_provider.store_total_count().await
557 })
558 .await;
559 match total_count {
560 Ok(total_count) => {
561 self.total_count = total_count as i64;
562 self.total_count_initialized = true;
563 }
564 Err(error) => {
565 self.total_count_initialized = false;
566 relay_log::error!(
567 error = &error as &dyn Error,
568 "failed to load the total envelope count of the store",
569 );
570 }
571 };
572 self.track_total_count();
573 }
574
575 fn track_total_count(&self) {
577 let total_count = self.total_count as f64;
578 let initialized = match self.total_count_initialized {
579 true => "true",
580 false => "false",
581 };
582 relay_statsd::metric!(
583 gauge(RelayGauges::BufferEnvelopesCount) = total_count,
584 initialized = initialized,
585 stack_type = self.stack_provider.stack_type(),
586 partition_id = &self.partition_tag
587 );
588 }
589}
590
591pub enum Peek {
593 Empty,
594 Ready {
595 project_key_pair: ProjectKeyPair,
596 last_received_at: DateTime<Utc>,
597 },
598 NotReady {
599 project_key_pair: ProjectKeyPair,
600 next_project_fetch: Instant,
601 last_received_at: DateTime<Utc>,
602 },
603}
604
605impl Peek {
606 pub fn last_received_at(&self) -> Option<DateTime<Utc>> {
607 match self {
608 Self::Empty => None,
609 Self::Ready {
610 last_received_at, ..
611 }
612 | Self::NotReady {
613 last_received_at, ..
614 } => Some(*last_received_at),
615 }
616 }
617}
618
619#[derive(Debug)]
620struct QueueItem<K, V> {
621 key: K,
622 value: V,
623}
624
625impl<K, V> std::borrow::Borrow<K> for QueueItem<K, V> {
626 fn borrow(&self) -> &K {
627 &self.key
628 }
629}
630
631impl<K: std::hash::Hash, V> std::hash::Hash for QueueItem<K, V> {
632 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
633 self.key.hash(state);
634 }
635}
636
637impl<K: PartialEq, V> PartialEq for QueueItem<K, V> {
638 fn eq(&self, other: &Self) -> bool {
639 self.key == other.key
640 }
641}
642
643impl<K: PartialEq, V> Eq for QueueItem<K, V> {}
644
645#[derive(Debug, Clone)]
646struct Priority {
647 readiness: Readiness,
648 received_at: DateTime<Utc>,
649 next_project_fetch: Instant,
650}
651
652impl Priority {
653 fn new(received_at: DateTime<Utc>) -> Self {
654 Self {
655 readiness: Readiness::new(),
656 received_at,
657 next_project_fetch: Instant::now(),
658 }
659 }
660}
661
662impl Ord for Priority {
663 fn cmp(&self, other: &Self) -> Ordering {
664 match (self.readiness.ready(), other.readiness.ready()) {
665 (true, true) => self.received_at.cmp(&other.received_at),
669 (true, false) => Ordering::Greater,
670 (false, true) => Ordering::Less,
671 (false, false) => self
674 .next_project_fetch
675 .cmp(&other.next_project_fetch)
676 .reverse()
677 .then(self.received_at.cmp(&other.received_at).reverse()),
678 }
679 }
680}
681
682impl PartialOrd for Priority {
683 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
684 Some(self.cmp(other))
685 }
686}
687
688impl PartialEq for Priority {
689 fn eq(&self, other: &Self) -> bool {
690 self.cmp(other).is_eq()
691 }
692}
693
694impl Eq for Priority {}
695
696#[derive(Debug, Clone, Copy)]
697struct Readiness {
698 own_project_ready: bool,
699 sampling_project_ready: bool,
700}
701
702impl Readiness {
703 fn new() -> Self {
704 Self {
707 own_project_ready: true,
708 sampling_project_ready: true,
709 }
710 }
711
712 fn ready(&self) -> bool {
713 self.own_project_ready && self.sampling_project_ready
714 }
715}
716
717#[cfg(test)]
718mod tests {
719 use relay_base_schema::project::ProjectId;
720 use relay_common::Dsn;
721 use relay_config::Config;
722 use relay_event_schema::protocol::EventId;
723 use relay_sampling::DynamicSamplingContext;
724 use std::str::FromStr;
725 use std::sync::Arc;
726 use uuid::Uuid;
727
728 use crate::SqliteEnvelopeStore;
729 use crate::envelope::{Item, ItemType};
730 use crate::extractors::RequestMeta;
731 use crate::services::buffer::common::ProjectKeyPair;
732 use crate::services::buffer::envelope_store::sqlite::DatabaseEnvelope;
733 use crate::services::buffer::testutils::utils::mock_envelopes;
734 use crate::utils::MemoryStat;
735
736 use super::*;
737
738 impl Peek {
739 fn is_empty(&self) -> bool {
740 matches!(self, Peek::Empty)
741 }
742 }
743
744 fn new_envelope(
745 own_key: ProjectKey,
746 sampling_key: Option<ProjectKey>,
747 event_id: Option<EventId>,
748 ) -> Box<Envelope> {
749 let mut envelope = Envelope::from_request(
750 None,
751 RequestMeta::new(Dsn::from_str(&format!("http://{own_key}@localhost/1")).unwrap()),
752 );
753 if let Some(sampling_key) = sampling_key {
754 envelope.set_dsc(DynamicSamplingContext {
755 public_key: sampling_key,
756 project_id: Some(ProjectId::new(42)),
757 trace_id: "67e5504410b1426f9247bb680e5fe0c8".parse().unwrap(),
758 release: None,
759 user: Default::default(),
760 replay_id: None,
761 environment: None,
762 transaction: None,
763 sample_rate: None,
764 sampled: None,
765 other: Default::default(),
766 });
767 envelope.add_item(Item::new(ItemType::Transaction));
768 }
769 if let Some(event_id) = event_id {
770 envelope.set_event_id(event_id);
771 }
772 envelope
773 }
774
775 fn mock_config(path: &str) -> Arc<Config> {
776 Config::from_json_value(serde_json::json!({
777 "spool": {
778 "envelopes": {
779 "path": path
780 }
781 }
782 }))
783 .unwrap()
784 .into()
785 }
786
787 fn mock_memory_checker() -> MemoryChecker {
788 MemoryChecker::new(MemoryStat::default(), mock_config("my/db/path").clone())
789 }
790
791 async fn peek_received_at(buffer: &mut EnvelopeBuffer<MemoryStackProvider>) -> DateTime<Utc> {
792 buffer.peek().await.unwrap().last_received_at().unwrap()
793 }
794
795 #[tokio::test]
796 async fn test_insert_pop() {
797 let mut buffer = EnvelopeBuffer::<MemoryStackProvider>::new(0, mock_memory_checker());
798
799 let project_key1 = ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fed").unwrap();
800 let project_key2 = ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fee").unwrap();
801 let project_key3 = ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fef").unwrap();
802
803 assert!(buffer.pop().await.unwrap().is_none());
804 assert!(buffer.peek().await.unwrap().is_empty());
805
806 let envelope1 = new_envelope(project_key1, None, None);
807 let time1 = envelope1.meta().received_at();
808 buffer.push(envelope1).await.unwrap();
809
810 let envelope2 = new_envelope(project_key2, None, None);
811 let time2 = envelope2.meta().received_at();
812 buffer.push(envelope2).await.unwrap();
813
814 assert_eq!(peek_received_at(&mut buffer).await, time2);
816
817 buffer.mark_ready(&project_key1, false);
818 buffer.mark_ready(&project_key2, false);
819
820 assert_eq!(peek_received_at(&mut buffer).await, time1);
822
823 let envelope3 = new_envelope(project_key3, None, None);
824 let time3 = envelope3.meta().received_at();
825 buffer.push(envelope3).await.unwrap();
826 buffer.mark_ready(&project_key3, false);
827
828 assert_eq!(peek_received_at(&mut buffer).await, time1);
830
831 buffer.mark_ready(&project_key3, true);
833 assert_eq!(peek_received_at(&mut buffer).await, time3);
834 assert_eq!(
835 buffer.pop().await.unwrap().unwrap().meta().public_key(),
836 project_key3
837 );
838
839 assert_eq!(peek_received_at(&mut buffer).await, time1);
841
842 buffer.mark_ready(&project_key1, true);
844 assert_eq!(peek_received_at(&mut buffer).await, time1);
845
846 buffer.mark_ready(&project_key2, true);
848 assert_eq!(peek_received_at(&mut buffer).await, time2);
849 assert_eq!(
850 buffer.pop().await.unwrap().unwrap().meta().public_key(),
851 project_key2
852 );
853
854 assert_eq!(
856 buffer.pop().await.unwrap().unwrap().meta().public_key(),
857 project_key1
858 );
859 assert!(buffer.pop().await.unwrap().is_none());
860 assert!(buffer.peek().await.unwrap().is_empty());
861 }
862
863 #[tokio::test]
864 async fn test_project_internal_order() {
865 let mut buffer = EnvelopeBuffer::<MemoryStackProvider>::new(0, mock_memory_checker());
866
867 let project_key = ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fed").unwrap();
868
869 let envelope1 = new_envelope(project_key, None, None);
870 let time1 = envelope1.meta().received_at();
871 let envelope2 = new_envelope(project_key, None, None);
872 let time2 = envelope2.meta().received_at();
873
874 assert!(time2 > time1);
875
876 buffer.push(envelope1).await.unwrap();
877 buffer.push(envelope2).await.unwrap();
878
879 assert_eq!(
880 buffer.pop().await.unwrap().unwrap().meta().received_at(),
881 time2
882 );
883 assert_eq!(
884 buffer.pop().await.unwrap().unwrap().meta().received_at(),
885 time1
886 );
887 assert!(buffer.pop().await.unwrap().is_none());
888 }
889
890 #[tokio::test]
891 async fn test_sampling_projects() {
892 let mut buffer = EnvelopeBuffer::<MemoryStackProvider>::new(0, mock_memory_checker());
893
894 let project_key1 = ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fed").unwrap();
895 let project_key2 = ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fef").unwrap();
896
897 let envelope1 = new_envelope(project_key1, None, None);
898 let time1 = envelope1.received_at();
899 buffer.push(envelope1).await.unwrap();
900
901 let envelope2 = new_envelope(project_key2, None, None);
902 let time2 = envelope2.received_at();
903 buffer.push(envelope2).await.unwrap();
904
905 let envelope3 = new_envelope(project_key1, Some(project_key2), None);
906 let time3 = envelope3.meta().received_at();
907 buffer.push(envelope3).await.unwrap();
908
909 buffer.mark_ready(&project_key1, false);
910 buffer.mark_ready(&project_key2, false);
911
912 assert_eq!(
914 buffer.peek().await.unwrap().last_received_at().unwrap(),
915 time1
916 );
917
918 buffer.mark_ready(&project_key2, true);
920 assert_eq!(
921 buffer.peek().await.unwrap().last_received_at().unwrap(),
922 time2
923 );
924
925 buffer.mark_ready(&project_key2, false);
927 assert_eq!(
928 buffer.peek().await.unwrap().last_received_at().unwrap(),
929 time1
930 );
931
932 buffer.mark_ready(&project_key1, true);
934 assert_eq!(
935 buffer.peek().await.unwrap().last_received_at().unwrap(),
936 time1
937 );
938
939 buffer.mark_ready(&project_key2, true);
941 assert_eq!(
942 buffer.pop().await.unwrap().unwrap().meta().received_at(),
943 time3
944 );
945 assert_eq!(
946 buffer.peek().await.unwrap().last_received_at().unwrap(),
947 time2
948 );
949
950 buffer.mark_ready(&project_key2, false);
951 assert_eq!(buffer.pop().await.unwrap().unwrap().received_at(), time1);
952 assert_eq!(buffer.pop().await.unwrap().unwrap().received_at(), time2);
953
954 assert!(buffer.pop().await.unwrap().is_none());
955 }
956
957 #[tokio::test]
958 async fn test_project_keys_distinct() {
959 let project_key1 = ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fed").unwrap();
960 let project_key2 = ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fef").unwrap();
961
962 let project_key_pair1 = ProjectKeyPair::new(project_key1, project_key2);
963 let project_key_pair2 = ProjectKeyPair::new(project_key2, project_key1);
964
965 assert_ne!(project_key_pair1, project_key_pair2);
966
967 let mut buffer = EnvelopeBuffer::<MemoryStackProvider>::new(0, mock_memory_checker());
968 buffer
969 .push(new_envelope(project_key1, Some(project_key2), None))
970 .await
971 .unwrap();
972 buffer
973 .push(new_envelope(project_key2, Some(project_key1), None))
974 .await
975 .unwrap();
976 assert_eq!(buffer.priority_queue.len(), 2);
977 }
978
979 #[test]
980 fn test_total_order() {
981 let p1 = Priority {
982 readiness: Readiness {
983 own_project_ready: true,
984 sampling_project_ready: true,
985 },
986 received_at: Utc::now(),
987 next_project_fetch: Instant::now(),
988 };
989 let mut p2 = p1.clone();
990 p2.next_project_fetch += Duration::from_millis(1);
991
992 assert_eq!(p1.cmp(&p2), Ordering::Equal);
994 assert_eq!(p1, p2);
995 }
996
997 #[tokio::test]
998 async fn test_last_peek_internal_order() {
999 let mut buffer = EnvelopeBuffer::<MemoryStackProvider>::new(0, mock_memory_checker());
1000
1001 let project_key_1 = ProjectKey::parse("a94ae32be2584e0bbd7a4cbb95971fed").unwrap();
1002 let event_id_1 = EventId::new();
1003 let envelope1 = new_envelope(project_key_1, None, Some(event_id_1));
1004 let time1 = envelope1.received_at();
1005
1006 let project_key_2 = ProjectKey::parse("b56ae32be2584e0bbd7a4cbb95971fed").unwrap();
1007 let event_id_2 = EventId::new();
1008 let envelope2 = new_envelope(project_key_2, None, Some(event_id_2));
1009 let time2 = envelope2.received_at();
1010
1011 buffer.push(envelope1).await.unwrap();
1012 buffer.push(envelope2).await.unwrap();
1013
1014 buffer.mark_ready(&project_key_1, false);
1015 buffer.mark_ready(&project_key_2, false);
1016
1017 let Peek::NotReady {
1019 last_received_at, ..
1020 } = buffer.peek().await.unwrap()
1021 else {
1022 panic!();
1023 };
1024 assert_eq!(last_received_at, time1);
1025
1026 let Peek::NotReady {
1028 last_received_at,
1029 project_key_pair,
1030 ..
1031 } = buffer.peek().await.unwrap()
1032 else {
1033 panic!();
1034 };
1035 assert_eq!(last_received_at, time1);
1036 assert_ne!(last_received_at, time2);
1037
1038 buffer.mark_seen(&project_key_pair, Duration::ZERO);
1039
1040 let Peek::NotReady {
1042 last_received_at, ..
1043 } = buffer.peek().await.unwrap()
1044 else {
1045 panic!();
1046 };
1047 assert_eq!(last_received_at, time2);
1048 assert_ne!(last_received_at, time1);
1049
1050 let Peek::NotReady {
1051 last_received_at,
1052 project_key_pair,
1053 ..
1054 } = buffer.peek().await.unwrap()
1055 else {
1056 panic!();
1057 };
1058 assert_eq!(last_received_at, time2);
1059 assert_ne!(last_received_at, time1);
1060
1061 buffer.mark_seen(&project_key_pair, Duration::ZERO);
1062
1063 let Peek::NotReady {
1065 last_received_at, ..
1066 } = buffer.peek().await.unwrap()
1067 else {
1068 panic!();
1069 };
1070 assert_eq!(last_received_at, time1);
1071 assert_ne!(last_received_at, time2);
1072 }
1073
1074 #[tokio::test]
1075 async fn test_initialize_buffer() {
1076 let path = std::env::temp_dir()
1077 .join(Uuid::new_v4().to_string())
1078 .into_os_string()
1079 .into_string()
1080 .unwrap();
1081 let config = mock_config(&path);
1082 let current_config = config.current();
1083 let mut store = SqliteEnvelopeStore::prepare(0, ¤t_config)
1084 .await
1085 .unwrap();
1086 let mut buffer = EnvelopeBuffer::<SqliteStackProvider>::new(0, ¤t_config)
1087 .await
1088 .unwrap();
1089
1090 let envelopes = mock_envelopes(10);
1093 assert!(
1094 store
1095 .insert_batch(
1096 envelopes
1097 .into_iter()
1098 .map(|e| DatabaseEnvelope::try_from(e.as_ref()).unwrap())
1099 .collect::<Vec<_>>()
1100 .try_into()
1101 .unwrap()
1102 )
1103 .await
1104 .is_ok()
1105 );
1106
1107 assert!(buffer.priority_queue.is_empty());
1109 assert!(buffer.stacks_by_project.is_empty());
1110
1111 buffer.initialize().await;
1112
1113 assert_eq!(buffer.priority_queue.len(), 1);
1116 assert_eq!(buffer.stacks_by_project.len(), 2);
1119 }
1120}