1use super::{BlockReason, Collector, CollectorFor, PushOutcome, RunnableChain};
17use crate::checkpoint::AckRef;
18use crate::deser::{Deserializer, EmitRecord, RecFamily};
19use crate::error::{DeserError, ErrorClass, ErrorPolicy, FatalError};
20use crate::metrics::{DeserMetrics, OperatorMetrics};
21use crate::record::{Flow, RawPayload, Record, RecordMeta};
22use crate::source::PayloadBatch;
23use crate::telemetry::RateLimit;
24use std::marker::PhantomData;
25use std::sync::Arc;
26use std::time::{Duration, Instant};
27
28pub trait StageLifecycle {
32 fn on_batch_end(&mut self, elapsed: Duration);
37
38 fn take_fatal(&mut self) -> Option<FatalError>;
40
41 fn relieve(&mut self) -> Flow;
44
45 fn flush_terminal(&mut self) -> Flow;
48}
49
50#[derive(Debug, Default)]
53pub(crate) struct OpMeter {
54 handle: Option<Arc<OperatorMetrics>>,
55 records_in: u64,
56 records_out: u64,
57 filtered: u64,
58 skipped: u64,
59 unrouted: u64,
60 record_errors: u64,
61}
62
63impl OpMeter {
64 pub(crate) fn new(handle: Option<Arc<OperatorMetrics>>) -> Self {
65 OpMeter {
66 handle,
67 ..Default::default()
68 }
69 }
70
71 #[inline(always)]
72 pub(crate) fn seen(&mut self) {
73 self.records_in += 1;
74 }
75
76 #[inline(always)]
77 pub(crate) fn out(&mut self) {
78 self.records_out += 1;
79 }
80
81 #[inline(always)]
82 pub(crate) fn out_n(&mut self, n: u64) {
83 self.records_out += n;
84 }
85
86 #[inline(always)]
87 pub(crate) fn filtered(&mut self) {
88 self.filtered += 1;
89 }
90
91 #[inline(always)]
92 pub(crate) fn skipped(&mut self) {
93 self.skipped += 1;
94 }
95
96 #[inline(always)]
97 pub(crate) fn unrouted(&mut self) {
98 self.unrouted += 1;
99 }
100
101 #[inline(always)]
102 pub(crate) fn record_error(&mut self) {
103 self.record_errors += 1;
104 }
105
106 pub(crate) fn flush(&mut self, elapsed: Duration) {
107 if let Some(h) = &self.handle {
108 h.batch(self.records_in, self.records_out, elapsed);
109 if self.filtered > 0 {
110 h.filtered(self.filtered);
111 }
112 if self.skipped > 0 {
113 h.skipped(self.skipped);
114 }
115 if self.unrouted > 0 {
116 h.unrouted(self.unrouted);
117 }
118 if self.record_errors > 0 {
119 h.errors(ErrorClass::RecordLevel, self.record_errors);
120 }
121 }
122 self.records_in = 0;
123 self.records_out = 0;
124 self.filtered = 0;
125 self.skipped = 0;
126 self.unrouted = 0;
127 self.record_errors = 0;
128 }
129}
130
131#[derive(Debug, Default)]
134pub(crate) struct OpMeterSlot(pub(crate) OpMeter);
135
136impl Clone for OpMeterSlot {
137 fn clone(&self) -> Self {
138 OpMeterSlot(OpMeter::new(self.0.handle.clone()))
139 }
140}
141
142#[derive(Debug, Default)]
144pub(crate) struct FatalSlot(pub(crate) Option<FatalError>);
145
146impl Clone for FatalSlot {
147 fn clone(&self) -> Self {
148 FatalSlot(None)
149 }
150}
151
152#[derive(Clone, Debug)]
154pub struct Map<G, N> {
155 pub(crate) f: G,
156 pub(crate) next: N,
157 pub(crate) meter: OpMeterSlot,
158}
159
160impl<In, Out, G, N> Collector<In> for Map<G, N>
161where
162 G: FnMut(In) -> Out,
163 N: Collector<Out>,
164{
165 #[inline(always)]
166 fn push(&mut self, rec: Record<In>) -> Flow {
167 self.meter.0.seen();
168 self.meter.0.out();
169 self.next.push(rec.map(&mut self.f))
170 }
171}
172
173#[derive(Clone, Debug)]
176pub struct Filter<P, N> {
177 pub(crate) p: P,
178 pub(crate) next: N,
179 pub(crate) meter: OpMeterSlot,
180}
181
182impl<T, P, N> Collector<T> for Filter<P, N>
183where
184 P: FnMut(&T) -> bool,
185 N: Collector<T>,
186{
187 #[inline(always)]
188 fn push(&mut self, rec: Record<T>) -> Flow {
189 self.meter.0.seen();
190 if (self.p)(&rec.payload) {
191 self.meter.0.out();
192 self.next.push(rec)
193 } else {
194 self.meter.0.filtered();
195 Flow::Continue
196 }
197 }
198}
199
200#[derive(Clone, Debug)]
202pub struct Inspect<G, N> {
203 pub(crate) f: G,
204 pub(crate) next: N,
205}
206
207impl<T, G, N> Collector<T> for Inspect<G, N>
208where
209 G: FnMut(&T),
210 N: Collector<T>,
211{
212 #[inline(always)]
213 fn push(&mut self, rec: Record<T>) -> Flow {
214 (self.f)(&rec.payload);
215 self.next.push(rec)
216 }
217}
218
219static TRY_MAP_SKIP_WARN: RateLimit = RateLimit::new(5, Duration::from_secs(10));
220
221#[derive(Clone, Debug)]
225pub struct TryMap<G, N> {
226 pub(crate) f: G,
227 pub(crate) next: N,
228 pub(crate) policy: ErrorPolicy,
229 pub(crate) component: Arc<str>,
230 pub(crate) meter: OpMeterSlot,
231 pub(crate) fatal: FatalSlot,
232}
233
234impl<In, Out, E, G, N> Collector<In> for TryMap<G, N>
235where
236 G: FnMut(In) -> Result<Out, E>,
237 E: std::fmt::Display,
238 N: Collector<Out>,
239{
240 #[inline(always)]
241 fn push(&mut self, rec: Record<In>) -> Flow {
242 self.meter.0.seen();
243 if self.fatal.0.is_some() {
244 return Flow::Continue;
247 }
248 let Record { payload, meta, ack } = rec;
249 match (self.f)(payload) {
250 Ok(out) => {
251 self.meter.0.out();
252 self.next.push(Record {
253 payload: out,
254 meta,
255 ack,
256 })
257 }
258 Err(e) => {
259 match self.policy {
260 ErrorPolicy::Skip => {
261 self.meter.0.skipped();
262 self.meter.0.record_error();
263 crate::rate_limited_warn!(
264 TRY_MAP_SKIP_WARN,
265 component = &*self.component,
266 error = %e,
267 "record skipped by error policy"
268 );
269 }
270 _ => {
271 self.fatal.0 = Some(FatalError {
272 component: self.component.to_string(),
273 reason: e.to_string(),
274 });
275 }
276 }
277 Flow::Continue
278 }
279 }
280 }
281}
282
283pub struct Emitter<'a, OutF: RecFamily> {
288 next: &'a mut dyn CollectorFor<OutF>,
289 meta: RecordMeta,
290 ack: &'a AckRef,
291 emitted: u64,
292 flow: Flow,
293}
294
295impl<OutF: RecFamily> std::fmt::Debug for Emitter<'_, OutF> {
296 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
297 f.debug_struct("Emitter")
298 .field("emitted", &self.emitted)
299 .field("flow", &self.flow)
300 .finish_non_exhaustive()
301 }
302}
303
304impl<OutF: RecFamily> Emitter<'_, OutF> {
305 #[inline(always)]
308 pub fn emit<'buf>(&mut self, payload: OutF::Rec<'buf>) -> Flow {
309 let flow = self.next.push_rec(Record {
310 payload,
311 meta: self.meta,
312 ack: self.ack.clone(),
313 });
314 self.emitted += 1;
315 if self.flow != Flow::Blocked {
316 self.flow = flow;
317 }
318 flow
319 }
320
321 #[must_use]
323 pub fn meta(&self) -> RecordMeta {
324 self.meta
325 }
326}
327
328#[derive(Clone, Debug)]
332pub struct FlatMap<OutF: RecFamily, G, N> {
333 pub(crate) g: G,
334 pub(crate) next: N,
335 pub(crate) meter: OpMeterSlot,
336 pub(crate) _out: PhantomData<fn() -> OutF>,
337}
338
339impl<In, OutF, G, N> Collector<In> for FlatMap<OutF, G, N>
340where
341 OutF: RecFamily,
342 G: FnMut(In, &mut Emitter<'_, OutF>),
343 N: for<'buf> Collector<<OutF as RecFamily>::Rec<'buf>>,
344{
345 #[inline(always)]
346 fn push(&mut self, rec: Record<In>) -> Flow {
347 self.meter.0.seen();
348 let Record { payload, meta, ack } = rec;
349 let mut em = Emitter {
350 next: &mut self.next,
351 meta,
352 ack: &ack,
353 emitted: 0,
354 flow: Flow::Continue,
355 };
356 (self.g)(payload, &mut em);
357 let (emitted, flow) = (em.emitted, em.flow);
358 self.meter.0.out_n(emitted);
359 flow
360 }
361}
362
363impl<G, N: StageLifecycle> StageLifecycle for Map<G, N> {
366 fn on_batch_end(&mut self, elapsed: Duration) {
367 self.meter.0.flush(elapsed);
368 self.next.on_batch_end(elapsed);
369 }
370 fn take_fatal(&mut self) -> Option<FatalError> {
371 self.next.take_fatal()
372 }
373 fn relieve(&mut self) -> Flow {
374 self.next.relieve()
375 }
376 fn flush_terminal(&mut self) -> Flow {
377 self.next.flush_terminal()
378 }
379}
380
381impl<P, N: StageLifecycle> StageLifecycle for Filter<P, N> {
382 fn on_batch_end(&mut self, elapsed: Duration) {
383 self.meter.0.flush(elapsed);
384 self.next.on_batch_end(elapsed);
385 }
386 fn take_fatal(&mut self) -> Option<FatalError> {
387 self.next.take_fatal()
388 }
389 fn relieve(&mut self) -> Flow {
390 self.next.relieve()
391 }
392 fn flush_terminal(&mut self) -> Flow {
393 self.next.flush_terminal()
394 }
395}
396
397impl<G, N: StageLifecycle> StageLifecycle for Inspect<G, N> {
398 fn on_batch_end(&mut self, elapsed: Duration) {
399 self.next.on_batch_end(elapsed);
400 }
401 fn take_fatal(&mut self) -> Option<FatalError> {
402 self.next.take_fatal()
403 }
404 fn relieve(&mut self) -> Flow {
405 self.next.relieve()
406 }
407 fn flush_terminal(&mut self) -> Flow {
408 self.next.flush_terminal()
409 }
410}
411
412impl<G, N: StageLifecycle> StageLifecycle for TryMap<G, N> {
413 fn on_batch_end(&mut self, elapsed: Duration) {
414 self.meter.0.flush(elapsed);
415 self.next.on_batch_end(elapsed);
416 }
417 fn take_fatal(&mut self) -> Option<FatalError> {
418 self.fatal.0.take().or_else(|| self.next.take_fatal())
419 }
420 fn relieve(&mut self) -> Flow {
421 self.next.relieve()
422 }
423 fn flush_terminal(&mut self) -> Flow {
424 self.next.flush_terminal()
425 }
426}
427
428impl<OutF: RecFamily, G, N: StageLifecycle> StageLifecycle for FlatMap<OutF, G, N> {
429 fn on_batch_end(&mut self, elapsed: Duration) {
430 self.meter.0.flush(elapsed);
431 self.next.on_batch_end(elapsed);
432 }
433 fn take_fatal(&mut self) -> Option<FatalError> {
434 self.next.take_fatal()
435 }
436 fn relieve(&mut self) -> Flow {
437 self.next.relieve()
438 }
439 fn flush_terminal(&mut self) -> Flow {
440 self.next.flush_terminal()
441 }
442}
443
444struct OpsEmit<'a, Ops> {
449 ops: &'a mut Ops,
450 emitted: &'a mut u64,
451 flow: &'a mut Flow,
452}
453
454impl<'buf, T, Ops> EmitRecord<'buf, T> for OpsEmit<'_, Ops>
455where
456 Ops: Collector<T>,
457{
458 #[inline(always)]
459 fn emit(&mut self, rec: Record<T>) -> Flow {
460 *self.emitted += 1;
461 let flow = self.ops.push(rec);
462 if *self.flow != Flow::Blocked {
463 *self.flow = flow;
464 }
465 flow
466 }
467}
468
469static DESER_SKIP_WARN: RateLimit = RateLimit::new(5, Duration::from_secs(10));
470
471#[derive(Debug)]
477struct PendingPayload {
478 bytes: Vec<u8>,
479 key: Option<Vec<u8>>,
480 partition: crate::record::PartitionId,
481 offset: i64,
482 timestamp_ms: i64,
483}
484
485impl PendingPayload {
486 fn from_raw(raw: &RawPayload<'_>) -> Self {
487 PendingPayload {
488 bytes: raw.bytes.to_vec(),
489 key: raw.key.map(<[u8]>::to_vec),
490 partition: raw.partition,
491 offset: raw.offset,
492 timestamp_ms: raw.timestamp_ms,
493 }
494 }
495
496 fn as_raw(&self) -> RawPayload<'_> {
497 RawPayload {
498 bytes: &self.bytes,
499 key: self.key.as_deref(),
500 partition: self.partition,
501 offset: self.offset,
502 timestamp_ms: self.timestamp_ms,
503 }
504 }
505}
506
507enum Step {
509 Continue,
511 Backpressure,
514 NotReady,
517 Fatal(FatalError),
519}
520
521pub struct TypedChain<F: RecFamily, D, Ops> {
526 deser: D,
527 ops: Ops,
528 deser_policy: ErrorPolicy,
529 deser_metrics: Option<Arc<DeserMetrics>>,
530 cursor: usize,
532 mid_batch: bool,
533 pending: Option<PendingPayload>,
536 _family: PhantomData<fn() -> F>,
537}
538
539impl<F: RecFamily, D, Ops> TypedChain<F, D, Ops> {
540 pub(crate) fn new(
541 deser: D,
542 ops: Ops,
543 deser_policy: ErrorPolicy,
544 deser_metrics: Option<Arc<DeserMetrics>>,
545 ) -> Self {
546 TypedChain {
547 deser,
548 ops,
549 deser_policy,
550 deser_metrics,
551 cursor: 0,
552 mid_batch: false,
553 pending: None,
554 _family: PhantomData,
555 }
556 }
557}
558
559impl<F, D, Ops> TypedChain<F, D, Ops>
560where
561 F: RecFamily,
562 D: Deserializer<F>,
563 Ops: for<'buf> Collector<<F as RecFamily>::Rec<'buf>> + StageLifecycle + Send,
564{
565 fn deser_step(
567 &mut self,
568 raw: &RawPayload<'_>,
569 ack: &AckRef,
570 ok: &mut u64,
571 errors: &mut u64,
572 ) -> Step {
573 let mut flow = Flow::Continue;
574 let mut emitted = 0u64;
575 let result = self.deser.deserialize(
576 raw,
577 ack,
578 &mut OpsEmit {
579 ops: &mut self.ops,
580 emitted: &mut emitted,
581 flow: &mut flow,
582 },
583 );
584 *ok += emitted;
585 match result {
586 Err(DeserError::NotReady { .. }) => {
587 debug_assert_eq!(
588 emitted, 0,
589 "NotReady after emitting records would duplicate them on replay"
590 );
591 return Step::NotReady;
592 }
593 Err(e) => {
594 *errors += 1;
595 match self.deser_policy {
596 ErrorPolicy::Skip => {
597 crate::rate_limited_warn!(
598 DESER_SKIP_WARN,
599 partition = raw.partition.0,
600 offset = raw.offset,
601 error = %e,
602 "payload skipped by deserializer error policy"
603 );
604 }
605 _ => {
606 return Step::Fatal(FatalError {
607 component: "deserializer".to_string(),
608 reason: e.to_string(),
609 });
610 }
611 }
612 }
613 Ok(()) => {}
614 }
615 if let Some(fatal) = self.ops.take_fatal() {
616 return Step::Fatal(fatal);
617 }
618 if flow == Flow::Blocked || self.ops.relieve() == Flow::Blocked {
621 return Step::Backpressure;
622 }
623 Step::Continue
624 }
625}
626
627impl<F: RecFamily, D, Ops> std::fmt::Debug for TypedChain<F, D, Ops> {
628 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
629 f.debug_struct("TypedChain")
630 .field("cursor", &self.cursor)
631 .field("mid_batch", &self.mid_batch)
632 .finish_non_exhaustive()
633 }
634}
635
636impl<F, D, Ops> RunnableChain for TypedChain<F, D, Ops>
637where
638 F: RecFamily,
639 D: Deserializer<F>,
640 Ops: for<'buf> Collector<<F as RecFamily>::Rec<'buf>> + StageLifecycle + Send,
641{
642 fn push_batch<'buf>(&mut self, batch: &mut dyn PayloadBatch<'buf>, from: usize) -> PushOutcome {
643 if self.mid_batch {
644 debug_assert_eq!(
645 from, self.cursor,
646 "resume must continue where Blocked stopped"
647 );
648 } else {
649 debug_assert_eq!(from, 0, "fresh batches start at payload 0");
650 debug_assert!(
651 self.pending.is_none(),
652 "a not-ready payload must not survive its batch"
653 );
654 self.cursor = 0;
655 self.pending = None;
656 self.mid_batch = true;
657 }
658
659 if self.ops.relieve() == Flow::Blocked {
662 return PushOutcome::Blocked {
663 resume_at: self.cursor,
664 reason: BlockReason::Capacity,
665 };
666 }
667
668 let started = Instant::now();
669 let mut ok: u64 = 0;
670 let mut errors: u64 = 0;
671 let mut not_ready: u64 = 0;
672 let mut outcome: Option<PushOutcome> = None;
673
674 if let Some(p) = self.pending.take() {
677 let raw = p.as_raw();
678 match self.deser_step(&raw, batch.ack(), &mut ok, &mut errors) {
679 Step::Continue => self.cursor += 1,
680 Step::Backpressure => {
681 self.cursor += 1;
682 outcome = Some(PushOutcome::Blocked {
683 resume_at: self.cursor,
684 reason: BlockReason::Capacity,
685 });
686 }
687 Step::NotReady => {
688 not_ready += 1;
689 self.pending = Some(p);
690 outcome = Some(PushOutcome::Blocked {
691 resume_at: self.cursor,
692 reason: BlockReason::NotReady,
693 });
694 }
695 Step::Fatal(f) => outcome = Some(PushOutcome::Fatal(f)),
696 }
697 }
698
699 while outcome.is_none() {
700 let Some(raw) = batch.next_payload() else {
701 break;
702 };
703 match self.deser_step(&raw, batch.ack(), &mut ok, &mut errors) {
704 Step::Continue => self.cursor += 1,
705 Step::Backpressure => {
706 self.cursor += 1;
707 outcome = Some(PushOutcome::Blocked {
708 resume_at: self.cursor,
709 reason: BlockReason::Capacity,
710 });
711 }
712 Step::NotReady => {
713 not_ready += 1;
714 self.pending = Some(PendingPayload::from_raw(&raw));
715 outcome = Some(PushOutcome::Blocked {
716 resume_at: self.cursor,
717 reason: BlockReason::NotReady,
718 });
719 }
720 Step::Fatal(f) => outcome = Some(PushOutcome::Fatal(f)),
721 }
722 }
723
724 let elapsed = started.elapsed();
725 if let Some(m) = &self.deser_metrics {
726 m.batch(ok, errors, elapsed);
727 if errors > 0 && matches!(self.deser_policy, ErrorPolicy::Skip) {
728 m.dropped(errors);
729 }
730 if not_ready > 0 {
731 m.not_ready(not_ready);
732 }
733 }
734 self.ops.on_batch_end(elapsed);
735
736 match outcome {
737 Some(PushOutcome::Fatal(f)) => {
738 batch.ack().fail();
739 self.mid_batch = false;
740 self.pending = None;
741 PushOutcome::Fatal(f)
742 }
743 Some(blocked) => blocked,
744 None => {
745 self.mid_batch = false;
746 PushOutcome::Done
747 }
748 }
749 }
750
751 fn flush(&mut self) -> PushOutcome {
752 if let Some(fatal) = self.ops.take_fatal() {
753 return PushOutcome::Fatal(fatal);
754 }
755 match self.ops.flush_terminal() {
756 Flow::Continue => PushOutcome::Done,
757 Flow::Blocked => PushOutcome::Blocked {
758 resume_at: self.cursor,
759 reason: BlockReason::Capacity,
760 },
761 }
762 }
763
764 fn abandon_batch(&mut self) {
765 self.mid_batch = false;
771 self.cursor = 0;
772 self.pending = None;
773 }
774}