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