Skip to main content

relay_server/managed/
managed.rs

1#[cfg(debug_assertions)]
2use std::collections::BTreeMap;
3use std::convert::Infallible;
4use std::fmt;
5use std::iter::FusedIterator;
6use std::mem::ManuallyDrop;
7use std::net::IpAddr;
8use std::sync::Arc;
9use std::sync::atomic::{AtomicBool, Ordering};
10
11use chrono::{DateTime, Utc};
12use itertools::Either;
13use relay_event_schema::protocol::EventId;
14use relay_quotas::{DataCategory, Scoping};
15use relay_system::Addr;
16use smallvec::SmallVec;
17
18use crate::Envelope;
19use crate::endpoints::common::BadStoreRequest;
20use crate::extractors::RequestMeta;
21use crate::managed::{Counted, ManagedEnvelope, Quantities};
22use crate::services::outcome::{DiscardReason, Outcome, TrackOutcome};
23use crate::services::processor::ProcessingError;
24
25#[cfg(debug_assertions)]
26mod debug;
27#[cfg(test)]
28mod test;
29
30#[cfg(test)]
31pub use self::test::*;
32
33/// An error which can be extracted into an outcome.
34pub trait OutcomeError {
35    /// Produced error, without attached outcome.
36    type Error;
37
38    /// Consumes the error and returns an outcome and [`Self::Error`].
39    ///
40    /// Returning a `None` outcome should discard the item(s) silently.
41    fn consume(self) -> (Option<Outcome>, Self::Error);
42}
43
44impl OutcomeError for Outcome {
45    type Error = ();
46
47    fn consume(self) -> (Option<Outcome>, Self::Error) {
48        (self, ()).consume()
49    }
50}
51
52impl OutcomeError for Option<Outcome> {
53    type Error = ();
54
55    fn consume(self) -> (Option<Outcome>, Self::Error) {
56        (self, ()).consume()
57    }
58}
59
60impl<E> OutcomeError for (Outcome, E) {
61    type Error = E;
62
63    fn consume(self) -> (Option<Outcome>, Self::Error) {
64        (Some(self.0), self.1)
65    }
66}
67
68impl<E> OutcomeError for (Option<Outcome>, E) {
69    type Error = E;
70
71    fn consume(self) -> (Option<Outcome>, Self::Error) {
72        self
73    }
74}
75
76impl OutcomeError for ProcessingError {
77    type Error = Self;
78
79    fn consume(self) -> (Option<Outcome>, Self::Error) {
80        (self.to_outcome(), self)
81    }
82}
83
84impl OutcomeError for Infallible {
85    type Error = Self;
86
87    fn consume(self) -> (Option<Outcome>, Self::Error) {
88        match self {}
89    }
90}
91
92impl OutcomeError for BadStoreRequest {
93    type Error = Self;
94
95    fn consume(self) -> (Option<Outcome>, Self) {
96        (self.to_outcome(), self)
97    }
98}
99
100/// A wrapper type which ensures outcomes have been emitted for an error.
101///
102/// [`Managed`] wraps an error in [`Rejected`] once outcomes for have been emitted for the managed
103/// item.
104#[derive(Debug, Clone, Copy)]
105#[must_use = "a rejection must be propagated"]
106pub struct Rejected<T>(T);
107
108impl<T> Rejected<T> {
109    /// Extracts the underlying error.
110    pub fn into_inner(self) -> T {
111        self.0
112    }
113
114    /// Maps the rejected error to a different error.
115    pub fn map<F, S>(self, f: F) -> Rejected<S>
116    where
117        F: FnOnce(T) -> S,
118    {
119        Rejected(f(self.0))
120    }
121}
122
123impl<T> std::error::Error for Rejected<T>
124where
125    T: std::error::Error,
126{
127    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
128        self.0.source()
129    }
130}
131
132impl<T> std::fmt::Display for Rejected<T>
133where
134    T: std::fmt::Display,
135{
136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137        self.0.fmt(f)
138    }
139}
140
141impl<T> axum::response::IntoResponse for Rejected<T>
142where
143    T: axum::response::IntoResponse,
144{
145    fn into_response(self) -> axum::response::Response {
146        self.0.into_response()
147    }
148}
149
150/// The [`Managed`] wrapper ensures outcomes are correctly emitted for the contained item.
151pub struct Managed<T: Counted> {
152    value: T,
153    meta: Arc<Meta>,
154    done: AtomicBool,
155}
156
157impl Managed<Box<Envelope>> {
158    /// Creates a managed instance from an unmanaged envelope.
159    pub fn from_envelope(envelope: Box<Envelope>, outcome_aggregator: Addr<TrackOutcome>) -> Self {
160        let meta = Arc::new(Meta {
161            outcome_aggregator,
162            received_at: envelope.received_at(),
163            scoping: envelope.meta().get_partial_scoping().into_scoping(),
164            event_id: envelope.event_id(),
165            remote_addr: envelope.meta().remote_addr(),
166        });
167
168        Self::from_parts(envelope, meta)
169    }
170}
171
172/// Helper trait to abstract over `Vec` and `SmallVec` in [`Managed::retain`].
173pub trait RetainMut<I> {
174    /// Retains only the elements specified by the predicate.
175    fn retain_mut(&mut self, f: impl FnMut(&mut I) -> bool);
176}
177
178impl<I> RetainMut<I> for Vec<I> {
179    fn retain_mut(&mut self, f: impl FnMut(&mut I) -> bool) {
180        Vec::retain_mut(self, f)
181    }
182}
183impl<I, const N: usize> RetainMut<I> for SmallVec<[I; N]> {
184    fn retain_mut(&mut self, f: impl FnMut(&mut I) -> bool) {
185        SmallVec::retain_mut(self, f)
186    }
187}
188
189impl<T: Counted> Managed<T> {
190    /// Creates new [`Managed`] instance with the provided `value` and metadata from a [`ManagedEnvelope`].
191    ///
192    /// The [`Managed`] instance, inherits all metadata from the passed [`ManagedEnvelope`],
193    /// like received time or scoping.
194    pub fn with_meta_from_managed_envelope(envelope: &ManagedEnvelope, value: T) -> Self {
195        Self::from_parts(
196            value,
197            Arc::new(Meta {
198                outcome_aggregator: envelope.outcome_aggregator().clone(),
199                received_at: envelope.received_at(),
200                scoping: envelope.scoping(),
201                event_id: envelope.envelope().event_id(),
202                remote_addr: envelope.meta().remote_addr(),
203            }),
204        )
205    }
206
207    /// Creates new [`Managed`] instance with the provided `value` and metadata from `request_meta`.
208    pub fn with_meta_from_request_meta(
209        request_meta: &RequestMeta,
210        outcome_aggregator: &Addr<TrackOutcome>,
211        value: T,
212    ) -> Self {
213        Self::from_parts(
214            value,
215            Arc::new(Meta {
216                outcome_aggregator: outcome_aggregator.clone(),
217                received_at: request_meta.received_at(),
218                scoping: request_meta.get_partial_scoping().into_scoping(),
219                event_id: None,
220                remote_addr: request_meta.remote_addr(),
221            }),
222        )
223    }
224
225    /// Creates another [`Managed`] instance, with a new value but shared metadata.
226    pub fn wrap<S>(&self, other: S) -> Managed<S>
227    where
228        S: Counted,
229    {
230        Managed::from_parts(other, Arc::clone(&self.meta))
231    }
232
233    /// Boxes the contained value.
234    pub fn boxed(self) -> Managed<Box<T>> {
235        self.map(|value, _| Box::new(value))
236    }
237
238    /// Original received timestamp.
239    pub fn received_at(&self) -> DateTime<Utc> {
240        self.meta.received_at
241    }
242
243    /// Scoping information stored in this context.
244    pub fn scoping(&self) -> Scoping {
245        self.meta.scoping
246    }
247
248    /// Optional remote addr from where the data was received.
249    pub fn remote_addr(&self) -> Option<IpAddr> {
250        self.meta.remote_addr
251    }
252
253    /// Updates the scoping stored in this context.
254    ///
255    /// Special care has to be taken when items contained in the managed instance also store a
256    /// scoping. Such a scoping will **not** be updated.
257    ///
258    /// Conversions between `Managed<Box<Envelope>>` and `ManagedEnvelope` transfer the scoping
259    /// correctly.
260    ///
261    /// See also: [`ManagedEnvelope::scope`].
262    pub fn scope(&mut self, scoping: Scoping) {
263        let meta = Arc::make_mut(&mut self.meta);
264        meta.scoping = scoping;
265    }
266
267    /// Merge [`Self`] with another [`Managed`] instance using a mapping function.
268    ///
269    /// The caller's closure is expected to merge `other`'s inner value into `self`'s inner value.
270    /// The outcome records of `self` are automatically offset by the records of `other`.
271    pub fn merge_with<S, F>(&mut self, other: Managed<S>, f: F)
272    where
273        S: Counted,
274        F: FnOnce(&mut T, S, &mut RecordKeeper),
275    {
276        self.modify(|s, records| {
277            for (category, quantity) in other.quantities() {
278                records.modify_by(category, quantity as isize);
279            }
280            other.accept(|o| f(s, o, records));
281        })
282    }
283    /// Zips two managed instances into one managed tuple.
284    ///
285    /// The returned instance uses the metadata from `first`. `second` is accepted, transferring
286    /// outcome responsibility to the merged instance.
287    pub fn zip<S>(first: Self, second: Managed<S>) -> Managed<(T, S)>
288    where
289        S: Counted,
290    {
291        debug_assert_eq!(
292            first.scoping(),
293            second.scoping(),
294            "cannot zip Managed values with different metadata"
295        );
296        first.map(|first, records| {
297            for (category, quantity) in second.quantities() {
298                records.modify_by(category, quantity as isize);
299            }
300            let second = second.accept(|second| second);
301            (first, second)
302        })
303    }
304
305    /// Splits [`Self`] into two other [`Managed`] items.
306    ///
307    /// The two resulting managed instances together are expected to have the same outcomes as the original instance..
308    /// Since splitting may introduce a new type of item, which some of the original
309    /// quantities are transferred to, there may be new additional data categories created.
310    pub fn split_once<F, S, U>(self, f: F) -> (Managed<S>, Managed<U>)
311    where
312        F: FnOnce(T, &mut RecordKeeper) -> (S, U),
313        S: Counted,
314        U: Counted,
315    {
316        debug_assert!(!self.is_done());
317
318        let (value, meta) = self.destructure();
319        let quantities = value.quantities();
320
321        let mut records = RecordKeeper::new(&meta, quantities);
322
323        let (a, b) = f(value, &mut records);
324
325        let mut quantities = a.quantities();
326        quantities.extend(b.quantities());
327        records.success(quantities);
328
329        (
330            Managed::from_parts(a, Arc::clone(&meta)),
331            Managed::from_parts(b, meta),
332        )
333    }
334
335    /// Splits [`Self`] into a variable amount if individual items.
336    ///
337    /// Useful when the current instance contains multiple items of the same type
338    /// and must be split into individually managed items.
339    pub fn split<F, I, S>(self, f: F) -> Split<I::IntoIter, I::Item>
340    where
341        F: FnOnce(T) -> I,
342        I: IntoIterator<Item = S>,
343        S: Counted,
344    {
345        self.split_with_context(|value| (f(value), ())).0
346    }
347
348    /// Splits [`Self`] into a variable amount if individual items.
349    ///
350    /// Like [`Self::split`] but also allows returning an untracked context,
351    /// a way of returning additional data when deconstructing the original item.
352    pub fn split_with_context<F, I, S, C>(self, f: F) -> (Split<I::IntoIter, I::Item>, C)
353    where
354        F: FnOnce(T) -> (I, C),
355        I: IntoIterator<Item = S>,
356        S: Counted,
357    {
358        debug_assert!(!self.is_done());
359
360        let (value, meta) = self.destructure();
361        #[cfg(debug_assertions)]
362        let quantities = value.quantities();
363
364        let (items, context) = f(value);
365
366        (
367            Split {
368                #[cfg(debug_assertions)]
369                quantities,
370                items: items.into_iter(),
371                meta,
372                exhausted: false,
373            },
374            context,
375        )
376    }
377
378    /// Filters individual items and emits outcomes for them if they are removed.
379    ///
380    /// This is particularly useful when the managed instance is a container of individual items,
381    /// which need to be processed or filtered on a case by case basis.
382    ///
383    /// # Examples:
384    ///
385    /// ```
386    /// # use relay_server::managed::{Counted, Managed, Quantities};
387    /// # #[derive(Copy, Clone)]
388    /// # struct Context<'a>(&'a u32);
389    /// # struct Item;
390    /// struct Items {
391    ///     items: Vec<Item>,
392    /// }
393    /// # impl Counted for Items {
394    /// #   fn quantities(&self) -> Quantities {
395    /// #       todo!()
396    /// #   }
397    /// # }
398    /// # impl Counted for Item {
399    /// #   fn quantities(&self) -> Quantities {
400    /// #       todo!()
401    /// #   }
402    /// # }
403    /// # type Error = std::convert::Infallible;
404    ///
405    /// fn process_items(items: &mut Managed<Items>, ctx: Context<'_>) {
406    ///     items.retain(|items| &mut items.items, |item, _| process(item, ctx));
407    /// }
408    ///
409    /// fn process(item: &mut Item, ctx: Context<'_>) -> Result<(), Error> {
410    ///     todo!()
411    /// }
412    /// ```
413    pub fn retain<S, I, U, E, V>(&mut self, select: S, mut retain: U)
414    where
415        S: FnOnce(&mut T) -> &mut V,
416        I: Counted,
417        U: FnMut(&mut I, &mut RecordKeeper<'_>) -> Result<(), E>,
418        E: OutcomeError,
419        V: RetainMut<I>,
420    {
421        self.retain_with_context(
422            |inner| (select(inner), &()),
423            |item, _, records| retain(item, records),
424        );
425    }
426
427    /// Filters individual items and emits outcomes for them if they are removed.
428    ///
429    /// Like [`Self::retain`], but it allows for an additional context extracted from the managed
430    /// object passed to the retain function.
431    ///
432    /// # Examples:
433    ///
434    /// ```
435    /// # use relay_server::managed::{Counted, Managed, Quantities};
436    /// # #[derive(Copy, Clone)]
437    /// # struct Context<'a>(&'a u32);
438    /// # struct Item;
439    /// struct Items {
440    ///     ty: String,
441    ///     items: Vec<Item>,
442    /// }
443    /// # impl Counted for Items {
444    /// #   fn quantities(&self) -> Quantities {
445    /// #       todo!()
446    /// #   }
447    /// # }
448    /// # impl Counted for Item {
449    /// #   fn quantities(&self) -> Quantities {
450    /// #       todo!()
451    /// #   }
452    /// # }
453    /// # type Error = std::convert::Infallible;
454    ///
455    /// fn process_items(items: &mut Managed<Items>, ctx: Context<'_>) {
456    ///     items.retain_with_context(|items| (&mut items.items, &items.ty), |item, ty, _| process(item, ty, ctx));
457    /// }
458    ///
459    /// fn process(item: &mut Item, ty: &str, ctx: Context<'_>) -> Result<(), Error> {
460    ///     todo!()
461    /// }
462    /// ```
463    pub fn retain_with_context<S, C, I, U, E, V>(&mut self, select: S, mut retain: U)
464    where
465        // Returning `&'a C` here is not optimal, ideally we return C here and express the correct
466        // bound of `C: 'a` but this is, to my knowledge, currently not possible to express in stable Rust.
467        //
468        // This is unfortunately a bit limiting but for most of our purposes it is enough.
469        for<'a> S: FnOnce(&'a mut T) -> (&'a mut V, &'a C),
470        I: Counted,
471        U: FnMut(&mut I, &C, &mut RecordKeeper<'_>) -> Result<(), E>,
472        E: OutcomeError,
473        V: RetainMut<I>,
474    {
475        self.modify(|inner, records| {
476            let (items, ctx) = select(inner);
477            items.retain_mut(|item| match retain(item, ctx, records) {
478                Ok(()) => true,
479                Err(err) => {
480                    records.reject_err(err, &*item);
481                    false
482                }
483            })
484        });
485    }
486
487    /// Maps a [`Managed<T>`] to [`Managed<S>`] by applying the mapping function `f`.
488    ///
489    /// Like [`Self::try_map`] but not fallible.
490    pub fn map<S, F>(self, f: F) -> Managed<S>
491    where
492        F: FnOnce(T, &mut RecordKeeper) -> S,
493        S: Counted,
494    {
495        self.try_map(move |inner, records| Ok::<_, Infallible>(f(inner, records)))
496            .unwrap_or_else(|e| match e.0 {})
497    }
498
499    /// Maps a [`Managed<T>`] to [`Managed<S>`] by applying the mapping function `f`.
500    ///
501    /// The mapping function gets access to a [`RecordKeeper`], to emit outcomes for partial
502    /// discards.
503    ///
504    /// If the mapping function returns an error, the entire (original) [`Self`] is rejected,
505    /// no partial outcomes are emitted.
506    pub fn try_map<S, F, E>(self, f: F) -> Result<Managed<S>, Rejected<E::Error>>
507    where
508        F: FnOnce(T, &mut RecordKeeper) -> Result<S, E>,
509        S: Counted,
510        E: OutcomeError,
511    {
512        debug_assert!(!self.is_done());
513
514        let (value, meta) = self.destructure();
515        let quantities = value.quantities();
516
517        let mut records = RecordKeeper::new(&meta, quantities);
518
519        match f(value, &mut records) {
520            Ok(value) => {
521                records.success(value.quantities());
522                Ok(Managed::from_parts(value, meta))
523            }
524            Err(err) => Err(records.failure(err)),
525        }
526    }
527
528    /// Gives mutable access to the contained value to modify it.
529    ///
530    /// Like [`Self::try_modify`] but not fallible.
531    pub fn modify<F>(&mut self, f: F)
532    where
533        F: FnOnce(&mut T, &mut RecordKeeper),
534    {
535        self.try_modify(move |inner, records| {
536            f(inner, records);
537            Ok::<_, Infallible>(())
538        })
539        .unwrap_or_else(|e| match e {})
540    }
541
542    /// Gives mutable access to the contained value to modify it.
543    ///
544    /// The modifying function gets access to a [`RecordKeeper`], to emit outcomes for partial
545    /// discards.
546    ///
547    /// If the modifying function returns an error, the entire (original) [`Self`] is rejected,
548    /// no partial outcomes are emitted.
549    pub fn try_modify<F, E>(&mut self, f: F) -> Result<(), Rejected<E::Error>>
550    where
551        F: FnOnce(&mut T, &mut RecordKeeper) -> Result<(), E>,
552        E: OutcomeError,
553    {
554        debug_assert!(!self.is_done());
555
556        let quantities = self.value.quantities();
557        let mut records = RecordKeeper::new(&self.meta, quantities);
558
559        match f(&mut self.value, &mut records) {
560            Ok(()) => {
561                records.success(self.value.quantities());
562                Ok(())
563            }
564            Err(err) => {
565                let err = records.failure(err);
566                self.done.store(true, Ordering::Relaxed);
567                Err(err)
568            }
569        }
570    }
571
572    /// Accepts the item of this managed instance.
573    ///
574    /// This should be called if the item has been or is about to be accepted by the upstream, which means that
575    /// the responsibility for logging outcomes has been moved. This function will not log any
576    /// outcomes.
577    ///
578    /// Like [`Self::try_accept`], but infallible.
579    pub fn accept<F, S>(self, f: F) -> S
580    where
581        F: FnOnce(T) -> S,
582    {
583        self.try_accept(|item| Ok::<_, Infallible>(f(item)))
584            .unwrap_or_else(|err| match err.0 {})
585    }
586
587    /// Accepts the item of this managed instance.
588    ///
589    /// This should be called if the item has been or is about to be accepted by the upstream.
590    ///
591    /// Outcomes are only emitted when the accepting closure returns an error, which means that
592    /// in the success case the responsibility for logging outcomes has been moved to the
593    /// caller/upstream.
594    pub fn try_accept<F, S, E>(self, f: F) -> Result<S, Rejected<E::Error>>
595    where
596        F: FnOnce(T) -> Result<S, E>,
597        E: OutcomeError,
598    {
599        debug_assert!(!self.is_done());
600
601        let (value, meta) = self.destructure();
602        let records = RecordKeeper::new(&meta, value.quantities());
603
604        match f(value) {
605            Ok(value) => {
606                records.accept();
607                Ok(value)
608            }
609            Err(err) => Err(records.failure(err)),
610        }
611    }
612
613    /// Rejects the entire [`Managed`] instance with an internal error.
614    ///
615    /// Internal errors should be reserved for uses where logical invariants are violated.
616    /// Cases which should never happen and always indicate a logical bug.
617    ///
618    /// This function will panic in debug builds, but discard the item
619    /// with an internal discard reason in release builds.
620    #[track_caller]
621    pub fn internal_error(&self, reason: &'static str) -> Rejected<()> {
622        relay_log::error!("internal error: {reason}");
623        debug_assert!(false, "internal error: {reason}");
624        self.reject_err((Outcome::Invalid(DiscardReason::Internal), ()))
625    }
626
627    /// Rejects the entire [`Managed`] instance.
628    pub fn reject_err<E>(&self, error: E) -> Rejected<E::Error>
629    where
630        E: OutcomeError,
631    {
632        debug_assert!(!self.is_done());
633
634        let (outcome, error) = error.consume();
635        self.do_reject(outcome);
636        Rejected(error)
637    }
638
639    fn do_reject(&self, outcome: Option<Outcome>) {
640        // Always set the internal state to `done`, even if there is no outcome to be emitted.
641        // All bookkeeping has been done.
642        let is_done = self.done.fetch_or(true, Ordering::Relaxed);
643
644        // No outcome to emit, we're done.
645        let Some(outcome) = outcome else {
646            return;
647        };
648
649        // Only emit outcomes if we were not yet done.
650        //
651        // Callers should guard against accidentally calling `do_reject` when the `is_done` flag is
652        // already set, but internal uses (like `Drop`) can rely on this double emission
653        // prevention.
654        if !is_done {
655            for (category, quantity) in self.value.quantities() {
656                self.meta.track_outcome(outcome.clone(), category, quantity);
657            }
658        }
659    }
660
661    /// De-structures this managed instance into its own parts.
662    ///
663    /// While de-structured no outcomes will be emitted on drop.
664    ///
665    /// Currently no `Managed`, which already has outcomes emitted, should be de-structured
666    /// as this status is lost.
667    fn destructure(self) -> (T, Arc<Meta>) {
668        // SAFETY: this follows an approach mentioned in the RFC
669        // <https://github.com/rust-lang/rfcs/pull/3466> to move fields out of
670        // a type with a drop implementation.
671        //
672        // The original type is wrapped in a manual drop to prevent running the
673        // drop handler, afterwards all fields are moved out of the type.
674        //
675        // And the original type is forgotten, de-structuring the original type
676        // without running its drop implementation.
677        let this = ManuallyDrop::new(self);
678        let Managed { value, meta, done } = &*this;
679
680        let value = unsafe { std::ptr::read(value) };
681        let meta = unsafe { std::ptr::read(meta) };
682        let done = unsafe { std::ptr::read(done) };
683        // This is a current invariant, if we ever need to change the invariant,
684        // the done status should be preserved and returned instead.
685        debug_assert!(
686            !done.load(Ordering::Relaxed),
687            "a `done` managed should never be destructured"
688        );
689
690        (value, meta)
691    }
692
693    fn from_parts(value: T, meta: Arc<Meta>) -> Self {
694        Self {
695            value,
696            meta,
697            done: AtomicBool::new(false),
698        }
699    }
700
701    fn is_done(&self) -> bool {
702        self.done.load(Ordering::Relaxed)
703    }
704}
705
706impl<T: Counted> Drop for Managed<T> {
707    fn drop(&mut self) {
708        self.do_reject(Some(Outcome::Invalid(DiscardReason::Internal)));
709    }
710}
711
712impl<T: Counted + fmt::Debug> fmt::Debug for Managed<T> {
713    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
714        write!(f, "Managed<{}>[", std::any::type_name::<T>())?;
715        for (i, (category, quantity)) in self.value.quantities().iter().enumerate() {
716            if i > 0 {
717                write!(f, ",")?;
718            }
719            write!(f, "{category}:{quantity}")?;
720        }
721        write!(f, "](")?;
722        self.value.fmt(f)?;
723        write!(f, ")")
724    }
725}
726
727impl<T: Counted> Managed<Option<T>> {
728    /// Turns a managed option into an optional [`Managed`].
729    pub fn transpose(self) -> Option<Managed<T>> {
730        let (o, meta) = self.destructure();
731        o.map(|t| Managed::from_parts(t, meta))
732    }
733}
734
735impl<L: Counted, R: Counted> Managed<Either<L, R>> {
736    /// Turns a managed [`Either`] into an [`Either`] of [`Managed`].
737    pub fn transpose(self) -> Either<Managed<L>, Managed<R>> {
738        let (either, meta) = self.destructure();
739        match either {
740            Either::Left(value) => Either::Left(Managed::from_parts(value, meta)),
741            Either::Right(value) => Either::Right(Managed::from_parts(value, meta)),
742        }
743    }
744}
745
746impl From<Managed<Box<Envelope>>> for ManagedEnvelope {
747    fn from(value: Managed<Box<Envelope>>) -> Self {
748        let (value, meta) = value.destructure();
749        let mut envelope = ManagedEnvelope::new(value, meta.outcome_aggregator.clone());
750        envelope.scope(meta.scoping);
751        envelope
752    }
753}
754
755impl<T: Counted> AsRef<T> for Managed<T> {
756    fn as_ref(&self) -> &T {
757        &self.value
758    }
759}
760
761impl<T: Counted> std::ops::Deref for Managed<T> {
762    type Target = T;
763
764    fn deref(&self) -> &Self::Target {
765        &self.value
766    }
767}
768
769/// Internal metadata attached with a [`Managed`] instance.
770#[derive(Debug, Clone)]
771struct Meta {
772    /// Outcome aggregator service.
773    outcome_aggregator: Addr<TrackOutcome>,
774    /// Received timestamp, when the contained payload/information was received.
775    ///
776    /// See also: [`crate::extractors::RequestMeta::received_at`].
777    received_at: DateTime<Utc>,
778    /// Data scoping information of the contained item.
779    scoping: Scoping,
780    /// Optional event id associated with the contained data.
781    event_id: Option<EventId>,
782    /// Optional remote addr from where the data was received.
783    remote_addr: Option<IpAddr>,
784}
785
786impl Meta {
787    pub fn track_outcome(&self, outcome: Outcome, category: DataCategory, quantity: usize) {
788        self.outcome_aggregator.send(TrackOutcome {
789            timestamp: self.received_at,
790            scoping: self.scoping,
791            outcome,
792            event_id: self.event_id,
793            remote_addr: self.remote_addr,
794            category,
795            quantity: quantity as _,
796        });
797    }
798}
799
800/// A record keeper makes sure modifications done on a [`Managed`] item are all accounted for
801/// correctly.
802pub struct RecordKeeper<'a> {
803    meta: &'a Meta,
804    on_drop: Quantities,
805    #[cfg(debug_assertions)]
806    lenient: SmallVec<[DataCategory; 1]>,
807    #[cfg(debug_assertions)]
808    modifications: BTreeMap<DataCategory, isize>,
809    in_flight: SmallVec<[(DataCategory, usize, Option<Outcome>); 2]>,
810}
811
812impl<'a> RecordKeeper<'a> {
813    fn new(meta: &'a Meta, quantities: Quantities) -> Self {
814        Self {
815            meta,
816            on_drop: quantities,
817            #[cfg(debug_assertions)]
818            lenient: Default::default(),
819            #[cfg(debug_assertions)]
820            modifications: Default::default(),
821            in_flight: Default::default(),
822        }
823    }
824
825    /// Marking a data category as lenient exempts this category from outcome quantity validations.
826    ///
827    /// Consider using [`Self::modify_by`] instead.
828    ///
829    /// This can be used in cases where the quantity is knowingly modified, which is quite common
830    /// for data categories which count bytes.
831    pub fn lenient(&mut self, category: DataCategory) {
832        let _category = category;
833        #[cfg(debug_assertions)]
834        self.lenient.push(_category);
835    }
836
837    /// Modifies the expected count for a category.
838    ///
839    /// When extracting payloads category counts may expectedly change, these changes can be
840    /// tracked using this function.
841    ///
842    /// Prefer using [`Self::modify_by`] over [`Self::lenient`] as lenient completely disables
843    /// validation for the entire category.
844    pub fn modify_by(&mut self, category: DataCategory, offset: isize) {
845        let _category = category;
846        let _offset = offset;
847        #[cfg(debug_assertions)]
848        {
849            *self.modifications.entry(_category).or_default() += offset;
850        }
851    }
852
853    /// Finalizes all records and emits the necessary outcomes.
854    ///
855    /// This uses the quantities of the original item.
856    fn failure<E>(mut self, error: E) -> Rejected<E::Error>
857    where
858        E: OutcomeError,
859    {
860        let (outcome, error) = error.consume();
861
862        if let Some(outcome) = outcome {
863            for (category, quantity) in std::mem::take(&mut self.on_drop) {
864                self.meta.track_outcome(outcome.clone(), category, quantity);
865            }
866        }
867
868        Rejected(error)
869    }
870
871    /// Finalizes all records and asserts that no additional outcomes have been tracked.
872    ///
873    /// Unlike [`Self::success`], this method does not allow for intermediate or partial outcomes,
874    /// it also does not verify any outcomes.
875    ///
876    /// This method is useful for using the record keeper to track failure outcomes, either
877    /// explicit failures or panics.
878    fn accept(mut self) {
879        debug_assert!(
880            self.in_flight.is_empty(),
881            "records accepted, but intermediate outcomes tracked"
882        );
883        self.on_drop.clear();
884    }
885
886    /// Finalizes all records and emits the created outcomes.
887    ///
888    /// This only emits the outcomes that have been explicitly registered.
889    /// In a debug build, the function also ensure no outcomes have been missed by comparing
890    /// quantities of the item before and after.
891    fn success(mut self, new: Quantities) {
892        let original = std::mem::take(&mut self.on_drop);
893        self.assert_quantities(original, new);
894
895        self.on_drop.clear();
896        for (category, quantity, outcome) in std::mem::take(&mut self.in_flight) {
897            if let Some(outcome) = outcome {
898                self.meta.track_outcome(outcome, category, quantity);
899            }
900        }
901    }
902
903    /// Asserts that there have been no quantities lost.
904    ///
905    /// The original amount of quantities should match the new amount of quantities + all emitted
906    /// outcomes.
907    #[cfg(debug_assertions)]
908    fn assert_quantities(&self, original: Quantities, new: Quantities) {
909        macro_rules! emit {
910            ($category:expr, $($tt:tt)*) => {{
911                match self.lenient.contains(&$category) {
912                    // Certain categories are known to be not always correct,
913                    // they are logged instead.
914                    true => relay_log::debug!($($tt)*),
915                    false  => {
916                        relay_log::error!("Original: {original:?}");
917                        relay_log::error!("New: {new:?}");
918                        relay_log::error!("Modifications: {:?}", self.modifications);
919                        relay_log::error!("In Flight: {:?}", self.in_flight);
920                        panic!($($tt)*)
921                    }
922                }
923            }};
924        }
925
926        let mut sums = debug::Quantities::from(&original).0;
927        for (category, offset) in &self.modifications {
928            let v = sums.entry(*category).or_default();
929            match v.checked_add_signed(*offset) {
930                Some(result) => *v = result,
931                None => emit!(
932                    category,
933                    "Attempted to modify original quantity {v} into the negative ({offset})"
934                ),
935            }
936        }
937
938        for (category, quantity, outcome) in &self.in_flight {
939            match sums.get_mut(category) {
940                Some(c) if *c >= *quantity => *c -= *quantity,
941                Some(c) => emit!(
942                    category,
943                    "Emitted {quantity} outcomes ({outcome:?}) for {category}, but there were only {c} items in the category originally"
944                ),
945                None => emit!(
946                    category,
947                    "Emitted {quantity} outcomes ({outcome:?}) for {category}, but there never was an item in this category"
948                ),
949            }
950        }
951
952        for (category, quantity) in &new {
953            match sums.get_mut(category) {
954                Some(c) if *c >= *quantity => *c -= *quantity,
955                Some(c) => emit!(
956                    category,
957                    "New item has {quantity} items in category '{category}', but original (after emitted outcomes) only has {c} left"
958                ),
959                None => emit!(
960                    category,
961                    "New item has {quantity} items in category '{category}', but after emitted outcomes there are none left"
962                ),
963            }
964        }
965
966        for (category, quantity) in sums {
967            if quantity > 0 {
968                emit!(
969                    category,
970                    "Missing outcomes or mismatched quantity in category '{category}', off by {quantity}"
971                );
972            }
973        }
974    }
975
976    #[cfg(not(debug_assertions))]
977    fn assert_quantities(&self, _: Quantities, _: Quantities) {}
978}
979
980impl<'a> Drop for RecordKeeper<'a> {
981    fn drop(&mut self) {
982        for (category, quantity) in std::mem::take(&mut self.on_drop) {
983            self.meta.track_outcome(
984                Outcome::Invalid(DiscardReason::Internal),
985                category,
986                quantity,
987            );
988        }
989    }
990}
991
992impl RecordKeeper<'_> {
993    /// Rejects an item if the passed result is an error and returns a default value.
994    ///
995    /// Similar to [`Self::reject_err`], this emits the necessary outcomes for an
996    /// item, if there is an error.
997    pub fn or_default<T, E, Q>(&mut self, r: Result<T, E>, q: Q) -> T
998    where
999        T: Default,
1000        E: OutcomeError,
1001        Q: Counted,
1002    {
1003        match r {
1004            Ok(result) => result,
1005            Err(err) => {
1006                self.reject_err(err, q);
1007                T::default()
1008            }
1009        }
1010    }
1011
1012    /// Rejects an item with an error.
1013    ///
1014    /// Makes sure the correct outcomes are tracked for the item, that is discarded due to an
1015    /// error.
1016    pub fn reject_err<E, Q>(&mut self, err: E, q: Q) -> E::Error
1017    where
1018        E: OutcomeError,
1019        Q: Counted,
1020    {
1021        let (outcome, err) = err.consume();
1022        for (category, quantity) in q.quantities() {
1023            self.in_flight.push((category, quantity, outcome.clone()))
1024        }
1025        err
1026    }
1027
1028    /// Rejects an item with an internal error.
1029    ///
1030    /// See also: [`Managed::internal_error`].
1031    #[track_caller]
1032    pub fn internal_error<E, Q>(&mut self, error: E, q: Q)
1033    where
1034        E: std::error::Error + 'static,
1035        Q: Counted,
1036    {
1037        relay_log::error!(error = &error as &dyn std::error::Error, "internal error");
1038        debug_assert!(false, "internal error: {error}");
1039        self.reject_err((Outcome::Invalid(DiscardReason::Internal), ()), q);
1040    }
1041}
1042
1043/// Iterator returned by [`Managed::split`].
1044pub struct Split<I, S>
1045where
1046    I: Iterator<Item = S>,
1047    S: Counted,
1048{
1049    #[cfg(debug_assertions)]
1050    quantities: Quantities,
1051    items: I,
1052    meta: Arc<Meta>,
1053    exhausted: bool,
1054}
1055
1056impl<I, S> Split<I, S>
1057where
1058    I: Iterator<Item = S>,
1059    S: Counted,
1060{
1061    /// Subtracts passed quantities from the total quantities to verify total quantity counts are
1062    /// matching.
1063    #[cfg(debug_assertions)]
1064    fn subtract(&mut self, q: Quantities) {
1065        for (category, quantities) in q {
1066            let Some(orig_quantities) = self
1067                .quantities
1068                .iter_mut()
1069                .find_map(|(c, q)| (*c == category).then_some(q))
1070            else {
1071                debug_assert!(
1072                    false,
1073                    "mismatching quantities, item split into category {category}, \
1074                    which originally was not present"
1075                );
1076                continue;
1077            };
1078
1079            if *orig_quantities >= quantities {
1080                *orig_quantities -= quantities;
1081            } else {
1082                debug_assert!(
1083                    false,
1084                    "in total more items produced in category {category} than originally available"
1085                );
1086            }
1087        }
1088    }
1089}
1090
1091impl<I, S> Iterator for Split<I, S>
1092where
1093    I: Iterator<Item = S>,
1094    S: Counted,
1095{
1096    type Item = Managed<S>;
1097
1098    fn next(&mut self) -> Option<Self::Item> {
1099        let next = match self.items.next() {
1100            Some(next) => next,
1101            None => {
1102                self.exhausted = true;
1103                return None;
1104            }
1105        };
1106
1107        #[cfg(debug_assertions)]
1108        self.subtract(next.quantities());
1109
1110        Some(Managed::from_parts(next, Arc::clone(&self.meta)))
1111    }
1112}
1113
1114impl<I, S> Drop for Split<I, S>
1115where
1116    I: Iterator<Item = S>,
1117    S: Counted,
1118{
1119    fn drop(&mut self) {
1120        // If the inner iterator was exhausted, no items should be remaining.
1121        #[cfg(debug_assertions)]
1122        if self.exhausted {
1123            for (category, quantities) in &self.quantities {
1124                debug_assert!(
1125                    *quantities == 0,
1126                    "items split, but still {quantities} remaining in category {category}"
1127                );
1128            }
1129        }
1130
1131        if self.exhausted {
1132            return;
1133        }
1134
1135        // There may be items remaining in the iterator for multiple reasons:
1136        // - there was a panic
1137        // - the iterator was never fully consumed
1138        //
1139        // In any case, outcomes must be emitted for the remaining items.
1140        for item in &mut self.items {
1141            for (category, quantity) in item.quantities() {
1142                self.meta.track_outcome(
1143                    Outcome::Invalid(DiscardReason::Internal),
1144                    category,
1145                    quantity,
1146                );
1147            }
1148        }
1149    }
1150}
1151
1152impl<I, S> FusedIterator for Split<I, S>
1153where
1154    I: Iterator<Item = S> + FusedIterator,
1155    S: Counted,
1156{
1157}
1158
1159#[cfg(test)]
1160mod tests {
1161    use super::*;
1162
1163    use relay_base_schema::project::ProjectId;
1164
1165    struct CountedVec(Vec<u32>);
1166
1167    impl Counted for CountedVec {
1168        fn quantities(&self) -> Quantities {
1169            smallvec::smallvec![(DataCategory::Error, self.0.len())]
1170        }
1171    }
1172
1173    struct CountedValue(u32);
1174
1175    impl Counted for CountedValue {
1176        fn quantities(&self) -> Quantities {
1177            smallvec::smallvec![(DataCategory::Error, 1)]
1178        }
1179    }
1180
1181    #[test]
1182    fn test_reject_err_no_outcome() {
1183        let value = CountedVec(vec![0, 1, 2, 3, 4, 5]);
1184        let (managed, mut handle) = Managed::for_test(value).build();
1185
1186        // Rejecting with no outcome, should not emit any outcomes.
1187        let _ = managed.reject_err((None, ()));
1188        handle.assert_no_outcomes();
1189
1190        // Now dropping the manged instance, should not record any (internal) outcomes either.
1191        drop(managed);
1192        handle.assert_no_outcomes();
1193    }
1194
1195    #[test]
1196    fn test_merge() {
1197        let (mut a, mut handle_a) = Managed::for_test(CountedVec(vec![1, 2])).build();
1198        let (b, mut handle_b) = Managed::for_test(CountedVec(vec![3, 4])).build();
1199
1200        a.merge_with(b, |a, b, _| a.0.extend(b.0));
1201
1202        assert_eq!(a.0, vec![1, 2, 3, 4]);
1203        drop(a);
1204        handle_a.assert_internal_outcome(DataCategory::Error, 4);
1205        handle_b.assert_no_outcomes();
1206    }
1207
1208    #[test]
1209    fn test_zip_into_tuple() {
1210        let (a, mut handle_a) = Managed::for_test(CountedVec(vec![1, 2])).build();
1211        let (b, mut handle_b) = Managed::for_test(CountedValue(3)).build();
1212
1213        let z = Managed::zip(a, b);
1214
1215        assert_eq!((z.as_ref().0).0, vec![1, 2]);
1216        assert_eq!((z.as_ref().1).0, 3);
1217        drop(z);
1218        handle_a.assert_internal_outcome(DataCategory::Error, 2);
1219        handle_a.assert_internal_outcome(DataCategory::Error, 1);
1220        handle_b.assert_no_outcomes();
1221    }
1222
1223    #[test]
1224    fn test_zip_rejects_different_metadata() {
1225        let (a, mut handle_a) = Managed::for_test(CountedVec(vec![1, 2])).build();
1226        let (b, mut handle_b) = Managed::for_test(CountedValue(3))
1227            .scoping(Scoping {
1228                project_id: ProjectId::new(45),
1229                ..a.scoping()
1230            })
1231            .build();
1232
1233        let result = std::panic::catch_unwind(move || {
1234            Managed::zip(a, b);
1235        });
1236
1237        assert!(
1238            result.is_err(),
1239            "cannot zip Managed values with different metadata"
1240        );
1241        handle_a.assert_internal_outcome(DataCategory::Error, 2);
1242        handle_b.assert_internal_outcome(DataCategory::Error, 1);
1243    }
1244
1245    #[test]
1246    fn test_merge_mismatched_records_should_panic() {
1247        let (mut a, mut handle_a) = Managed::for_test(CountedVec(vec![1, 2])).build();
1248        let (b, _handle_b) = Managed::for_test(CountedVec(vec![3, 4])).build();
1249
1250        let r = std::panic::catch_unwind(move || {
1251            a.merge_with(b, |_a, _b, _| {});
1252        });
1253
1254        assert!(
1255            r.is_err(),
1256            "expected merge to panic because of mismatched outcome records"
1257        );
1258        handle_a.assert_internal_outcome(DataCategory::Error, 2);
1259    }
1260
1261    #[test]
1262    fn test_split_fully_consumed() {
1263        let value = CountedVec(vec![0, 1, 2, 3, 4, 5]);
1264        let (managed, mut handle) = Managed::for_test(value).build();
1265
1266        let s = managed
1267            .split(|value| value.0.into_iter().map(CountedValue))
1268            // Fully consume the iterator to make sure there aren't any outcomes emitted on drop.
1269            .collect::<Vec<_>>();
1270
1271        handle.assert_no_outcomes();
1272
1273        for (i, s) in s.into_iter().enumerate() {
1274            assert_eq!(s.as_ref().0, i as u32);
1275            let outcome = Outcome::Invalid(DiscardReason::Cors);
1276            let _ = s.reject_err((outcome.clone(), ()));
1277            handle.assert_outcome(&outcome, DataCategory::Error, 1);
1278        }
1279    }
1280
1281    #[test]
1282    fn test_split_partially_consumed_emits_remaining() {
1283        let value = CountedVec(vec![0, 1, 2, 3, 4, 5]);
1284        let (managed, mut handle) = Managed::for_test(value).build();
1285
1286        let mut s = managed.split(|value| value.0.into_iter().map(CountedValue));
1287        handle.assert_no_outcomes();
1288
1289        drop(s.next());
1290        handle.assert_internal_outcome(DataCategory::Error, 1);
1291        drop(s.next());
1292        handle.assert_internal_outcome(DataCategory::Error, 1);
1293        drop(s.next());
1294        handle.assert_internal_outcome(DataCategory::Error, 1);
1295        handle.assert_no_outcomes();
1296
1297        drop(s);
1298
1299        handle.assert_internal_outcome(DataCategory::Error, 1);
1300        handle.assert_internal_outcome(DataCategory::Error, 1);
1301        handle.assert_internal_outcome(DataCategory::Error, 1);
1302    }
1303
1304    #[test]
1305    fn test_split_changing_quantities_should_panic() {
1306        let value = CountedVec(vec![0, 1, 2, 3, 4, 5]);
1307        let (managed, mut handle) = Managed::for_test(value).build();
1308
1309        let mut s = managed.split(|_| std::iter::once(CountedValue(0)));
1310
1311        s.next().unwrap().accept(|_| {});
1312        handle.assert_no_outcomes();
1313
1314        assert!(s.next().is_none());
1315
1316        let r = std::panic::catch_unwind(move || {
1317            drop(s);
1318        });
1319
1320        assert!(
1321            r.is_err(),
1322            "expected split to panic because of mismatched (not enough) outcomes"
1323        );
1324    }
1325
1326    #[test]
1327    fn test_split_more_outcomes_than_before_should_panic() {
1328        let value = CountedVec(vec![0]);
1329        let (managed, mut handle) = Managed::for_test(value).build();
1330
1331        let mut s = managed.split(|_| vec![CountedValue(0), CountedValue(2)].into_iter());
1332
1333        s.next().unwrap().accept(|_| {});
1334        handle.assert_no_outcomes();
1335
1336        let r = std::panic::catch_unwind(move || {
1337            s.next();
1338        });
1339
1340        assert!(
1341            r.is_err(),
1342            "expected split to panic because of mismatched (too many) outcomes"
1343        );
1344    }
1345
1346    #[test]
1347    fn test_split_changing_categories_should_panic() {
1348        struct Special;
1349        impl Counted for Special {
1350            fn quantities(&self) -> Quantities {
1351                smallvec::smallvec![(DataCategory::Error, 1), (DataCategory::Transaction, 1)]
1352            }
1353        }
1354
1355        let value = CountedVec(vec![0]);
1356        let (managed, _handle) = Managed::for_test(value).build();
1357
1358        let mut s = managed.split(|value| value.0.into_iter().map(|_| Special));
1359
1360        let r = std::panic::catch_unwind(move || {
1361            let _ = s.next();
1362        });
1363
1364        assert!(
1365            r.is_err(),
1366            "expected split to panic because of mismatched outcome categories"
1367        );
1368    }
1369
1370    #[test]
1371    fn test_split_assert_fused() {
1372        fn only_fused<T: FusedIterator>(_: T) {}
1373
1374        let (managed, mut handle) = Managed::for_test(CountedVec(vec![0])).build();
1375        only_fused(managed.split(|value| value.0.into_iter().map(CountedValue)));
1376        handle.assert_internal_outcome(DataCategory::Error, 1);
1377    }
1378}