1use std::sync::atomic::{AtomicU64, Ordering};
11use std::sync::{Arc, Mutex};
12use std::time::{Duration, Instant};
13
14use async_trait::async_trait;
15use tokio::sync::{Mutex as TokioMutex, mpsc};
16use tokio::task::JoinHandle;
17use tower::Service;
18
19pub mod batch;
20pub mod stream;
21
22use camel_api::{
23 BoxProcessor, CamelError, MetricsCollector, StepLifecycle, StepShutdownReason,
24 exchange::Exchange, message::Message, processor::SyncBoxProcessor,
25};
26
27const INOUT_WARN_INTERVAL: Duration = Duration::from_secs(30);
29
30#[derive(Clone, Default)]
32pub struct ResequencerConfig {
33 pub allow_inout: bool,
36 pub metrics: Option<Arc<dyn MetricsCollector>>,
38 pub route_id: Option<String>,
40}
41
42impl std::fmt::Debug for ResequencerConfig {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 f.debug_struct("ResequencerConfig")
45 .field("allow_inout", &self.allow_inout)
46 .field("metrics", &self.metrics.as_ref().map(|_| "<metrics>"))
47 .field("route_id", &self.route_id)
48 .finish()
49 }
50}
51
52pub const CAMEL_RESEQUENCER_ACCEPTED: &str = "CamelResequencerAccepted";
56
57pub const CAMEL_RESEQUENCER_DROPPED: &str = "CamelResequencerDropped";
59
60pub const CAMEL_RESEQUENCER_INOUT_WARN: &str = "CamelResequencerInoutWarn";
62
63#[async_trait]
69pub trait ResequencePolicy: Send + Sync + 'static {
70 async fn accept(&self, input: Exchange) -> Vec<Exchange>;
72
73 async fn flush(&self) -> Vec<Exchange>;
75
76 fn name(&self) -> &'static str;
78
79 fn buffered(&self) -> usize {
84 0
85 }
86
87 fn set_timeout_tx(&self, _tx: tokio::sync::mpsc::Sender<Exchange>) {}
91}
92
93#[derive(Clone)]
101pub struct ResequencerService {
102 policy: Arc<dyn ResequencePolicy>,
103 config: ResequencerConfig,
104 input_tx: Arc<Mutex<Option<mpsc::Sender<Exchange>>>>,
107 driver_tx: Arc<Mutex<Option<mpsc::Sender<Exchange>>>>,
111 actor_handle: Arc<Mutex<Option<JoinHandle<()>>>>,
112 driver_handle: Arc<Mutex<Option<JoinHandle<()>>>>,
113 shutdown_started: Arc<Mutex<bool>>,
114 post_lifecycles: Arc<Mutex<Vec<Arc<dyn StepLifecycle>>>>,
117 inout_counter: Arc<AtomicU64>,
119 last_inout_warn: Arc<TokioMutex<Option<Instant>>>,
121 metrics: Option<Arc<dyn MetricsCollector>>,
123 route_id: Option<String>,
125}
126
127impl std::fmt::Debug for ResequencerService {
128 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129 f.debug_struct("ResequencerService")
130 .field("policy", &self.policy.name())
131 .finish_non_exhaustive()
132 }
133}
134
135impl ResequencerService {
136 pub fn new(
148 policy: Arc<dyn ResequencePolicy>,
149 post_continuation: BoxProcessor,
150 input_capacity: usize,
151 post_lifecycles: Vec<Arc<dyn StepLifecycle>>,
152 ) -> Self {
153 Self::with_config(
154 policy,
155 post_continuation,
156 input_capacity,
157 post_lifecycles,
158 ResequencerConfig::default(),
159 )
160 }
161
162 pub fn with_config(
168 policy: Arc<dyn ResequencePolicy>,
169 post_continuation: BoxProcessor,
170 input_capacity: usize,
171 post_lifecycles: Vec<Arc<dyn StepLifecycle>>,
172 config: ResequencerConfig,
173 ) -> Self {
174 let (input_tx, mut input_rx) = mpsc::channel::<Exchange>(input_capacity);
176
177 let (driver_tx, mut driver_rx) = mpsc::channel::<Exchange>(input_capacity);
179
180 let input_tx_shared: Arc<Mutex<Option<mpsc::Sender<Exchange>>>> =
182 Arc::new(Mutex::new(Some(input_tx)));
183 let driver_tx_shared: Arc<Mutex<Option<mpsc::Sender<Exchange>>>> =
184 Arc::new(Mutex::new(Some(driver_tx.clone()))); let actor_handle: Arc<Mutex<Option<JoinHandle<()>>>> = Arc::new(Mutex::new(None));
186 let driver_handle: Arc<Mutex<Option<JoinHandle<()>>>> = Arc::new(Mutex::new(None));
187 let shutdown_started = Arc::new(Mutex::new(false));
188
189 policy.set_timeout_tx(driver_tx.clone());
191
192 let sync_post = SyncBoxProcessor::new(post_continuation);
194
195 {
197 let policy = Arc::clone(&policy);
198 let actor_h = Arc::clone(&actor_handle);
199 let actor_driver_tx = driver_tx; let queue_metrics = config.metrics.clone();
205 let queue_label = config
206 .route_id
207 .as_ref()
208 .map(|route| format!("resequencer:{route}"));
209 let handle = tokio::spawn(async move {
210 while let Some(input) = input_rx.recv().await {
211 let ready = policy.accept(input).await;
212 for ex in ready {
213 if actor_driver_tx.send(ex).await.is_err() {
215 return;
217 }
218 }
219 if let (Some(metrics), Some(label)) =
220 (queue_metrics.as_ref(), queue_label.as_ref())
221 {
222 metrics.set_queue_depth(label, policy.buffered());
223 }
224 }
225 });
227 *actor_h.lock().expect("actor_handle lock poisoned") = Some(handle); }
229
230 {
232 let post = sync_post.clone();
233 let driver_h = Arc::clone(&driver_handle);
234 let metrics = config.metrics.clone();
235 let route_id = config.route_id.clone();
236 let handle = tokio::spawn(async move {
237 while let Some(ex) = driver_rx.recv().await {
238 if camel_api::is_camel_stop(&ex) {
240 tracing::debug!(
241 "resequencer post-driver: skipping continuation for CamelStop exchange"
242 );
243 continue;
244 }
245 let mut proc = post.clone_inner();
247 match proc.call(ex).await {
248 Ok(_) => {}
249 Err(e) => {
250 tracing::warn!(
252 error = %e,
253 "resequencer post-driver: continuation call failed after ack (best-effort)"
254 );
255 if let Some(ref m) = metrics {
256 m.increment_errors(
257 route_id.as_deref().unwrap_or("unknown"),
258 "resequencer:post_ack_failure",
259 );
260 }
261 }
262 }
263 }
264 });
265 *driver_h.lock().expect("driver_handle lock poisoned") = Some(handle); }
267
268 let metrics = config.metrics.clone();
269 let route_id = config.route_id.clone();
270 Self {
271 policy,
272 config,
273 input_tx: input_tx_shared,
274 driver_tx: driver_tx_shared,
275 actor_handle,
276 driver_handle,
277 shutdown_started,
278 post_lifecycles: Arc::new(Mutex::new(post_lifecycles)),
279 inout_counter: Arc::new(AtomicU64::new(0)),
280 last_inout_warn: Arc::new(TokioMutex::new(None)),
281 metrics,
282 route_id,
283 }
284 }
285}
286
287impl Service<Exchange> for ResequencerService {
290 type Response = Exchange;
291 type Error = CamelError;
292 type Future =
293 std::pin::Pin<Box<dyn std::future::Future<Output = Result<Exchange, CamelError>> + Send>>;
294
295 fn poll_ready(
296 &mut self,
297 _cx: &mut std::task::Context<'_>,
298 ) -> std::task::Poll<Result<(), CamelError>> {
299 std::task::Poll::Ready(Ok(()))
301 }
302
303 fn call(&mut self, input: Exchange) -> Self::Future {
304 let config = self.config.clone();
305 let inout_counter = Arc::clone(&self.inout_counter);
306 let last_inout_warn = Arc::clone(&self.last_inout_warn);
307 let tx_opt = Arc::clone(&self.input_tx);
308 let metrics = self.metrics.clone();
309 let route_id = self.route_id.clone();
310
311 Box::pin(async move {
312 let mut ack = Exchange::new(Message::default());
314
315 if input.pattern == camel_api::exchange::ExchangePattern::InOut && !config.allow_inout {
317 inout_counter.fetch_add(1, Ordering::Relaxed);
318 ack.set_property(CAMEL_RESEQUENCER_INOUT_WARN, true);
319 if let Some(ref m) = metrics {
320 m.increment_errors(
321 route_id.as_deref().unwrap_or("unknown"),
322 "resequencer:inout_warning",
323 );
324 }
325 let now = Instant::now();
326 let mut last_guard = last_inout_warn.lock().await;
327 let should_warn = last_guard
328 .map(|t| now.duration_since(t) >= INOUT_WARN_INTERVAL)
329 .unwrap_or(true);
330 if should_warn {
331 let count = inout_counter.load(Ordering::Relaxed);
332 tracing::warn!(
333 inout_count = count,
334 "InOut exchange reached resequencer ({count} total); \
335 consider using InOnly pattern. \
336 Set allow_inout=true to suppress this warning."
337 );
338 *last_guard = Some(now);
339 }
340 }
341
342 let tx = {
345 let guard = tx_opt.lock().unwrap_or_else(|e| e.into_inner());
346 guard.clone()
347 };
348 if let Some(tx) = tx {
349 match tx.send(input).await {
351 Ok(()) => {
352 ack.set_property(CAMEL_RESEQUENCER_ACCEPTED, true);
353 }
354 Err(tokio::sync::mpsc::error::SendError(input)) => {
355 tracing::warn!(
356 correlation_id = %input.correlation_id,
357 "resequencer input dropped during shutdown"
358 );
359 ack.set_property(CAMEL_RESEQUENCER_ACCEPTED, false);
360 ack.set_property(CAMEL_RESEQUENCER_DROPPED, true);
361 }
362 }
363 }
364
365 Ok(ack)
366 })
367 }
368}
369
370#[async_trait]
373impl StepLifecycle for ResequencerService {
374 fn name(&self) -> &'static str {
375 self.policy.name()
376 }
377
378 async fn shutdown(&self, reason: StepShutdownReason) -> Result<(), CamelError> {
386 tracing::debug!(
390 reason = ?reason,
391 policy = self.policy.name(),
392 "ResequencerService shutdown via StepLifecycle"
393 );
394
395 {
397 let mut started = self
398 .shutdown_started
399 .lock()
400 .unwrap_or_else(|e| e.into_inner());
401 if *started {
402 tracing::debug!(
403 "ResequencerService shutdown already started (idempotent); skipping"
404 );
405 return Ok(());
406 }
407 *started = true;
408 }
409
410 {
414 let mut guard = self.input_tx.lock().unwrap_or_else(|e| e.into_inner());
415 *guard = None; }
417
418 let actor_handle_to_await = {
421 let mut guard = self.actor_handle.lock().unwrap_or_else(|e| e.into_inner());
422 guard.take()
423 };
424 if let Some(handle) = actor_handle_to_await {
425 let _ = tokio::time::timeout(Duration::from_secs(5), handle).await;
427 }
428
429 let flushed = self.policy.flush().await;
431 if !flushed.is_empty() {
432 let dt = {
433 let guard = self.driver_tx.lock().unwrap_or_else(|e| e.into_inner());
434 guard.clone()
435 };
436 if let Some(driver_tx) = dt {
437 for ex in flushed {
438 if driver_tx.send(ex).await.is_err() {
439 tracing::warn!(
440 "resequencer shutdown flush: post-driver channel closed early"
441 );
442 break;
443 }
444 }
445 }
446 }
447
448 {
450 let mut guard = self.driver_tx.lock().unwrap_or_else(|e| e.into_inner());
451 *guard = None; }
453
454 let driver_handle_to_await = {
456 let mut guard = self.driver_handle.lock().unwrap_or_else(|e| e.into_inner());
457 guard.take()
458 };
459 if let Some(handle) = driver_handle_to_await {
460 let result = tokio::time::timeout(Duration::from_secs(5), handle).await;
461 if result.is_err() {
462 tracing::warn!(
463 "resequencer post-driver task did not finish within 5s deadline; \
464 leaking handle (best-effort)"
465 );
466 }
467 }
468
469 {
472 let post_lcs: Vec<Arc<dyn StepLifecycle>> = {
473 let mut guard = self
474 .post_lifecycles
475 .lock()
476 .unwrap_or_else(|e| e.into_inner());
477 std::mem::take(&mut *guard)
478 };
479 for lc in &post_lcs {
480 if let Err(e) = lc.shutdown(reason).await {
481 tracing::warn!(
482 step = lc.name(),
483 error = %e,
484 "resequencer post-step lifecycle shutdown failed (best-effort)"
485 );
486 }
487 }
488 }
489
490 Ok(())
491 }
492}
493
494#[derive(Debug)]
499pub struct PassthroughPolicy;
500
501#[async_trait]
502impl ResequencePolicy for PassthroughPolicy {
503 async fn accept(&self, input: Exchange) -> Vec<Exchange> {
504 vec![input]
505 }
506
507 async fn flush(&self) -> Vec<Exchange> {
508 vec![]
509 }
510
511 fn name(&self) -> &'static str {
512 "passthrough"
513 }
514}
515
516#[cfg(test)]
519mod tests {
520 use super::*;
521 use std::time::Duration;
522 use tokio::time::timeout;
523 use tower::ServiceExt;
524
525 #[derive(Clone)]
527 struct CapturePost {
528 tx: mpsc::UnboundedSender<Exchange>,
529 }
530
531 impl Service<Exchange> for CapturePost {
532 type Response = Exchange;
533 type Error = CamelError;
534 type Future = std::pin::Pin<
535 Box<dyn std::future::Future<Output = Result<Exchange, CamelError>> + Send>,
536 >;
537
538 fn poll_ready(
539 &mut self,
540 _cx: &mut std::task::Context<'_>,
541 ) -> std::task::Poll<Result<(), CamelError>> {
542 std::task::Poll::Ready(Ok(()))
543 }
544
545 fn call(&mut self, exchange: Exchange) -> Self::Future {
546 let tx = self.tx.clone();
547 Box::pin(async move {
548 let _ = tx.send(exchange.clone());
550 Ok(exchange)
551 })
552 }
553 }
554
555 #[tokio::test]
556 async fn resequencer_boundary_passthrough_ack_and_continuation() {
557 use camel_api::body::Body;
558
559 let policy: Arc<dyn ResequencePolicy> = Arc::new(PassthroughPolicy);
561 let (capture_tx, mut capture_rx) = mpsc::unbounded_channel::<Exchange>();
562 let capture = CapturePost { tx: capture_tx };
563 let post_continuation: BoxProcessor = BoxProcessor::new(capture);
564
565 let service = ResequencerService::new(policy, post_continuation, 1024, vec![]);
566
567 let mut input = Exchange::new(Message::new(Body::Text("hello".into())));
569 input.set_property("seq", 1);
570
571 let ack = service.clone().oneshot(input).await.unwrap();
572
573 assert!(
575 matches!(ack.input.body, Body::Empty),
576 "ack body should be Empty, got {:?}",
577 ack.input.body
578 );
579 assert_eq!(
580 ack.property(CAMEL_RESEQUENCER_ACCEPTED)
581 .and_then(|v| v.as_bool()),
582 Some(true),
583 "CAMEL_RESEQUENCER_ACCEPTED should be true"
584 );
585
586 let captured = tokio::time::timeout(Duration::from_millis(500), capture_rx.recv())
588 .await
589 .expect("post-continuation did not receive exchange within 500ms timeout")
590 .expect("capture channel closed without receiving exchange");
591 let body = captured.input.body.as_text();
592 assert_eq!(
593 body,
594 Some("hello"),
595 "post-continuation received body should match"
596 );
597
598 service
600 .shutdown(StepShutdownReason::RouteStop)
601 .await
602 .expect("first shutdown should succeed");
603
604 service
605 .shutdown(StepShutdownReason::RouteStop)
606 .await
607 .expect("second shutdown should succeed (idempotent)");
608 }
609
610 #[tokio::test]
611 async fn resequencer_boundary_camel_stop_skipped() {
612 use camel_api::body::Body;
613
614 let policy: Arc<dyn ResequencePolicy> = Arc::new(PassthroughPolicy);
615 let (capture_tx, mut capture_rx) = mpsc::unbounded_channel::<Exchange>();
616 let capture = CapturePost { tx: capture_tx };
617 let post_continuation: BoxProcessor = BoxProcessor::new(capture);
618
619 let service = ResequencerService::new(policy, post_continuation, 1024, vec![]);
620
621 let mut input = Exchange::new(Message::new(Body::Text(
623 "should-not-reach-continuation".into(),
624 )));
625 input.set_property(camel_api::exchange::CAMEL_STOP, true);
626
627 let ack = service.clone().oneshot(input).await.unwrap();
628
629 assert_eq!(
631 ack.property(CAMEL_RESEQUENCER_ACCEPTED)
632 .and_then(|v| v.as_bool()),
633 Some(true),
634 "CamelStop exchange should still be accepted by resequencer actor"
635 );
636
637 let did_receive = tokio::time::timeout(Duration::from_millis(500), capture_rx.recv()).await;
639 match did_receive {
640 Ok(Some(_)) => panic!("CamelStop exchange should NOT reach post-continuation"),
641 Ok(None) => {} Err(_elapsed) => {} }
644
645 service
647 .shutdown(StepShutdownReason::RouteStop)
648 .await
649 .expect("shutdown should succeed");
650 }
651
652 #[tokio::test]
653 async fn inout_guard_increments_counter() {
654 let policy: Arc<dyn ResequencePolicy> = Arc::new(PassthroughPolicy);
655 let (tx, _rx) = mpsc::unbounded_channel::<Exchange>();
656 let post: BoxProcessor = BoxProcessor::new(CapturePost { tx });
657 let config = ResequencerConfig::default();
658 let service = ResequencerService::with_config(policy, post, 16, vec![], config);
659
660 let ex_inonly = Exchange::new(Message::new("inonly"));
662 let _ = service.clone().oneshot(ex_inonly).await.unwrap();
663
664 let ex_inout = Exchange::new_in_out(Message::new("inout"));
666 let _ = service.clone().oneshot(ex_inout).await.unwrap();
667 assert!(
668 service.inout_counter.load(Ordering::Relaxed) > 0,
669 "InOut counter should be > 0 after InOut exchange"
670 );
671
672 service
673 .shutdown(StepShutdownReason::RouteStop)
674 .await
675 .expect("shutdown");
676 }
677
678 #[tokio::test]
679 async fn inout_guard_allow_inout_suppresses() {
680 let policy: Arc<dyn ResequencePolicy> = Arc::new(PassthroughPolicy);
681 let (tx, _rx) = mpsc::unbounded_channel::<Exchange>();
682 let post: BoxProcessor = BoxProcessor::new(CapturePost { tx });
683 let config = ResequencerConfig {
684 allow_inout: true,
685 ..Default::default()
686 };
687 let service = ResequencerService::with_config(policy, post, 16, vec![], config);
688
689 let ex_inout = Exchange::new_in_out(Message::new("inout-allowed"));
690 let _ = service.clone().oneshot(ex_inout).await.unwrap();
691 assert_eq!(
692 service.inout_counter.load(Ordering::Relaxed),
693 0,
694 "InOut counter should be 0 when allow_inout=true"
695 );
696
697 service
698 .shutdown(StepShutdownReason::RouteStop)
699 .await
700 .expect("shutdown");
701 }
702
703 struct SeqExpr;
715
716 #[async_trait]
717 impl camel_language_api::Expression for SeqExpr {
718 async fn evaluate(
719 &self,
720 exchange: &Exchange,
721 ) -> Result<serde_json::Value, camel_language_api::LanguageError> {
722 Ok(exchange.property("seq").cloned().unwrap_or_default())
723 }
724 }
725
726 fn seq_exchange(seq: u64) -> Exchange {
727 let mut ex = Exchange::new(Message::new(format!("msg-{seq}")));
728 ex.set_property("seq", serde_json::json!(seq));
729 ex
730 }
731
732 fn stream_service(capture_tx: mpsc::UnboundedSender<Exchange>) -> ResequencerService {
733 let policy: Arc<dyn ResequencePolicy> = stream::StreamPolicy::new_cyclic(
734 Arc::new(SeqExpr),
735 100,
736 600_000, camel_api::resequencer::GapPolicy::EmitPartial,
738 camel_api::resequencer::CapacityPolicy::LogAndDrop,
739 false,
740 );
741 let post: BoxProcessor = BoxProcessor::new(CapturePost { tx: capture_tx });
742 ResequencerService::new(policy, post, 1024, vec![])
743 }
744
745 async fn await_in_flight(counter: &Arc<AtomicU64>, want: u64) {
748 let deadline = Instant::now() + Duration::from_secs(2);
749 while counter.load(Ordering::SeqCst) != want {
750 assert!(
751 Instant::now() < deadline,
752 "in-flight counter did not reach {want} within 2s (now {})",
753 counter.load(Ordering::SeqCst)
754 );
755 tokio::time::sleep(Duration::from_millis(10)).await;
756 }
757 }
758
759 #[tokio::test]
760 async fn claim_held_while_buffered_released_on_completion() {
761 let counter = Arc::new(AtomicU64::new(0));
762 let (capture_tx, mut capture_rx) = mpsc::unbounded_channel::<Exchange>();
763 let service = stream_service(capture_tx);
764
765 let mut first = seq_exchange(2);
769 first.in_flight_claim = Some(camel_api::InFlightClaim::attach(&counter));
770 let ack = service.clone().oneshot(first).await.unwrap();
771 assert_eq!(
772 ack.property(CAMEL_RESEQUENCER_ACCEPTED)
773 .and_then(|v| v.as_bool()),
774 Some(true)
775 );
776 assert_eq!(
777 counter.load(Ordering::SeqCst),
778 1,
779 "buffered exchange must stay counted after the ack resolves"
780 );
781
782 let mut second = seq_exchange(1);
785 second.in_flight_claim = Some(camel_api::InFlightClaim::attach(&counter));
786 let _ = service.clone().oneshot(second).await.unwrap();
787 timeout(Duration::from_secs(2), capture_rx.recv())
788 .await
789 .expect("resequencer first capture within 2s")
790 .expect("capture channel alive");
791 timeout(Duration::from_secs(2), capture_rx.recv())
792 .await
793 .expect("resequencer second capture within 2s")
794 .expect("capture channel alive");
795 await_in_flight(&counter, 0).await;
796
797 service
798 .shutdown(StepShutdownReason::RouteStop)
799 .await
800 .expect("shutdown");
801 }
802
803 #[tokio::test]
804 async fn claim_released_on_shutdown_flush() {
805 let counter = Arc::new(AtomicU64::new(0));
806 let (capture_tx, mut capture_rx) = mpsc::unbounded_channel::<Exchange>();
807 let service = stream_service(capture_tx);
808
809 let mut buffered = seq_exchange(2);
812 buffered.in_flight_claim = Some(camel_api::InFlightClaim::attach(&counter));
813 let _ = service.clone().oneshot(buffered).await.unwrap();
814 assert_eq!(counter.load(Ordering::SeqCst), 1);
815
816 service
817 .shutdown(StepShutdownReason::RouteStop)
818 .await
819 .expect("shutdown");
820 timeout(Duration::from_secs(2), capture_rx.recv())
821 .await
822 .expect("resequencer shutdown capture within 2s")
823 .expect("capture channel alive");
824 await_in_flight(&counter, 0).await;
825 }
826
827 #[tokio::test]
828 async fn claim_released_when_input_dropped_after_shutdown() {
829 let counter = Arc::new(AtomicU64::new(0));
830 let (capture_tx, _capture_rx) = mpsc::unbounded_channel::<Exchange>();
831 let service = stream_service(capture_tx);
832 service
833 .shutdown(StepShutdownReason::RouteStop)
834 .await
835 .expect("shutdown first");
836
837 let mut dropped = seq_exchange(1);
841 dropped.in_flight_claim = Some(camel_api::InFlightClaim::attach(&counter));
842 let ack = service.clone().oneshot(dropped).await.unwrap();
843 assert!(
844 ack.property(CAMEL_RESEQUENCER_ACCEPTED).is_none(),
845 "shutdown-intake exchange must not be accepted"
846 );
847 assert_eq!(
848 counter.load(Ordering::SeqCst),
849 0,
850 "dropped input releases its claim immediately"
851 );
852 }
853
854 #[tokio::test]
855 async fn claim_released_when_capacity_policy_drops_exchange() {
856 let counter = Arc::new(AtomicU64::new(0));
857 let (capture_tx, _capture_rx) = mpsc::unbounded_channel::<Exchange>();
858 let policy: Arc<dyn ResequencePolicy> = stream::StreamPolicy::new_cyclic(
862 Arc::new(SeqExpr),
863 1,
864 600_000,
865 camel_api::resequencer::GapPolicy::EmitPartial,
866 camel_api::resequencer::CapacityPolicy::LogAndDrop,
867 false,
868 );
869 let post: BoxProcessor = BoxProcessor::new(CapturePost { tx: capture_tx });
870 let service = ResequencerService::new(policy, post, 1024, vec![]);
871
872 let mut held = seq_exchange(2);
873 held.in_flight_claim = Some(camel_api::InFlightClaim::attach(&counter));
874 let _ = service.clone().oneshot(held).await.unwrap();
875 assert_eq!(counter.load(Ordering::SeqCst), 1);
876
877 let mut overflow = seq_exchange(3);
878 overflow.in_flight_claim = Some(camel_api::InFlightClaim::attach(&counter));
879 let _ = service.clone().oneshot(overflow).await.unwrap();
880 await_in_flight(&counter, 1).await;
881
882 service
883 .shutdown(StepShutdownReason::RouteStop)
884 .await
885 .expect("shutdown flushes the held exchange and releases it");
886 await_in_flight(&counter, 0).await;
887 }
888}