1use crate::pipeline::apply::opts::ApplyOpts;
11use crate::pipeline::apply::transform::BatchTransformer;
12use crate::pipeline::apply::{
13 ApplyEvent, ChangeFeed, ChangeFeedRef, CheckpointPolicy, FailurePolicy, PositionedEvent,
14 RuntimeExit, SourceDriver, SourceRuntimeOpts,
15};
16use crate::pipeline::pipeline::Pipeline;
17use anyhow::{anyhow, bail, Context, Result};
18use std::collections::{HashMap, HashSet};
19use std::sync::Arc;
20use std::time::Instant;
21use surreal_sync_core::SurrealSink;
22use surreal_sync_core::{Change, ChangeOp, Relation, RelationChange, Row};
23use tokio::task::JoinSet;
24use tracing::warn;
25
26pub async fn write_rows<S: SurrealSink>(
33 sink: &S,
34 pipeline: &Pipeline,
35 rows: Vec<Row>,
36 opts: &ApplyOpts,
37) -> Result<()> {
38 write_rows_with(sink, Arc::new(pipeline.clone()), rows, opts).await
39}
40
41pub async fn write_relations<S: SurrealSink>(
44 sink: &S,
45 pipeline: &Pipeline,
46 relations: Vec<Relation>,
47 opts: &ApplyOpts,
48) -> Result<()> {
49 write_relations_with(sink, Arc::new(pipeline.clone()), relations, opts).await
50}
51
52pub async fn apply_changes<S: SurrealSink>(
54 sink: &S,
55 pipeline: &Pipeline,
56 changes: Vec<Change>,
57 opts: &ApplyOpts,
58) -> Result<()> {
59 apply_changes_with(sink, Arc::new(pipeline.clone()), changes, opts).await
60}
61
62pub async fn apply_relation_changes<S: SurrealSink>(
65 sink: &S,
66 pipeline: &Pipeline,
67 changes: Vec<RelationChange>,
68 opts: &ApplyOpts,
69) -> Result<()> {
70 apply_relation_changes_with(sink, Arc::new(pipeline.clone()), changes, opts).await
71}
72
73pub async fn apply_changes_with<S, T>(
77 sink: &S,
78 transformer: Arc<T>,
79 changes: Vec<Change>,
80 opts: &ApplyOpts,
81) -> Result<()>
82where
83 S: SurrealSink,
84 T: BatchTransformer + 'static,
85{
86 if changes.is_empty() {
87 return Ok(());
88 }
89 let mut ctx = ApplyContext::new(sink, transformer, opts);
90 for (i, change) in changes.into_iter().enumerate() {
91 ctx.push_change(change, i as u64).await?;
92 }
93 ctx.flush().await?;
94 Ok(())
95}
96
97pub async fn apply_relation_changes_with<S, T>(
99 sink: &S,
100 transformer: Arc<T>,
101 changes: Vec<RelationChange>,
102 opts: &ApplyOpts,
103) -> Result<()>
104where
105 S: SurrealSink,
106 T: BatchTransformer + 'static,
107{
108 if changes.is_empty() {
109 return Ok(());
110 }
111 let mut ctx = ApplyContext::new(sink, transformer, opts);
112 for (i, change) in changes.into_iter().enumerate() {
113 ctx.push_relation_change(change, i as u64).await?;
114 }
115 ctx.flush().await?;
116 Ok(())
117}
118
119pub async fn write_rows_with<S, T>(
121 sink: &S,
122 transformer: Arc<T>,
123 rows: Vec<Row>,
124 opts: &ApplyOpts,
125) -> Result<()>
126where
127 S: SurrealSink,
128 T: BatchTransformer + 'static,
129{
130 if rows.is_empty() {
131 return Ok(());
132 }
133 let mut ctx = ApplyContext::new(sink, transformer, opts);
136 for (i, row) in rows.into_iter().enumerate() {
137 let change = Change::update(row.table, row.id, row.fields);
138 ctx.push_change(change, i as u64).await?;
139 }
140 ctx.flush().await?;
141 Ok(())
142}
143
144pub async fn write_relations_with<S, T>(
146 sink: &S,
147 transformer: Arc<T>,
148 relations: Vec<Relation>,
149 opts: &ApplyOpts,
150) -> Result<()>
151where
152 S: SurrealSink,
153 T: BatchTransformer + 'static,
154{
155 if relations.is_empty() {
156 return Ok(());
157 }
158 let mut ctx = ApplyContext::new(sink, transformer, opts);
159 for (i, relation) in relations.into_iter().enumerate() {
160 let change = RelationChange::update(relation);
161 ctx.push_relation_change(change, i as u64).await?;
162 }
163 ctx.flush().await?;
164 Ok(())
165}
166
167pub(crate) async fn apply_transformed_sink_events<S: SurrealSink>(
175 sink: &S,
176 events: &[ApplyEvent],
177) -> Result<()> {
178 if events.is_empty() {
179 return Ok(());
180 }
181 if let Some(rows) = try_coalesce_row_upserts(events) {
182 return sink.write_rows(&rows).await.context("sink write_rows");
183 }
184 if let Some(relations) = try_coalesce_relation_upserts(events) {
185 return sink
186 .write_relations(&relations)
187 .await
188 .context("sink write_relations");
189 }
190 for event in events {
191 match event {
192 ApplyEvent::Change(change) => {
193 sink.apply_change(change)
194 .await
195 .context("sink apply_change")?;
196 }
197 ApplyEvent::RelationChange(change) => {
198 sink.apply_relation_change(change)
199 .await
200 .context("sink apply_relation_change")?;
201 }
202 }
203 }
204 Ok(())
205}
206
207fn try_coalesce_row_upserts(events: &[ApplyEvent]) -> Option<Vec<Row>> {
208 let mut rows = Vec::with_capacity(events.len());
209 for event in events {
210 match event {
211 ApplyEvent::Change(change) if change.operation == ChangeOp::Update => {
212 let data = change.fields.as_ref()?.clone();
213 rows.push(Row::new(change.table.clone(), 0, change.id.clone(), data));
214 }
215 _ => return None,
216 }
217 }
218 Some(rows)
219}
220
221fn try_coalesce_relation_upserts(events: &[ApplyEvent]) -> Option<Vec<Relation>> {
222 let mut relations = Vec::with_capacity(events.len());
223 for event in events {
224 match event {
225 ApplyEvent::RelationChange(change) if change.operation == ChangeOp::Update => {
226 relations.push(change.relation.clone());
227 }
228 _ => return None,
229 }
230 }
231 Some(relations)
232}
233
234pub async fn run_change_feed<F, S>(
241 feed: &mut F,
242 sink: &S,
243 pipeline: &Pipeline,
244 opts: &ApplyOpts,
245) -> Result<()>
246where
247 F: ChangeFeed,
248 S: SurrealSink,
249{
250 run_change_feed_with(feed, sink, Arc::new(pipeline.clone()), opts).await
251}
252
253pub async fn run_change_feed_with<F, S, T>(
255 feed: &mut F,
256 sink: &S,
257 transformer: Arc<T>,
258 opts: &ApplyOpts,
259) -> Result<()>
260where
261 F: ChangeFeed,
262 S: SurrealSink,
263 T: BatchTransformer + 'static,
264{
265 let mut driver = ChangeFeedRef::new(feed);
266 let exit = crate::pipeline::apply::source_driver::run_source_runtime_with(
267 &mut driver,
268 sink,
269 transformer,
270 opts,
271 &SourceRuntimeOpts::default(),
272 )
273 .await?;
274 match exit {
275 RuntimeExit::Stopped(_) => Ok(()),
276 }
277}
278
279struct CompletedBatch<P> {
280 batch_id: u64,
281 last_position: P,
282 event_count: u64,
284 result: Result<Vec<ApplyEvent>>,
285}
286
287struct TransformOutcome<P> {
288 epoch: u64,
289 seq: u64,
290 batch_id: u64,
291 last_position: P,
292 event_count: u64,
293 result: Result<Vec<ApplyEvent>>,
294}
295
296pub(crate) struct PreparedSinkBatch<P> {
298 pub(crate) batch_id: u64,
299 pub(crate) last_position: P,
300 pub(crate) event_count: u64,
301 pub(crate) result: Result<Vec<ApplyEvent>>,
302}
303
304pub struct ApplyContext<'a, S, T, P = ()> {
322 sink: &'a S,
323 transformer: Arc<T>,
324 opts: &'a ApplyOpts,
325 buffer: Vec<PositionedEvent<P>>,
326 next_batch_id: u64,
327 next_seq: u64,
328 next_to_apply: u64,
329 in_flight: HashSet<u64>,
330 completed: HashMap<u64, CompletedBatch<P>>,
331 join_set: JoinSet<TransformOutcome<P>>,
332 sink_in_flight: bool,
334 buffer_started: Option<tokio::time::Instant>,
335 epoch: u64,
337 poisoned: bool,
339 sunk_since_take: u64,
341 pending_checkpoint: Option<P>,
343 last_checkpoint_persist: Instant,
345}
346
347impl<'a, S, T, P> ApplyContext<'a, S, T, P>
348where
349 S: SurrealSink,
350 T: BatchTransformer + 'static,
351 P: Clone + Send + Sync + 'static,
352{
353 pub fn new(sink: &'a S, transformer: Arc<T>, opts: &'a ApplyOpts) -> Self {
355 Self {
356 sink,
357 transformer,
358 opts,
359 buffer: Vec::new(),
360 next_batch_id: 1,
361 next_seq: 0,
362 next_to_apply: 0,
363 in_flight: HashSet::new(),
364 completed: HashMap::new(),
365 join_set: JoinSet::new(),
366 sink_in_flight: false,
367 buffer_started: None,
368 epoch: 0,
369 poisoned: false,
370 sunk_since_take: 0,
371 pending_checkpoint: None,
372 last_checkpoint_persist: Instant::now(),
373 }
374 }
375
376 pub fn is_poisoned(&self) -> bool {
378 self.poisoned
379 }
380
381 pub fn buffer_len(&self) -> usize {
383 self.buffer.len()
384 }
385
386 pub fn in_flight_count(&self) -> usize {
388 self.in_flight.len()
389 }
390
391 pub fn sink_in_flight(&self) -> bool {
393 self.sink_in_flight
394 }
395
396 pub fn window_occupancy(&self) -> usize {
401 self.in_flight.len() + self.completed.len() + usize::from(self.sink_in_flight)
402 }
403
404 pub fn take_sunk_change_count(&mut self) -> u64 {
406 std::mem::take(&mut self.sunk_since_take)
407 }
408
409 pub fn completed_waiting_count(&self) -> usize {
411 self.completed.len()
412 }
413
414 pub fn has_unsunk_work(&self) -> bool {
419 self.buffer_len() > 0
420 || self.in_flight_count() > 0
421 || self.completed_waiting_count() > 0
422 || self.sink_in_flight
423 }
424
425 pub fn is_fully_drained(&self) -> bool {
427 !self.has_unsunk_work()
428 }
429
430 fn ensure_not_poisoned(&self) -> Result<()> {
431 if self.poisoned {
432 bail!(
433 "ApplyContext is poisoned after FailurePolicy::Fail; \
434 create a new context and replay from the last successful advance_watermark"
435 );
436 }
437 Ok(())
438 }
439
440 pub(crate) fn push_buffered_event(&mut self, pe: PositionedEvent<P>) {
441 if self.buffer.is_empty() {
442 self.buffer_started = Some(tokio::time::Instant::now());
443 }
444 self.buffer.push(pe);
445 }
446
447 pub(crate) fn should_flush_partial_public(&self) -> bool {
448 self.should_flush_partial()
449 }
450
451 fn should_flush_partial(&self) -> bool {
452 match self.buffer_started {
453 Some(started) => started.elapsed() >= self.opts.batch_max_wait,
454 None => false,
455 }
456 }
457
458 pub async fn push_change(&mut self, change: Change, position: P) -> Result<Option<P>> {
462 self.ensure_not_poisoned()?;
463 self.push_buffered_event(PositionedEvent::change(change, position));
464 while self.try_start_full_batch() {}
465 self.collect_ready_transforms().await?;
466 self.drain_ordered_no_advance().await
467 }
468
469 pub async fn push_relation_change(
471 &mut self,
472 change: RelationChange,
473 position: P,
474 ) -> Result<Option<P>> {
475 self.ensure_not_poisoned()?;
476 self.push_buffered_event(PositionedEvent::relation_change(change, position));
477 while self.try_start_full_batch() {}
478 self.collect_ready_transforms().await?;
479 self.drain_ordered_no_advance().await
480 }
481
482 pub async fn push_event(&mut self, event: ApplyEvent, position: P) -> Result<Option<P>> {
484 self.ensure_not_poisoned()?;
485 self.push_buffered_event(PositionedEvent::new(event, position));
486 while self.try_start_full_batch() {}
487 self.collect_ready_transforms().await?;
488 self.drain_ordered_no_advance().await
489 }
490
491 async fn collect_ready_transforms(&mut self) -> Result<()> {
497 self.poll_join_ready().await
498 }
499
500 pub async fn flush(&mut self) -> Result<Option<P>> {
502 self.ensure_not_poisoned()?;
503 let mut last = None;
504 loop {
505 while self.try_start_partial_batch() {}
506
507 if let Some(p) = self.drain_ordered_no_advance().await? {
508 last = Some(p);
509 }
510
511 if self.buffer.is_empty()
512 && self.in_flight.is_empty()
513 && self.completed.is_empty()
514 && !self.sink_in_flight
515 {
516 break;
517 }
518
519 if self.in_flight_count() > 0 {
520 self.wait_one_completion().await?;
521 continue;
522 }
523
524 if !self.buffer.is_empty() {
525 if !self.try_start_partial_batch() {
526 bail!(
527 "flush: {} buffered event(s) but window would not accept a batch",
528 self.buffer.len()
529 );
530 }
531 continue;
532 }
533
534 bail!(
535 "flush: {} completed batch(es) waiting but none is next_to_apply={}",
536 self.completed.len(),
537 self.next_to_apply
538 );
539 }
540 Ok(last)
541 }
542
543 pub async fn write_rows(&self, rows: Vec<Row>) -> Result<()> {
545 self.ensure_not_poisoned()?;
546 write_rows_with(self.sink, Arc::clone(&self.transformer), rows, self.opts).await
547 }
548
549 pub async fn write_relations(&self, relations: Vec<Relation>) -> Result<()> {
551 self.ensure_not_poisoned()?;
552 write_relations_with(
553 self.sink,
554 Arc::clone(&self.transformer),
555 relations,
556 self.opts,
557 )
558 .await
559 }
560
561 pub(crate) fn try_start_full_batch(&mut self) -> bool {
562 if self.buffer.len() < self.opts.batch_size {
563 return false;
564 }
565 self.start_batch_from_buffer(self.opts.batch_size)
566 }
567
568 pub(crate) fn try_start_partial_batch(&mut self) -> bool {
569 if self.buffer.is_empty() {
570 return false;
571 }
572 let n = self.buffer.len();
573 self.start_batch_from_buffer(n)
574 }
575
576 fn start_batch_from_buffer(&mut self, n: usize) -> bool {
577 if n == 0 {
578 return false;
579 }
580 if self.window_occupancy() >= self.opts.max_in_flight {
583 return false;
584 }
585
586 let batch: Vec<PositionedEvent<P>> = self.buffer.drain(..n).collect();
587 if self.buffer.is_empty() {
588 self.buffer_started = None;
589 } else {
590 self.buffer_started = Some(tokio::time::Instant::now());
591 }
592
593 let last_position = batch.last().expect("n > 0").position.clone();
594 let events: Vec<ApplyEvent> = batch.into_iter().map(|pe| pe.event).collect();
595 let event_count = events.len() as u64;
596
597 let batch_id = self.next_batch_id;
598 self.next_batch_id = self.next_batch_id.saturating_add(1);
599 let seq = self.next_seq;
600 self.next_seq = self.next_seq.saturating_add(1);
601
602 self.in_flight.insert(seq);
603
604 let transformer = Arc::clone(&self.transformer);
605 let timeout = self.opts.timeout;
606 let epoch = self.epoch;
607 let identity = transformer.is_identity();
610 self.join_set.spawn(async move {
611 let result = if identity {
612 Ok(events)
613 } else {
614 match tokio::time::timeout(timeout, transformer.transform_events(batch_id, events))
615 .await
616 {
617 Ok(inner) => inner,
618 Err(_) => Err(anyhow!(
619 "transform timeout after {:?} for batch_id={batch_id}",
620 timeout
621 )),
622 }
623 };
624 TransformOutcome {
625 epoch,
626 seq,
627 batch_id,
628 last_position,
629 event_count,
630 result,
631 }
632 });
633 true
634 }
635
636 pub(crate) async fn poll_join_ready_public(&mut self) -> Result<()> {
637 self.poll_join_ready().await
638 }
639
640 pub(crate) async fn wait_one_completion_public(&mut self) -> Result<()> {
641 self.wait_one_completion().await
642 }
643
644 pub(crate) async fn try_interval_persist_public(
645 &mut self,
646 driver: &mut impl SourceDriver<Position = P>,
647 ) -> Result<()> {
648 self.try_interval_persist(driver).await
649 }
650
651 fn try_poll_join(&mut self) -> Option<Result<TransformOutcome<P>>> {
652 let handle = self.join_set.try_join_next()?;
653 Some(match handle {
654 Ok(outcome) => Ok(outcome),
655 Err(e) => Err(anyhow!("transform task join error: {e}")),
656 })
657 }
658
659 async fn poll_join_ready(&mut self) -> Result<()> {
660 while let Some(outcome) = self.try_poll_join() {
661 self.handle_outcome(outcome?)?;
662 }
663 if self.in_flight_count() > 0 {
664 tokio::task::yield_now().await;
665 while let Some(outcome) = self.try_poll_join() {
666 self.handle_outcome(outcome?)?;
667 }
668 }
669 Ok(())
670 }
671
672 pub(crate) async fn wait_one_completion(&mut self) -> Result<()> {
673 let outcome = self
674 .join_set
675 .join_next()
676 .await
677 .ok_or_else(|| anyhow!("no in-flight transform tasks"))?
678 .map_err(|e| anyhow!("transform task join error: {e}"))?;
679 self.handle_outcome(outcome)
680 }
681
682 fn handle_outcome(&mut self, outcome: TransformOutcome<P>) -> Result<()> {
683 if outcome.epoch != self.epoch {
684 return Ok(());
685 }
686 if !self.in_flight.remove(&outcome.seq) {
687 return Ok(());
688 }
689 self.completed.insert(
690 outcome.seq,
691 CompletedBatch {
692 batch_id: outcome.batch_id,
693 last_position: outcome.last_position,
694 event_count: outcome.event_count,
695 result: outcome.result,
696 },
697 );
698 Ok(())
699 }
700
701 pub(crate) fn prepare_ordered_sink(&mut self) -> Option<PreparedSinkBatch<P>> {
707 if self.sink_in_flight {
708 return None;
709 }
710 let batch = self.completed.remove(&self.next_to_apply)?;
711 self.sink_in_flight = true;
712 Some(PreparedSinkBatch {
713 batch_id: batch.batch_id,
714 last_position: batch.last_position,
715 event_count: batch.event_count,
716 result: batch.result,
717 })
718 }
719
720 pub(crate) async fn finish_sink_ok_driver(
725 &mut self,
726 driver: &mut impl SourceDriver<Position = P>,
727 last_position: P,
728 sunk: u64,
729 ) -> Result<()> {
730 self.sunk_since_take = self.sunk_since_take.saturating_add(sunk);
731 driver.note_sunk_events(sunk);
732 driver
733 .advance_watermark(last_position.clone())
734 .await
735 .context("advance_watermark")?;
736 self.next_to_apply += 1;
739 self.sink_in_flight = false;
740 self.after_advance_persist(driver, last_position).await?;
741 Ok(())
742 }
743
744 pub(crate) async fn finish_sink_err_driver(
746 &mut self,
747 driver: &mut impl SourceDriver<Position = P>,
748 batch_id: u64,
749 last_position: P,
750 event_count: u64,
751 e: anyhow::Error,
752 ) -> Result<()> {
753 self.sink_in_flight = false;
754 self.fail_or_skip_driver(driver, batch_id, last_position, event_count, e)
755 .await
756 }
757
758 pub(crate) fn finish_sink_ok_no_advance(&mut self, last_position: P, sunk: u64) -> P {
760 self.sunk_since_take = self.sunk_since_take.saturating_add(sunk);
761 self.next_to_apply += 1;
762 self.sink_in_flight = false;
763 last_position
764 }
765
766 pub(crate) fn finish_sink_err_no_advance(
768 &mut self,
769 batch_id: u64,
770 last_position: P,
771 e: anyhow::Error,
772 ) -> Result<Option<P>> {
773 self.sink_in_flight = false;
774 self.fail_or_skip_no_feed(batch_id, last_position, e)
775 }
776
777 pub(crate) async fn drain_ordered_driver(
781 &mut self,
782 driver: &mut impl SourceDriver<Position = P>,
783 ) -> Result<()> {
784 loop {
785 self.poll_join_ready().await?;
787 let Some(batch) = self.prepare_ordered_sink() else {
788 break;
789 };
790 match batch.result {
791 Ok(events) => match self.apply_sink_events(&events).await {
792 Ok(()) => {
793 self.finish_sink_ok_driver(driver, batch.last_position, batch.event_count)
797 .await?;
798 }
799 Err(e) => {
800 self.finish_sink_err_driver(
801 driver,
802 batch.batch_id,
803 batch.last_position,
804 batch.event_count,
805 e,
806 )
807 .await?;
808 }
809 },
810 Err(e) => {
811 self.finish_sink_err_driver(
812 driver,
813 batch.batch_id,
814 batch.last_position,
815 batch.event_count,
816 e,
817 )
818 .await?;
819 }
820 }
821 }
822 self.try_interval_persist(driver).await
823 }
824
825 async fn after_advance_persist(
826 &mut self,
827 driver: &mut impl SourceDriver<Position = P>,
828 position: P,
829 ) -> Result<()> {
830 match driver.checkpoint_policy() {
831 CheckpointPolicy::PersistAfterAdvance => {
832 driver
833 .persist_checkpoint(position)
834 .await
835 .context("persist_checkpoint")?;
836 }
837 CheckpointPolicy::AdvanceOnly => {}
838 CheckpointPolicy::IntervalWhenDrained { .. } => {
839 self.pending_checkpoint = Some(position);
840 if self.is_fully_drained() {
844 self.flush_pending_checkpoint(driver).await?;
845 }
846 }
847 }
848 Ok(())
849 }
850
851 async fn try_interval_persist(
852 &mut self,
853 driver: &mut impl SourceDriver<Position = P>,
854 ) -> Result<()> {
855 let CheckpointPolicy::IntervalWhenDrained { interval } = driver.checkpoint_policy() else {
856 return Ok(());
857 };
858 if !self.is_fully_drained() {
859 return Ok(());
860 }
861 if self.last_checkpoint_persist.elapsed() < interval {
862 return Ok(());
863 }
864 if self.pending_checkpoint.is_some() {
865 return self.flush_pending_checkpoint(driver).await;
866 }
867 if let Some(position) = driver
870 .read_progress_for_persist()
871 .await
872 .context("read_progress_for_persist")?
873 {
874 driver
875 .persist_checkpoint(position)
876 .await
877 .context("persist_checkpoint")?;
878 self.last_checkpoint_persist = Instant::now();
879 }
880 Ok(())
881 }
882
883 async fn flush_pending_checkpoint(
884 &mut self,
885 driver: &mut impl SourceDriver<Position = P>,
886 ) -> Result<()> {
887 if let Some(position) = self.pending_checkpoint.take() {
888 driver
889 .persist_checkpoint(position)
890 .await
891 .context("persist_checkpoint")?;
892 self.last_checkpoint_persist = Instant::now();
893 }
894 Ok(())
895 }
896
897 pub(crate) async fn flush_for_driver(
899 &mut self,
900 driver: &mut impl SourceDriver<Position = P>,
901 ) -> Result<()> {
902 if self.poisoned {
903 return Ok(());
904 }
905 loop {
906 while self.try_start_partial_batch() {}
907 self.drain_ordered_driver(driver).await?;
908 if self.buffer.is_empty()
909 && self.in_flight.is_empty()
910 && self.completed.is_empty()
911 && !self.sink_in_flight
912 {
913 self.flush_pending_checkpoint(driver).await?;
915 return Ok(());
916 }
917 if self.in_flight_count() > 0 {
918 self.wait_one_completion().await?;
919 continue;
920 }
921 if !self.buffer.is_empty() {
922 if !self.try_start_partial_batch() {
923 bail!(
924 "flush_for_driver: {} buffered but window would not accept a batch",
925 self.buffer.len()
926 );
927 }
928 continue;
929 }
930 bail!(
931 "flush_for_driver: {} completed waiting but none is next_to_apply={}",
932 self.completed.len(),
933 self.next_to_apply
934 );
935 }
936 }
937
938 async fn drain_ordered_no_advance(&mut self) -> Result<Option<P>> {
939 let mut last = None;
940 loop {
941 self.poll_join_ready().await?;
942 let Some(batch) = self.prepare_ordered_sink() else {
943 break;
944 };
945 match batch.result {
946 Ok(events) => match self.apply_sink_events(&events).await {
947 Ok(()) => {
948 last = Some(
951 self.finish_sink_ok_no_advance(batch.last_position, batch.event_count),
952 );
953 }
954 Err(e) => {
955 last = self.finish_sink_err_no_advance(
956 batch.batch_id,
957 batch.last_position,
958 e,
959 )?;
960 }
961 },
962 Err(e) => {
963 last =
964 self.finish_sink_err_no_advance(batch.batch_id, batch.last_position, e)?;
965 }
966 }
967 }
968 Ok(last)
969 }
970
971 async fn fail_or_skip_driver(
972 &mut self,
973 driver: &mut impl SourceDriver<Position = P>,
974 batch_id: u64,
975 last_position: P,
976 event_count: u64,
977 e: anyhow::Error,
978 ) -> Result<()> {
979 match self.opts.failure_policy {
980 FailurePolicy::Fail => {
981 self.discard_successors();
982 Err(e).with_context(|| format!("batch {batch_id} failed"))
983 }
984 FailurePolicy::Skip => {
985 warn!(
986 batch_id,
987 error = %e,
988 "skipping failed batch (failure_policy=skip); advancing watermark past it"
989 );
990 driver.note_sunk_events(event_count);
993 driver
994 .advance_watermark(last_position.clone())
995 .await
996 .context("advance_watermark after skip")?;
997 self.after_advance_persist(driver, last_position).await?;
998 self.next_to_apply += 1;
999 Ok(())
1000 }
1001 }
1002 }
1003
1004 fn fail_or_skip_no_feed(
1005 &mut self,
1006 batch_id: u64,
1007 last_position: P,
1008 e: anyhow::Error,
1009 ) -> Result<Option<P>> {
1010 match self.opts.failure_policy {
1011 FailurePolicy::Fail => {
1012 self.discard_successors();
1013 Err(e).with_context(|| format!("batch {batch_id} failed"))
1014 }
1015 FailurePolicy::Skip => {
1016 warn!(
1017 batch_id,
1018 error = %e,
1019 "skipping failed batch (failure_policy=skip)"
1020 );
1021 self.next_to_apply += 1;
1022 Ok(Some(last_position))
1023 }
1024 }
1025 }
1026
1027 fn discard_successors(&mut self) {
1028 self.epoch = self.epoch.saturating_add(1);
1029 self.in_flight.clear();
1030 self.completed.clear();
1031 self.buffer.clear();
1032 self.buffer_started = None;
1033 self.sink_in_flight = false;
1034 self.join_set.abort_all();
1035 self.poisoned = true;
1036 }
1037
1038 async fn apply_sink_events(&self, events: &[ApplyEvent]) -> Result<()> {
1039 apply_transformed_sink_events(self.sink, events).await
1040 }
1041}