1use super::*;
27use crate::stream::async_boundary::{
28 AsyncBoundaryMessage as AsyncLinearMessage, RactorBoundaryCommand, RactorBoundarySourceActor,
29 RactorBoundarySourceState, ractor_boundary_runtime,
30};
31
32#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
49pub(crate) enum ExecutorMode {
50 #[default]
52 Auto,
53 ErasedOnly,
55 TypedOnly,
57}
58
59impl<In, Out> GraphBlueprint<FlowShape<In, Out>>
60where
61 In: Clone + Send + 'static,
62 Out: Send + 'static,
63{
64 pub fn run_with_input<I>(&self, input: I) -> StreamResult<Vec<Out>>
65 where
66 I: IntoIterator<Item = In>,
67 {
68 Ok(self
69 .run_with_input_report(input, FusedExecutionConfig::default())?
70 .output)
71 }
72
73 pub fn run_with_input_report<I>(
74 &self,
75 input: I,
76 config: FusedExecutionConfig,
77 ) -> StreamResult<FusedExecutionReport<Out>>
78 where
79 I: IntoIterator<Item = In>,
80 {
81 self.run_with_input_report_mode(input, config, ExecutorMode::Auto)
82 }
83
84 pub(crate) fn run_with_input_report_mode<I>(
85 &self,
86 input: I,
87 config: FusedExecutionConfig,
88 mode: ExecutorMode,
89 ) -> StreamResult<FusedExecutionReport<Out>>
90 where
91 I: IntoIterator<Item = In>,
92 {
93 if mode != ExecutorMode::ErasedOnly {
95 let linear_plan = try_typed_flow_plan::<In, Out>(
97 &self.stages,
98 &self.edges,
99 self.shape.inlet().id(),
100 self.shape.outlet().id(),
101 );
102 if let Some(plan) = linear_plan {
103 let input = input.into_iter();
104 let mut output = Vec::with_capacity(input.size_hint().0);
105 let mut events = 0usize;
106 let mut async_boundary_crossings = 0usize;
107 for item in input {
108 let out =
109 plan.run_item(item, config, &mut events, &mut async_boundary_crossings)?;
110 output.push(out);
111 }
112 return Ok(FusedExecutionReport {
113 output,
114 events,
115 async_boundary_crossings,
116 });
117 }
118
119 let inlet_id = self.shape.inlet().id();
123 let outlet_id = self.shape.outlet().id();
124 if let Some(runner) = try_build_typed_acyclic_junction_dispatch::<In, Out>(
125 &self.stages,
126 &self.edges,
127 inlet_id,
128 outlet_id,
129 ) {
130 let mut input_iter = input.into_iter();
131 let output = runner(&mut input_iter)?;
132 return Ok(FusedExecutionReport {
133 output,
134 events: 0,
135 async_boundary_crossings: 0,
136 });
137 }
138
139 let ms_plan = try_typed_merge_sequence_plan::<In, Out>(
141 &self.stages,
142 &self.edges,
143 self.shape.inlet().id(),
144 self.shape.outlet().id(),
145 );
146 if let Some(mut plan) = ms_plan {
147 let output = run_typed_merge_sequence(&mut plan, input)?;
148 return Ok(FusedExecutionReport {
149 output,
150 events: 0,
151 async_boundary_crossings: 0,
152 });
153 }
154
155 if let Some(runner) = try_build_typed_merge_latest_dispatch::<In, Out>(
169 &self.stages,
170 &self.edges,
171 inlet_id,
172 outlet_id,
173 ) {
174 let mut input_iter = input.into_iter();
175 let output = runner(&mut input_iter)?;
176 return Ok(FusedExecutionReport {
177 output,
178 events: 0,
179 async_boundary_crossings: 0,
180 });
181 }
182
183 if let Some(runner) = try_build_typed_cyclic_feedback_dispatch::<In, Out>(
186 &self.stages,
187 &self.edges,
188 inlet_id,
189 outlet_id,
190 ) {
191 let mut input_iter = input.into_iter();
192 return runner(&mut input_iter, config);
193 }
194
195 if mode == ExecutorMode::TypedOnly {
196 return Err(StreamError::GraphValidation(
197 "typed executor does not support this graph shape".into(),
198 ));
199 }
200 }
201
202 let input = input.into_iter();
204 let mut executor = FusedExecutor::new(self, config);
205 let inlet = self.shape.inlet().id();
206 let outlet = self.shape.outlet().id();
207 let mut output = Vec::with_capacity(input.size_hint().0);
208
209 {
210 let mut output_sink = VecOutputSink {
211 output: &mut output,
212 };
213 executor.request(outlet, outlet, &mut output_sink)?;
214 executor.drain_timer_events_nonblocking(outlet, &mut output_sink)?;
215 for item in input {
216 executor.deliver(inlet, datum(item), outlet, &mut output_sink)?;
217 executor.drain_timer_events_nonblocking(outlet, &mut output_sink)?;
218 }
219 executor.complete(inlet, outlet, &mut output_sink)?;
220 executor.drain_timer_events_until_idle(outlet, &mut output_sink)?;
221 }
222
223 Ok(FusedExecutionReport {
224 output,
225 events: executor.events,
226 async_boundary_crossings: executor.async_boundary_crossings,
227 })
228 }
229
230 pub fn run_count_with_input<I>(&self, input: I) -> StreamResult<usize>
231 where
232 I: IntoIterator<Item = In>,
233 {
234 Ok(self
235 .run_count_with_input_report(input, FusedExecutionConfig::default())?
236 .result)
237 }
238
239 pub fn run_count_with_input_report<I>(
240 &self,
241 input: I,
242 config: FusedExecutionConfig,
243 ) -> StreamResult<FusedTerminalReport<usize>>
244 where
245 I: IntoIterator<Item = In>,
246 {
247 self.run_count_with_input_report_mode(input, config, ExecutorMode::Auto)
248 }
249
250 pub(crate) fn run_count_with_input_report_mode<I>(
251 &self,
252 input: I,
253 config: FusedExecutionConfig,
254 mode: ExecutorMode,
255 ) -> StreamResult<FusedTerminalReport<usize>>
256 where
257 I: IntoIterator<Item = In>,
258 {
259 if mode != ExecutorMode::ErasedOnly {
261 let plan = try_typed_flow_plan::<In, Out>(
262 &self.stages,
263 &self.edges,
264 self.shape.inlet().id(),
265 self.shape.outlet().id(),
266 );
267 if let Some(plan) = plan {
268 let mut count = 0usize;
269 let mut events = 0usize;
270 let mut async_boundary_crossings = 0usize;
271 for item in input {
272 plan.run_item_count(item, config, &mut events, &mut async_boundary_crossings)?;
273 count += 1;
274 }
275 return Ok(FusedTerminalReport {
276 result: count,
277 events,
278 async_boundary_crossings,
279 });
280 } else if mode == ExecutorMode::TypedOnly {
281 return Err(StreamError::GraphValidation(
282 "typed executor does not support this graph shape".into(),
283 ));
284 }
285 }
286
287 let mut executor = FusedExecutor::new(self, config);
289 let inlet = self.shape.inlet().id();
290 let outlet = self.shape.outlet().id();
291 let mut output_sink = CountOutputSink { count: 0 };
292
293 executor.request::<Out>(outlet, outlet, &mut output_sink)?;
294 executor.drain_timer_events_nonblocking::<Out>(outlet, &mut output_sink)?;
295 for item in input {
296 executor.deliver::<Out>(inlet, datum(item), outlet, &mut output_sink)?;
297 executor.drain_timer_events_nonblocking::<Out>(outlet, &mut output_sink)?;
298 }
299 executor.complete::<Out>(inlet, outlet, &mut output_sink)?;
300 executor.drain_timer_events_until_idle::<Out>(outlet, &mut output_sink)?;
301
302 Ok(FusedTerminalReport {
303 result: output_sink.count,
304 events: executor.events,
305 async_boundary_crossings: executor.async_boundary_crossings,
306 })
307 }
308
309 pub fn run_fold_with_input<I, Acc, F>(&self, input: I, zero: Acc, fold: F) -> StreamResult<Acc>
310 where
311 I: IntoIterator<Item = In>,
312 F: FnMut(Acc, Out) -> Acc,
313 {
314 Ok(self
315 .run_fold_with_input_report(input, zero, fold, FusedExecutionConfig::default())?
316 .result)
317 }
318
319 pub fn run_fold_with_input_report<I, Acc, F>(
320 &self,
321 input: I,
322 zero: Acc,
323 fold: F,
324 config: FusedExecutionConfig,
325 ) -> StreamResult<FusedTerminalReport<Acc>>
326 where
327 I: IntoIterator<Item = In>,
328 F: FnMut(Acc, Out) -> Acc,
329 {
330 self.run_fold_with_input_report_mode(input, zero, fold, config, ExecutorMode::Auto)
331 }
332
333 pub(crate) fn run_fold_with_input_report_mode<I, Acc, F>(
334 &self,
335 input: I,
336 zero: Acc,
337 mut fold: F,
338 config: FusedExecutionConfig,
339 mode: ExecutorMode,
340 ) -> StreamResult<FusedTerminalReport<Acc>>
341 where
342 I: IntoIterator<Item = In>,
343 F: FnMut(Acc, Out) -> Acc,
344 {
345 if mode != ExecutorMode::ErasedOnly {
347 let plan = try_typed_flow_plan::<In, Out>(
348 &self.stages,
349 &self.edges,
350 self.shape.inlet().id(),
351 self.shape.outlet().id(),
352 );
353 if let Some(plan) = plan {
354 let mut accumulator = zero;
355 let mut events = 0usize;
356 let mut async_boundary_crossings = 0usize;
357 for item in input {
358 let out =
359 plan.run_item(item, config, &mut events, &mut async_boundary_crossings)?;
360 accumulator = fold(accumulator, out);
361 }
362 return Ok(FusedTerminalReport {
363 result: accumulator,
364 events,
365 async_boundary_crossings,
366 });
367 } else if mode == ExecutorMode::TypedOnly {
368 return Err(StreamError::GraphValidation(
369 "typed executor does not support this graph shape".into(),
370 ));
371 }
372 }
373
374 let mut executor = FusedExecutor::new(self, config);
376 let inlet = self.shape.inlet().id();
377 let outlet = self.shape.outlet().id();
378 let mut output_sink = FoldOutputSink {
379 accumulator: Some(zero),
380 fold,
381 };
382
383 executor.request(outlet, outlet, &mut output_sink)?;
384 executor.drain_timer_events_nonblocking(outlet, &mut output_sink)?;
385 for item in input {
386 executor.deliver(inlet, datum(item), outlet, &mut output_sink)?;
387 executor.drain_timer_events_nonblocking(outlet, &mut output_sink)?;
388 }
389 executor.complete(inlet, outlet, &mut output_sink)?;
390 executor.drain_timer_events_until_idle(outlet, &mut output_sink)?;
391
392 Ok(FusedTerminalReport {
393 result: output_sink.finish(),
394 events: executor.events,
395 async_boundary_crossings: executor.async_boundary_crossings,
396 })
397 }
398
399 #[cfg_attr(not(test), allow(dead_code))]
409 pub(crate) fn run_with_input_mode<I>(
410 &self,
411 input: I,
412 mode: ExecutorMode,
413 ) -> StreamResult<Vec<Out>>
414 where
415 I: IntoIterator<Item = In>,
416 {
417 Ok(self
418 .run_with_input_report_mode(input, FusedExecutionConfig::default(), mode)?
419 .output)
420 }
421
422 #[allow(dead_code)]
426 pub(crate) fn run_count_with_input_mode<I>(
427 &self,
428 input: I,
429 mode: ExecutorMode,
430 ) -> StreamResult<usize>
431 where
432 I: IntoIterator<Item = In>,
433 {
434 Ok(self
435 .run_count_with_input_report_mode(input, FusedExecutionConfig::default(), mode)?
436 .result)
437 }
438
439 #[allow(dead_code)]
443 pub(crate) fn run_fold_with_input_mode<I, Acc, F>(
444 &self,
445 input: I,
446 zero: Acc,
447 fold: F,
448 mode: ExecutorMode,
449 ) -> StreamResult<Acc>
450 where
451 I: IntoIterator<Item = In>,
452 F: FnMut(Acc, Out) -> Acc,
453 {
454 Ok(self
455 .run_fold_with_input_report_mode(
456 input,
457 zero,
458 fold,
459 FusedExecutionConfig::default(),
460 mode,
461 )?
462 .result)
463 }
464}
465
466impl<T> GraphBlueprint<FlowShape<T, T>>
467where
468 T: Send + 'static,
469{
470 pub fn run_typed_linear_with_input<I>(&self, input: I) -> StreamResult<Vec<T>>
471 where
472 I: IntoIterator<Item = T>,
473 {
474 Ok(self
475 .run_typed_linear_with_input_report(input, FusedExecutionConfig::default())?
476 .output)
477 }
478
479 pub fn run_typed_linear_with_input_report<I>(
480 &self,
481 input: I,
482 config: FusedExecutionConfig,
483 ) -> StreamResult<FusedExecutionReport<T>>
484 where
485 I: IntoIterator<Item = T>,
486 {
487 let input = input.into_iter();
488 let plan = self.typed_linear_plan()?;
489 let mut output = Vec::with_capacity(input.size_hint().0);
490 let mut events = 0;
491 let mut async_boundary_crossings = 0;
492
493 for item in input {
494 let item = plan.run_item(item, config, &mut events, &mut async_boundary_crossings)?;
495 output.push(item);
496 }
497
498 Ok(FusedExecutionReport {
499 output,
500 events,
501 async_boundary_crossings,
502 })
503 }
504
505 pub fn run_typed_linear_count_with_input<I>(&self, input: I) -> StreamResult<usize>
506 where
507 I: IntoIterator<Item = T>,
508 {
509 Ok(self
510 .run_typed_linear_count_with_input_report(input, FusedExecutionConfig::default())?
511 .result)
512 }
513
514 pub fn run_typed_linear_count_with_input_report<I>(
515 &self,
516 input: I,
517 config: FusedExecutionConfig,
518 ) -> StreamResult<FusedTerminalReport<usize>>
519 where
520 I: IntoIterator<Item = T>,
521 {
522 let plan = self.typed_linear_plan()?;
523 let mut count = 0;
524 let mut events = 0;
525 let mut async_boundary_crossings = 0;
526
527 for item in input {
528 let _ = plan.run_item(item, config, &mut events, &mut async_boundary_crossings)?;
529 count += 1;
530 }
531
532 Ok(FusedTerminalReport {
533 result: count,
534 events,
535 async_boundary_crossings,
536 })
537 }
538
539 pub fn run_typed_linear_fold_with_input<I, Acc, F>(
540 &self,
541 input: I,
542 zero: Acc,
543 fold: F,
544 ) -> StreamResult<Acc>
545 where
546 I: IntoIterator<Item = T>,
547 F: FnMut(Acc, T) -> Acc,
548 {
549 Ok(self
550 .run_typed_linear_fold_with_input_report(
551 input,
552 zero,
553 fold,
554 FusedExecutionConfig::default(),
555 )?
556 .result)
557 }
558
559 pub fn run_typed_linear_fold_with_input_report<I, Acc, F>(
560 &self,
561 input: I,
562 zero: Acc,
563 mut fold: F,
564 config: FusedExecutionConfig,
565 ) -> StreamResult<FusedTerminalReport<Acc>>
566 where
567 I: IntoIterator<Item = T>,
568 F: FnMut(Acc, T) -> Acc,
569 {
570 let plan = self.typed_linear_plan()?;
571 let mut accumulator = zero;
572 let mut events = 0;
573 let mut async_boundary_crossings = 0;
574
575 for item in input {
576 let item = plan.run_item(item, config, &mut events, &mut async_boundary_crossings)?;
577 accumulator = fold(accumulator, item);
578 }
579
580 Ok(FusedTerminalReport {
581 result: accumulator,
582 events,
583 async_boundary_crossings,
584 })
585 }
586
587 pub fn run_async_boundary_count_with_input_report<I>(
594 &self,
595 input: I,
596 config: AsyncBoundaryExecutionConfig,
597 ) -> StreamResult<FusedTerminalReport<usize>>
598 where
599 I: IntoIterator<Item = T> + Send,
600 I::IntoIter: Send + 'static,
601 {
602 let segments = self.typed_linear_async_segments()?;
603 BoundaryCountExecutor::Ractor.run_count(input, segments, config)
604 }
605
606 fn typed_linear_plan(&self) -> StreamResult<TypedLinearPlan<T>> {
607 let graph_inlet = self.shape.inlet().id();
608 let graph_outlet = self.shape.outlet().id();
609 let type_id = TypeId::of::<T>();
610 let mut current_inlet = graph_inlet;
611 let mut seen = HashSet::new();
612 let mut steps = Vec::new();
613
614 loop {
615 let stage_index = self
616 .stages
617 .iter()
618 .position(|stage| {
619 stage
620 .spec
621 .inlets
622 .iter()
623 .any(|inlet| inlet.id() == current_inlet)
624 })
625 .ok_or_else(|| {
626 StreamError::GraphValidation(format!(
627 "typed linear fast path could not find inlet {}",
628 current_inlet.as_usize()
629 ))
630 })?;
631 if !seen.insert(stage_index) {
632 return Err(StreamError::GraphValidation(
633 "typed linear fast path does not support cycles".into(),
634 ));
635 }
636
637 let stage = &self.stages[stage_index];
638 if stage.spec.inlets.len() != 1 || stage.spec.outlets.len() != 1 {
639 return Err(StreamError::GraphValidation(format!(
640 "typed linear fast path requires single-inlet single-outlet stages; {} has {} inlet(s) and {} outlet(s)",
641 stage.spec.name(),
642 stage.spec.inlets.len(),
643 stage.spec.outlets.len()
644 )));
645 }
646 let inlet = &stage.spec.inlets[0];
647 let outlet = &stage.spec.outlets[0];
648 if inlet.type_id() != type_id || outlet.type_id() != type_id {
649 return Err(StreamError::GraphValidation(format!(
650 "typed linear fast path requires every port to use {}",
651 type_name::<T>()
652 )));
653 }
654
655 let step = match &stage.spec.kind {
656 StageKind::Identity | StageKind::Opaque => TypedLinearStep::Pass,
657 StageKind::AsyncBoundary => TypedLinearStep::AsyncBoundary,
658 StageKind::Map(map) => {
659 let mapper = map
660 .typed
661 .as_ref()
662 .downcast_ref::<Arc<dyn Fn(T) -> T + Send + Sync>>()
663 .ok_or_else(|| {
664 StreamError::GraphValidation(format!(
665 "typed linear fast path could not downcast map stage {}",
666 stage.spec.name()
667 ))
668 })?;
669 TypedLinearStep::Map(Arc::clone(mapper))
670 }
671 _ => {
672 return Err(StreamError::GraphValidation(format!(
673 "typed linear fast path does not support {}",
674 stage.spec.name()
675 )));
676 }
677 };
678 steps.push(step);
679
680 if outlet.id() == graph_outlet {
681 break;
682 }
683 current_inlet = self
684 .edges
685 .iter()
686 .find_map(|edge| (edge.outlet == outlet.id()).then_some(edge.inlet))
687 .ok_or_else(|| {
688 StreamError::GraphValidation(format!(
689 "typed linear fast path could not follow outlet {}",
690 outlet.id().as_usize()
691 ))
692 })?;
693 }
694
695 if seen.len() != self.stages.len() {
696 return Err(StreamError::GraphValidation(
697 "typed linear fast path requires all stages to be on the result path".into(),
698 ));
699 }
700
701 Ok(TypedLinearPlan { steps })
702 }
703
704 pub(super) fn typed_linear_async_segments(&self) -> StreamResult<TypedLinearSegments<T>> {
705 let plan = self.typed_linear_plan()?;
706 let mut segments = Vec::new();
707 let mut current = Vec::new();
708
709 for step in plan.steps {
710 match step {
711 TypedLinearStep::AsyncBoundary => {
712 segments.push(current);
713 current = Vec::new();
714 }
715 step => current.push(step),
716 }
717 }
718 segments.push(current);
719
720 if segments.len() == 1 {
721 return Err(StreamError::GraphValidation(
722 "async boundary execution requires at least one AsyncBoundary stage".into(),
723 ));
724 }
725
726 Ok(TypedLinearSegments { segments })
727 }
728}
729
730pub(super) struct TypedLinearPlan<T> {
731 steps: Vec<TypedLinearStep<T>>,
732}
733
734pub(super) struct TypedLinearSegments<T> {
735 segments: Vec<Vec<TypedLinearStep<T>>>,
736}
737
738pub(super) enum TypedLinearStep<T> {
739 Pass,
740 Map(Arc<dyn Fn(T) -> T + Send + Sync>),
741 AsyncBoundary,
742}
743
744impl<T> Clone for TypedLinearStep<T> {
745 fn clone(&self) -> Self {
746 match self {
747 Self::Pass => Self::Pass,
748 Self::Map(mapper) => Self::Map(Arc::clone(mapper)),
749 Self::AsyncBoundary => Self::AsyncBoundary,
750 }
751 }
752}
753
754impl<T> TypedLinearPlan<T> {
755 fn run_item(
756 &self,
757 mut item: T,
758 config: FusedExecutionConfig,
759 events: &mut usize,
760 async_boundary_crossings: &mut usize,
761 ) -> StreamResult<T>
762 where
763 T: Send + 'static,
764 {
765 for step in &self.steps {
766 bump_fused_event(events, config)?;
767 match step {
768 TypedLinearStep::Pass => {}
769 TypedLinearStep::Map(mapper) => {
770 item = mapper(item);
771 }
772 TypedLinearStep::AsyncBoundary => {
773 *async_boundary_crossings += 1;
774 }
775 }
776 bump_fused_event(events, config)?;
777 }
778 Ok(item)
779 }
780}
781
782#[allow(dead_code)]
793pub(crate) struct TypedSlot<T>(Option<T>);
794
795#[allow(dead_code)]
796impl<T> TypedSlot<T> {
797 pub(crate) fn empty() -> Self {
798 Self(None)
799 }
800
801 pub(crate) fn put(&mut self, value: T) {
802 self.0 = Some(value);
803 }
804
805 pub(crate) fn take(&mut self) -> Option<T> {
806 self.0.take()
807 }
808
809 pub(crate) fn is_some(&self) -> bool {
810 self.0.is_some()
811 }
812}
813
814#[allow(dead_code)]
820pub(crate) struct TypedPortRegistry {
821 slots: HashMap<PortId, Box<dyn Any + Send>>,
822}
823
824#[allow(dead_code)]
825impl TypedPortRegistry {
826 pub(crate) fn new() -> Self {
827 Self {
828 slots: HashMap::new(),
829 }
830 }
831
832 pub(crate) fn register<T: Any + Send>(&mut self, port_id: PortId) {
835 let prev = self
836 .slots
837 .insert(port_id, Box::new(TypedSlot::<T>::empty()));
838 assert!(prev.is_none(), "port {port_id:?} registered twice");
839 }
840
841 pub(crate) fn get_mut<T: Any + Send>(&mut self, port_id: PortId) -> Option<&mut TypedSlot<T>> {
846 self.slots.get_mut(&port_id)?.downcast_mut::<TypedSlot<T>>()
847 }
848}
849
850#[allow(dead_code)]
858pub(crate) trait TypedKernel<In, Out>: Send + Sync {
859 fn run(&self, input: In) -> Out;
860}
861
862#[allow(dead_code)]
870pub(crate) trait TypedStageFactory<In, Out>: Send + Sync {
871 fn try_build(&self, spec: &StageSpec) -> Option<Box<dyn TypedKernel<In, Out>>>;
872}
873
874enum TypedMiddleStep<T: 'static> {
884 Pass,
886 Map(Arc<dyn Fn(T) -> T + Send + Sync>),
888 AsyncBoundary,
890}
891
892enum TypedLastStep<In: 'static, Out: 'static> {
905 Map(Arc<dyn Fn(In) -> Out + Send + Sync>),
907 Identity(Arc<dyn Fn(In) -> Out + Send + Sync>),
911}
912
913pub(crate) struct TypedFlowPlan<In: 'static, Out: 'static> {
924 middle_steps: Vec<TypedMiddleStep<In>>,
926 last_step: TypedLastStep<In, Out>,
928 #[allow(dead_code)]
931 stage_count: usize,
932}
933
934impl<In: Send + 'static, Out: Send + 'static> TypedFlowPlan<In, Out> {
935 pub(crate) fn run_item(
940 &self,
941 item: In,
942 config: FusedExecutionConfig,
943 events: &mut usize,
944 async_boundary_crossings: &mut usize,
945 ) -> StreamResult<Out> {
946 let mut val = item;
947 for step in &self.middle_steps {
948 bump_fused_event(events, config)?;
949 val = match step {
950 TypedMiddleStep::Pass => val,
951 TypedMiddleStep::Map(f) => f(val),
952 TypedMiddleStep::AsyncBoundary => {
953 *async_boundary_crossings += 1;
954 val
955 }
956 };
957 bump_fused_event(events, config)?;
958 }
959 bump_fused_event(events, config)?;
961 let out = match &self.last_step {
962 TypedLastStep::Map(f) => f(val),
963 TypedLastStep::Identity(f) => f(val),
964 };
965 bump_fused_event(events, config)?;
966 Ok(out)
967 }
968
969 pub(crate) fn run_item_count(
978 &self,
979 item: In,
980 config: FusedExecutionConfig,
981 events: &mut usize,
982 async_boundary_crossings: &mut usize,
983 ) -> StreamResult<()> {
984 let mut val = item;
985 for step in &self.middle_steps {
986 bump_fused_event(events, config)?;
987 val = match step {
988 TypedMiddleStep::Pass => val,
989 TypedMiddleStep::Map(f) => f(val),
990 TypedMiddleStep::AsyncBoundary => {
991 *async_boundary_crossings += 1;
992 val
993 }
994 };
995 bump_fused_event(events, config)?;
996 }
997 bump_fused_event(events, config)?;
999 match &self.last_step {
1000 TypedLastStep::Map(f) => {
1001 let _ = f(val);
1002 }
1003 TypedLastStep::Identity(_) => {
1004 drop(val);
1006 }
1007 }
1008 bump_fused_event(events, config)?;
1009 Ok(())
1010 }
1011}
1012
1013pub(crate) fn try_typed_flow_plan<In, Out>(
1027 stages: &[super::builder::StageRecord],
1028 edges: &[super::builder::Edge],
1029 graph_inlet: PortId,
1030 graph_outlet: PortId,
1031) -> Option<TypedFlowPlan<In, Out>>
1032where
1033 In: Clone + Send + 'static,
1034 Out: Send + 'static,
1035{
1036 let in_type_id = TypeId::of::<In>();
1037 let out_type_id = TypeId::of::<Out>();
1038
1039 let mut current_inlet = graph_inlet;
1040 let mut seen = HashSet::new();
1041 let mut stage_infos: Vec<(&StageKind, TypeId)> = Vec::new();
1043
1044 loop {
1045 let stage_index = stages.iter().position(|s| {
1046 s.spec
1047 .inlets
1048 .iter()
1049 .any(|inlet| inlet.id() == current_inlet)
1050 })?;
1051 if !seen.insert(stage_index) {
1052 return None;
1054 }
1055 let stage = &stages[stage_index];
1056 if stage.spec.inlets.len() != 1 || stage.spec.outlets.len() != 1 {
1057 return None;
1059 }
1060 let inlet = &stage.spec.inlets[0];
1061 let outlet = &stage.spec.outlets[0];
1062
1063 if inlet.type_id() != in_type_id {
1065 return None;
1066 }
1067
1068 stage_infos.push((&stage.spec.kind, outlet.type_id()));
1069
1070 if outlet.id() == graph_outlet {
1071 break;
1072 }
1073 current_inlet = edges
1075 .iter()
1076 .find_map(|e| (e.outlet == outlet.id()).then_some(e.inlet))?;
1077 }
1078
1079 if seen.len() != stages.len() {
1080 return None;
1082 }
1083
1084 let (last_kind, last_outlet_type) = stage_infos.last()?;
1087 if *last_outlet_type != out_type_id {
1089 return None;
1090 }
1091
1092 let total = stage_infos.len();
1093 let mut middle_steps: Vec<TypedMiddleStep<In>> = Vec::with_capacity(total.saturating_sub(1));
1094
1095 for (kind, outlet_type) in &stage_infos[..total.saturating_sub(1)] {
1096 if *outlet_type != in_type_id {
1098 return None;
1099 }
1100 let step = match kind {
1101 StageKind::Identity => TypedMiddleStep::Pass,
1102 StageKind::Opaque => return None,
1106 StageKind::AsyncBoundary => TypedMiddleStep::AsyncBoundary,
1107 StageKind::Map(map) => {
1108 let f = map
1110 .typed
1111 .downcast_ref::<Arc<dyn Fn(In) -> In + Send + Sync>>()?;
1112 TypedMiddleStep::Map(Arc::clone(f))
1113 }
1114 _ => return None,
1115 };
1116 middle_steps.push(step);
1117 }
1118
1119 let last_step: TypedLastStep<In, Out> = match last_kind {
1121 StageKind::Identity => {
1122 if in_type_id != out_type_id {
1124 return None;
1125 }
1126 TypedLastStep::Identity(Arc::new(|x: In| -> Out {
1129 let boxed: Box<dyn Any + Send> = Box::new(x);
1130 *boxed
1131 .downcast::<Out>()
1132 .expect("TypeId equality verified at plan time")
1133 }))
1134 }
1135 StageKind::Opaque => return None,
1137 StageKind::AsyncBoundary => {
1138 if in_type_id != out_type_id {
1141 return None;
1142 }
1143 TypedLastStep::Identity(Arc::new(|x: In| -> Out {
1144 let boxed: Box<dyn Any + Send> = Box::new(x);
1145 *boxed
1146 .downcast::<Out>()
1147 .expect("TypeId equality verified at plan time")
1148 }))
1149 }
1150 StageKind::Map(map) => {
1151 let f = map
1153 .typed
1154 .downcast_ref::<Arc<dyn Fn(In) -> Out + Send + Sync>>()?;
1155 TypedLastStep::Map(Arc::clone(f))
1156 }
1157 _ => return None,
1158 };
1159
1160 Some(TypedFlowPlan {
1161 middle_steps,
1162 last_step,
1163 stage_count: total,
1164 })
1165}
1166
1167pub(crate) struct MergeSequenceCore<T> {
1182 next_sequence: u64,
1183 pending: Vec<(u64, T)>,
1185 output_buffer: VecDeque<T>,
1187 completed_count: usize,
1189 input_count: usize,
1191 completed: bool,
1193}
1194
1195impl<T> MergeSequenceCore<T> {
1196 pub(crate) fn new(input_count: usize) -> Self {
1197 Self {
1198 next_sequence: 0,
1199 pending: Vec::new(),
1200 output_buffer: VecDeque::new(),
1201 completed_count: 0,
1202 input_count,
1203 completed: false,
1204 }
1205 }
1206
1207 fn reset(&mut self) {
1209 self.next_sequence = 0;
1210 self.pending.clear();
1211 self.output_buffer.clear();
1212 self.completed_count = 0;
1213 self.completed = false;
1214 }
1215
1216 fn push_item(&mut self, seq: u64, val: T) -> StreamResult<()> {
1222 if seq == self.next_sequence {
1223 self.output_buffer.push_back(val);
1224 self.next_sequence += 1;
1225 while let Some(index) = self
1227 .pending
1228 .iter()
1229 .position(|(s, _)| *s == self.next_sequence)
1230 {
1231 let (_, item) = self.pending.remove(index);
1232 self.output_buffer.push_back(item);
1233 self.next_sequence += 1;
1234 }
1235 } else {
1236 if self.pending.iter().any(|(s, _)| *s == seq) {
1237 return Err(StreamError::Failed(format!(
1238 "duplicate sequence {seq} on merge sequence"
1239 )));
1240 }
1241 self.pending.push((seq, val));
1242 self.pending.sort_by_key(|(s, _)| *s);
1243 while let Some(index) = self
1245 .pending
1246 .iter()
1247 .position(|(s, _)| *s == self.next_sequence)
1248 {
1249 let (_, item) = self.pending.remove(index);
1250 self.output_buffer.push_back(item);
1251 self.next_sequence += 1;
1252 }
1253 }
1254 Ok(())
1255 }
1256
1257 fn on_inlet_complete(&mut self) -> StreamResult<bool> {
1265 self.completed_count += 1;
1266 if self.completed_count >= self.input_count && self.output_buffer.is_empty() {
1267 if !self.pending.is_empty() {
1268 return Err(StreamError::Failed(format!(
1269 "expected sequence {}, but all input ports have pushed or are complete",
1270 self.next_sequence,
1271 )));
1272 }
1273 self.completed = true;
1274 Ok(true)
1275 } else {
1276 Ok(false)
1277 }
1278 }
1279
1280 fn drain_into(&mut self, out: &mut Vec<T>) {
1282 out.extend(self.output_buffer.drain(..));
1283 }
1284}
1285
1286pub(crate) struct TypedMergeSequencePlan<In, T> {
1300 splits: Vec<Arc<dyn Fn(In) -> T + Send + Sync>>,
1303 extract_sequence: Arc<dyn Fn(&T) -> u64 + Send + Sync>,
1305 core: MergeSequenceCore<T>,
1307}
1308
1309impl<In: Clone + Send + 'static, T: Send + 'static> TypedMergeSequencePlan<In, T> {
1310 fn push_item(&mut self, item: In, out: &mut Vec<T>) -> StreamResult<()> {
1313 for split_fn in &self.splits {
1314 let val = split_fn(item.clone());
1315 let seq = (self.extract_sequence)(&val);
1316 self.core.push_item(seq, val)?;
1317 }
1318 self.core.drain_into(out);
1319 Ok(())
1320 }
1321
1322 fn finish(&mut self, out: &mut Vec<T>) -> StreamResult<()> {
1326 for _ in 0..self.splits.len() {
1327 self.core.on_inlet_complete()?;
1328 }
1329 self.core.drain_into(out);
1330 Ok(())
1331 }
1332
1333 fn reset(&mut self) {
1335 self.core.reset();
1336 }
1337}
1338
1339pub(crate) fn try_typed_merge_sequence_plan<In, Out>(
1351 stages: &[super::builder::StageRecord],
1352 edges: &[super::builder::Edge],
1353 graph_inlet: PortId,
1354 graph_outlet: PortId,
1355) -> Option<TypedMergeSequencePlan<In, Out>>
1356where
1357 In: Clone + Send + 'static,
1358 Out: Send + 'static,
1359{
1360 if stages.len() != 2 {
1362 return None;
1363 }
1364
1365 let in_type_id = TypeId::of::<In>();
1366 let out_type_id = TypeId::of::<Out>();
1367
1368 let unzip_idx = stages
1370 .iter()
1371 .position(|s| s.spec.inlets.len() == 1 && s.spec.inlets[0].id() == graph_inlet)?;
1372 let unzip_stage = &stages[unzip_idx];
1373
1374 let typed_split_any = match &unzip_stage.spec.kind {
1376 StageKind::Unzip { typed_split, .. } => Arc::clone(typed_split),
1377 _ => return None,
1378 };
1379
1380 if unzip_stage.spec.inlets[0].type_id() != in_type_id {
1382 return None;
1383 }
1384
1385 let k = unzip_stage.spec.outlets.len();
1387 if k == 0 {
1388 return None;
1389 }
1390 for outlet in &unzip_stage.spec.outlets {
1391 if outlet.type_id() != out_type_id {
1392 return None;
1393 }
1394 }
1395
1396 let ms_idx = 1 - unzip_idx;
1398 let ms_stage = &stages[ms_idx];
1399
1400 let (ms_input_count, typed_extract_any) = match &ms_stage.spec.kind {
1402 StageKind::MergeSequence {
1403 input_count,
1404 typed_extract,
1405 ..
1406 } => (*input_count, Arc::clone(typed_extract)),
1407 _ => return None,
1408 };
1409
1410 if ms_stage.spec.inlets.len() != k || ms_stage.spec.outlets.len() != 1 {
1411 return None;
1412 }
1413 if ms_input_count != k {
1414 return None;
1415 }
1416 for inlet in &ms_stage.spec.inlets {
1418 if inlet.type_id() != out_type_id {
1419 return None;
1420 }
1421 }
1422 if ms_stage.spec.outlets[0].type_id() != out_type_id {
1423 return None;
1424 }
1425
1426 if ms_stage.spec.outlets[0].id() != graph_outlet {
1428 return None;
1429 }
1430
1431 let unzip_outlet_ids: Vec<PortId> =
1435 unzip_stage.spec.outlets.iter().map(AnyOutlet::id).collect();
1436 let ms_inlet_ids: Vec<PortId> = ms_stage.spec.inlets.iter().map(AnyInlet::id).collect();
1437
1438 let mut outlet_to_ms_inlet: Vec<Option<usize>> = vec![None; k];
1440 for edge in edges {
1441 if let Some(uo_idx) = unzip_outlet_ids.iter().position(|&id| id == edge.outlet) {
1442 let mi_idx = ms_inlet_ids.iter().position(|&id| id == edge.inlet)?;
1444 outlet_to_ms_inlet[uo_idx] = Some(mi_idx);
1445 }
1446 }
1447 if outlet_to_ms_inlet.iter().any(|x| x.is_none()) {
1448 return None; }
1450
1451 if k != 2 {
1458 return None;
1460 }
1461
1462 let typed_split =
1464 typed_split_any.downcast_ref::<Arc<dyn Fn(In) -> (Out, Out) + Send + Sync>>()?;
1465 let typed_split = Arc::clone(typed_split);
1466
1467 let typed_extract =
1469 typed_extract_any.downcast_ref::<Arc<dyn Fn(&Out) -> u64 + Send + Sync>>()?;
1470 let typed_extract = Arc::clone(typed_extract);
1471
1472 #[allow(clippy::type_complexity)]
1478 let mut splits: Vec<Option<Arc<dyn Fn(In) -> Out + Send + Sync>>> = vec![None; k];
1479
1480 let split0 = Arc::clone(&typed_split);
1481 let split1 = Arc::clone(&typed_split);
1482
1483 let ms_idx_for_out0 = outlet_to_ms_inlet[0].unwrap();
1484 let ms_idx_for_out1 = outlet_to_ms_inlet[1].unwrap();
1485
1486 splits[ms_idx_for_out0] = Some(Arc::new(move |input: In| split0(input).0));
1487 splits[ms_idx_for_out1] = Some(Arc::new(move |input: In| split1(input).1));
1488
1489 if splits.iter().any(|s| s.is_none()) {
1491 return None;
1492 }
1493 let splits: Vec<Arc<dyn Fn(In) -> Out + Send + Sync>> =
1494 splits.into_iter().map(|s| s.unwrap()).collect();
1495
1496 Some(TypedMergeSequencePlan {
1497 splits,
1498 extract_sequence: typed_extract,
1499 core: MergeSequenceCore::new(k),
1500 })
1501}
1502
1503pub(crate) fn run_typed_merge_sequence<In, T, I>(
1507 plan: &mut TypedMergeSequencePlan<In, T>,
1508 input: I,
1509) -> StreamResult<Vec<T>>
1510where
1511 In: Clone + Send + 'static,
1512 T: Send + 'static,
1513 I: IntoIterator<Item = In>,
1514{
1515 plan.reset();
1516 let input = input.into_iter();
1518 let hint = input.size_hint().0;
1519 let mut output: Vec<T> = Vec::with_capacity(hint * plan.splits.len());
1520 for item in input {
1521 plan.push_item(item, &mut output)?;
1522 }
1523 plan.finish(&mut output)?;
1524 Ok(output)
1525}
1526
1527pub(crate) struct MergeLatestCore<T> {
1540 latest: Vec<Option<T>>,
1542 seen_count: usize,
1544 completed_count: usize,
1546 input_count: usize,
1548 pending: VecDeque<Vec<T>>,
1550 completed: bool,
1552 eager_complete: bool,
1554}
1555
1556impl<T: Clone> MergeLatestCore<T> {
1557 pub(crate) fn new(input_count: usize, eager_complete: bool) -> Self {
1558 Self {
1559 latest: vec![None; input_count],
1560 seen_count: 0,
1561 completed_count: 0,
1562 input_count,
1563 pending: VecDeque::new(),
1564 completed: false,
1565 eager_complete,
1566 }
1567 }
1568
1569 fn reset(&mut self) {
1571 for slot in &mut self.latest {
1572 *slot = None;
1573 }
1574 self.seen_count = 0;
1575 self.completed_count = 0;
1576 self.pending.clear();
1577 self.completed = false;
1578 }
1579
1580 fn push_item(&mut self, inlet_index: usize, val: T) {
1583 if self.latest[inlet_index].is_none() {
1584 self.seen_count += 1;
1585 }
1586 self.latest[inlet_index] = Some(val);
1587 if self.seen_count >= self.input_count {
1588 let snapshot: Vec<T> = self
1590 .latest
1591 .iter()
1592 .map(|s| s.clone().expect("merge-latest typed: slot seen but None"))
1593 .collect();
1594 self.pending.push_back(snapshot);
1595 }
1596 }
1597
1598 fn on_inlet_complete(&mut self) -> bool {
1603 self.completed_count += 1;
1604 let all_done = self.completed_count >= self.input_count;
1605 let eager_done = self.eager_complete && self.pending.is_empty();
1606 if all_done || eager_done {
1607 self.completed = true;
1608 true
1609 } else {
1610 false
1611 }
1612 }
1613
1614 fn drain_into(&mut self, out: &mut Vec<Vec<T>>) {
1616 out.extend(self.pending.drain(..));
1617 }
1618}
1619
1620pub(crate) struct TypedMergeLatestPlan<In, T> {
1634 splits: Vec<Arc<dyn Fn(In) -> T + Send + Sync>>,
1637 core: MergeLatestCore<T>,
1639}
1640
1641impl<In: Clone + Send + 'static, T: Clone + Send + 'static> TypedMergeLatestPlan<In, T> {
1642 fn push_item(&mut self, item: In, out: &mut Vec<Vec<T>>) {
1644 for (idx, split_fn) in self.splits.iter().enumerate() {
1645 let val = split_fn(item.clone());
1646 self.core.push_item(idx, val);
1647 }
1648 self.core.drain_into(out);
1649 }
1650
1651 fn finish(&mut self) -> bool {
1654 for _ in 0..self.splits.len() {
1656 if self.core.on_inlet_complete() {
1657 return true;
1658 }
1659 }
1660 true
1661 }
1662
1663 fn reset(&mut self) {
1665 self.core.reset();
1666 }
1667}
1668
1669pub(crate) fn try_typed_merge_latest_plan<In, T>(
1685 stages: &[super::builder::StageRecord],
1686 edges: &[super::builder::Edge],
1687 graph_inlet: PortId,
1688 graph_outlet: PortId,
1689) -> Option<TypedMergeLatestPlan<In, T>>
1690where
1691 In: Clone + Send + 'static,
1692 T: Clone + Send + 'static,
1693{
1694 if stages.len() != 2 {
1696 return None;
1697 }
1698
1699 let in_type_id = TypeId::of::<In>();
1700 let elem_type_id = TypeId::of::<T>();
1701 let vec_type_id = TypeId::of::<Vec<T>>();
1702
1703 let unzip_idx = stages
1705 .iter()
1706 .position(|s| s.spec.inlets.len() == 1 && s.spec.inlets[0].id() == graph_inlet)?;
1707 let unzip_stage = &stages[unzip_idx];
1708
1709 let typed_split_any = match &unzip_stage.spec.kind {
1711 StageKind::Unzip { typed_split, .. } => Arc::clone(typed_split),
1712 _ => return None,
1713 };
1714
1715 if unzip_stage.spec.inlets[0].type_id() != in_type_id {
1717 return None;
1718 }
1719
1720 let k = unzip_stage.spec.outlets.len();
1722 if k == 0 {
1723 return None;
1724 }
1725 for outlet in &unzip_stage.spec.outlets {
1726 if outlet.type_id() != elem_type_id {
1727 return None;
1728 }
1729 }
1730
1731 let ml_idx = 1 - unzip_idx;
1733 let ml_stage = &stages[ml_idx];
1734
1735 let (ml_input_count, typed_snapshot_any) = match &ml_stage.spec.kind {
1737 StageKind::MergeLatest {
1738 input_count,
1739 typed_snapshot,
1740 ..
1741 } => (*input_count, Arc::clone(typed_snapshot)),
1742 _ => return None,
1743 };
1744
1745 if ml_stage.spec.inlets.len() != k || ml_stage.spec.outlets.len() != 1 {
1746 return None;
1747 }
1748 if ml_input_count != k {
1749 return None;
1750 }
1751 for inlet in &ml_stage.spec.inlets {
1753 if inlet.type_id() != elem_type_id {
1754 return None;
1755 }
1756 }
1757 if ml_stage.spec.outlets[0].type_id() != vec_type_id {
1759 return None;
1760 }
1761
1762 if ml_stage.spec.outlets[0].id() != graph_outlet {
1764 return None;
1765 }
1766
1767 let unzip_outlet_ids: Vec<PortId> =
1769 unzip_stage.spec.outlets.iter().map(AnyOutlet::id).collect();
1770 let ml_inlet_ids: Vec<PortId> = ml_stage.spec.inlets.iter().map(AnyInlet::id).collect();
1771
1772 let mut outlet_to_ml_inlet: Vec<Option<usize>> = vec![None; k];
1773 for edge in edges {
1774 if let Some(uo_idx) = unzip_outlet_ids.iter().position(|&id| id == edge.outlet) {
1775 let mi_idx = ml_inlet_ids.iter().position(|&id| id == edge.inlet)?;
1777 outlet_to_ml_inlet[uo_idx] = Some(mi_idx);
1778 }
1779 }
1780 if outlet_to_ml_inlet.iter().any(|x| x.is_none()) {
1781 return None; }
1783
1784 if k != 2 {
1786 return None;
1787 }
1788
1789 type SplitFn<A, B> = Arc<dyn Fn(A) -> (B, B) + Send + Sync>;
1791 let typed_split = typed_split_any.downcast_ref::<SplitFn<In, T>>()?;
1792 let typed_split = Arc::clone(typed_split);
1793
1794 type SnapshotFn<U> = Arc<dyn Fn(&[Option<U>]) -> Vec<U> + Send + Sync>;
1796 typed_snapshot_any.downcast_ref::<SnapshotFn<T>>()?;
1797
1798 #[allow(clippy::type_complexity)]
1800 let mut splits: Vec<Option<Arc<dyn Fn(In) -> T + Send + Sync>>> = vec![None; k];
1801
1802 let split0 = Arc::clone(&typed_split);
1803 let split1 = Arc::clone(&typed_split);
1804
1805 let ml_idx_for_out0 = outlet_to_ml_inlet[0].unwrap();
1806 let ml_idx_for_out1 = outlet_to_ml_inlet[1].unwrap();
1807
1808 splits[ml_idx_for_out0] = Some(Arc::new(move |input: In| split0(input).0));
1809 splits[ml_idx_for_out1] = Some(Arc::new(move |input: In| split1(input).1));
1810
1811 if splits.iter().any(|s| s.is_none()) {
1812 return None;
1813 }
1814 let splits: Vec<Arc<dyn Fn(In) -> T + Send + Sync>> =
1815 splits.into_iter().map(|s| s.unwrap()).collect();
1816
1817 let eager_complete = match &ml_stage.spec.kind {
1819 StageKind::MergeLatest { eager_complete, .. } => *eager_complete,
1820 _ => return None,
1821 };
1822
1823 Some(TypedMergeLatestPlan {
1824 splits,
1825 core: MergeLatestCore::new(k, eager_complete),
1826 })
1827}
1828
1829type MergeLatestRunner<In, Out> =
1840 Box<dyn FnOnce(&mut dyn Iterator<Item = In>) -> StreamResult<Vec<Out>>>;
1841
1842pub(crate) fn try_build_typed_merge_latest_dispatch<In, Out>(
1862 stages: &[super::builder::StageRecord],
1863 edges: &[super::builder::Edge],
1864 inlet: PortId,
1865 outlet: PortId,
1866) -> Option<MergeLatestRunner<In, Out>>
1867where
1868 In: Clone + Send + 'static,
1869 Out: Send + 'static,
1870{
1871 let elem_type_id: TypeId = stages.iter().find_map(|s| {
1873 if let StageKind::MergeLatest { .. } = &s.spec.kind {
1874 s.spec.inlets.first().map(|i| i.type_id())
1875 } else {
1876 None
1877 }
1878 })?;
1879
1880 macro_rules! try_elem {
1890 ($($T:ty),*) => {
1891 $(
1892 if elem_type_id == TypeId::of::<$T>() {
1893 let mut plan = try_typed_merge_latest_plan::<In, $T>(stages, edges, inlet, outlet)?;
1894 let runner: MergeLatestRunner<In, Out> = Box::new(
1897 move |iter: &mut dyn Iterator<Item = In>| {
1898 plan.reset();
1899 let hint = iter.size_hint().0;
1900 let mut output: Vec<Vec<$T>> = Vec::with_capacity(hint);
1901 for item in iter {
1902 plan.push_item(item, &mut output);
1903 }
1904 plan.finish();
1905 let boxed: Box<dyn Any + Send> = Box::new(output);
1909 boxed
1910 .downcast::<Vec<Out>>()
1911 .map(|b| *b)
1912 .map_err(|_| StreamError::Failed(
1913 "merge-latest typed runner: Out type mismatch".into()
1914 ))
1915 }
1916 );
1917 return Some(runner);
1918 }
1919 )*
1920 };
1921 }
1922
1923 try_elem!(
1924 u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64, bool, String
1925 );
1926
1927 None
1929}
1930
1931enum CyclicFeedbackStep<T> {
1965 Pass,
1967 Map(Arc<dyn Fn(T) -> T + Send + Sync>),
1969 TakeWhile(Arc<dyn Fn(&T) -> bool + Send + Sync>),
1972}
1973
1974type CyclicFeedbackRunner<In, Out> = Box<
1975 dyn FnOnce(
1976 &mut dyn Iterator<Item = In>,
1977 FusedExecutionConfig,
1978 ) -> StreamResult<FusedExecutionReport<Out>>,
1979>;
1980
1981fn run_cyclic_feedback_chain<T>(
1987 steps: &[CyclicFeedbackStep<T>],
1988 mut value: T,
1989 feedback_open: &mut bool,
1990) -> Option<T> {
1991 for step in steps {
1992 match step {
1993 CyclicFeedbackStep::Pass => {}
1994 CyclicFeedbackStep::Map(f) => value = f(value),
1995 CyclicFeedbackStep::TakeWhile(predicate) => {
1996 if !predicate(&value) {
1997 *feedback_open = false;
1998 return None;
1999 }
2000 }
2001 }
2002 }
2003 Some(value)
2004}
2005
2006pub(crate) fn try_build_typed_cyclic_feedback_dispatch<In, Out>(
2012 stages: &[super::builder::StageRecord],
2013 edges: &[super::builder::Edge],
2014 graph_inlet: PortId,
2015 graph_outlet: PortId,
2016) -> Option<CyclicFeedbackRunner<In, Out>>
2017where
2018 In: Clone + Send + 'static,
2019 Out: Send + 'static,
2020{
2021 let in_type_id = TypeId::of::<In>();
2022 if in_type_id != TypeId::of::<Out>() {
2025 return None;
2026 }
2027
2028 let inlet_for_outlet = |outlet: PortId| {
2030 edges
2031 .iter()
2032 .find_map(|e| (e.outlet == outlet).then_some(e.inlet))
2033 };
2034 let stage_owning_inlet = |inlet: PortId| {
2035 stages
2036 .iter()
2037 .enumerate()
2038 .find(|(_, s)| s.spec.inlets.iter().any(|i| i.id() == inlet))
2039 };
2040
2041 let (merge_index, merge) = stage_owning_inlet(graph_inlet)?;
2043 if !matches!(merge.spec.kind, StageKind::MergePreferred) {
2044 return None;
2045 }
2046 if merge.spec.inlets.len() != 2 || merge.spec.outlets.len() != 1 {
2048 return None;
2049 }
2050 let preferred_inlet = merge.spec.inlets[0].id();
2051 if preferred_inlet == graph_inlet || merge.spec.inlets[1].id() != graph_inlet {
2053 return None;
2054 }
2055 if merge.spec.inlets.iter().any(|i| i.type_id() != in_type_id)
2056 || merge.spec.outlets[0].type_id() != in_type_id
2057 {
2058 return None;
2059 }
2060
2061 let broadcast_inlet = inlet_for_outlet(merge.spec.outlets[0].id())?;
2063 let (broadcast_index, broadcast) = stage_owning_inlet(broadcast_inlet)?;
2064 if !matches!(broadcast.spec.kind, StageKind::Broadcast) {
2065 return None;
2066 }
2067 if broadcast.spec.inlets.len() != 1 || broadcast.spec.outlets.len() != 2 {
2068 return None;
2069 }
2070 if broadcast.spec.inlets[0].type_id() != in_type_id
2071 || broadcast
2072 .spec
2073 .outlets
2074 .iter()
2075 .any(|o| o.type_id() != in_type_id)
2076 {
2077 return None;
2078 }
2079
2080 if broadcast.spec.outlets[0].id() != graph_outlet
2092 || broadcast.spec.outlets[1].id() == graph_outlet
2093 {
2094 return None;
2095 }
2096 let feedback_outlet = broadcast.spec.outlets[1].id();
2097
2098 let mut visited: HashSet<usize> = HashSet::new();
2100 visited.insert(merge_index);
2101 visited.insert(broadcast_index);
2102 let mut steps: Vec<CyclicFeedbackStep<In>> = Vec::new();
2103 let mut current_outlet = feedback_outlet;
2104 loop {
2105 let inlet = inlet_for_outlet(current_outlet)?;
2106 if inlet == preferred_inlet {
2107 break; }
2109 let (stage_index, stage) = stage_owning_inlet(inlet)?;
2110 if stage.spec.inlets.len() != 1 || stage.spec.outlets.len() != 1 {
2111 return None;
2112 }
2113 if stage.spec.inlets[0].type_id() != in_type_id
2114 || stage.spec.outlets[0].type_id() != in_type_id
2115 {
2116 return None;
2117 }
2118 if !visited.insert(stage_index) {
2119 return None; }
2121 let step = match &stage.spec.kind {
2122 StageKind::Identity => CyclicFeedbackStep::Pass,
2123 StageKind::Map(map) => {
2124 let f = map
2125 .typed
2126 .downcast_ref::<Arc<dyn Fn(In) -> In + Send + Sync>>()?;
2127 CyclicFeedbackStep::Map(Arc::clone(f))
2128 }
2129 StageKind::Opaque => match stage.spec.typed_cyclic.as_ref()? {
2130 TypedCyclicOp::BufferPassthrough => CyclicFeedbackStep::Pass,
2131 TypedCyclicOp::TakeWhile(predicate) => {
2132 let p = predicate.downcast_ref::<Arc<dyn Fn(&In) -> bool + Send + Sync>>()?;
2133 CyclicFeedbackStep::TakeWhile(Arc::clone(p))
2134 }
2135 },
2136 _ => return None,
2137 };
2138 steps.push(step);
2139 current_outlet = stage.spec.outlets[0].id();
2140 }
2141
2142 if visited.len() != stages.len() {
2144 return None;
2145 }
2146
2147 let runner: CyclicFeedbackRunner<In, Out> = Box::new(
2148 move |iter: &mut dyn Iterator<Item = In>, config: FusedExecutionConfig| {
2149 let limit = config.event_limit;
2150 let mut output: Vec<In> = Vec::with_capacity(iter.size_hint().0);
2151 let mut pending: VecDeque<In> = VecDeque::new();
2154 let mut feedback_open = true;
2155 let mut events: usize = 0;
2156
2157 for item in iter {
2158 pending.push_back(item);
2159 while let Some(value) = pending.pop_front() {
2160 events += 1;
2163 if events > limit {
2164 return Err(StreamError::EventLimitExceeded { limit });
2165 }
2166 output.push(value.clone());
2168 if feedback_open {
2170 events += steps.len();
2171 if events > limit {
2172 return Err(StreamError::EventLimitExceeded { limit });
2173 }
2174 if let Some(next) =
2175 run_cyclic_feedback_chain(&steps, value, &mut feedback_open)
2176 {
2177 pending.push_back(next);
2178 }
2179 }
2180 }
2181 }
2182
2183 let output = downcast_output_vec::<In, Out>(output, "cyclic feedback")?;
2184 Ok(FusedExecutionReport {
2185 output,
2186 events,
2187 async_boundary_crossings: 0,
2188 })
2189 },
2190 );
2191 Some(runner)
2192}
2193
2194type AcyclicJunctionRunner<In, Out> =
2199 Box<dyn FnOnce(&mut dyn Iterator<Item = In>) -> StreamResult<Vec<Out>>>;
2200
2201fn downcast_output_vec<T, Out>(output: Vec<T>, context: &'static str) -> StreamResult<Vec<Out>>
2202where
2203 T: Send + 'static,
2204 Out: Send + 'static,
2205{
2206 let boxed: Box<dyn Any + Send> = Box::new(output);
2207 boxed
2208 .downcast::<Vec<Out>>()
2209 .map(|b| *b)
2210 .map_err(|_| StreamError::Failed(format!("{context} typed runner: output type mismatch")))
2211}
2212
2213fn stage_with_graph_inlet(
2214 stages: &[super::builder::StageRecord],
2215 graph_inlet: PortId,
2216) -> Option<(usize, &super::builder::StageRecord)> {
2217 stages.iter().enumerate().find(|(_, stage)| {
2218 stage
2219 .spec
2220 .inlets
2221 .iter()
2222 .any(|inlet| inlet.id() == graph_inlet)
2223 })
2224}
2225
2226fn other_stage(
2227 stages: &[super::builder::StageRecord],
2228 index: usize,
2229) -> Option<(usize, &super::builder::StageRecord)> {
2230 if stages.len() != 2 {
2231 return None;
2232 }
2233 let other = 1usize.checked_sub(index)?;
2234 stages.get(other).map(|stage| (other, stage))
2235}
2236
2237fn edge_target_index(
2238 edges: &[super::builder::Edge],
2239 outlet: PortId,
2240 inlets: &[AnyInlet],
2241) -> Option<usize> {
2242 let inlet_id = edges
2243 .iter()
2244 .find_map(|edge| (edge.outlet == outlet).then_some(edge.inlet))?;
2245 inlets.iter().position(|inlet| inlet.id() == inlet_id)
2246}
2247
2248fn outlets_cover_inlets(
2249 edges: &[super::builder::Edge],
2250 outlets: &[AnyOutlet],
2251 inlets: &[AnyInlet],
2252) -> Option<Vec<usize>> {
2253 if outlets.len() != inlets.len() {
2254 return None;
2255 }
2256 let mut seen = vec![false; inlets.len()];
2257 let mut mapping = Vec::with_capacity(outlets.len());
2258 for outlet in outlets {
2259 let inlet_index = edge_target_index(edges, outlet.id(), inlets)?;
2260 if seen[inlet_index] {
2261 return None;
2262 }
2263 seen[inlet_index] = true;
2264 mapping.push(inlet_index);
2265 }
2266 seen.iter().all(|item| *item).then_some(mapping)
2267}
2268
2269pub(crate) fn try_build_typed_acyclic_junction_dispatch<In, Out>(
2270 stages: &[super::builder::StageRecord],
2271 edges: &[super::builder::Edge],
2272 graph_inlet: PortId,
2273 graph_outlet: PortId,
2274) -> Option<AcyclicJunctionRunner<In, Out>>
2275where
2276 In: Clone + Send + 'static,
2277 Out: Send + 'static,
2278{
2279 if let Some(runner) =
2280 try_typed_broadcast_zip_runner::<In, Out>(stages, edges, graph_inlet, graph_outlet)
2281 {
2282 return Some(runner);
2283 }
2284 if let Some(runner) =
2285 try_typed_balance_merge_runner::<In, Out>(stages, edges, graph_inlet, graph_outlet)
2286 {
2287 return Some(runner);
2288 }
2289 if let Some(runner) =
2290 try_typed_partition_merge_runner::<In, Out>(stages, edges, graph_inlet, graph_outlet)
2291 {
2292 return Some(runner);
2293 }
2294 if let Some(runner) =
2295 try_build_typed_unzip_zip_dispatch::<In, Out>(stages, edges, graph_inlet, graph_outlet)
2296 {
2297 return Some(runner);
2298 }
2299 try_build_typed_merge_sorted_dispatch::<In, Out>(stages, edges, graph_inlet, graph_outlet)
2300}
2301
2302fn try_typed_broadcast_zip_runner<In, Out>(
2303 stages: &[super::builder::StageRecord],
2304 edges: &[super::builder::Edge],
2305 graph_inlet: PortId,
2306 graph_outlet: PortId,
2307) -> Option<AcyclicJunctionRunner<In, Out>>
2308where
2309 In: Clone + Send + 'static,
2310 Out: Send + 'static,
2311{
2312 if stages.len() != 2 || edges.len() != 2 {
2313 return None;
2314 }
2315 let in_type = TypeId::of::<In>();
2316 let pair_type = TypeId::of::<(In, In)>();
2317 if TypeId::of::<Out>() != pair_type {
2318 return None;
2319 }
2320
2321 let (broadcast_idx, broadcast_stage) = stage_with_graph_inlet(stages, graph_inlet)?;
2322 if !matches!(broadcast_stage.spec.kind, StageKind::Broadcast) {
2323 return None;
2324 }
2325 let (_, zip_stage) = other_stage(stages, broadcast_idx)?;
2326 if !matches!(zip_stage.spec.kind, StageKind::Zip(_)) {
2327 return None;
2328 }
2329
2330 if broadcast_stage.spec.inlets.len() != 1
2331 || broadcast_stage.spec.outlets.len() != 2
2332 || zip_stage.spec.inlets.len() != 2
2333 || zip_stage.spec.outlets.len() != 1
2334 || zip_stage.spec.outlets[0].id() != graph_outlet
2335 || zip_stage.spec.outlets[0].type_id() != pair_type
2336 || broadcast_stage.spec.inlets[0].type_id() != in_type
2337 || broadcast_stage
2338 .spec
2339 .outlets
2340 .iter()
2341 .any(|outlet| outlet.type_id() != in_type)
2342 || zip_stage
2343 .spec
2344 .inlets
2345 .iter()
2346 .any(|inlet| inlet.type_id() != in_type)
2347 {
2348 return None;
2349 }
2350 outlets_cover_inlets(edges, &broadcast_stage.spec.outlets, &zip_stage.spec.inlets)?;
2351
2352 Some(Box::new(|iter| {
2353 let mut output = Vec::with_capacity(iter.size_hint().0);
2354 for item in iter {
2355 output.push((item.clone(), item));
2356 }
2357 downcast_output_vec(output, "broadcast-zip")
2358 }))
2359}
2360
2361fn try_typed_balance_merge_runner<In, Out>(
2362 stages: &[super::builder::StageRecord],
2363 edges: &[super::builder::Edge],
2364 graph_inlet: PortId,
2365 graph_outlet: PortId,
2366) -> Option<AcyclicJunctionRunner<In, Out>>
2367where
2368 In: Clone + Send + 'static,
2369 Out: Send + 'static,
2370{
2371 if stages.len() != 2 || TypeId::of::<In>() != TypeId::of::<Out>() {
2372 return None;
2373 }
2374 let in_type = TypeId::of::<In>();
2375 let (balance_idx, balance_stage) = stage_with_graph_inlet(stages, graph_inlet)?;
2376 if !matches!(balance_stage.spec.kind, StageKind::Balance) {
2377 return None;
2378 }
2379 let (_, merge_stage) = other_stage(stages, balance_idx)?;
2380 if !matches!(merge_stage.spec.kind, StageKind::Merge) {
2381 return None;
2382 }
2383
2384 if balance_stage.spec.inlets.len() != 1
2385 || balance_stage.spec.outlets.is_empty()
2386 || merge_stage.spec.outlets.len() != 1
2387 || merge_stage.spec.outlets[0].id() != graph_outlet
2388 || edges.len() != balance_stage.spec.outlets.len()
2389 || balance_stage.spec.inlets[0].type_id() != in_type
2390 || merge_stage.spec.outlets[0].type_id() != in_type
2391 || balance_stage
2392 .spec
2393 .outlets
2394 .iter()
2395 .any(|outlet| outlet.type_id() != in_type)
2396 || merge_stage
2397 .spec
2398 .inlets
2399 .iter()
2400 .any(|inlet| inlet.type_id() != in_type)
2401 {
2402 return None;
2403 }
2404 outlets_cover_inlets(edges, &balance_stage.spec.outlets, &merge_stage.spec.inlets)?;
2405
2406 Some(Box::new(|iter| {
2407 let mut output = Vec::with_capacity(iter.size_hint().0);
2408 output.extend(iter);
2409 downcast_output_vec(output, "balance-merge")
2410 }))
2411}
2412
2413fn try_typed_partition_merge_runner<In, Out>(
2414 stages: &[super::builder::StageRecord],
2415 edges: &[super::builder::Edge],
2416 graph_inlet: PortId,
2417 graph_outlet: PortId,
2418) -> Option<AcyclicJunctionRunner<In, Out>>
2419where
2420 In: Clone + Send + 'static,
2421 Out: Send + 'static,
2422{
2423 if stages.len() != 2 || TypeId::of::<In>() != TypeId::of::<Out>() {
2424 return None;
2425 }
2426 let in_type = TypeId::of::<In>();
2427 let (partition_idx, partition_stage) = stage_with_graph_inlet(stages, graph_inlet)?;
2428 let (output_count, typed_partitioner) = match &partition_stage.spec.kind {
2429 StageKind::Partition {
2430 output_count,
2431 typed_partitioner,
2432 ..
2433 } => (*output_count, Arc::clone(typed_partitioner)),
2434 _ => return None,
2435 };
2436 let (_, merge_stage) = other_stage(stages, partition_idx)?;
2437 if !matches!(merge_stage.spec.kind, StageKind::Merge) {
2438 return None;
2439 }
2440
2441 if partition_stage.spec.inlets.len() != 1
2442 || partition_stage.spec.outlets.len() != output_count
2443 || merge_stage.spec.outlets.len() != 1
2444 || merge_stage.spec.outlets[0].id() != graph_outlet
2445 || merge_stage.spec.inlets.len() != output_count
2446 || edges.len() != output_count
2447 || partition_stage.spec.inlets[0].type_id() != in_type
2448 || merge_stage.spec.outlets[0].type_id() != in_type
2449 || partition_stage
2450 .spec
2451 .outlets
2452 .iter()
2453 .any(|outlet| outlet.type_id() != in_type)
2454 || merge_stage
2455 .spec
2456 .inlets
2457 .iter()
2458 .any(|inlet| inlet.type_id() != in_type)
2459 {
2460 return None;
2461 }
2462 outlets_cover_inlets(
2463 edges,
2464 &partition_stage.spec.outlets,
2465 &merge_stage.spec.inlets,
2466 )?;
2467
2468 let partitioner =
2469 typed_partitioner.downcast_ref::<Arc<dyn Fn(&In) -> usize + Send + Sync>>()?;
2470 let partitioner = Arc::clone(partitioner);
2471
2472 Some(Box::new(move |iter| {
2473 let mut output = Vec::with_capacity(iter.size_hint().0);
2474 for item in iter {
2475 let idx = partitioner(&item);
2476 if idx >= output_count {
2477 return Err(StreamError::Failed(format!(
2478 "partitioner returned out-of-bounds index {idx} for {output_count} outputs"
2479 )));
2480 }
2481 output.push(item);
2482 }
2483 downcast_output_vec(output, "partition-merge")
2484 }))
2485}
2486
2487fn try_build_typed_unzip_zip_dispatch<In, Out>(
2488 stages: &[super::builder::StageRecord],
2489 edges: &[super::builder::Edge],
2490 graph_inlet: PortId,
2491 graph_outlet: PortId,
2492) -> Option<AcyclicJunctionRunner<In, Out>>
2493where
2494 In: Clone + Send + 'static,
2495 Out: Send + 'static,
2496{
2497 let elem_type_id = stages.iter().find_map(|stage| {
2498 if let StageKind::Zip(_) = stage.spec.kind {
2499 let [left, right] = stage.spec.inlets.as_slice() else {
2500 return None;
2501 };
2502 (left.type_id() == right.type_id()).then_some(left.type_id())
2503 } else {
2504 None
2505 }
2506 })?;
2507
2508 macro_rules! try_elem {
2509 ($($T:ty),*) => {
2510 $(
2511 if elem_type_id == TypeId::of::<$T>() {
2512 return try_typed_unzip_zip_runner_same::<In, $T, Out>(
2513 stages,
2514 edges,
2515 graph_inlet,
2516 graph_outlet,
2517 );
2518 }
2519 )*
2520 };
2521 }
2522
2523 try_elem!(
2524 u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64, bool, char,
2525 String
2526 );
2527 None
2528}
2529
2530fn try_typed_unzip_zip_runner_same<In, T, Out>(
2531 stages: &[super::builder::StageRecord],
2532 edges: &[super::builder::Edge],
2533 graph_inlet: PortId,
2534 graph_outlet: PortId,
2535) -> Option<AcyclicJunctionRunner<In, Out>>
2536where
2537 In: Clone + Send + 'static,
2538 T: Send + 'static,
2539 Out: Send + 'static,
2540{
2541 if stages.len() != 2 || edges.len() != 2 || TypeId::of::<Out>() != TypeId::of::<(T, T)>() {
2542 return None;
2543 }
2544 let in_type = TypeId::of::<In>();
2545 let elem_type = TypeId::of::<T>();
2546 let pair_type = TypeId::of::<(T, T)>();
2547
2548 let (unzip_idx, unzip_stage) = stage_with_graph_inlet(stages, graph_inlet)?;
2549 let typed_split = match &unzip_stage.spec.kind {
2550 StageKind::Unzip { typed_split, .. } => Arc::clone(typed_split),
2551 _ => return None,
2552 };
2553 let (_, zip_stage) = other_stage(stages, unzip_idx)?;
2554 if !matches!(zip_stage.spec.kind, StageKind::Zip(_)) {
2555 return None;
2556 }
2557
2558 if unzip_stage.spec.inlets.len() != 1
2559 || unzip_stage.spec.outlets.len() != 2
2560 || zip_stage.spec.inlets.len() != 2
2561 || zip_stage.spec.outlets.len() != 1
2562 || zip_stage.spec.outlets[0].id() != graph_outlet
2563 || unzip_stage.spec.inlets[0].type_id() != in_type
2564 || zip_stage.spec.outlets[0].type_id() != pair_type
2565 || unzip_stage
2566 .spec
2567 .outlets
2568 .iter()
2569 .any(|outlet| outlet.type_id() != elem_type)
2570 || zip_stage
2571 .spec
2572 .inlets
2573 .iter()
2574 .any(|inlet| inlet.type_id() != elem_type)
2575 {
2576 return None;
2577 }
2578 let mapping = outlets_cover_inlets(edges, &unzip_stage.spec.outlets, &zip_stage.spec.inlets)?;
2579 let out0_to_left = mapping.first().copied()? == 0;
2580
2581 let split = typed_split.downcast_ref::<Arc<dyn Fn(In) -> (T, T) + Send + Sync>>()?;
2582 let split = Arc::clone(split);
2583
2584 Some(Box::new(move |iter| {
2585 let mut output = Vec::with_capacity(iter.size_hint().0);
2586 for item in iter {
2587 let (left, right) = split(item);
2588 if out0_to_left {
2589 output.push((left, right));
2590 } else {
2591 output.push((right, left));
2592 }
2593 }
2594 downcast_output_vec(output, "unzip-zip")
2595 }))
2596}
2597
2598fn try_build_typed_merge_sorted_dispatch<In, Out>(
2599 stages: &[super::builder::StageRecord],
2600 edges: &[super::builder::Edge],
2601 graph_inlet: PortId,
2602 graph_outlet: PortId,
2603) -> Option<AcyclicJunctionRunner<In, Out>>
2604where
2605 In: Clone + Send + 'static,
2606 Out: Send + 'static,
2607{
2608 let elem_type_id = stages.iter().find_map(|stage| {
2609 if let StageKind::MergeSorted(_) = stage.spec.kind {
2610 let [left, right] = stage.spec.inlets.as_slice() else {
2611 return None;
2612 };
2613 (left.type_id() == right.type_id()).then_some(left.type_id())
2614 } else {
2615 None
2616 }
2617 })?;
2618
2619 macro_rules! try_elem {
2620 ($($T:ty),*) => {
2621 $(
2622 if elem_type_id == TypeId::of::<$T>() {
2623 return try_typed_merge_sorted_runner::<In, $T, Out>(
2624 stages,
2625 edges,
2626 graph_inlet,
2627 graph_outlet,
2628 );
2629 }
2630 )*
2631 };
2632 }
2633
2634 try_elem!(
2635 u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, bool, char, String
2636 );
2637 None
2638}
2639
2640fn try_typed_merge_sorted_runner<In, T, Out>(
2641 stages: &[super::builder::StageRecord],
2642 edges: &[super::builder::Edge],
2643 graph_inlet: PortId,
2644 graph_outlet: PortId,
2645) -> Option<AcyclicJunctionRunner<In, Out>>
2646where
2647 In: Clone + Send + 'static,
2648 T: Ord + Send + 'static,
2649 Out: Send + 'static,
2650{
2651 if stages.len() != 2 || edges.len() != 2 || TypeId::of::<Out>() != TypeId::of::<T>() {
2652 return None;
2653 }
2654 let in_type = TypeId::of::<In>();
2655 let elem_type = TypeId::of::<T>();
2656
2657 let (unzip_idx, unzip_stage) = stage_with_graph_inlet(stages, graph_inlet)?;
2658 let typed_split = match &unzip_stage.spec.kind {
2659 StageKind::Unzip { typed_split, .. } => Arc::clone(typed_split),
2660 _ => return None,
2661 };
2662 let (_, merge_stage) = other_stage(stages, unzip_idx)?;
2663 if !matches!(merge_stage.spec.kind, StageKind::MergeSorted(_)) {
2664 return None;
2665 }
2666
2667 if unzip_stage.spec.inlets.len() != 1
2668 || unzip_stage.spec.outlets.len() != 2
2669 || merge_stage.spec.inlets.len() != 2
2670 || merge_stage.spec.outlets.len() != 1
2671 || merge_stage.spec.outlets[0].id() != graph_outlet
2672 || unzip_stage.spec.inlets[0].type_id() != in_type
2673 || merge_stage.spec.outlets[0].type_id() != elem_type
2674 || unzip_stage
2675 .spec
2676 .outlets
2677 .iter()
2678 .any(|outlet| outlet.type_id() != elem_type)
2679 || merge_stage
2680 .spec
2681 .inlets
2682 .iter()
2683 .any(|inlet| inlet.type_id() != elem_type)
2684 {
2685 return None;
2686 }
2687 let mapping = outlets_cover_inlets(edges, &unzip_stage.spec.outlets, &merge_stage.spec.inlets)?;
2688 let out0_to_left = mapping.first().copied()? == 0;
2689
2690 let split = typed_split.downcast_ref::<Arc<dyn Fn(In) -> (T, T) + Send + Sync>>()?;
2691 let split = Arc::clone(split);
2692
2693 Some(Box::new(move |iter| {
2694 let mut left = VecDeque::new();
2695 let mut right = VecDeque::new();
2696 let mut output = Vec::with_capacity(iter.size_hint().0.saturating_mul(2));
2697 for item in iter {
2698 let (first, second) = split(item);
2699 if out0_to_left {
2700 left.push_back(first);
2701 right.push_back(second);
2702 } else {
2703 left.push_back(second);
2704 right.push_back(first);
2705 }
2706 drain_merge_sorted(&mut left, &mut right, false, false, &mut output);
2707 }
2708 drain_merge_sorted(&mut left, &mut right, true, true, &mut output);
2709 downcast_output_vec(output, "merge-sorted")
2710 }))
2711}
2712
2713fn drain_merge_sorted<T: Ord>(
2714 left: &mut VecDeque<T>,
2715 right: &mut VecDeque<T>,
2716 left_closed: bool,
2717 right_closed: bool,
2718 output: &mut Vec<T>,
2719) {
2720 loop {
2721 let next = match (left.front(), right.front()) {
2722 (Some(left_item), Some(right_item)) => {
2723 if left_item <= right_item {
2724 left.pop_front()
2725 } else {
2726 right.pop_front()
2727 }
2728 }
2729 (Some(_), None) if right_closed => left.pop_front(),
2730 (None, Some(_)) if left_closed => right.pop_front(),
2731 _ => None,
2732 };
2733 let Some(item) = next else {
2734 break;
2735 };
2736 output.push(item);
2737 }
2738}
2739
2740pub(super) enum BoundaryCountExecutor {
2743 #[cfg(test)]
2744 Threaded,
2745 Ractor,
2746}
2747
2748impl BoundaryCountExecutor {
2749 pub(super) fn run_count<I, T>(
2750 &self,
2751 input: I,
2752 segments: TypedLinearSegments<T>,
2753 config: AsyncBoundaryExecutionConfig,
2754 ) -> StreamResult<FusedTerminalReport<usize>>
2755 where
2756 I: IntoIterator<Item = T> + Send,
2757 I::IntoIter: Send + 'static,
2758 T: Send + 'static,
2759 {
2760 match self {
2761 #[cfg(test)]
2762 Self::Threaded => run_threaded_async_linear_count(input, segments, config),
2763 Self::Ractor => run_ractor_async_linear_count(input, segments, config),
2764 }
2765 }
2766}
2767
2768#[cfg(test)]
2769mod tests {
2770 use super::*;
2771 use std::time::Duration;
2772
2773 #[derive(Default)]
2774 struct BufferedFlowState {
2775 queued: VecDeque<i32>,
2776 upstream_closed: bool,
2777 pull_calls: usize,
2778 finish_calls: usize,
2779 }
2780
2781 struct BufferedFlowOnPull {
2782 state: Arc<Mutex<BufferedFlowState>>,
2783 }
2784
2785 impl GraphStage for BufferedFlowOnPull {
2786 type Shape = FlowShape<i32, i32>;
2787
2788 fn name(&self) -> &str {
2789 "BufferedFlowOnPull"
2790 }
2791
2792 fn allocate_shape(&self, _allocator: &mut PortAllocator) -> Self::Shape {
2793 let first_id = next_port_id_block(2);
2794 FlowShape::new(
2795 Inlet::with_id(first_id, "buffered-flow.in"),
2796 Outlet::with_id(first_id.offset(1), "buffered-flow.out"),
2797 )
2798 }
2799
2800 fn stage_spec(&self, shape: &Self::Shape) -> StageSpec {
2801 StageSpec::opaque(self.name(), shape.inlets(), shape.outlets())
2802 }
2803
2804 fn create_logic(&self, shape: &Self::Shape) -> GraphStageLogic {
2805 struct In {
2806 state: Arc<Mutex<BufferedFlowState>>,
2807 }
2808
2809 impl InHandler for In {
2810 fn on_push(
2811 &mut self,
2812 logic: &mut GraphStageLogic,
2813 inlet: AnyInlet,
2814 ) -> StreamResult<()> {
2815 let value: i32 = logic.grab_datum(inlet.id()).and_then(|value| {
2816 downcast_datum(value, "grab", || format!("inlet#{}", inlet.id().as_usize()))
2817 })?;
2818 self.state.lock().unwrap().queued.push_back(value);
2819 Ok(())
2820 }
2821
2822 fn on_upstream_finish(
2823 &mut self,
2824 _logic: &mut GraphStageLogic,
2825 _inlet: AnyInlet,
2826 ) -> StreamResult<()> {
2827 self.state.lock().unwrap().upstream_closed = true;
2828 Ok(())
2829 }
2830 }
2831
2832 struct Out {
2833 outlet: Outlet<i32>,
2834 state: Arc<Mutex<BufferedFlowState>>,
2835 }
2836
2837 impl OutHandler for Out {
2838 fn on_pull(
2839 &mut self,
2840 logic: &mut GraphStageLogic,
2841 _outlet: AnyOutlet,
2842 ) -> StreamResult<()> {
2843 let (next, upstream_closed) = {
2844 let mut state = self.state.lock().unwrap();
2845 state.pull_calls += 1;
2846 (state.queued.pop_front(), state.upstream_closed)
2847 };
2848 if let Some(value) = next {
2849 logic.emit(&self.outlet, value)
2850 } else if upstream_closed {
2851 logic.complete(&self.outlet)
2852 } else {
2853 Ok(())
2854 }
2855 }
2856
2857 fn on_downstream_finish(
2858 &mut self,
2859 logic: &mut GraphStageLogic,
2860 _outlet: AnyOutlet,
2861 ) -> StreamResult<()> {
2862 self.state.lock().unwrap().finish_calls += 1;
2863 logic.complete_stage()
2864 }
2865 }
2866
2867 let mut logic = GraphStageLogic::new(shape);
2868 logic
2869 .set_handler(
2870 &shape.inlet(),
2871 Box::new(In {
2872 state: Arc::clone(&self.state),
2873 }),
2874 )
2875 .unwrap();
2876 logic
2877 .set_out_handler(
2878 &shape.outlet(),
2879 Box::new(Out {
2880 outlet: shape.outlet(),
2881 state: Arc::clone(&self.state),
2882 }),
2883 )
2884 .unwrap();
2885 logic
2886 }
2887 }
2888
2889 struct EmitMultipleThenFailOnPush;
2890
2891 impl GraphStage for EmitMultipleThenFailOnPush {
2892 type Shape = FlowShape<i32, i32>;
2893
2894 fn name(&self) -> &str {
2895 "EmitMultipleThenFailOnPush"
2896 }
2897
2898 fn allocate_shape(&self, _allocator: &mut PortAllocator) -> Self::Shape {
2899 let first_id = next_port_id_block(2);
2900 FlowShape::new(
2901 Inlet::with_id(first_id, "emit-fail.in"),
2902 Outlet::with_id(first_id.offset(1), "emit-fail.out"),
2903 )
2904 }
2905
2906 fn stage_spec(&self, shape: &Self::Shape) -> StageSpec {
2907 StageSpec::opaque(self.name(), shape.inlets(), shape.outlets())
2908 }
2909
2910 fn create_logic(&self, shape: &Self::Shape) -> GraphStageLogic {
2911 struct Handler {
2912 outlet: Outlet<i32>,
2913 }
2914
2915 impl InHandler for Handler {
2916 fn on_push(
2917 &mut self,
2918 logic: &mut GraphStageLogic,
2919 _inlet: AnyInlet,
2920 ) -> StreamResult<()> {
2921 logic.emit_multiple(&self.outlet, [1, 2])?;
2922 Err(StreamError::Failed("emit_multiple boom".into()))
2923 }
2924 }
2925
2926 let mut logic = GraphStageLogic::new(shape);
2927 logic
2928 .set_handler(
2929 &shape.inlet(),
2930 Box::new(Handler {
2931 outlet: shape.outlet(),
2932 }),
2933 )
2934 .unwrap();
2935 logic
2936 }
2937 }
2938
2939 struct ReadNThenFailOnFinish;
2940
2941 struct EmitMultipleOnPush;
2942
2943 impl GraphStage for EmitMultipleOnPush {
2944 type Shape = FlowShape<i32, i32>;
2945
2946 fn name(&self) -> &str {
2947 "EmitMultipleOnPush"
2948 }
2949
2950 fn allocate_shape(&self, _allocator: &mut PortAllocator) -> Self::Shape {
2951 let first_id = next_port_id_block(2);
2952 FlowShape::new(
2953 Inlet::with_id(first_id, "emit-multiple.in"),
2954 Outlet::with_id(first_id.offset(1), "emit-multiple.out"),
2955 )
2956 }
2957
2958 fn stage_spec(&self, shape: &Self::Shape) -> StageSpec {
2959 StageSpec::opaque(self.name(), shape.inlets(), shape.outlets())
2960 }
2961
2962 fn create_logic(&self, shape: &Self::Shape) -> GraphStageLogic {
2963 struct Handler {
2964 outlet: Outlet<i32>,
2965 }
2966
2967 impl InHandler for Handler {
2968 fn on_push(
2969 &mut self,
2970 logic: &mut GraphStageLogic,
2971 _inlet: AnyInlet,
2972 ) -> StreamResult<()> {
2973 logic.emit_multiple(&self.outlet, [1, 2])
2974 }
2975 }
2976
2977 let mut logic = GraphStageLogic::new(shape);
2978 logic
2979 .set_handler(
2980 &shape.inlet(),
2981 Box::new(Handler {
2982 outlet: shape.outlet(),
2983 }),
2984 )
2985 .unwrap();
2986 logic
2987 }
2988 }
2989
2990 impl GraphStage for ReadNThenFailOnFinish {
2991 type Shape = FlowShape<i32, i32>;
2992
2993 fn name(&self) -> &str {
2994 "ReadNThenFailOnFinish"
2995 }
2996
2997 fn allocate_shape(&self, _allocator: &mut PortAllocator) -> Self::Shape {
2998 let first_id = next_port_id_block(2);
2999 FlowShape::new(
3000 Inlet::with_id(first_id, "read-n.in"),
3001 Outlet::with_id(first_id.offset(1), "read-n.out"),
3002 )
3003 }
3004
3005 fn stage_spec(&self, shape: &Self::Shape) -> StageSpec {
3006 StageSpec::opaque(self.name(), shape.inlets(), shape.outlets())
3007 }
3008
3009 fn create_logic(&self, shape: &Self::Shape) -> GraphStageLogic {
3010 struct Handler {
3011 inlet: Inlet<i32>,
3012 armed: bool,
3013 }
3014
3015 impl InHandler for Handler {
3016 fn on_push(
3017 &mut self,
3018 logic: &mut GraphStageLogic,
3019 _inlet: AnyInlet,
3020 ) -> StreamResult<()> {
3021 if !self.armed {
3022 self.armed = true;
3023 logic.read_n(&self.inlet, 2, |_values| {}, |_values| {})
3024 } else {
3025 Ok(())
3026 }
3027 }
3028
3029 fn on_upstream_finish(
3030 &mut self,
3031 _logic: &mut GraphStageLogic,
3032 _inlet: AnyInlet,
3033 ) -> StreamResult<()> {
3034 Err(StreamError::Failed("read_n finish boom".into()))
3035 }
3036 }
3037
3038 let mut logic = GraphStageLogic::new(shape);
3039 logic
3040 .set_handler(
3041 &shape.inlet(),
3042 Box::new(Handler {
3043 inlet: shape.inlet(),
3044 armed: false,
3045 }),
3046 )
3047 .unwrap();
3048 logic
3049 }
3050 }
3051
3052 fn single_opaque_stage_graph<G>(stage: G) -> GraphBlueprint<FlowShape<i32, i32>>
3053 where
3054 G: GraphStage<Shape = FlowShape<i32, i32>>,
3055 {
3056 GraphDsl::create(|builder| builder.add(stage)).unwrap()
3057 }
3058
3059 fn run_flow_with_timeout(
3060 graph: GraphBlueprint<FlowShape<i32, i32>>,
3061 input: Vec<i32>,
3062 ) -> StreamResult<Vec<i32>> {
3063 let (tx, rx) = mpsc::channel();
3064 thread::spawn(move || {
3065 tx.send(graph.run_with_input(input))
3066 .expect("test receiver is alive");
3067 });
3068 rx.recv_timeout(Duration::from_secs(2))
3069 .expect("graph run completed before timeout")
3070 }
3071
3072 #[derive(Default)]
3073 struct OneShotTimerChecks {
3074 inactive_before_schedule: bool,
3075 active_after_schedule: bool,
3076 inactive_on_timer: bool,
3077 }
3078
3079 struct OneShotTimerStage {
3080 checks: Arc<Mutex<OneShotTimerChecks>>,
3081 }
3082
3083 impl GraphStage for OneShotTimerStage {
3084 type Shape = FlowShape<i32, i32>;
3085
3086 fn name(&self) -> &str {
3087 "OneShotTimerStage"
3088 }
3089
3090 fn allocate_shape(&self, _allocator: &mut PortAllocator) -> Self::Shape {
3091 let first_id = next_port_id_block(2);
3092 FlowShape::new(
3093 Inlet::with_id(first_id, "one-shot-timer.in"),
3094 Outlet::with_id(first_id.offset(1), "one-shot-timer.out"),
3095 )
3096 }
3097
3098 fn stage_spec(&self, shape: &Self::Shape) -> StageSpec {
3099 StageSpec::opaque(self.name(), shape.inlets(), shape.outlets())
3100 }
3101
3102 fn create_logic(&self, shape: &Self::Shape) -> GraphStageLogic {
3103 struct Out {
3104 armed: bool,
3105 checks: Arc<Mutex<OneShotTimerChecks>>,
3106 }
3107
3108 impl OutHandler for Out {
3109 fn on_pull(
3110 &mut self,
3111 logic: &mut GraphStageLogic,
3112 _outlet: AnyOutlet,
3113 ) -> StreamResult<()> {
3114 if self.armed {
3115 return Ok(());
3116 }
3117 self.armed = true;
3118 {
3119 let mut checks = self.checks.lock().unwrap();
3120 checks.inactive_before_schedule = !logic.is_timer_active("once");
3121 }
3122 logic.schedule_once("once", Duration::from_millis(1))?;
3123 self.checks.lock().unwrap().active_after_schedule =
3124 logic.is_timer_active("once");
3125 Ok(())
3126 }
3127 }
3128
3129 struct Timer {
3130 outlet: Outlet<i32>,
3131 checks: Arc<Mutex<OneShotTimerChecks>>,
3132 }
3133
3134 impl TimerHandler for Timer {
3135 fn on_timer(&mut self, logic: &mut GraphStageLogic, key: &str) -> StreamResult<()> {
3136 assert_eq!(key, "once");
3137 self.checks.lock().unwrap().inactive_on_timer = !logic.is_timer_active("once");
3138 logic.push(&self.outlet, 42)?;
3139 logic.complete(&self.outlet)
3140 }
3141 }
3142
3143 let mut logic = GraphStageLogic::new(shape);
3144 logic
3145 .set_out_handler(
3146 &shape.outlet(),
3147 Box::new(Out {
3148 armed: false,
3149 checks: Arc::clone(&self.checks),
3150 }),
3151 )
3152 .unwrap();
3153 logic.set_timer_handler(Box::new(Timer {
3154 outlet: shape.outlet(),
3155 checks: Arc::clone(&self.checks),
3156 }));
3157 logic
3158 }
3159 }
3160
3161 #[derive(Default)]
3162 struct PeriodicTimerChecks {
3163 active_after_schedule: bool,
3164 active_on_first_tick: bool,
3165 inactive_after_cancel: bool,
3166 ticks: usize,
3167 }
3168
3169 struct PeriodicTimerStage {
3170 checks: Arc<Mutex<PeriodicTimerChecks>>,
3171 }
3172
3173 impl GraphStage for PeriodicTimerStage {
3174 type Shape = FlowShape<i32, i32>;
3175
3176 fn name(&self) -> &str {
3177 "PeriodicTimerStage"
3178 }
3179
3180 fn allocate_shape(&self, _allocator: &mut PortAllocator) -> Self::Shape {
3181 let first_id = next_port_id_block(2);
3182 FlowShape::new(
3183 Inlet::with_id(first_id, "periodic-timer.in"),
3184 Outlet::with_id(first_id.offset(1), "periodic-timer.out"),
3185 )
3186 }
3187
3188 fn stage_spec(&self, shape: &Self::Shape) -> StageSpec {
3189 StageSpec::opaque(self.name(), shape.inlets(), shape.outlets())
3190 }
3191
3192 fn create_logic(&self, shape: &Self::Shape) -> GraphStageLogic {
3193 struct Out {
3194 armed: bool,
3195 checks: Arc<Mutex<PeriodicTimerChecks>>,
3196 }
3197
3198 impl OutHandler for Out {
3199 fn on_pull(
3200 &mut self,
3201 logic: &mut GraphStageLogic,
3202 _outlet: AnyOutlet,
3203 ) -> StreamResult<()> {
3204 if self.armed {
3205 return Ok(());
3206 }
3207 self.armed = true;
3208 logic.schedule_periodically_with_initial_delay(
3209 "periodic",
3210 Duration::ZERO,
3211 Duration::from_millis(2),
3212 )?;
3213 self.checks.lock().unwrap().active_after_schedule =
3214 logic.is_timer_active("periodic");
3215 Ok(())
3216 }
3217 }
3218
3219 struct Timer {
3220 outlet: Outlet<i32>,
3221 checks: Arc<Mutex<PeriodicTimerChecks>>,
3222 }
3223
3224 impl TimerHandler for Timer {
3225 fn on_timer(&mut self, logic: &mut GraphStageLogic, key: &str) -> StreamResult<()> {
3226 assert_eq!(key, "periodic");
3227 let tick = {
3228 let mut checks = self.checks.lock().unwrap();
3229 checks.ticks += 1;
3230 if checks.ticks == 1 {
3231 checks.active_on_first_tick = logic.is_timer_active("periodic");
3232 }
3233 checks.ticks
3234 };
3235 logic.push(&self.outlet, tick as i32)?;
3236 if tick == 3 {
3237 assert!(logic.cancel_timer("periodic"));
3238 self.checks.lock().unwrap().inactive_after_cancel =
3239 !logic.is_timer_active("periodic");
3240 logic.complete(&self.outlet)?;
3241 }
3242 Ok(())
3243 }
3244 }
3245
3246 let mut logic = GraphStageLogic::new(shape);
3247 logic
3248 .set_out_handler(
3249 &shape.outlet(),
3250 Box::new(Out {
3251 armed: false,
3252 checks: Arc::clone(&self.checks),
3253 }),
3254 )
3255 .unwrap();
3256 logic.set_timer_handler(Box::new(Timer {
3257 outlet: shape.outlet(),
3258 checks: Arc::clone(&self.checks),
3259 }));
3260 logic
3261 }
3262 }
3263
3264 struct CompletingTimerStage {
3265 fired: Arc<AtomicUsize>,
3266 }
3267
3268 impl GraphStage for CompletingTimerStage {
3269 type Shape = FlowShape<i32, i32>;
3270
3271 fn name(&self) -> &str {
3272 "CompletingTimerStage"
3273 }
3274
3275 fn allocate_shape(&self, _allocator: &mut PortAllocator) -> Self::Shape {
3276 let first_id = next_port_id_block(2);
3277 FlowShape::new(
3278 Inlet::with_id(first_id, "completing-timer.in"),
3279 Outlet::with_id(first_id.offset(1), "completing-timer.out"),
3280 )
3281 }
3282
3283 fn stage_spec(&self, shape: &Self::Shape) -> StageSpec {
3284 StageSpec::opaque(self.name(), shape.inlets(), shape.outlets())
3285 }
3286
3287 fn create_logic(&self, shape: &Self::Shape) -> GraphStageLogic {
3288 struct Out {
3289 outlet: Outlet<i32>,
3290 }
3291
3292 impl OutHandler for Out {
3293 fn on_pull(
3294 &mut self,
3295 logic: &mut GraphStageLogic,
3296 _outlet: AnyOutlet,
3297 ) -> StreamResult<()> {
3298 logic.schedule_once("late", Duration::from_millis(1))?;
3299 logic.complete(&self.outlet)
3300 }
3301 }
3302
3303 struct Timer {
3304 fired: Arc<AtomicUsize>,
3305 }
3306
3307 impl TimerHandler for Timer {
3308 fn on_timer(
3309 &mut self,
3310 _logic: &mut GraphStageLogic,
3311 _key: &str,
3312 ) -> StreamResult<()> {
3313 self.fired.fetch_add(1, Ordering::SeqCst);
3314 Ok(())
3315 }
3316 }
3317
3318 let mut logic = GraphStageLogic::new(shape);
3319 logic
3320 .set_out_handler(
3321 &shape.outlet(),
3322 Box::new(Out {
3323 outlet: shape.outlet(),
3324 }),
3325 )
3326 .unwrap();
3327 logic.set_timer_handler(Box::new(Timer {
3328 fired: Arc::clone(&self.fired),
3329 }));
3330 logic
3331 }
3332 }
3333
3334 struct FailingTimerStage {
3335 fired: Arc<AtomicUsize>,
3336 }
3337
3338 impl GraphStage for FailingTimerStage {
3339 type Shape = FlowShape<i32, i32>;
3340
3341 fn name(&self) -> &str {
3342 "FailingTimerStage"
3343 }
3344
3345 fn allocate_shape(&self, _allocator: &mut PortAllocator) -> Self::Shape {
3346 let first_id = next_port_id_block(2);
3347 FlowShape::new(
3348 Inlet::with_id(first_id, "failing-timer.in"),
3349 Outlet::with_id(first_id.offset(1), "failing-timer.out"),
3350 )
3351 }
3352
3353 fn stage_spec(&self, shape: &Self::Shape) -> StageSpec {
3354 StageSpec::opaque(self.name(), shape.inlets(), shape.outlets())
3355 }
3356
3357 fn create_logic(&self, shape: &Self::Shape) -> GraphStageLogic {
3358 struct Out;
3359
3360 impl OutHandler for Out {
3361 fn on_pull(
3362 &mut self,
3363 logic: &mut GraphStageLogic,
3364 _outlet: AnyOutlet,
3365 ) -> StreamResult<()> {
3366 logic.schedule_once("late", Duration::from_millis(1))?;
3367 logic.fail_stage(StreamError::Failed("timer stage failed".into()))
3368 }
3369 }
3370
3371 struct Timer {
3372 fired: Arc<AtomicUsize>,
3373 }
3374
3375 impl TimerHandler for Timer {
3376 fn on_timer(
3377 &mut self,
3378 _logic: &mut GraphStageLogic,
3379 _key: &str,
3380 ) -> StreamResult<()> {
3381 self.fired.fetch_add(1, Ordering::SeqCst);
3382 Ok(())
3383 }
3384 }
3385
3386 let mut logic = GraphStageLogic::new(shape);
3387 logic
3388 .set_out_handler(&shape.outlet(), Box::new(Out))
3389 .unwrap();
3390 logic.set_timer_handler(Box::new(Timer {
3391 fired: Arc::clone(&self.fired),
3392 }));
3393 logic
3394 }
3395 }
3396
3397 #[test]
3398 fn graph_stage_logic_one_shot_timer_fires_on_executor_thread() {
3399 let checks = Arc::new(Mutex::new(OneShotTimerChecks::default()));
3400 let graph = single_opaque_stage_graph(OneShotTimerStage {
3401 checks: Arc::clone(&checks),
3402 });
3403
3404 let output = run_flow_with_timeout(graph, Vec::new()).unwrap();
3405
3406 assert_eq!(output, vec![42]);
3407 let checks = checks.lock().unwrap();
3408 assert!(checks.inactive_before_schedule);
3409 assert!(checks.active_after_schedule);
3410 assert!(checks.inactive_on_timer);
3411 }
3412
3413 #[test]
3414 fn graph_stage_logic_periodic_timer_fires_until_cancelled() {
3415 let checks = Arc::new(Mutex::new(PeriodicTimerChecks::default()));
3416 let graph = single_opaque_stage_graph(PeriodicTimerStage {
3417 checks: Arc::clone(&checks),
3418 });
3419
3420 let output = run_flow_with_timeout(graph, Vec::new()).unwrap();
3421
3422 assert_eq!(output, vec![1, 2, 3]);
3423 let checks = checks.lock().unwrap();
3424 assert_eq!(checks.ticks, 3);
3425 assert!(checks.active_after_schedule);
3426 assert!(checks.active_on_first_tick);
3427 assert!(checks.inactive_after_cancel);
3428 }
3429
3430 #[test]
3431 fn graph_stage_logic_cancels_timers_when_stage_completes() {
3432 let fired = Arc::new(AtomicUsize::new(0));
3433 let graph = single_opaque_stage_graph(CompletingTimerStage {
3434 fired: Arc::clone(&fired),
3435 });
3436
3437 let output = run_flow_with_timeout(graph, Vec::new()).unwrap();
3438
3439 assert!(output.is_empty());
3440 assert_eq!(fired.load(Ordering::SeqCst), 0);
3441 }
3442
3443 #[test]
3444 fn graph_stage_logic_cancels_timers_when_stage_fails() {
3445 let fired = Arc::new(AtomicUsize::new(0));
3446 let graph = single_opaque_stage_graph(FailingTimerStage {
3447 fired: Arc::clone(&fired),
3448 });
3449
3450 let output = run_flow_with_timeout(graph, Vec::new()).unwrap();
3451
3452 assert!(output.is_empty());
3453 assert_eq!(fired.load(Ordering::SeqCst), 0);
3454 }
3455
3456 #[test]
3457 fn process_push_restores_handler_before_emit_multiple_error_propagates() {
3458 let graph = single_opaque_stage_graph(EmitMultipleThenFailOnPush);
3459 let inlet = graph.shape.inlet().id();
3460 let mut executor = FusedExecutor::new(&graph, FusedExecutionConfig::default());
3461
3462 let result = executor.process_stage(0, inlet, datum(10));
3463
3464 assert!(matches!(
3465 result,
3466 Err(StreamError::Failed(message)) if message == "emit_multiple boom"
3467 ));
3468 assert!(
3469 executor.opaque_logics[0]
3470 .as_mut()
3471 .unwrap()
3472 .get_in_handler_mut(inlet)
3473 .is_some()
3474 );
3475 }
3476
3477 #[test]
3478 fn process_completion_restores_handler_before_read_n_finish_error_propagates() {
3479 let graph = single_opaque_stage_graph(ReadNThenFailOnFinish);
3480 let inlet = graph.shape.inlet().id();
3481 let mut executor = FusedExecutor::new(&graph, FusedExecutionConfig::default());
3482
3483 executor.process_stage(0, inlet, datum(1)).unwrap();
3484 executor.process_stage(0, inlet, datum(2)).unwrap();
3485 let result = executor.process_completion(0, inlet);
3486
3487 assert!(matches!(
3488 result,
3489 Err(StreamError::Failed(message)) if message == "read_n finish boom"
3490 ));
3491 assert!(
3492 executor.opaque_logics[0]
3493 .as_mut()
3494 .unwrap()
3495 .get_in_handler_mut(inlet)
3496 .is_some()
3497 );
3498 }
3499
3500 #[test]
3501 fn opaque_request_drives_out_handler_for_buffered_output() {
3502 let state = Arc::new(Mutex::new(BufferedFlowState::default()));
3503 let graph = single_opaque_stage_graph(BufferedFlowOnPull {
3504 state: Arc::clone(&state),
3505 });
3506 let inlet = graph.shape.inlet().id();
3507 let outlet = graph.shape.outlet().id();
3508 let mut executor = FusedExecutor::new(&graph, FusedExecutionConfig::default());
3509 let mut output = Vec::<i32>::new();
3510 let mut output_sink = VecOutputSink {
3511 output: &mut output,
3512 };
3513
3514 executor
3515 .deliver(inlet, datum(7_i32), outlet, &mut output_sink)
3516 .unwrap();
3517 assert!(output_sink.output.is_empty());
3518
3519 executor.request(outlet, outlet, &mut output_sink).unwrap();
3520
3521 assert_eq!(&*output_sink.output, &[7]);
3522 let state = state.lock().unwrap();
3523 assert_eq!(state.pull_calls, 2);
3524 assert_eq!(state.finish_calls, 0);
3525 }
3526
3527 #[test]
3528 fn opaque_downstream_finish_before_first_demand_invokes_out_handler() {
3529 let state = Arc::new(Mutex::new(BufferedFlowState::default()));
3530 let graph = single_opaque_stage_graph(BufferedFlowOnPull {
3531 state: Arc::clone(&state),
3532 });
3533 let outlet = graph.shape.outlet().id();
3534 let mut executor = FusedExecutor::new(&graph, FusedExecutionConfig::default());
3535 let mut output = Vec::<i32>::new();
3536 let mut output_sink = VecOutputSink {
3537 output: &mut output,
3538 };
3539
3540 executor
3541 .downstream_finish(outlet, outlet, &mut output_sink)
3542 .unwrap();
3543 executor.request(outlet, outlet, &mut output_sink).unwrap();
3544
3545 assert!(output_sink.output.is_empty());
3546 let state = state.lock().unwrap();
3547 assert_eq!(state.pull_calls, 0);
3548 assert_eq!(state.finish_calls, 1);
3549 }
3550
3551 #[test]
3552 fn opaque_downstream_finish_drops_buffered_output_after_upstream_complete() {
3553 let state = Arc::new(Mutex::new(BufferedFlowState::default()));
3554 let graph = single_opaque_stage_graph(BufferedFlowOnPull {
3555 state: Arc::clone(&state),
3556 });
3557 let inlet = graph.shape.inlet().id();
3558 let outlet = graph.shape.outlet().id();
3559 let mut executor = FusedExecutor::new(&graph, FusedExecutionConfig::default());
3560 let mut output = Vec::<i32>::new();
3561 let mut output_sink = VecOutputSink {
3562 output: &mut output,
3563 };
3564
3565 executor
3566 .deliver(inlet, datum(11_i32), outlet, &mut output_sink)
3567 .unwrap();
3568 executor.complete(inlet, outlet, &mut output_sink).unwrap();
3569 executor
3570 .downstream_finish(outlet, outlet, &mut output_sink)
3571 .unwrap();
3572 executor.request(outlet, outlet, &mut output_sink).unwrap();
3573
3574 assert!(output_sink.output.is_empty());
3575 let state = state.lock().unwrap();
3576 assert_eq!(state.finish_calls, 1);
3577 }
3578
3579 #[test]
3580 fn broadcast_cancels_upstream_only_after_all_outlets_cancel() {
3581 let graph = GraphDsl::try_create(|builder| {
3582 let broadcast = builder.add(Broadcast::<i32>::new(2));
3583 let merge = builder.add(Merge::<i32>::new(2));
3584 builder.connect(broadcast.outlet(0)?, merge.inlet(0)?)?;
3585 builder.connect(broadcast.outlet(1)?, merge.inlet(1)?)?;
3586 Ok(FlowShape::new(broadcast.inlet(), merge.outlet()))
3587 })
3588 .unwrap();
3589
3590 let mut executor = FusedExecutor::new(&graph, FusedExecutionConfig::default());
3591 let broadcast_index = *executor
3592 .stage_by_inlet
3593 .get(&graph.shape.inlet().id())
3594 .unwrap();
3595 let first = graph.stages[broadcast_index].spec.outlets[0].id();
3596 let second = graph.stages[broadcast_index].spec.outlets[1].id();
3597
3598 let first_transition = executor
3599 .process_downstream_finish(broadcast_index, first)
3600 .unwrap();
3601 assert!(first_transition.cancelled_inlets.is_empty());
3602
3603 let second_transition = executor
3604 .process_downstream_finish(broadcast_index, second)
3605 .unwrap();
3606 assert_eq!(
3607 second_transition.cancelled_inlets,
3608 vec![graph.stages[broadcast_index].spec.inlets[0].id()]
3609 );
3610 }
3611
3612 #[test]
3613 fn downstream_finish_propagates_through_merge_and_broadcast() {
3614 let graph = GraphDsl::try_create(|builder| {
3615 let broadcast = builder.add(Broadcast::<i32>::new(2));
3616 let merge = builder.add(Merge::<i32>::new(2));
3617 builder.connect(broadcast.outlet(0)?, merge.inlet(0)?)?;
3618 builder.connect(broadcast.outlet(1)?, merge.inlet(1)?)?;
3619 Ok(FlowShape::new(broadcast.inlet(), merge.outlet()))
3620 })
3621 .unwrap();
3622
3623 let outlet = graph.shape.outlet().id();
3624 let mut executor = FusedExecutor::new(&graph, FusedExecutionConfig::default());
3625 let mut output = Vec::<i32>::new();
3626 let mut output_sink = VecOutputSink {
3627 output: &mut output,
3628 };
3629
3630 executor
3631 .downstream_finish(outlet, outlet, &mut output_sink)
3632 .unwrap();
3633
3634 let broadcast_index = *executor
3635 .stage_by_inlet
3636 .get(&graph.shape.inlet().id())
3637 .unwrap();
3638 let StageState::Broadcast {
3639 live_outlets,
3640 cancelled_outlets,
3641 ..
3642 } = &executor.stage_states[broadcast_index]
3643 else {
3644 panic!("expected broadcast state");
3645 };
3646 assert_eq!(*live_outlets, 0);
3647 assert_eq!(cancelled_outlets, &vec![true, true]);
3648 }
3649
3650 #[test]
3651 fn cyclic_graph_clears_pending_events_when_output_cancels() {
3652 struct CancelAfterFirst {
3653 emitted: usize,
3654 }
3655
3656 impl FusedOutputSink<i32> for CancelAfterFirst {
3657 fn emit(&mut self, _value: i32) -> StreamResult<()> {
3658 self.emitted += 1;
3659 Err(StreamError::Cancelled)
3660 }
3661 }
3662
3663 let graph = GraphDsl::try_create(|builder| {
3664 let merge = builder.add(MergePreferred::<i32>::new(1));
3665 let broadcast = builder.add(Broadcast::<i32>::new(2));
3666 let buffer = builder.add(Buffer::<i32>::new(8, OverflowStrategy::Backpressure));
3667 let positive = builder.add(TakeWhile::<i32>::new(|item| *item > 0));
3668 let decrement = builder.add(MapStage::new(|item: i32| item - 1));
3669
3670 builder.connect(merge.outlet(), broadcast.inlet())?;
3671 builder.connect(broadcast.outlet(1)?, buffer.inlet())?;
3672 builder.connect(buffer.outlet(), positive.inlet())?;
3673 builder.connect(positive.outlet(), decrement.inlet())?;
3674 builder.connect(decrement.outlet(), merge.preferred())?;
3675
3676 Ok(FlowShape::new(merge.secondary(0)?, broadcast.outlet(0)?))
3677 })
3678 .unwrap();
3679
3680 let graph_outlet = graph.shape.outlet().id();
3681 let graph_inlet = graph.shape.inlet().id();
3682 let mut executor = FusedExecutor::new(&graph, FusedExecutionConfig::default());
3683 let mut output = CancelAfterFirst { emitted: 0 };
3684
3685 executor
3686 .request(graph_outlet, graph_outlet, &mut output)
3687 .unwrap();
3688 let result = executor.deliver(graph_inlet, datum(1_i32), graph_outlet, &mut output);
3689
3690 assert_eq!(result, Err(StreamError::Cancelled));
3691 assert_eq!(output.emitted, 1);
3692 assert!(executor.event_stack.is_empty());
3693 }
3694
3695 const CYCLE_LIMIT: FusedExecutionConfig = FusedExecutionConfig {
3702 event_limit: 5_000_000,
3703 };
3704
3705 fn cyclic_feedback_i32(
3708 buffer_cap: usize,
3709 strategy: OverflowStrategy,
3710 ) -> GraphBlueprint<FlowShape<i32, i32>> {
3711 GraphDsl::try_create(|builder| {
3712 let merge = builder.add(MergePreferred::<i32>::new(1));
3713 let broadcast = builder.add(Broadcast::<i32>::new(2));
3714 let buffer = builder.add(Buffer::<i32>::new(buffer_cap, strategy));
3715 let positive = builder.add(TakeWhile::<i32>::new(|item| *item > 0));
3716 let decrement = builder.add(MapStage::new(|item: i32| item - 1));
3717
3718 builder.connect(merge.outlet(), broadcast.inlet())?;
3719 builder.connect(broadcast.outlet(1)?, buffer.inlet())?;
3720 builder.connect(buffer.outlet(), positive.inlet())?;
3721 builder.connect(positive.outlet(), decrement.inlet())?;
3722 builder.connect(decrement.outlet(), merge.preferred())?;
3723
3724 Ok(FlowShape::new(merge.secondary(0)?, broadcast.outlet(0)?))
3725 })
3726 .unwrap()
3727 }
3728
3729 fn assert_cyclic_equiv_i32(graph: &GraphBlueprint<FlowShape<i32, i32>>, input: Vec<i32>) {
3732 let erased = graph
3733 .run_with_input_report_mode(input.clone(), CYCLE_LIMIT, ExecutorMode::ErasedOnly)
3734 .map(|r| r.output);
3735 let typed = graph
3736 .run_with_input_report_mode(input.clone(), CYCLE_LIMIT, ExecutorMode::TypedOnly)
3737 .map(|r| r.output);
3738 let auto = graph
3739 .run_with_input_report_mode(input.clone(), CYCLE_LIMIT, ExecutorMode::Auto)
3740 .map(|r| r.output);
3741 assert!(
3742 typed.is_ok(),
3743 "typed cyclic path was not selected for {input:?}: {typed:?}"
3744 );
3745 assert_eq!(erased, typed, "typed != erased for input {input:?}");
3746 assert_eq!(erased, auto, "auto != erased for input {input:?}");
3747 }
3748
3749 #[test]
3750 fn cyclic_typed_matches_erased_single_and_multi_input() {
3751 let graph = cyclic_feedback_i32(16, OverflowStrategy::Backpressure);
3752 for input in [
3753 vec![],
3754 vec![0],
3755 vec![1],
3756 vec![5],
3757 vec![100],
3758 vec![2, 5],
3759 vec![5, 2],
3760 vec![0, 3],
3761 vec![3, 0, 2],
3762 vec![1, 1, 1],
3763 vec![-1],
3764 vec![4, -3, 7],
3765 ] {
3766 assert_cyclic_equiv_i32(&graph, input);
3767 }
3768 }
3769
3770 #[test]
3771 fn cyclic_typed_matches_erased_across_buffer_configs() {
3772 for cap in [1usize, 2, 8, 64] {
3775 for strategy in [
3776 OverflowStrategy::Backpressure,
3777 OverflowStrategy::DropHead,
3778 OverflowStrategy::DropTail,
3779 OverflowStrategy::DropBuffer,
3780 OverflowStrategy::DropNew,
3781 OverflowStrategy::Fail,
3782 ] {
3783 let graph = cyclic_feedback_i32(cap, strategy);
3784 for input in [vec![6], vec![3, 0, 4], vec![1, 1]] {
3785 assert_cyclic_equiv_i32(&graph, input);
3786 }
3787 }
3788 }
3789 }
3790
3791 #[test]
3792 fn cyclic_typed_falls_back_for_feedback_first_broadcast_orientation() {
3793 let graph = GraphDsl::try_create(|builder| {
3799 let merge = builder.add(MergePreferred::<i32>::new(1));
3800 let broadcast = builder.add(Broadcast::<i32>::new(2));
3801 let buffer = builder.add(Buffer::<i32>::new(4, OverflowStrategy::Backpressure));
3802 let positive = builder.add(TakeWhile::<i32>::new(|item| *item > 0));
3803 let decrement = builder.add(MapStage::new(|item: i32| item - 1));
3804
3805 builder.connect(merge.outlet(), broadcast.inlet())?;
3806 builder.connect(broadcast.outlet(0)?, buffer.inlet())?;
3808 builder.connect(buffer.outlet(), positive.inlet())?;
3809 builder.connect(positive.outlet(), decrement.inlet())?;
3810 builder.connect(decrement.outlet(), merge.preferred())?;
3811
3812 Ok(FlowShape::new(merge.secondary(0)?, broadcast.outlet(1)?))
3813 })
3814 .unwrap();
3815 for input in [vec![5], vec![2, 4], vec![0, 3]] {
3816 let typed = graph.run_with_input_report_mode(
3817 input.clone(),
3818 CYCLE_LIMIT,
3819 ExecutorMode::TypedOnly,
3820 );
3821 assert!(
3822 matches!(typed, Err(StreamError::GraphValidation(_))),
3823 "feedback-first orientation should not be typed-supported: {typed:?}"
3824 );
3825 let erased = graph
3826 .run_with_input_report_mode(input.clone(), CYCLE_LIMIT, ExecutorMode::ErasedOnly)
3827 .map(|r| r.output);
3828 let auto = graph
3829 .run_with_input_report_mode(input.clone(), CYCLE_LIMIT, ExecutorMode::Auto)
3830 .map(|r| r.output);
3831 assert_eq!(
3832 erased, auto,
3833 "auto must match erased on fallback for {input:?}"
3834 );
3835 }
3836 }
3837
3838 #[test]
3839 fn cyclic_typed_matches_erased_map_before_takewhile_and_identity() {
3840 let graph = GraphDsl::try_create(|builder| {
3842 let merge = builder.add(MergePreferred::<i32>::new(1));
3843 let broadcast = builder.add(Broadcast::<i32>::new(2));
3844 let decrement = builder.add(MapStage::new(|item: i32| item - 1));
3845 let nonneg = builder.add(TakeWhile::<i32>::new(|item| *item >= 0));
3846 let passthrough = builder.add(Identity::<i32>::new());
3847
3848 builder.connect(merge.outlet(), broadcast.inlet())?;
3849 builder.connect(broadcast.outlet(1)?, decrement.inlet())?;
3850 builder.connect(decrement.outlet(), nonneg.inlet())?;
3851 builder.connect(nonneg.outlet(), passthrough.inlet())?;
3852 builder.connect(passthrough.outlet(), merge.preferred())?;
3853
3854 Ok(FlowShape::new(merge.secondary(0)?, broadcast.outlet(0)?))
3855 })
3856 .unwrap();
3857 for input in [vec![5], vec![0], vec![3, 1, 4], vec![10, 0]] {
3858 assert_cyclic_equiv_i32(&graph, input);
3859 }
3860 }
3861
3862 #[test]
3863 fn cyclic_typed_matches_erased_u64_elements() {
3864 let graph = GraphDsl::try_create(|builder| {
3865 let merge = builder.add(MergePreferred::<u64>::new(1));
3866 let broadcast = builder.add(Broadcast::<u64>::new(2));
3867 let buffer = builder.add(Buffer::<u64>::new(16, OverflowStrategy::Backpressure));
3868 let positive = builder.add(TakeWhile::<u64>::new(|item| *item > 0));
3869 let decrement = builder.add(MapStage::new(|item: u64| item - 1));
3870
3871 builder.connect(merge.outlet(), broadcast.inlet())?;
3872 builder.connect(broadcast.outlet(1)?, buffer.inlet())?;
3873 builder.connect(buffer.outlet(), positive.inlet())?;
3874 builder.connect(positive.outlet(), decrement.inlet())?;
3875 builder.connect(decrement.outlet(), merge.preferred())?;
3876
3877 Ok(FlowShape::new(merge.secondary(0)?, broadcast.outlet(0)?))
3878 })
3879 .unwrap();
3880 for input in [vec![0u64], vec![7u64], vec![3u64, 5], vec![10_000u64]] {
3881 let erased = graph
3882 .run_with_input_report_mode(input.clone(), CYCLE_LIMIT, ExecutorMode::ErasedOnly)
3883 .map(|r| r.output);
3884 let typed = graph
3885 .run_with_input_report_mode(input.clone(), CYCLE_LIMIT, ExecutorMode::TypedOnly)
3886 .map(|r| r.output);
3887 let auto = graph
3888 .run_with_input_report_mode(input.clone(), CYCLE_LIMIT, ExecutorMode::Auto)
3889 .map(|r| r.output);
3890 assert!(typed.is_ok(), "typed not selected for u64 input {input:?}");
3891 assert_eq!(erased, typed, "u64 typed != erased for {input:?}");
3892 assert_eq!(erased, auto, "u64 auto != erased for {input:?}");
3893 }
3894 }
3895
3896 #[test]
3897 fn cyclic_typed_unproductive_cycle_surfaces_event_limit_like_erased() {
3898 let graph = GraphDsl::try_create(|builder| {
3901 let merge = builder.add(MergePreferred::<i32>::new(1));
3902 let broadcast = builder.add(Broadcast::<i32>::new(2));
3903 let buffer = builder.add(Buffer::<i32>::new(8, OverflowStrategy::Backpressure));
3904 let increment = builder.add(MapStage::new(|item: i32| item + 1));
3905
3906 builder.connect(merge.outlet(), broadcast.inlet())?;
3907 builder.connect(broadcast.outlet(1)?, buffer.inlet())?;
3908 builder.connect(buffer.outlet(), increment.inlet())?;
3909 builder.connect(increment.outlet(), merge.preferred())?;
3910
3911 Ok(FlowShape::new(merge.secondary(0)?, broadcast.outlet(0)?))
3912 })
3913 .unwrap();
3914 let config = FusedExecutionConfig { event_limit: 512 };
3915 let erased = graph.run_with_input_report_mode(vec![1], config, ExecutorMode::ErasedOnly);
3916 let typed = graph.run_with_input_report_mode(vec![1], config, ExecutorMode::TypedOnly);
3917 let auto = graph.run_with_input_report_mode(vec![1], config, ExecutorMode::Auto);
3918 assert_eq!(
3919 erased.map(|r| r.output),
3920 Err(StreamError::EventLimitExceeded { limit: 512 })
3921 );
3922 assert_eq!(
3923 typed.map(|r| r.output),
3924 Err(StreamError::EventLimitExceeded { limit: 512 })
3925 );
3926 assert_eq!(
3927 auto.map(|r| r.output),
3928 Err(StreamError::EventLimitExceeded { limit: 512 })
3929 );
3930 }
3931
3932 #[test]
3933 fn cyclic_typed_falls_back_for_plain_merge() {
3934 let graph = GraphDsl::try_create(|builder| {
3937 let merge = builder.add(Merge::<i32>::new(2));
3938 let broadcast = builder.add(Broadcast::<i32>::new(2));
3939 let buffer = builder.add(Buffer::<i32>::new(8, OverflowStrategy::Backpressure));
3940 let positive = builder.add(TakeWhile::<i32>::new(|item| *item > 0));
3941 let decrement = builder.add(MapStage::new(|item: i32| item - 1));
3942
3943 builder.connect(merge.outlet(), broadcast.inlet())?;
3944 builder.connect(broadcast.outlet(1)?, buffer.inlet())?;
3945 builder.connect(buffer.outlet(), positive.inlet())?;
3946 builder.connect(positive.outlet(), decrement.inlet())?;
3947 builder.connect(decrement.outlet(), merge.inlet(1)?)?;
3948
3949 Ok(FlowShape::new(merge.inlet(0)?, broadcast.outlet(0)?))
3950 })
3951 .unwrap();
3952 let typed = graph.run_with_input_report_mode(vec![3], CYCLE_LIMIT, ExecutorMode::TypedOnly);
3953 assert!(
3954 matches!(typed, Err(StreamError::GraphValidation(_))),
3955 "plain Merge cycle should not be typed-supported: {typed:?}"
3956 );
3957 let erased = graph
3958 .run_with_input_report_mode(vec![3], CYCLE_LIMIT, ExecutorMode::ErasedOnly)
3959 .map(|r| r.output);
3960 let auto = graph
3961 .run_with_input_report_mode(vec![3], CYCLE_LIMIT, ExecutorMode::Auto)
3962 .map(|r| r.output);
3963 assert_eq!(erased, auto, "auto must match erased on fallback");
3964 }
3965
3966 #[test]
3967 fn cyclic_typed_falls_back_for_custom_opaque_in_feedback() {
3968 let graph = GraphDsl::try_create(|builder| {
3970 let merge = builder.add(MergePreferred::<i32>::new(1));
3971 let broadcast = builder.add(Broadcast::<i32>::new(2));
3972 let positive = builder.add(TakeWhile::<i32>::new(|item| *item > 0));
3973 let decrement = builder.add(MapStage::new(|item: i32| item - 1));
3974 let custom = builder.add(BufferedFlowOnPull {
3975 state: Arc::new(Mutex::new(BufferedFlowState::default())),
3976 });
3977
3978 builder.connect(merge.outlet(), broadcast.inlet())?;
3979 builder.connect(broadcast.outlet(1)?, positive.inlet())?;
3980 builder.connect(positive.outlet(), decrement.inlet())?;
3981 builder.connect(decrement.outlet(), custom.inlet())?;
3982 builder.connect(custom.outlet(), merge.preferred())?;
3983
3984 Ok(FlowShape::new(merge.secondary(0)?, broadcast.outlet(0)?))
3985 })
3986 .unwrap();
3987 let typed = graph.run_with_input_report_mode(vec![4], CYCLE_LIMIT, ExecutorMode::TypedOnly);
3988 assert!(
3989 matches!(typed, Err(StreamError::GraphValidation(_))),
3990 "custom opaque feedback stage should fall back: {typed:?}"
3991 );
3992 let erased = graph
3993 .run_with_input_report_mode(vec![4], CYCLE_LIMIT, ExecutorMode::ErasedOnly)
3994 .map(|r| r.output);
3995 let auto = graph
3996 .run_with_input_report_mode(vec![4], CYCLE_LIMIT, ExecutorMode::Auto)
3997 .map(|r| r.output);
3998 assert_eq!(erased, auto, "auto must match erased on fallback");
3999 }
4000
4001 #[test]
4002 fn cyclic_typed_matches_erased_randomized() {
4003 let graph = cyclic_feedback_i32(16, OverflowStrategy::Backpressure);
4006 let mut state: u64 = 0x9E37_79B9_7F4A_7C15;
4007 let mut next = || {
4008 state ^= state << 13;
4009 state ^= state >> 7;
4010 state ^= state << 17;
4011 state
4012 };
4013 for _ in 0..200 {
4014 let len = (next() % 6) as usize;
4015 let input: Vec<i32> = (0..len).map(|_| (next() % 20) as i32 - 5).collect();
4016 assert_cyclic_equiv_i32(&graph, input);
4017 }
4018 }
4019
4020 #[test]
4021 fn partition_holds_routed_element_until_target_outlet_pulls() {
4022 let graph = GraphDsl::create(|builder| {
4023 builder.add(Partition::<i32>::new(2, |value| usize::from(*value >= 10)))
4024 })
4025 .unwrap();
4026
4027 let stage_index = 0usize;
4028 let inlet = graph.shape.inlet().id();
4029 let out0 = graph.shape.outlet(0).unwrap().id();
4030 let out1 = graph.shape.outlet(1).unwrap().id();
4031 let mut executor = FusedExecutor::new(&graph, FusedExecutionConfig::default());
4032
4033 executor.process_pull(stage_index, out0).unwrap();
4034 let transition = executor
4035 .process_stage(stage_index, inlet, datum(11_i32))
4036 .unwrap();
4037 assert!(matches!(transition.emissions, StageEmissions::None));
4038
4039 let pull_transition = executor.process_pull(stage_index, out1).unwrap();
4040 match pull_transition.emissions {
4041 StageEmissions::One(port, value) => {
4042 assert_eq!(port, out1);
4043 assert_eq!(
4044 downcast_datum::<i32, _>(value, "emit", || "Partition.out1").unwrap(),
4045 11
4046 );
4047 }
4048 _ => panic!("expected one pending partition emission"),
4049 }
4050 }
4051
4052 #[test]
4053 fn partition_cancels_upstream_only_after_all_outlets_cancel_when_not_eager() {
4054 let graph = GraphDsl::create(|builder| {
4055 builder.add(Partition::<i32>::new(2, |value| usize::from(*value >= 10)))
4056 })
4057 .unwrap();
4058
4059 let stage_index = 0usize;
4060 let out0 = graph.shape.outlet(0).unwrap().id();
4061 let out1 = graph.shape.outlet(1).unwrap().id();
4062 let mut executor = FusedExecutor::new(&graph, FusedExecutionConfig::default());
4063
4064 let first = executor
4065 .process_downstream_finish(stage_index, out0)
4066 .unwrap();
4067 assert!(first.cancelled_inlets.is_empty());
4068
4069 let second = executor
4070 .process_downstream_finish(stage_index, out1)
4071 .unwrap();
4072 assert_eq!(second.cancelled_inlets, vec![graph.shape.inlet().id()]);
4073 }
4074
4075 #[test]
4076 fn unzip_continues_emitting_to_live_outlet_after_peer_cancels() {
4077 let graph =
4078 GraphDsl::create(|builder| builder.add(Unzip::<i32, &'static str>::new())).unwrap();
4079
4080 let stage_index = 0usize;
4081 let inlet = graph.shape.inlet().id();
4082 let out0 = graph.shape.out0().id();
4083 let out1 = graph.shape.out1().id();
4084 let mut executor = FusedExecutor::new(&graph, FusedExecutionConfig::default());
4085
4086 executor.process_pull(stage_index, out0).unwrap();
4087 executor.process_pull(stage_index, out1).unwrap();
4088 let cancel = executor
4089 .process_downstream_finish(stage_index, out1)
4090 .unwrap();
4091 assert!(cancel.cancelled_inlets.is_empty());
4092
4093 let transition = executor
4094 .process_stage(stage_index, inlet, datum((7_i32, "seven")))
4095 .unwrap();
4096 match transition.emissions {
4097 StageEmissions::One(port, value) => {
4098 assert_eq!(port, out0);
4099 assert_eq!(
4100 downcast_datum::<i32, _>(value, "emit", || "Unzip.out0").unwrap(),
4101 7
4102 );
4103 }
4104 StageEmissions::Many(values) => {
4105 assert_eq!(values.len(), 1);
4106 assert_eq!(values[0].0, out0);
4107 }
4108 _ => panic!("expected emission to the remaining live unzip outlet"),
4109 }
4110 }
4111
4112 #[test]
4113 fn unzip_cancels_upstream_only_after_both_outlets_cancel() {
4114 let graph =
4115 GraphDsl::create(|builder| builder.add(Unzip::<i32, &'static str>::new())).unwrap();
4116
4117 let stage_index = 0usize;
4118 let out0 = graph.shape.out0().id();
4119 let out1 = graph.shape.out1().id();
4120 let mut executor = FusedExecutor::new(&graph, FusedExecutionConfig::default());
4121
4122 let first = executor
4123 .process_downstream_finish(stage_index, out0)
4124 .unwrap();
4125 assert!(first.cancelled_inlets.is_empty());
4126
4127 let second = executor
4128 .process_downstream_finish(stage_index, out1)
4129 .unwrap();
4130 assert_eq!(second.cancelled_inlets, vec![graph.shape.inlet().id()]);
4131 }
4132
4133 #[test]
4134 fn opaque_internal_outlet_repulls_after_first_emission() {
4135 let graph = GraphDsl::try_create(|builder| {
4136 let opaque = builder.add(EmitMultipleOnPush);
4137 let identity = builder.add(Identity::<i32>::new());
4138 builder.connect(opaque.outlet(), identity.inlet())?;
4139 Ok(FlowShape::new(opaque.inlet(), identity.outlet()))
4140 })
4141 .unwrap();
4142
4143 assert_eq!(graph.run_with_input([10]).unwrap(), vec![1, 2]);
4144 }
4145
4146 #[test]
4159 fn executor_mode_auto_erased_identical_typed_errors() {
4160 let graph = GraphDsl::try_create(|builder| {
4161 let broadcast = builder.add(Broadcast::<i32>::new(2));
4162 let merge = builder.add(Merge::<i32>::new(2));
4163 builder.connect(broadcast.outlet(0)?, merge.inlet(0)?)?;
4164 builder.connect(broadcast.outlet(1)?, merge.inlet(1)?)?;
4165 Ok(FlowShape::new(broadcast.inlet(), merge.outlet()))
4166 })
4167 .unwrap();
4168
4169 let input = vec![1, 2, 3];
4170
4171 let auto_result = graph
4172 .run_with_input_mode(input.clone(), ExecutorMode::Auto)
4173 .unwrap();
4174 let erased_result = graph
4175 .run_with_input_mode(input.clone(), ExecutorMode::ErasedOnly)
4176 .unwrap();
4177
4178 assert_eq!(auto_result, erased_result);
4181 assert_eq!(auto_result.len(), input.len() * 2);
4183
4184 let typed_result = graph.run_with_input_mode(input.clone(), ExecutorMode::TypedOnly);
4186 assert!(
4187 matches!(
4188 typed_result,
4189 Err(StreamError::GraphValidation(ref msg))
4190 if msg.contains("typed executor does not support this graph shape")
4191 ),
4192 "expected TypedOnly to error for junction graph, got: {typed_result:?}"
4193 );
4194 }
4195
4196 fn identity_chain_bp(n: usize) -> GraphBlueprint<FlowShape<i64, i64>> {
4200 assert!(n >= 1);
4201 GraphDsl::try_create(|builder| {
4202 let first = builder.add(Identity::<i64>::new());
4203 let inlet = first.inlet();
4204 let mut outlet = first.outlet();
4205 for _ in 1..n {
4206 let next = builder.add(Identity::<i64>::new());
4207 builder.connect(outlet, next.inlet())?;
4208 outlet = next.outlet();
4209 }
4210 Ok(FlowShape::new(inlet, outlet))
4211 })
4212 .unwrap()
4213 }
4214
4215 fn map_chain_bp(n: usize) -> GraphBlueprint<FlowShape<i64, i64>> {
4217 assert!(n >= 1);
4218 GraphDsl::try_create(|builder| {
4219 let first = builder.add(MapStage::new(|x: i64| x.wrapping_mul(2)));
4220 let inlet = first.inlet();
4221 let mut outlet = first.outlet();
4222 for _ in 1..n {
4223 let next = builder.add(MapStage::new(|x: i64| x.wrapping_mul(2)));
4224 builder.connect(outlet, next.inlet())?;
4225 outlet = next.outlet();
4226 }
4227 Ok(FlowShape::new(inlet, outlet))
4228 })
4229 .unwrap()
4230 }
4231
4232 #[test]
4235 fn typed_erased_equivalence_identity_collect() {
4236 let graph = identity_chain_bp(5);
4237 let input: Vec<i64> = (0..20).collect();
4238
4239 let erased = graph
4240 .run_with_input_mode(input.clone(), ExecutorMode::ErasedOnly)
4241 .unwrap();
4242 let typed = graph
4243 .run_with_input_mode(input.clone(), ExecutorMode::TypedOnly)
4244 .unwrap();
4245
4246 assert_eq!(
4247 typed, erased,
4248 "typed and erased paths disagree on identity×5"
4249 );
4250 }
4251
4252 #[test]
4255 fn typed_erased_equivalence_identity_count() {
4256 let graph = identity_chain_bp(5);
4257 let input: Vec<i64> = (0..20).collect();
4258 let config = FusedExecutionConfig::default();
4259
4260 let erased = graph
4261 .run_count_with_input_report_mode(input.clone(), config, ExecutorMode::ErasedOnly)
4262 .unwrap()
4263 .result;
4264 let typed = graph
4265 .run_count_with_input_report_mode(input.clone(), config, ExecutorMode::TypedOnly)
4266 .unwrap()
4267 .result;
4268
4269 assert_eq!(typed, erased, "typed and erased count differ on identity×5");
4270 }
4271
4272 #[test]
4275 fn typed_erased_equivalence_map_fold() {
4276 let graph = map_chain_bp(5);
4277 let input: Vec<i64> = (1..=10).collect();
4278 let config = FusedExecutionConfig::default();
4279
4280 let erased = graph
4281 .run_fold_with_input_report_mode(
4282 input.clone(),
4283 0i64,
4284 |acc, x| acc.wrapping_add(x),
4285 config,
4286 ExecutorMode::ErasedOnly,
4287 )
4288 .unwrap()
4289 .result;
4290 let typed = graph
4291 .run_fold_with_input_report_mode(
4292 input.clone(),
4293 0i64,
4294 |acc, x| acc.wrapping_add(x),
4295 config,
4296 ExecutorMode::TypedOnly,
4297 )
4298 .unwrap()
4299 .result;
4300
4301 assert_eq!(typed, erased, "typed and erased fold differ on map×5");
4302 }
4303
4304 #[test]
4307 fn typed_erased_equivalence_map_collect() {
4308 let graph = map_chain_bp(5);
4309 let input: Vec<i64> = (0..20).collect();
4310
4311 let erased = graph
4312 .run_with_input_mode(input.clone(), ExecutorMode::ErasedOnly)
4313 .unwrap();
4314 let typed = graph
4315 .run_with_input_mode(input.clone(), ExecutorMode::TypedOnly)
4316 .unwrap();
4317
4318 assert_eq!(typed, erased, "typed and erased paths disagree on map×5");
4319 }
4320
4321 #[test]
4324 fn typed_only_errors_on_junction_graph() {
4325 let graph = GraphDsl::try_create(|builder| {
4326 let broadcast = builder.add(Broadcast::<i32>::new(2));
4327 let merge = builder.add(Merge::<i32>::new(2));
4328 builder.connect(broadcast.outlet(0)?, merge.inlet(0)?)?;
4329 builder.connect(broadcast.outlet(1)?, merge.inlet(1)?)?;
4330 Ok(FlowShape::new(broadcast.inlet(), merge.outlet()))
4331 })
4332 .unwrap();
4333
4334 let result = graph.run_with_input_mode(vec![1], ExecutorMode::TypedOnly);
4335 assert!(
4336 matches!(
4337 result,
4338 Err(StreamError::GraphValidation(ref msg))
4339 if msg.contains("typed executor does not support this graph shape")
4340 ),
4341 "expected TypedOnly to error on junction, got: {result:?}"
4342 );
4343 }
4344
4345 #[test]
4347 fn auto_falls_back_silently_for_junction_graph() {
4348 let graph = GraphDsl::try_create(|builder| {
4349 let broadcast = builder.add(Broadcast::<i32>::new(2));
4350 let merge = builder.add(Merge::<i32>::new(2));
4351 builder.connect(broadcast.outlet(0)?, merge.inlet(0)?)?;
4352 builder.connect(broadcast.outlet(1)?, merge.inlet(1)?)?;
4353 Ok(FlowShape::new(broadcast.inlet(), merge.outlet()))
4354 })
4355 .unwrap();
4356
4357 let result = graph.run_with_input_mode(vec![1, 2, 3], ExecutorMode::Auto);
4358 assert!(result.is_ok(), "Auto should succeed (fallback to erased)");
4359 assert_eq!(result.unwrap().len(), 6); }
4361
4362 fn merge_sequence_graph() -> GraphBlueprint<FlowShape<(u64, u64), u64>> {
4366 GraphDsl::try_create(|builder| {
4367 let unzip = builder.add(Unzip::<u64, u64>::new());
4368 let merge = builder.add(MergeSequence::<u64>::new(2, |item| *item));
4369 builder.connect(unzip.out0(), merge.inlet(0)?)?;
4370 builder.connect(unzip.out1(), merge.inlet(1)?)?;
4371 Ok(FlowShape::new(unzip.inlet(), merge.outlet()))
4372 })
4373 .unwrap()
4374 }
4375
4376 #[test]
4379 fn typed_only_accepts_merge_sequence_topology() {
4380 let graph = merge_sequence_graph();
4381 let result =
4382 graph.run_with_input_mode(vec![(0u64, 1u64), (2u64, 3u64)], ExecutorMode::TypedOnly);
4383 assert!(
4384 result.is_ok(),
4385 "TypedOnly should accept Unzip→MergeSequence topology, got: {result:?}"
4386 );
4387 }
4388
4389 #[test]
4392 fn typed_erased_equivalence_merge_sequence_in_order() {
4393 let graph = merge_sequence_graph();
4394 let input: Vec<(u64, u64)> = (0..10).step_by(2).map(|i| (i, i + 1)).collect();
4395
4396 let erased = graph
4397 .run_with_input_mode(input.clone(), ExecutorMode::ErasedOnly)
4398 .unwrap();
4399 let typed = graph
4400 .run_with_input_mode(input.clone(), ExecutorMode::TypedOnly)
4401 .unwrap();
4402
4403 assert_eq!(
4404 typed, erased,
4405 "typed and erased disagree on in-order merge_sequence"
4406 );
4407 let expected: Vec<u64> = (0..10).collect();
4409 assert_eq!(typed, expected);
4410 }
4411
4412 #[test]
4416 fn typed_erased_equivalence_merge_sequence_out_of_order() {
4417 let graph = merge_sequence_graph();
4418 let input: Vec<(u64, u64)> = vec![(1, 0), (3, 2), (5, 4)];
4420
4421 let erased = graph
4422 .run_with_input_mode(input.clone(), ExecutorMode::ErasedOnly)
4423 .unwrap();
4424 let typed = graph
4425 .run_with_input_mode(input.clone(), ExecutorMode::TypedOnly)
4426 .unwrap();
4427
4428 assert_eq!(
4429 typed, erased,
4430 "typed and erased disagree on out-of-order merge_sequence"
4431 );
4432 assert_eq!(typed, vec![0u64, 1, 2, 3, 4, 5]);
4434 }
4435
4436 #[test]
4441 fn typed_erased_equivalence_merge_sequence_gap_failure() {
4442 let graph = merge_sequence_graph();
4445 let input = vec![(1u64, 2u64)];
4446
4447 let erased = graph.run_with_input_mode(input.clone(), ExecutorMode::ErasedOnly);
4448 let typed = graph.run_with_input_mode(input.clone(), ExecutorMode::TypedOnly);
4449
4450 assert!(
4451 matches!(&erased, Err(StreamError::Failed(msg)) if msg.contains("expected sequence")),
4452 "ErasedOnly should fail on gap: {erased:?}"
4453 );
4454 assert!(
4455 matches!(&typed, Err(StreamError::Failed(msg)) if msg.contains("expected sequence")),
4456 "TypedOnly should fail on gap: {typed:?}"
4457 );
4458 }
4459
4460 #[test]
4463 fn typed_erased_equivalence_merge_sequence_completion() {
4464 let graph = merge_sequence_graph();
4465 let input = vec![(0u64, 1u64)];
4466
4467 let erased = graph
4468 .run_with_input_mode(input.clone(), ExecutorMode::ErasedOnly)
4469 .unwrap();
4470 let typed = graph
4471 .run_with_input_mode(input.clone(), ExecutorMode::TypedOnly)
4472 .unwrap();
4473
4474 assert_eq!(typed, erased, "typed and erased disagree on completion");
4475 assert_eq!(typed, vec![0u64, 1u64]);
4476 }
4477
4478 #[test]
4481 fn auto_selects_typed_for_merge_sequence_topology() {
4482 let graph = merge_sequence_graph();
4483 let input: Vec<(u64, u64)> = (0..20).step_by(2).map(|i| (i, i + 1)).collect();
4484
4485 let auto_result = graph
4486 .run_with_input_mode(input.clone(), ExecutorMode::Auto)
4487 .unwrap();
4488 let erased_result = graph
4489 .run_with_input_mode(input.clone(), ExecutorMode::ErasedOnly)
4490 .unwrap();
4491
4492 assert_eq!(
4493 auto_result, erased_result,
4494 "Auto and ErasedOnly disagree on merge_sequence topology"
4495 );
4496 }
4497
4498 fn merge_latest_graph_exec() -> GraphBlueprint<FlowShape<(u64, u64), Vec<u64>>> {
4502 GraphDsl::try_create(|builder| {
4503 let unzip = builder.add(Unzip::<u64, u64>::new());
4504 let merge = builder.add(MergeLatest::<u64>::new(2, false));
4505 builder.connect(unzip.out0(), merge.inlet(0)?)?;
4506 builder.connect(unzip.out1(), merge.inlet(1)?)?;
4507 Ok(FlowShape::new(unzip.inlet(), merge.outlet()))
4508 })
4509 .unwrap()
4510 }
4511
4512 fn merge_latest_eager_graph_exec() -> GraphBlueprint<FlowShape<(i32, i32), Vec<i32>>> {
4514 GraphDsl::try_create(|builder| {
4515 let unzip = builder.add(Unzip::<i32, i32>::new());
4516 let merge = builder.add(MergeLatest::<i32>::new(2, true));
4517 builder.connect(unzip.out0(), merge.inlet(0)?)?;
4518 builder.connect(unzip.out1(), merge.inlet(1)?)?;
4519 Ok(FlowShape::new(unzip.inlet(), merge.outlet()))
4520 })
4521 .unwrap()
4522 }
4523
4524 #[test]
4526 fn typed_only_accepts_merge_latest_topology() {
4527 let graph = merge_latest_graph_exec();
4528 let result =
4529 graph.run_with_input_mode(vec![(0u64, 1u64), (2u64, 3u64)], ExecutorMode::TypedOnly);
4530 assert!(
4531 result.is_ok(),
4532 "TypedOnly should accept Unzip→MergeLatest topology, got: {result:?}"
4533 );
4534 }
4535
4536 #[test]
4538 fn typed_erased_equivalence_merge_latest_snapshot_ordering() {
4539 let graph = merge_latest_graph_exec();
4540 let input: Vec<(u64, u64)> = (0..10).map(|i| (i, i + 100)).collect();
4543
4544 let erased = graph
4545 .run_with_input_mode(input.clone(), ExecutorMode::ErasedOnly)
4546 .unwrap();
4547 let typed = graph
4548 .run_with_input_mode(input.clone(), ExecutorMode::TypedOnly)
4549 .unwrap();
4550
4551 assert_eq!(
4552 typed, erased,
4553 "typed and erased disagree on snapshot ordering"
4554 );
4555 assert!(
4557 typed.iter().all(|s| s.len() == 2),
4558 "snapshots must have len 2"
4559 );
4560 }
4561
4562 #[test]
4564 fn typed_erased_equivalence_merge_latest_partial_fill() {
4565 let graph = merge_latest_graph_exec();
4568 let input = vec![(5u64, 42u64)];
4569
4570 let erased = graph
4571 .run_with_input_mode(input.clone(), ExecutorMode::ErasedOnly)
4572 .unwrap();
4573 let typed = graph
4574 .run_with_input_mode(input.clone(), ExecutorMode::TypedOnly)
4575 .unwrap();
4576
4577 assert_eq!(typed, erased, "typed and erased disagree on partial-fill");
4578 assert_eq!(typed.len(), 1, "expected exactly one snapshot");
4580 }
4581
4582 #[test]
4588 fn typed_erased_equivalence_merge_latest_eager_complete() {
4589 let graph_eager = merge_latest_eager_graph_exec();
4590 let input = vec![(1i32, 10i32)];
4591
4592 let erased = graph_eager
4593 .run_with_input_mode(input.clone(), ExecutorMode::ErasedOnly)
4594 .unwrap();
4595 let typed = graph_eager
4596 .run_with_input_mode(input.clone(), ExecutorMode::TypedOnly)
4597 .unwrap();
4598
4599 assert_eq!(
4600 typed, erased,
4601 "typed and erased disagree on eager-complete behavior"
4602 );
4603 assert!(
4604 !typed.is_empty(),
4605 "eager-complete graph should produce at least one snapshot"
4606 );
4607 }
4608
4609 #[test]
4611 fn typed_erased_equivalence_merge_latest_completion() {
4612 let graph = merge_latest_graph_exec();
4613 let input = vec![(0u64, 1u64)];
4614
4615 let erased = graph
4616 .run_with_input_mode(input.clone(), ExecutorMode::ErasedOnly)
4617 .unwrap();
4618 let typed = graph
4619 .run_with_input_mode(input.clone(), ExecutorMode::TypedOnly)
4620 .unwrap();
4621
4622 assert_eq!(typed, erased, "typed and erased disagree on completion");
4623 }
4624
4625 #[test]
4628 fn auto_selects_typed_for_merge_latest_topology() {
4629 let graph = merge_latest_graph_exec();
4630 let input: Vec<(u64, u64)> = (0..20).map(|i| (i, i + 1_000)).collect();
4631
4632 let auto_result = graph
4633 .run_with_input_mode(input.clone(), ExecutorMode::Auto)
4634 .unwrap();
4635 let erased_result = graph
4636 .run_with_input_mode(input.clone(), ExecutorMode::ErasedOnly)
4637 .unwrap();
4638
4639 assert_eq!(
4640 auto_result, erased_result,
4641 "Auto and ErasedOnly disagree on merge_latest topology"
4642 );
4643 }
4644
4645 fn broadcast_zip_graph_exec() -> GraphBlueprint<FlowShape<i64, (i64, i64)>> {
4648 GraphDsl::try_create(|builder| {
4649 let broadcast = builder.add(Broadcast::<i64>::new(2));
4650 let zip = builder.add(Zip::<i64, i64>::new());
4651 builder.connect(broadcast.outlet(0)?, zip.in0())?;
4652 builder.connect(broadcast.outlet(1)?, zip.in1())?;
4653 Ok(FlowShape::new(broadcast.inlet(), zip.outlet()))
4654 })
4655 .unwrap()
4656 }
4657
4658 fn balance_merge_graph_exec() -> GraphBlueprint<FlowShape<i64, i64>> {
4659 GraphDsl::try_create(|builder| {
4660 let balance = builder.add(Balance::<i64>::new(2));
4661 let merge = builder.add(Merge::<i64>::new(2));
4662 builder.connect(balance.outlet(0)?, merge.inlet(0)?)?;
4663 builder.connect(balance.outlet(1)?, merge.inlet(1)?)?;
4664 Ok(FlowShape::new(balance.inlet(), merge.outlet()))
4665 })
4666 .unwrap()
4667 }
4668
4669 fn partition_merge_graph_exec() -> GraphBlueprint<FlowShape<i64, i64>> {
4670 GraphDsl::try_create(|builder| {
4671 let partition = builder.add(Partition::<i64>::new(2, |item| {
4672 item.unsigned_abs() as usize % 2
4673 }));
4674 let merge = builder.add(Merge::<i64>::new(2));
4675 builder.connect(partition.outlet(0)?, merge.inlet(0)?)?;
4676 builder.connect(partition.outlet(1)?, merge.inlet(1)?)?;
4677 Ok(FlowShape::new(partition.inlet(), merge.outlet()))
4678 })
4679 .unwrap()
4680 }
4681
4682 fn unzip_zip_graph_exec() -> GraphBlueprint<FlowShape<i64, (i64, i64)>> {
4683 GraphDsl::try_create(|builder| {
4684 let unzip = builder.add(UnzipWith::<i64, i64, i64>::new(|item| (item, item + 10)));
4685 let zip = builder.add(Zip::<i64, i64>::new());
4686 builder.connect(unzip.out0(), zip.in0())?;
4687 builder.connect(unzip.out1(), zip.in1())?;
4688 Ok(FlowShape::new(unzip.inlet(), zip.outlet()))
4689 })
4690 .unwrap()
4691 }
4692
4693 fn merge_sorted_graph_exec() -> GraphBlueprint<FlowShape<(u64, u64), u64>> {
4694 GraphDsl::try_create(|builder| {
4695 let unzip = builder.add(Unzip::<u64, u64>::new());
4696 let merge = builder.add(MergeSorted::<u64>::new());
4697 builder.connect(unzip.out0(), merge.inlet(0)?)?;
4698 builder.connect(unzip.out1(), merge.inlet(1)?)?;
4699 Ok(FlowShape::new(unzip.inlet(), merge.outlet()))
4700 })
4701 .unwrap()
4702 }
4703
4704 #[test]
4705 fn typed_erased_equivalence_broadcast_zip() {
4706 let graph = broadcast_zip_graph_exec();
4707 let input: Vec<i64> = (-3..=3).collect();
4708
4709 let erased = graph
4710 .run_with_input_mode(input.clone(), ExecutorMode::ErasedOnly)
4711 .unwrap();
4712 let typed = graph
4713 .run_with_input_mode(input.clone(), ExecutorMode::TypedOnly)
4714 .unwrap();
4715 let auto = graph
4716 .run_with_input_mode(input, ExecutorMode::Auto)
4717 .unwrap();
4718
4719 assert_eq!(typed, erased, "typed and erased disagree on Broadcast->Zip");
4720 assert_eq!(
4721 auto, erased,
4722 "Auto and ErasedOnly disagree on Broadcast->Zip"
4723 );
4724 assert_eq!(typed.first().copied(), Some((-3, -3)));
4725 }
4726
4727 #[test]
4728 fn typed_erased_equivalence_balance_merge() {
4729 let graph = balance_merge_graph_exec();
4730 let input: Vec<i64> = (0..32).collect();
4731
4732 let erased = graph
4733 .run_with_input_mode(input.clone(), ExecutorMode::ErasedOnly)
4734 .unwrap();
4735 let typed = graph
4736 .run_with_input_mode(input.clone(), ExecutorMode::TypedOnly)
4737 .unwrap();
4738 let auto = graph
4739 .run_with_input_mode(input, ExecutorMode::Auto)
4740 .unwrap();
4741
4742 assert_eq!(typed, erased, "typed and erased disagree on Balance->Merge");
4743 assert_eq!(
4744 auto, erased,
4745 "Auto and ErasedOnly disagree on Balance->Merge"
4746 );
4747 assert_eq!(typed.len(), 32);
4748 }
4749
4750 #[test]
4751 fn typed_erased_equivalence_partition_merge() {
4752 let graph = partition_merge_graph_exec();
4753 let input: Vec<i64> = (-12..12).collect();
4754
4755 let erased = graph
4756 .run_with_input_mode(input.clone(), ExecutorMode::ErasedOnly)
4757 .unwrap();
4758 let typed = graph
4759 .run_with_input_mode(input.clone(), ExecutorMode::TypedOnly)
4760 .unwrap();
4761 let auto = graph
4762 .run_with_input_mode(input, ExecutorMode::Auto)
4763 .unwrap();
4764
4765 assert_eq!(
4766 typed, erased,
4767 "typed and erased disagree on Partition->Merge"
4768 );
4769 assert_eq!(
4770 auto, erased,
4771 "Auto and ErasedOnly disagree on Partition->Merge"
4772 );
4773 }
4774
4775 #[test]
4776 fn typed_erased_equivalence_partition_merge_error() {
4777 let graph = GraphDsl::try_create(|builder| {
4778 let partition = builder.add(Partition::<i64>::new(2, |_| 2));
4779 let merge = builder.add(Merge::<i64>::new(2));
4780 builder.connect(partition.outlet(0)?, merge.inlet(0)?)?;
4781 builder.connect(partition.outlet(1)?, merge.inlet(1)?)?;
4782 Ok(FlowShape::new(partition.inlet(), merge.outlet()))
4783 })
4784 .unwrap();
4785
4786 let erased = graph.run_with_input_mode(vec![7], ExecutorMode::ErasedOnly);
4787 let typed = graph.run_with_input_mode(vec![7], ExecutorMode::TypedOnly);
4788
4789 assert!(
4790 matches!(&erased, Err(StreamError::Failed(msg)) if msg.contains("out-of-bounds")),
4791 "ErasedOnly should fail on bad partitioner: {erased:?}"
4792 );
4793 assert!(
4794 matches!(&typed, Err(StreamError::Failed(msg)) if msg.contains("out-of-bounds")),
4795 "TypedOnly should fail on bad partitioner: {typed:?}"
4796 );
4797 }
4798
4799 #[test]
4800 fn typed_erased_equivalence_unzip_zip() {
4801 let graph = unzip_zip_graph_exec();
4802 let input: Vec<i64> = (0..16).collect();
4803
4804 let erased = graph
4805 .run_with_input_mode(input.clone(), ExecutorMode::ErasedOnly)
4806 .unwrap();
4807 let typed = graph
4808 .run_with_input_mode(input.clone(), ExecutorMode::TypedOnly)
4809 .unwrap();
4810 let auto = graph
4811 .run_with_input_mode(input, ExecutorMode::Auto)
4812 .unwrap();
4813
4814 assert_eq!(typed, erased, "typed and erased disagree on UnzipWith->Zip");
4815 assert_eq!(
4816 auto, erased,
4817 "Auto and ErasedOnly disagree on UnzipWith->Zip"
4818 );
4819 assert_eq!(typed[0], (0, 10));
4820 }
4821
4822 #[test]
4823 fn typed_erased_equivalence_merge_sorted() {
4824 let graph = merge_sorted_graph_exec();
4825 let input: Vec<(u64, u64)> = (0..20).step_by(2).map(|item| (item, item + 1)).collect();
4826
4827 let erased = graph
4828 .run_with_input_mode(input.clone(), ExecutorMode::ErasedOnly)
4829 .unwrap();
4830 let typed = graph
4831 .run_with_input_mode(input.clone(), ExecutorMode::TypedOnly)
4832 .unwrap();
4833 let auto = graph
4834 .run_with_input_mode(input, ExecutorMode::Auto)
4835 .unwrap();
4836
4837 assert_eq!(
4838 typed, erased,
4839 "typed and erased disagree on Unzip->MergeSorted"
4840 );
4841 assert_eq!(
4842 auto, erased,
4843 "Auto and ErasedOnly disagree on Unzip->MergeSorted"
4844 );
4845 assert_eq!(typed, (0..20).collect::<Vec<_>>());
4846 }
4847
4848 #[test]
4849 fn typed_erased_equivalence_prioritized_merge_helper() {
4850 let graph =
4851 GraphDsl::create(|builder| builder.add(MergePrioritized::<i64>::new(vec![2, 1])))
4852 .unwrap();
4853 let inputs = vec![vec![1, 2, 3, 4], vec![100, 101]];
4854
4855 let erased = graph
4856 .run_fan_in_report_mode(
4857 inputs.clone(),
4858 FusedExecutionConfig::default(),
4859 ExecutorMode::ErasedOnly,
4860 )
4861 .unwrap();
4862 let typed = graph
4863 .run_fan_in_report_mode(
4864 inputs.clone(),
4865 FusedExecutionConfig::default(),
4866 ExecutorMode::TypedOnly,
4867 )
4868 .unwrap();
4869 let auto = graph
4870 .run_fan_in_report_mode(inputs, FusedExecutionConfig::default(), ExecutorMode::Auto)
4871 .unwrap();
4872
4873 assert_eq!(
4874 typed, erased,
4875 "typed and erased disagree on MergePrioritized"
4876 );
4877 assert_eq!(
4878 auto, erased,
4879 "Auto and ErasedOnly disagree on MergePrioritized"
4880 );
4881 assert_eq!(typed.output, vec![1, 2, 100, 3, 4, 101]);
4882 }
4883
4884 #[test]
4885 fn typed_erased_equivalence_merge_preferred_helper() {
4886 let graph = GraphDsl::create(|builder| builder.add(MergePreferred::<i64>::new(2))).unwrap();
4887 let preferred = vec![1, 2, 3];
4888 let secondary = vec![vec![100, 101], vec![200, 201]];
4889
4890 let erased = graph
4891 .run_merge_preferred_report_mode(
4892 preferred.clone(),
4893 secondary.clone(),
4894 FusedExecutionConfig::default(),
4895 ExecutorMode::ErasedOnly,
4896 )
4897 .unwrap();
4898 let typed = graph
4899 .run_merge_preferred_report_mode(
4900 preferred.clone(),
4901 secondary.clone(),
4902 FusedExecutionConfig::default(),
4903 ExecutorMode::TypedOnly,
4904 )
4905 .unwrap();
4906 let auto = graph
4907 .run_merge_preferred_report_mode(
4908 preferred,
4909 secondary,
4910 FusedExecutionConfig::default(),
4911 ExecutorMode::Auto,
4912 )
4913 .unwrap();
4914
4915 assert_eq!(typed, erased, "typed and erased disagree on MergePreferred");
4916 assert_eq!(
4917 auto, erased,
4918 "Auto and ErasedOnly disagree on MergePreferred"
4919 );
4920 assert_eq!(typed.output, vec![1, 2, 3, 100, 200, 101, 201]);
4921 }
4922
4923 #[test]
4924 fn typed_erased_equivalence_concat_helper() {
4925 let graph = GraphDsl::create(|builder| builder.add(Concat::<i64>::new(3))).unwrap();
4926 let inputs = vec![vec![1, 2], vec![], vec![3, 4]];
4927
4928 let erased = graph
4929 .run_concat_report_mode(
4930 inputs.clone(),
4931 FusedExecutionConfig::default(),
4932 ExecutorMode::ErasedOnly,
4933 )
4934 .unwrap();
4935 let typed = graph
4936 .run_concat_report_mode(
4937 inputs.clone(),
4938 FusedExecutionConfig::default(),
4939 ExecutorMode::TypedOnly,
4940 )
4941 .unwrap();
4942 let auto = graph
4943 .run_concat_report_mode(inputs, FusedExecutionConfig::default(), ExecutorMode::Auto)
4944 .unwrap();
4945
4946 assert_eq!(typed, erased, "typed and erased disagree on Concat");
4947 assert_eq!(auto, erased, "Auto and ErasedOnly disagree on Concat");
4948 assert_eq!(typed.output, vec![1, 2, 3, 4]);
4949 }
4950
4951 #[test]
4952 fn typed_erased_equivalence_interleave_helper() {
4953 let graph = GraphDsl::create(|builder| builder.add(Interleave::<i64>::new(3, 2))).unwrap();
4954 let inputs = vec![vec![1, 2, 3], vec![10, 11, 12], vec![20]];
4955
4956 let erased = graph
4957 .run_interleave_report_mode(
4958 inputs.clone(),
4959 2,
4960 false,
4961 FusedExecutionConfig::default(),
4962 ExecutorMode::ErasedOnly,
4963 )
4964 .unwrap();
4965 let typed = graph
4966 .run_interleave_report_mode(
4967 inputs.clone(),
4968 2,
4969 false,
4970 FusedExecutionConfig::default(),
4971 ExecutorMode::TypedOnly,
4972 )
4973 .unwrap();
4974 let auto = graph
4975 .run_interleave_report_mode(
4976 inputs,
4977 2,
4978 false,
4979 FusedExecutionConfig::default(),
4980 ExecutorMode::Auto,
4981 )
4982 .unwrap();
4983
4984 assert_eq!(typed, erased, "typed and erased disagree on Interleave");
4985 assert_eq!(auto, erased, "Auto and ErasedOnly disagree on Interleave");
4986 assert_eq!(typed.output, vec![1, 2, 10, 11, 20, 3, 12]);
4987 }
4988
4989 #[test]
4990 fn typed_erased_equivalence_interleave_eager_close_helper() {
4991 let graph = GraphDsl::create(|builder| {
4992 builder.add(Interleave::<i64>::new_with_eager_close(2, 1, true))
4993 })
4994 .unwrap();
4995 let inputs = vec![vec![1], vec![10, 11]];
4996
4997 let erased = graph
4998 .run_interleave_report_mode(
4999 inputs.clone(),
5000 1,
5001 true,
5002 FusedExecutionConfig::default(),
5003 ExecutorMode::ErasedOnly,
5004 )
5005 .unwrap();
5006 let typed = graph
5007 .run_interleave_report_mode(
5008 inputs.clone(),
5009 1,
5010 true,
5011 FusedExecutionConfig::default(),
5012 ExecutorMode::TypedOnly,
5013 )
5014 .unwrap();
5015 let auto = graph
5016 .run_interleave_report_mode(
5017 inputs,
5018 1,
5019 true,
5020 FusedExecutionConfig::default(),
5021 ExecutorMode::Auto,
5022 )
5023 .unwrap();
5024
5025 assert_eq!(
5026 typed, erased,
5027 "typed and erased disagree on Interleave eager close"
5028 );
5029 assert_eq!(
5030 auto, erased,
5031 "Auto and ErasedOnly disagree on Interleave eager close"
5032 );
5033 assert_eq!(typed.output, vec![1, 10]);
5034 }
5035
5036 #[test]
5037 fn typed_erased_equivalence_helper_event_limit_failures() {
5038 let config = FusedExecutionConfig { event_limit: 1 };
5039
5040 let prioritized =
5041 GraphDsl::create(|builder| builder.add(MergePrioritized::<i64>::new(vec![2, 1])))
5042 .unwrap();
5043 let prioritized_inputs = vec![vec![1], vec![10]];
5044 let erased = prioritized.run_fan_in_report_mode(
5045 prioritized_inputs.clone(),
5046 config,
5047 ExecutorMode::ErasedOnly,
5048 );
5049 let typed = prioritized.run_fan_in_report_mode(
5050 prioritized_inputs.clone(),
5051 config,
5052 ExecutorMode::TypedOnly,
5053 );
5054 let auto =
5055 prioritized.run_fan_in_report_mode(prioritized_inputs, config, ExecutorMode::Auto);
5056 assert_eq!(typed, erased);
5057 assert_eq!(auto, erased);
5058
5059 let preferred =
5060 GraphDsl::create(|builder| builder.add(MergePreferred::<i64>::new(1))).unwrap();
5061 let erased = preferred.run_merge_preferred_report_mode(
5062 vec![1],
5063 vec![vec![10]],
5064 config,
5065 ExecutorMode::ErasedOnly,
5066 );
5067 let typed = preferred.run_merge_preferred_report_mode(
5068 vec![1],
5069 vec![vec![10]],
5070 config,
5071 ExecutorMode::TypedOnly,
5072 );
5073 let auto = preferred.run_merge_preferred_report_mode(
5074 vec![1],
5075 vec![vec![10]],
5076 config,
5077 ExecutorMode::Auto,
5078 );
5079 assert_eq!(typed, erased);
5080 assert_eq!(auto, erased);
5081
5082 let concat = GraphDsl::create(|builder| builder.add(Concat::<i64>::new(2))).unwrap();
5083 let concat_inputs = vec![vec![1], vec![10]];
5084 let erased =
5085 concat.run_concat_report_mode(concat_inputs.clone(), config, ExecutorMode::ErasedOnly);
5086 let typed =
5087 concat.run_concat_report_mode(concat_inputs.clone(), config, ExecutorMode::TypedOnly);
5088 let auto = concat.run_concat_report_mode(concat_inputs, config, ExecutorMode::Auto);
5089 assert_eq!(typed, erased);
5090 assert_eq!(auto, erased);
5091
5092 let interleave =
5093 GraphDsl::create(|builder| builder.add(Interleave::<i64>::new(2, 1))).unwrap();
5094 let interleave_inputs = vec![vec![1], vec![10]];
5095 let erased = interleave.run_interleave_report_mode(
5096 interleave_inputs.clone(),
5097 1,
5098 false,
5099 config,
5100 ExecutorMode::ErasedOnly,
5101 );
5102 let typed = interleave.run_interleave_report_mode(
5103 interleave_inputs.clone(),
5104 1,
5105 false,
5106 config,
5107 ExecutorMode::TypedOnly,
5108 );
5109 let auto = interleave.run_interleave_report_mode(
5110 interleave_inputs,
5111 1,
5112 false,
5113 config,
5114 ExecutorMode::Auto,
5115 );
5116 assert_eq!(typed, erased);
5117 assert_eq!(auto, erased);
5118 }
5119
5120 #[test]
5130 fn merge_latest_blueprint_sequential_reuse_is_independent() {
5131 let graph = merge_latest_graph_exec();
5132 let input_a: Vec<(u64, u64)> = (0..5).map(|i| (i, i + 100)).collect();
5133 let input_b: Vec<(u64, u64)> = (10..15).map(|i| (i, i + 200)).collect();
5134
5135 let result_a_typed = graph
5137 .run_with_input_mode(input_a.clone(), ExecutorMode::TypedOnly)
5138 .unwrap();
5139 let result_b_typed = graph
5140 .run_with_input_mode(input_b.clone(), ExecutorMode::TypedOnly)
5141 .unwrap();
5142
5143 let result_a_erased = graph
5145 .run_with_input_mode(input_a, ExecutorMode::ErasedOnly)
5146 .unwrap();
5147 let result_b_erased = graph
5148 .run_with_input_mode(input_b, ExecutorMode::ErasedOnly)
5149 .unwrap();
5150
5151 assert_eq!(
5152 result_a_typed, result_a_erased,
5153 "sequential run A: typed and erased disagree"
5154 );
5155 assert_eq!(
5156 result_b_typed, result_b_erased,
5157 "sequential run B: typed and erased disagree"
5158 );
5159 assert_ne!(
5161 result_a_typed, result_b_typed,
5162 "runs A and B should differ (different inputs)"
5163 );
5164 }
5165
5166 #[test]
5172 fn merge_latest_blueprint_concurrent_reuse_is_independent() {
5173 use std::sync::Arc as StdArc;
5174
5175 let graph = StdArc::new(merge_latest_graph_exec());
5177
5178 let input_a: Vec<(u64, u64)> = (0..50).map(|i| (i, i + 1_000)).collect();
5179 let input_b: Vec<(u64, u64)> = (100..150).map(|i| (i, i + 2_000)).collect();
5180
5181 let graph_a = StdArc::clone(&graph);
5182 let graph_b = StdArc::clone(&graph);
5183 let ia = input_a.clone();
5184 let ib = input_b.clone();
5185
5186 let handle_a =
5187 std::thread::spawn(move || graph_a.run_with_input_mode(ia, ExecutorMode::TypedOnly));
5188 let handle_b =
5189 std::thread::spawn(move || graph_b.run_with_input_mode(ib, ExecutorMode::TypedOnly));
5190
5191 let result_a = handle_a.join().expect("thread A panicked").unwrap();
5192 let result_b = handle_b.join().expect("thread B panicked").unwrap();
5193
5194 let ref_a = graph
5196 .run_with_input_mode(input_a, ExecutorMode::ErasedOnly)
5197 .unwrap();
5198 let ref_b = graph
5199 .run_with_input_mode(input_b, ExecutorMode::ErasedOnly)
5200 .unwrap();
5201
5202 assert_eq!(
5203 result_a, ref_a,
5204 "concurrent run A: typed and erased disagree"
5205 );
5206 assert_eq!(
5207 result_b, ref_b,
5208 "concurrent run B: typed and erased disagree"
5209 );
5210 assert_ne!(result_a, result_b, "concurrent runs must be independent");
5212 }
5213}
5214
5215#[cfg(test)]
5216fn run_threaded_async_linear_count<I, T>(
5217 input: I,
5218 segments: TypedLinearSegments<T>,
5219 config: AsyncBoundaryExecutionConfig,
5220) -> StreamResult<FusedTerminalReport<usize>>
5221where
5222 I: IntoIterator<Item = T> + Send,
5223 I::IntoIter: Send,
5224 T: Send + 'static,
5225{
5226 let channels = segments.segments.len() + 1;
5227 let mut senders = Vec::with_capacity(channels);
5228 let mut receivers = Vec::with_capacity(channels);
5229 for _ in 0..channels {
5230 let (sender, receiver) = mpsc::sync_channel(config.buffer_size);
5231 senders.push(sender);
5232 receivers.push(Some(receiver));
5233 }
5234
5235 let first_sender = senders
5236 .first()
5237 .expect("at least one async segment channel")
5238 .clone();
5239 let mut final_receiver = Some(
5240 receivers
5241 .last_mut()
5242 .expect("at least one async segment channel")
5243 .take()
5244 .expect("final receiver is present"),
5245 );
5246 let events = AtomicUsize::new(0);
5247 let async_boundary_crossings = AtomicUsize::new(0);
5248
5249 let result = thread::scope(|scope| {
5250 let input = input.into_iter().map(Ok::<T, StreamError>);
5251 let source = scope.spawn(move || feed_threaded_async_linear_input(input, first_sender));
5252 let mut workers = Vec::with_capacity(segments.segments.len());
5253
5254 for (index, steps) in segments.segments.iter().enumerate() {
5255 let input = receivers[index].take().expect("worker receiver is present");
5256 let output = senders[index + 1].clone();
5257 let has_boundary_after = index + 1 < segments.segments.len();
5258 let events = &events;
5259 let async_boundary_crossings = &async_boundary_crossings;
5260 workers.push(scope.spawn(move || {
5261 run_threaded_async_linear_segment(
5262 steps,
5263 input,
5264 output,
5265 has_boundary_after,
5266 events,
5267 async_boundary_crossings,
5268 config,
5269 )
5270 }));
5271 }
5272 drop(senders);
5273
5274 let final_rx = final_receiver.take().expect("final receiver present");
5275 let mut count = 0;
5276 let mut terminal_error = None;
5277 loop {
5278 match final_rx.recv() {
5279 Ok(AsyncLinearMessage::Item(_)) => count += 1,
5280 Ok(AsyncLinearMessage::Done) => break,
5281 Ok(AsyncLinearMessage::Failed(error)) => {
5282 terminal_error = Some(error);
5283 break;
5284 }
5285 Err(_) => {
5286 terminal_error = Some(StreamError::AbruptTermination);
5287 break;
5288 }
5289 }
5290 }
5291 drop(final_rx);
5292
5293 let mut worker_error = join_threaded_async_linear_worker(source)?;
5294 for worker in workers {
5295 if worker_error.is_none() {
5296 worker_error = join_threaded_async_linear_worker(worker)?;
5297 } else {
5298 let _ = join_threaded_async_linear_worker(worker);
5299 }
5300 }
5301
5302 match (terminal_error, worker_error) {
5303 (Some(error), _) if error != StreamError::AbruptTermination => return Err(error),
5304 (_, Some(error)) => return Err(error),
5305 (Some(error), None) => return Err(error),
5306 (None, None) => {}
5307 }
5308
5309 Ok(count)
5310 });
5311
5312 Ok(FusedTerminalReport {
5313 result: result?,
5314 events: events.load(Ordering::Relaxed),
5315 async_boundary_crossings: async_boundary_crossings.load(Ordering::Relaxed),
5316 })
5317}
5318
5319fn run_ractor_async_linear_count<I, T>(
5320 input: I,
5321 segments: TypedLinearSegments<T>,
5322 config: AsyncBoundaryExecutionConfig,
5323) -> StreamResult<FusedTerminalReport<usize>>
5324where
5325 I: IntoIterator<Item = T> + Send,
5326 I::IntoIter: Send + 'static,
5327 T: Send + 'static,
5328{
5329 if config.buffer_size == 0 {
5330 return Err(StreamError::GraphValidation(
5331 "ractor async boundary execution requires buffer_size greater than zero".into(),
5332 ));
5333 }
5334
5335 let input = input.into_iter().map(Ok::<T, StreamError>);
5336 let runtime = ractor_boundary_runtime()?;
5337 if tokio::runtime::Handle::try_current().is_ok() {
5338 thread::scope(|scope| {
5339 let handle = scope.spawn(move || {
5340 runtime.block_on(run_ractor_async_linear_count_on_runtime(
5341 input, segments, config,
5342 ))
5343 });
5344 handle.join().map_err(|_| {
5345 StreamError::Failed("ractor async boundary runtime thread panicked".into())
5346 })?
5347 })
5348 } else {
5349 runtime.block_on(run_ractor_async_linear_count_on_runtime(
5350 input, segments, config,
5351 ))
5352 }
5353}
5354
5355async fn run_ractor_async_linear_count_on_runtime<I, T>(
5356 input: I,
5357 segments: TypedLinearSegments<T>,
5358 config: AsyncBoundaryExecutionConfig,
5359) -> StreamResult<FusedTerminalReport<usize>>
5360where
5361 I: Iterator<Item = StreamResult<T>> + Send + 'static,
5362 T: Send + 'static,
5363{
5364 let channels = segments.segments.len() + 1;
5365 let mut senders = Vec::with_capacity(channels);
5366 let mut receivers = Vec::with_capacity(channels);
5367 for _ in 0..channels {
5368 let (sender, receiver) = ractor::concurrency::mpsc_bounded(config.buffer_size);
5369 senders.push(sender);
5370 receivers.push(Some(receiver));
5371 }
5372
5373 let first_sender = senders
5374 .first()
5375 .expect("at least one async segment channel")
5376 .clone();
5377 let mut final_receiver = receivers
5378 .last_mut()
5379 .expect("at least one async segment channel")
5380 .take()
5381 .expect("final receiver is present");
5382 let events = Arc::new(AtomicUsize::new(0));
5383 let async_boundary_crossings = Arc::new(AtomicUsize::new(0));
5384
5385 let (source_ref, source_handle) = Actor::spawn(
5386 None,
5387 RactorBoundarySourceActor::<I, T>::new(),
5388 RactorBoundarySourceState {
5389 input: Some(input),
5390 output: first_sender,
5391 },
5392 )
5393 .await
5394 .map_err(ractor_spawn_error)?;
5395
5396 let mut actors = Vec::with_capacity(segments.segments.len() + 1);
5397 actors.push((source_ref, source_handle));
5398
5399 for (index, steps) in segments.segments.into_iter().enumerate() {
5400 let input = receivers[index].take().expect("worker receiver is present");
5401 let output = senders[index + 1].clone();
5402 let has_boundary_after = index + 1 < channels - 1;
5403 let (worker_ref, worker_handle) = match Actor::spawn(
5404 None,
5405 RactorLinearSegmentActor::<T>::new(),
5406 RactorLinearSegmentState {
5407 steps,
5408 input,
5409 output,
5410 has_boundary_after,
5411 events: Arc::clone(&events),
5412 async_boundary_crossings: Arc::clone(&async_boundary_crossings),
5413 config,
5414 },
5415 )
5416 .await
5417 {
5418 Ok(actor) => actor,
5419 Err(error) => {
5420 let error = ractor_spawn_error(error);
5421 stop_ractor_async_linear_actors(&actors);
5422 let _ = join_ractor_async_linear_actors(actors).await;
5423 return Err(error);
5424 }
5425 };
5426 actors.push((worker_ref, worker_handle));
5427 }
5428 drop(senders);
5429
5430 let mut start_error = None;
5431 for (actor, _) in &actors {
5432 if actor.send_message(RactorBoundaryCommand::Run).is_err() {
5433 start_error = Some(StreamError::AbruptTermination);
5434 break;
5435 }
5436 }
5437 if let Some(error) = start_error {
5438 stop_ractor_async_linear_actors(&actors);
5439 let _ = join_ractor_async_linear_actors(actors).await;
5440 return Err(error);
5441 }
5442
5443 let mut count = 0;
5444 let mut terminal_error = None;
5445 loop {
5446 match final_receiver.recv().await {
5447 Some(AsyncLinearMessage::Item(_)) => count += 1,
5448 Some(AsyncLinearMessage::Done) => break,
5449 Some(AsyncLinearMessage::Failed(error)) => {
5450 terminal_error = Some(error);
5451 break;
5452 }
5453 None => {
5454 terminal_error = Some(StreamError::AbruptTermination);
5455 break;
5456 }
5457 }
5458 }
5459 drop(final_receiver);
5460
5461 stop_ractor_async_linear_actors(&actors);
5462 let actor_error = join_ractor_async_linear_actors(actors).await;
5463
5464 match (terminal_error, actor_error) {
5465 (Some(error), _) if error != StreamError::AbruptTermination => return Err(error),
5466 (_, Some(error)) => return Err(error),
5467 (Some(error), None) => return Err(error),
5468 (None, None) => {}
5469 }
5470
5471 Ok(FusedTerminalReport {
5472 result: count,
5473 events: events.load(Ordering::Relaxed),
5474 async_boundary_crossings: async_boundary_crossings.load(Ordering::Relaxed),
5475 })
5476}
5477
5478struct RactorLinearSegmentActor<T> {
5479 _marker: PhantomData<fn() -> T>,
5480}
5481
5482impl<T> RactorLinearSegmentActor<T> {
5483 fn new() -> Self {
5484 Self {
5485 _marker: PhantomData,
5486 }
5487 }
5488}
5489
5490struct RactorLinearSegmentState<T> {
5491 steps: Vec<TypedLinearStep<T>>,
5492 input: ractor::concurrency::MpscReceiver<AsyncLinearMessage<T>>,
5493 output: ractor::concurrency::MpscSender<AsyncLinearMessage<T>>,
5494 has_boundary_after: bool,
5495 events: Arc<AtomicUsize>,
5496 async_boundary_crossings: Arc<AtomicUsize>,
5497 config: AsyncBoundaryExecutionConfig,
5498}
5499
5500impl<T> Actor for RactorLinearSegmentActor<T>
5501where
5502 T: Send + 'static,
5503{
5504 type Msg = RactorBoundaryCommand;
5505 type State = RactorLinearSegmentState<T>;
5506 type Arguments = RactorLinearSegmentState<T>;
5507
5508 async fn pre_start(
5509 &self,
5510 _myself: ActorRef<Self::Msg>,
5511 args: Self::Arguments,
5512 ) -> Result<Self::State, ActorProcessingErr> {
5513 Ok(args)
5514 }
5515
5516 async fn handle(
5517 &self,
5518 myself: ActorRef<Self::Msg>,
5519 message: Self::Msg,
5520 state: &mut Self::State,
5521 ) -> Result<(), ActorProcessingErr> {
5522 match message {
5523 RactorBoundaryCommand::Run => {
5524 run_ractor_async_linear_segment(state)
5525 .await
5526 .map_err(actor_processing_error)?;
5527 myself.stop(None);
5528 }
5529 }
5530 Ok(())
5531 }
5532}
5533
5534async fn run_ractor_async_linear_segment<T>(
5535 state: &mut RactorLinearSegmentState<T>,
5536) -> StreamResult<()>
5537where
5538 T: Send + 'static,
5539{
5540 loop {
5541 match state.input.recv().await {
5542 Some(AsyncLinearMessage::Item(item)) => {
5543 let result =
5544 run_async_linear_item(item, &state.steps, &state.events, state.config.fused)
5545 .and_then(|item| {
5546 if state.has_boundary_after {
5547 bump_fused_event_atomic(&state.events, state.config.fused)?;
5548 state
5549 .async_boundary_crossings
5550 .fetch_add(1, Ordering::Relaxed);
5551 bump_fused_event_atomic(&state.events, state.config.fused)?;
5552 }
5553 Ok(item)
5554 });
5555
5556 match result {
5557 Ok(item) => state
5558 .output
5559 .send(AsyncLinearMessage::Item(item))
5560 .await
5561 .map_err(|_| StreamError::AbruptTermination)?,
5562 Err(error) => {
5563 let _ = state
5564 .output
5565 .send(AsyncLinearMessage::Failed(error.clone()))
5566 .await;
5567 return Err(error);
5568 }
5569 }
5570 }
5571 Some(AsyncLinearMessage::Done) => {
5572 state
5573 .output
5574 .send(AsyncLinearMessage::Done)
5575 .await
5576 .map_err(|_| StreamError::AbruptTermination)?;
5577 return Ok(());
5578 }
5579 Some(AsyncLinearMessage::Failed(error)) => {
5580 let _ = state
5581 .output
5582 .send(AsyncLinearMessage::Failed(error.clone()))
5583 .await;
5584 return Err(error);
5585 }
5586 None => return Err(StreamError::AbruptTermination),
5587 }
5588 }
5589}
5590
5591async fn join_ractor_async_linear_actor(
5592 handle: ractor::concurrency::JoinHandle<()>,
5593) -> StreamResult<()> {
5594 handle.await.map_err(|error| {
5595 StreamError::Failed(format!("ractor async boundary actor task failed: {error}"))
5596 })
5597}
5598
5599fn stop_ractor_async_linear_actors(
5600 actors: &[(
5601 ActorRef<RactorBoundaryCommand>,
5602 ractor::concurrency::JoinHandle<()>,
5603 )],
5604) {
5605 for (actor, _) in actors {
5606 actor.stop(None);
5607 }
5608}
5609
5610#[cfg_attr(not(test), allow(dead_code))]
5611async fn join_ractor_async_linear_actors(
5612 actors: Vec<(
5613 ActorRef<RactorBoundaryCommand>,
5614 ractor::concurrency::JoinHandle<()>,
5615 )>,
5616) -> Option<StreamError> {
5617 let mut actor_error = None;
5618 for (_, handle) in actors {
5619 let result = join_ractor_async_linear_actor(handle).await;
5620 if actor_error.is_some() {
5621 continue;
5622 }
5623 if let Err(error) = result {
5624 actor_error = Some(error);
5625 }
5626 }
5627 actor_error
5628}
5629
5630fn ractor_spawn_error(error: ractor::SpawnErr) -> StreamError {
5631 StreamError::Failed(format!(
5632 "ractor async boundary actor failed to spawn: {error}"
5633 ))
5634}
5635
5636fn actor_processing_error(error: StreamError) -> ActorProcessingErr {
5637 Box::new(error)
5638}
5639
5640#[cfg(test)]
5641fn feed_threaded_async_linear_input<I, T>(
5642 input: I,
5643 output: mpsc::SyncSender<AsyncLinearMessage<T>>,
5644) -> StreamResult<()>
5645where
5646 I: IntoIterator<Item = StreamResult<T>>,
5647{
5648 for item in input {
5649 match item {
5650 Ok(item) => output
5651 .send(AsyncLinearMessage::Item(item))
5652 .map_err(|_| StreamError::AbruptTermination)?,
5653 Err(error) => {
5654 let _ = output.send(AsyncLinearMessage::Failed(error.clone()));
5655 return Err(error);
5656 }
5657 }
5658 }
5659 output
5660 .send(AsyncLinearMessage::Done)
5661 .map_err(|_| StreamError::AbruptTermination)
5662}
5663
5664#[cfg(test)]
5665fn run_threaded_async_linear_segment<T>(
5666 steps: &[TypedLinearStep<T>],
5667 input: mpsc::Receiver<AsyncLinearMessage<T>>,
5668 output: mpsc::SyncSender<AsyncLinearMessage<T>>,
5669 has_boundary_after: bool,
5670 events: &AtomicUsize,
5671 async_boundary_crossings: &AtomicUsize,
5672 config: AsyncBoundaryExecutionConfig,
5673) -> StreamResult<()>
5674where
5675 T: Send + 'static,
5676{
5677 loop {
5678 match input.recv().map_err(|_| StreamError::AbruptTermination)? {
5679 AsyncLinearMessage::Item(item) => {
5680 let result =
5681 run_async_linear_item(item, steps, events, config.fused).and_then(|item| {
5682 if has_boundary_after {
5683 bump_fused_event_atomic(events, config.fused)?;
5684 async_boundary_crossings.fetch_add(1, Ordering::Relaxed);
5685 bump_fused_event_atomic(events, config.fused)?;
5686 }
5687 output
5688 .send(AsyncLinearMessage::Item(item))
5689 .map_err(|_| StreamError::AbruptTermination)
5690 });
5691 if let Err(error) = result {
5692 let _ = output.send(AsyncLinearMessage::Failed(error.clone()));
5693 return Err(error);
5694 }
5695 }
5696 AsyncLinearMessage::Done => {
5697 output
5698 .send(AsyncLinearMessage::Done)
5699 .map_err(|_| StreamError::AbruptTermination)?;
5700 return Ok(());
5701 }
5702 AsyncLinearMessage::Failed(error) => {
5703 let _ = output.send(AsyncLinearMessage::Failed(error.clone()));
5704 return Err(error);
5705 }
5706 }
5707 }
5708}
5709
5710fn run_async_linear_item<T>(
5711 mut item: T,
5712 steps: &[TypedLinearStep<T>],
5713 events: &AtomicUsize,
5714 config: FusedExecutionConfig,
5715) -> StreamResult<T>
5716where
5717 T: Send + 'static,
5718{
5719 for step in steps {
5720 bump_fused_event_atomic(events, config)?;
5721 match step {
5722 TypedLinearStep::Pass => {}
5723 TypedLinearStep::Map(mapper) => {
5724 item = mapper(item);
5725 }
5726 TypedLinearStep::AsyncBoundary => {
5727 return Err(StreamError::GraphValidation(
5728 "async boundary execution expects pre-split linear segments".into(),
5729 ));
5730 }
5731 }
5732 bump_fused_event_atomic(events, config)?;
5733 }
5734 Ok(item)
5735}
5736
5737#[cfg(test)]
5738fn join_threaded_async_linear_worker(
5739 handle: thread::ScopedJoinHandle<'_, StreamResult<()>>,
5740) -> StreamResult<Option<StreamError>> {
5741 match handle.join() {
5742 Ok(Ok(())) => Ok(None),
5743 Ok(Err(error)) => Ok(Some(error)),
5744 Err(_) => Err(StreamError::Failed("async boundary worker panicked".into())),
5745 }
5746}
5747
5748fn bump_fused_event_atomic(events: &AtomicUsize, config: FusedExecutionConfig) -> StreamResult<()> {
5749 let events = events.fetch_add(1, Ordering::Relaxed) + 1;
5750 if events > config.event_limit {
5751 return Err(StreamError::EventLimitExceeded {
5752 limit: config.event_limit,
5753 });
5754 }
5755 Ok(())
5756}
5757
5758fn add_typed_helper_events(
5759 events: &mut usize,
5760 config: FusedExecutionConfig,
5761 count: usize,
5762) -> StreamResult<()> {
5763 let next = events
5764 .checked_add(count)
5765 .ok_or_else(|| StreamError::Failed("typed helper event count overflow".into()))?;
5766 if next > config.event_limit {
5767 return Err(StreamError::EventLimitExceeded {
5768 limit: config.event_limit,
5769 });
5770 }
5771 *events = next;
5772 Ok(())
5773}
5774
5775fn typed_fan_in_success_events(output_len: usize, input_count: usize) -> StreamResult<usize> {
5776 output_len
5777 .checked_mul(3)
5778 .and_then(|events| events.checked_add(input_count))
5779 .and_then(|events| events.checked_add(1))
5780 .ok_or_else(|| StreamError::Failed("typed helper event count overflow".into()))
5781}
5782
5783fn direct_single_fan_in_stage<'a, T>(
5784 stages: &'a [super::builder::StageRecord],
5785 edges: &[super::builder::Edge],
5786 shape_inlets: &[AnyInlet],
5787 shape_outlet: &AnyOutlet,
5788) -> Option<&'a super::builder::StageRecord>
5789where
5790 T: 'static,
5791{
5792 if stages.len() != 1 || !edges.is_empty() {
5793 return None;
5794 }
5795 let stage = stages.first()?;
5796 let element_type = TypeId::of::<T>();
5797 if stage.spec.inlets.len() != shape_inlets.len()
5798 || stage.spec.outlets.len() != 1
5799 || stage.spec.outlets[0].id() != shape_outlet.id()
5800 || stage.spec.outlets[0].type_id() != element_type
5801 || shape_outlet.type_id() != element_type
5802 || stage
5803 .spec
5804 .inlets
5805 .iter()
5806 .map(AnyInlet::id)
5807 .ne(shape_inlets.iter().map(AnyInlet::id))
5808 || stage
5809 .spec
5810 .inlets
5811 .iter()
5812 .any(|inlet| inlet.type_id() != element_type)
5813 || shape_inlets
5814 .iter()
5815 .any(|inlet| inlet.type_id() != element_type)
5816 {
5817 return None;
5818 }
5819 Some(stage)
5820}
5821
5822fn run_typed_scheduled_fan_in<T>(
5823 inputs: Vec<Vec<T>>,
5824 schedule: &[usize],
5825 config: FusedExecutionConfig,
5826) -> StreamResult<FusedExecutionReport<T>>
5827where
5828 T: Send + 'static,
5829{
5830 let output_capacity = inputs.iter().map(Vec::len).sum();
5831 let events = typed_fan_in_success_events(output_capacity, inputs.len())?;
5832 let mut checked_events = 0;
5833 add_typed_helper_events(&mut checked_events, config, events)?;
5834
5835 let mut queues: Vec<_> = inputs.into_iter().map(Vec::into_iter).collect();
5836 let mut schedule_index = 0;
5837 let mut output = Vec::with_capacity(output_capacity);
5838 while output.len() < output_capacity {
5839 let input_index = next_scheduled_input(&queues, schedule, &mut schedule_index)
5840 .ok_or_else(|| StreamError::GraphValidation("no runnable fan-in input".into()))?;
5841 let item = queues[input_index]
5842 .next()
5843 .expect("scheduled input had an item");
5844 output.push(item);
5845 }
5846
5847 Ok(FusedExecutionReport {
5848 output,
5849 events,
5850 async_boundary_crossings: 0,
5851 })
5852}
5853
5854fn run_typed_concat<T>(
5855 inputs: Vec<Vec<T>>,
5856 config: FusedExecutionConfig,
5857) -> StreamResult<FusedExecutionReport<T>>
5858where
5859 T: Send + 'static,
5860{
5861 let output_capacity = inputs.iter().map(Vec::len).sum();
5862 let events = typed_fan_in_success_events(output_capacity, inputs.len())?;
5863 let mut checked_events = 0;
5864 add_typed_helper_events(&mut checked_events, config, events)?;
5865
5866 let mut output = Vec::with_capacity(output_capacity);
5867 for input in inputs {
5868 output.extend(input);
5869 }
5870
5871 Ok(FusedExecutionReport {
5872 output,
5873 events,
5874 async_boundary_crossings: 0,
5875 })
5876}
5877
5878fn run_typed_interleave<T>(
5879 inputs: Vec<Vec<T>>,
5880 segment_size: usize,
5881 eager_close: bool,
5882 config: FusedExecutionConfig,
5883) -> StreamResult<FusedExecutionReport<T>>
5884where
5885 T: Send + 'static,
5886{
5887 let output_capacity = inputs.iter().map(Vec::len).sum();
5888 let mut events = 0;
5889 if !eager_close {
5890 let total_events = typed_fan_in_success_events(output_capacity, inputs.len())?;
5891 add_typed_helper_events(&mut events, config, total_events)?;
5892 }
5893
5894 let mut queues: Vec<_> = inputs.into_iter().map(Vec::into_iter).collect();
5895 let mut completed = vec![false; queues.len()];
5896 let mut output = Vec::with_capacity(output_capacity);
5897
5898 for (index, queue) in queues.iter().enumerate() {
5899 if queue.len() == 0 {
5900 completed[index] = true;
5901 if eager_close {
5902 add_typed_helper_events(&mut events, config, 2)?;
5903 return Ok(FusedExecutionReport {
5904 output,
5905 events,
5906 async_boundary_crossings: 0,
5907 });
5908 }
5909 }
5910 }
5911
5912 let mut current = 0usize;
5913 while completed.iter().any(|done| !done) {
5914 if completed[current] {
5915 current = next_open_index(&completed, current)
5916 .ok_or_else(|| StreamError::GraphValidation("no open interleave input".into()))?;
5917 continue;
5918 }
5919
5920 let mut emitted = 0usize;
5921 while emitted < segment_size {
5922 match queues[current].next() {
5923 Some(item) => {
5924 if eager_close {
5925 add_typed_helper_events(&mut events, config, 3)?;
5926 }
5927 output.push(item);
5928 emitted += 1;
5929 }
5930 None => {
5931 completed[current] = true;
5932 if eager_close {
5933 add_typed_helper_events(&mut events, config, 2)?;
5934 return Ok(FusedExecutionReport {
5935 output,
5936 events,
5937 async_boundary_crossings: 0,
5938 });
5939 }
5940 break;
5941 }
5942 }
5943 }
5944
5945 if completed.iter().all(|done| *done) {
5946 break;
5947 }
5948 current = next_open_index(&completed, current)
5949 .ok_or_else(|| StreamError::GraphValidation("no open interleave input".into()))?;
5950 }
5951
5952 Ok(FusedExecutionReport {
5953 output,
5954 events,
5955 async_boundary_crossings: 0,
5956 })
5957}
5958
5959impl<T> GraphBlueprint<FanInShape<T, T>>
5960where
5961 T: Clone + Send + 'static,
5962{
5963 pub fn run_fan_in(&self, inputs: Vec<Vec<T>>) -> StreamResult<Vec<T>> {
5964 Ok(self
5965 .run_fan_in_report(inputs, FusedExecutionConfig::default())?
5966 .output)
5967 }
5968
5969 pub fn run_fan_in_report(
5970 &self,
5971 inputs: Vec<Vec<T>>,
5972 config: FusedExecutionConfig,
5973 ) -> StreamResult<FusedExecutionReport<T>> {
5974 self.run_fan_in_report_mode(inputs, config, ExecutorMode::Auto)
5975 }
5976
5977 pub(crate) fn run_fan_in_report_mode(
5978 &self,
5979 inputs: Vec<Vec<T>>,
5980 config: FusedExecutionConfig,
5981 mode: ExecutorMode,
5982 ) -> StreamResult<FusedExecutionReport<T>> {
5983 if inputs.len() != self.shape.inlet_count() {
5984 return Err(StreamError::GraphValidation(format!(
5985 "expected {} input streams, got {}",
5986 self.shape.inlet_count(),
5987 inputs.len()
5988 )));
5989 }
5990
5991 if mode != ExecutorMode::ErasedOnly {
5992 if let Some(schedule) = self.typed_prioritized_fan_in_schedule() {
5993 return run_typed_scheduled_fan_in(inputs, &schedule, config);
5994 }
5995 if mode == ExecutorMode::TypedOnly {
5996 return Err(StreamError::GraphValidation(
5997 "typed executor does not support this graph shape".into(),
5998 ));
5999 }
6000 }
6001
6002 self.run_fan_in_report_erased(inputs, config)
6003 }
6004
6005 fn typed_prioritized_fan_in_schedule(&self) -> Option<Vec<usize>> {
6006 let shape_inlets = self.shape.inlets();
6007 let shape_outlet = self.shape.outlet().erase();
6008 let stage = direct_single_fan_in_stage::<T>(
6009 &self.stages,
6010 &self.edges,
6011 &shape_inlets,
6012 &shape_outlet,
6013 )?;
6014 let StageKind::MergePrioritized { weights } = &stage.spec.kind else {
6015 return None;
6016 };
6017 if weights.len() != self.shape.inlet_count() || weights.contains(&0) {
6018 return None;
6019 }
6020 Some(
6021 weights
6022 .iter()
6023 .enumerate()
6024 .flat_map(|(index, weight)| std::iter::repeat_n(index, *weight))
6025 .collect(),
6026 )
6027 }
6028
6029 fn run_fan_in_report_erased(
6030 &self,
6031 inputs: Vec<Vec<T>>,
6032 config: FusedExecutionConfig,
6033 ) -> StreamResult<FusedExecutionReport<T>> {
6034 let output_capacity = inputs.iter().map(Vec::len).sum();
6035 let mut queues: Vec<_> = inputs.into_iter().map(Vec::into_iter).collect();
6036 let schedule = self.fan_in_schedule();
6037 let mut schedule_index = 0;
6038 let outlet = self.shape.outlet().id();
6039 let mut executor = FusedExecutor::new(self, config);
6040 let mut output = Vec::with_capacity(output_capacity);
6041 let mut completed = vec![false; queues.len()];
6042
6043 {
6044 let mut output_sink = VecOutputSink {
6045 output: &mut output,
6046 };
6047 for (index, queue) in queues.iter().enumerate() {
6048 if queue.len() == 0 {
6049 executor.complete(self.shape.inlet(index)?.id(), outlet, &mut output_sink)?;
6050 completed[index] = true;
6051 }
6052 }
6053 while queues.iter().any(|queue| queue.len() > 0) {
6054 let input_index = next_scheduled_input(&queues, &schedule, &mut schedule_index)
6055 .ok_or_else(|| {
6056 StreamError::GraphValidation("no runnable fan-in input".into())
6057 })?;
6058 let item = queues[input_index]
6059 .next()
6060 .expect("scheduled input had an item");
6061 executor.deliver(
6062 self.shape.inlet(input_index)?.id(),
6063 datum(item),
6064 outlet,
6065 &mut output_sink,
6066 )?;
6067 if queues[input_index].len() == 0 && !completed[input_index] {
6068 executor.complete(
6069 self.shape.inlet(input_index)?.id(),
6070 outlet,
6071 &mut output_sink,
6072 )?;
6073 completed[input_index] = true;
6074 }
6075 }
6076 }
6077
6078 Ok(FusedExecutionReport {
6079 output,
6080 events: executor.events,
6081 async_boundary_crossings: executor.async_boundary_crossings,
6082 })
6083 }
6084
6085 fn fan_in_schedule(&self) -> Vec<usize> {
6086 self.stages
6087 .iter()
6088 .find_map(|stage| match &stage.spec.kind {
6089 StageKind::MergePrioritized { weights }
6090 if weights.len() == self.shape.inlet_count()
6091 && stage.spec.outlets.len() == 1
6092 && stage.spec.outlets[0].id() == self.shape.outlet().id()
6093 && stage.spec.inlets.iter().map(AnyInlet::id).eq(self
6094 .shape
6095 .inlets()
6096 .iter()
6097 .map(|inlet| inlet.id())) =>
6098 {
6099 Some(
6100 weights
6101 .iter()
6102 .enumerate()
6103 .flat_map(|(index, weight)| std::iter::repeat_n(index, *weight))
6104 .collect(),
6105 )
6106 }
6107 _ => None,
6108 })
6109 .unwrap_or_else(|| (0..self.shape.inlet_count()).collect())
6110 }
6111
6112 pub fn run_concat(&self, inputs: Vec<Vec<T>>) -> StreamResult<Vec<T>> {
6113 Ok(self
6114 .run_concat_report(inputs, FusedExecutionConfig::default())?
6115 .output)
6116 }
6117
6118 pub fn run_concat_report(
6119 &self,
6120 inputs: Vec<Vec<T>>,
6121 config: FusedExecutionConfig,
6122 ) -> StreamResult<FusedExecutionReport<T>> {
6123 self.run_concat_report_mode(inputs, config, ExecutorMode::Auto)
6124 }
6125
6126 pub(crate) fn run_concat_report_mode(
6127 &self,
6128 inputs: Vec<Vec<T>>,
6129 config: FusedExecutionConfig,
6130 mode: ExecutorMode,
6131 ) -> StreamResult<FusedExecutionReport<T>> {
6132 if inputs.len() != self.shape.inlet_count() {
6133 return Err(StreamError::GraphValidation(format!(
6134 "expected {} input streams, got {}",
6135 self.shape.inlet_count(),
6136 inputs.len()
6137 )));
6138 }
6139
6140 if mode != ExecutorMode::ErasedOnly {
6141 if self.typed_concat_supported() {
6142 return run_typed_concat(inputs, config);
6143 }
6144 if mode == ExecutorMode::TypedOnly {
6145 return Err(StreamError::GraphValidation(
6146 "typed executor does not support this graph shape".into(),
6147 ));
6148 }
6149 }
6150
6151 self.run_concat_report_erased(inputs, config)
6152 }
6153
6154 fn typed_concat_supported(&self) -> bool {
6155 let shape_inlets = self.shape.inlets();
6156 let shape_outlet = self.shape.outlet().erase();
6157 direct_single_fan_in_stage::<T>(&self.stages, &self.edges, &shape_inlets, &shape_outlet)
6158 .is_some_and(|stage| matches!(&stage.spec.kind, StageKind::Concat))
6159 }
6160
6161 fn run_concat_report_erased(
6162 &self,
6163 inputs: Vec<Vec<T>>,
6164 config: FusedExecutionConfig,
6165 ) -> StreamResult<FusedExecutionReport<T>> {
6166 let output_capacity = inputs.iter().map(Vec::len).sum();
6167 let mut queues: Vec<_> = inputs.into_iter().map(Vec::into_iter).collect();
6168 let outlet = self.shape.outlet().id();
6169 let mut executor = FusedExecutor::new(self, config);
6170 let mut output = Vec::with_capacity(output_capacity);
6171
6172 {
6173 let mut output_sink = VecOutputSink {
6174 output: &mut output,
6175 };
6176 for (index, queue) in queues.iter_mut().enumerate() {
6177 for item in queue.by_ref() {
6178 executor.deliver(
6179 self.shape.inlet(index)?.id(),
6180 datum(item),
6181 outlet,
6182 &mut output_sink,
6183 )?;
6184 }
6185 executor.complete(self.shape.inlet(index)?.id(), outlet, &mut output_sink)?;
6186 }
6187 }
6188
6189 Ok(FusedExecutionReport {
6190 output,
6191 events: executor.events,
6192 async_boundary_crossings: executor.async_boundary_crossings,
6193 })
6194 }
6195
6196 pub fn run_or_else(&self, primary: Vec<T>, secondary: Vec<T>) -> StreamResult<Vec<T>> {
6197 Ok(self
6198 .run_or_else_report(primary, secondary, FusedExecutionConfig::default())?
6199 .output)
6200 }
6201
6202 pub fn run_or_else_report(
6203 &self,
6204 primary: Vec<T>,
6205 secondary: Vec<T>,
6206 config: FusedExecutionConfig,
6207 ) -> StreamResult<FusedExecutionReport<T>> {
6208 if self.shape.inlet_count() != 2 {
6209 return Err(StreamError::GraphValidation(format!(
6210 "or-else helper expected 2 inlets, got {}",
6211 self.shape.inlet_count()
6212 )));
6213 }
6214
6215 let primary = primary.into_iter();
6216 let secondary = secondary.into_iter();
6217 let primary_inlet = self.shape.inlet(0)?.id();
6218 let secondary_inlet = self.shape.inlet(1)?.id();
6219 let outlet = self.shape.outlet().id();
6220 let mut executor = FusedExecutor::new(self, config);
6221 let mut output = Vec::new();
6222 let mut primary_emitted = false;
6223
6224 {
6225 let mut output_sink = VecOutputSink {
6226 output: &mut output,
6227 };
6228 for item in primary {
6229 primary_emitted = true;
6230 executor.deliver(primary_inlet, datum(item), outlet, &mut output_sink)?;
6231 }
6232 executor.complete(primary_inlet, outlet, &mut output_sink)?;
6233
6234 if !primary_emitted {
6235 for item in secondary {
6236 executor.deliver(secondary_inlet, datum(item), outlet, &mut output_sink)?;
6237 }
6238 }
6239 executor.complete(secondary_inlet, outlet, &mut output_sink)?;
6240 }
6241
6242 Ok(FusedExecutionReport {
6243 output,
6244 events: executor.events,
6245 async_boundary_crossings: executor.async_boundary_crossings,
6246 })
6247 }
6248
6249 pub fn run_or_else_secondary_first(
6250 &self,
6251 primary: Vec<T>,
6252 secondary: Vec<T>,
6253 ) -> StreamResult<Vec<T>> {
6254 Ok(self
6255 .run_or_else_secondary_first_report(
6256 primary,
6257 secondary,
6258 FusedExecutionConfig::default(),
6259 )?
6260 .output)
6261 }
6262
6263 pub fn run_or_else_secondary_first_report(
6264 &self,
6265 primary: Vec<T>,
6266 secondary: Vec<T>,
6267 config: FusedExecutionConfig,
6268 ) -> StreamResult<FusedExecutionReport<T>> {
6269 if self.shape.inlet_count() != 2 {
6270 return Err(StreamError::GraphValidation(format!(
6271 "or-else helper expected 2 inlets, got {}",
6272 self.shape.inlet_count()
6273 )));
6274 }
6275
6276 let primary_inlet = self.shape.inlet(0)?.id();
6277 let secondary_inlet = self.shape.inlet(1)?.id();
6278 let outlet = self.shape.outlet().id();
6279 let mut executor = FusedExecutor::new(self, config);
6280 let mut output = Vec::new();
6281
6282 {
6283 let mut output_sink = VecOutputSink {
6284 output: &mut output,
6285 };
6286 for item in secondary {
6287 executor.deliver(secondary_inlet, datum(item), outlet, &mut output_sink)?;
6288 }
6289 for item in primary {
6290 executor.deliver(primary_inlet, datum(item), outlet, &mut output_sink)?;
6291 }
6292 executor.complete(primary_inlet, outlet, &mut output_sink)?;
6293 executor.complete(secondary_inlet, outlet, &mut output_sink)?;
6294 }
6295
6296 Ok(FusedExecutionReport {
6297 output,
6298 events: executor.events,
6299 async_boundary_crossings: executor.async_boundary_crossings,
6300 })
6301 }
6302
6303 pub fn run_or_else_secondary_closed_first(&self, secondary: Vec<T>) -> StreamResult<Vec<T>> {
6304 if self.shape.inlet_count() != 2 {
6305 return Err(StreamError::GraphValidation(format!(
6306 "or-else helper expected 2 inlets, got {}",
6307 self.shape.inlet_count()
6308 )));
6309 }
6310
6311 let primary_inlet = self.shape.inlet(0)?.id();
6312 let secondary_inlet = self.shape.inlet(1)?.id();
6313 let outlet = self.shape.outlet().id();
6314 let mut executor = FusedExecutor::new(self, FusedExecutionConfig::default());
6315 let mut output = Vec::new();
6316
6317 {
6318 let mut output_sink = VecOutputSink {
6319 output: &mut output,
6320 };
6321 for item in secondary {
6322 executor.deliver(secondary_inlet, datum(item), outlet, &mut output_sink)?;
6323 }
6324 executor.complete(secondary_inlet, outlet, &mut output_sink)?;
6325 executor.complete(primary_inlet, outlet, &mut output_sink)?;
6326 }
6327
6328 Ok(output)
6329 }
6330
6331 pub fn run_interleave(
6332 &self,
6333 inputs: Vec<Vec<T>>,
6334 segment_size: usize,
6335 eager_close: bool,
6336 ) -> StreamResult<Vec<T>> {
6337 Ok(self
6338 .run_interleave_report(
6339 inputs,
6340 segment_size,
6341 eager_close,
6342 FusedExecutionConfig::default(),
6343 )?
6344 .output)
6345 }
6346
6347 pub fn run_interleave_report(
6348 &self,
6349 inputs: Vec<Vec<T>>,
6350 segment_size: usize,
6351 eager_close: bool,
6352 config: FusedExecutionConfig,
6353 ) -> StreamResult<FusedExecutionReport<T>> {
6354 self.run_interleave_report_mode(
6355 inputs,
6356 segment_size,
6357 eager_close,
6358 config,
6359 ExecutorMode::Auto,
6360 )
6361 }
6362
6363 pub(crate) fn run_interleave_report_mode(
6364 &self,
6365 inputs: Vec<Vec<T>>,
6366 segment_size: usize,
6367 eager_close: bool,
6368 config: FusedExecutionConfig,
6369 mode: ExecutorMode,
6370 ) -> StreamResult<FusedExecutionReport<T>> {
6371 if inputs.len() != self.shape.inlet_count() {
6372 return Err(StreamError::GraphValidation(format!(
6373 "expected {} input streams, got {}",
6374 self.shape.inlet_count(),
6375 inputs.len()
6376 )));
6377 }
6378 if segment_size == 0 {
6379 return Err(StreamError::GraphValidation(
6380 "interleave segment size must be greater than zero".into(),
6381 ));
6382 }
6383
6384 if mode != ExecutorMode::ErasedOnly {
6385 if self.typed_interleave_supported(segment_size, eager_close) {
6386 return run_typed_interleave(inputs, segment_size, eager_close, config);
6387 }
6388 if mode == ExecutorMode::TypedOnly {
6389 return Err(StreamError::GraphValidation(
6390 "typed executor does not support this graph shape".into(),
6391 ));
6392 }
6393 }
6394
6395 self.run_interleave_report_erased(inputs, segment_size, eager_close, config)
6396 }
6397
6398 fn typed_interleave_supported(&self, segment_size: usize, eager_close: bool) -> bool {
6399 let shape_inlets = self.shape.inlets();
6400 let shape_outlet = self.shape.outlet().erase();
6401 direct_single_fan_in_stage::<T>(&self.stages, &self.edges, &shape_inlets, &shape_outlet)
6402 .is_some_and(|stage| {
6403 matches!(
6404 &stage.spec.kind,
6405 StageKind::Interleave {
6406 segment_size: stage_segment_size,
6407 eager_close: stage_eager_close,
6408 } if *stage_segment_size == segment_size && *stage_eager_close == eager_close
6409 )
6410 })
6411 }
6412
6413 fn run_interleave_report_erased(
6414 &self,
6415 inputs: Vec<Vec<T>>,
6416 segment_size: usize,
6417 eager_close: bool,
6418 config: FusedExecutionConfig,
6419 ) -> StreamResult<FusedExecutionReport<T>> {
6420 let output_capacity = inputs.iter().map(Vec::len).sum();
6421 let mut queues: Vec<_> = inputs.into_iter().map(Vec::into_iter).collect();
6422 let mut completed = vec![false; queues.len()];
6423 let outlet = self.shape.outlet().id();
6424 let mut executor = FusedExecutor::new(self, config);
6425 let mut output = Vec::with_capacity(output_capacity);
6426
6427 {
6428 let mut output_sink = VecOutputSink {
6429 output: &mut output,
6430 };
6431 for (index, queue) in queues.iter().enumerate() {
6432 if queue.len() == 0 {
6433 executor.complete(self.shape.inlet(index)?.id(), outlet, &mut output_sink)?;
6434 completed[index] = true;
6435 if eager_close {
6436 return Ok(FusedExecutionReport {
6437 output,
6438 events: executor.events,
6439 async_boundary_crossings: executor.async_boundary_crossings,
6440 });
6441 }
6442 }
6443 }
6444
6445 let mut current = 0usize;
6446 while completed.iter().any(|done| !done) {
6447 if completed[current] {
6448 current = next_open_index(&completed, current).ok_or_else(|| {
6449 StreamError::GraphValidation("no open interleave input".into())
6450 })?;
6451 continue;
6452 }
6453
6454 let mut emitted = 0usize;
6455 while emitted < segment_size {
6456 match queues[current].next() {
6457 Some(item) => {
6458 executor.deliver(
6459 self.shape.inlet(current)?.id(),
6460 datum(item),
6461 outlet,
6462 &mut output_sink,
6463 )?;
6464 emitted += 1;
6465 }
6466 None => {
6467 executor.complete(
6468 self.shape.inlet(current)?.id(),
6469 outlet,
6470 &mut output_sink,
6471 )?;
6472 completed[current] = true;
6473 if eager_close {
6474 return Ok(FusedExecutionReport {
6475 output,
6476 events: executor.events,
6477 async_boundary_crossings: executor.async_boundary_crossings,
6478 });
6479 }
6480 break;
6481 }
6482 }
6483 }
6484
6485 if completed.iter().all(|done| *done) {
6486 break;
6487 }
6488 current = next_open_index(&completed, current).ok_or_else(|| {
6489 StreamError::GraphValidation("no open interleave input".into())
6490 })?;
6491 }
6492 }
6493
6494 Ok(FusedExecutionReport {
6495 output,
6496 events: executor.events,
6497 async_boundary_crossings: executor.async_boundary_crossings,
6498 })
6499 }
6500}
6501
6502impl<T> GraphBlueprint<MergePreferredShape<T>>
6503where
6504 T: Clone + Send + 'static,
6505{
6506 pub fn run_merge_preferred(
6507 &self,
6508 preferred: Vec<T>,
6509 secondary_inputs: Vec<Vec<T>>,
6510 ) -> StreamResult<Vec<T>> {
6511 Ok(self
6512 .run_merge_preferred_report(
6513 preferred,
6514 secondary_inputs,
6515 FusedExecutionConfig::default(),
6516 )?
6517 .output)
6518 }
6519
6520 pub fn run_merge_preferred_report(
6521 &self,
6522 preferred: Vec<T>,
6523 secondary_inputs: Vec<Vec<T>>,
6524 config: FusedExecutionConfig,
6525 ) -> StreamResult<FusedExecutionReport<T>> {
6526 self.run_merge_preferred_report_mode(
6527 preferred,
6528 secondary_inputs,
6529 config,
6530 ExecutorMode::Auto,
6531 )
6532 }
6533
6534 pub(crate) fn run_merge_preferred_report_mode(
6535 &self,
6536 preferred: Vec<T>,
6537 secondary_inputs: Vec<Vec<T>>,
6538 config: FusedExecutionConfig,
6539 mode: ExecutorMode,
6540 ) -> StreamResult<FusedExecutionReport<T>> {
6541 if secondary_inputs.len() != self.shape.secondary_count() {
6542 return Err(StreamError::GraphValidation(format!(
6543 "expected {} secondary input streams, got {}",
6544 self.shape.secondary_count(),
6545 secondary_inputs.len()
6546 )));
6547 }
6548
6549 if mode != ExecutorMode::ErasedOnly {
6550 if self.typed_merge_preferred_supported() {
6551 return run_typed_merge_preferred(preferred, secondary_inputs, config);
6552 }
6553 if mode == ExecutorMode::TypedOnly {
6554 return Err(StreamError::GraphValidation(
6555 "typed executor does not support this graph shape".into(),
6556 ));
6557 }
6558 }
6559
6560 self.run_merge_preferred_report_erased(preferred, secondary_inputs, config)
6561 }
6562
6563 fn typed_merge_preferred_supported(&self) -> bool {
6564 let shape_inlets = self.shape.inlets();
6565 let shape_outlet = self.shape.outlet().erase();
6566 direct_single_fan_in_stage::<T>(&self.stages, &self.edges, &shape_inlets, &shape_outlet)
6567 .is_some_and(|stage| matches!(&stage.spec.kind, StageKind::MergePreferred))
6568 }
6569
6570 fn run_merge_preferred_report_erased(
6571 &self,
6572 preferred: Vec<T>,
6573 secondary_inputs: Vec<Vec<T>>,
6574 config: FusedExecutionConfig,
6575 ) -> StreamResult<FusedExecutionReport<T>> {
6576 let output_capacity =
6577 preferred.len() + secondary_inputs.iter().map(Vec::len).sum::<usize>();
6578 let mut preferred = preferred.into_iter();
6579 let mut secondary: Vec<_> = secondary_inputs.into_iter().map(Vec::into_iter).collect();
6580 let secondary_schedule = (0..secondary.len()).collect::<Vec<_>>();
6581 let mut secondary_index = 0;
6582 let outlet = self.shape.outlet().id();
6583 let preferred_inlet = self.shape.preferred().id();
6584 let mut executor = FusedExecutor::new(self, config);
6585 let mut output = Vec::with_capacity(output_capacity);
6586 let mut preferred_completed = false;
6587 let mut secondary_completed = vec![false; secondary.len()];
6588
6589 {
6590 let mut output_sink = VecOutputSink {
6591 output: &mut output,
6592 };
6593 if preferred.len() == 0 {
6594 executor.complete(preferred_inlet, outlet, &mut output_sink)?;
6595 preferred_completed = true;
6596 }
6597 for (index, queue) in secondary.iter().enumerate() {
6598 if queue.len() == 0 {
6599 executor.complete(
6600 self.shape.secondary(index)?.id(),
6601 outlet,
6602 &mut output_sink,
6603 )?;
6604 secondary_completed[index] = true;
6605 }
6606 }
6607 while preferred.len() > 0 || secondary.iter().any(|queue| queue.len() > 0) {
6608 if let Some(item) = preferred.next() {
6609 executor.deliver(preferred_inlet, datum(item), outlet, &mut output_sink)?;
6610 if preferred.len() == 0 && !preferred_completed {
6611 executor.complete(preferred_inlet, outlet, &mut output_sink)?;
6612 preferred_completed = true;
6613 }
6614 continue;
6615 }
6616
6617 let input_index =
6618 next_scheduled_input(&secondary, &secondary_schedule, &mut secondary_index)
6619 .ok_or_else(|| {
6620 StreamError::GraphValidation("no runnable secondary input".into())
6621 })?;
6622 let item = secondary[input_index]
6623 .next()
6624 .expect("scheduled secondary input had an item");
6625 executor.deliver(
6626 self.shape.secondary(input_index)?.id(),
6627 datum(item),
6628 outlet,
6629 &mut output_sink,
6630 )?;
6631 if secondary[input_index].len() == 0 && !secondary_completed[input_index] {
6632 executor.complete(
6633 self.shape.secondary(input_index)?.id(),
6634 outlet,
6635 &mut output_sink,
6636 )?;
6637 secondary_completed[input_index] = true;
6638 }
6639 }
6640 }
6641
6642 Ok(FusedExecutionReport {
6643 output,
6644 events: executor.events,
6645 async_boundary_crossings: executor.async_boundary_crossings,
6646 })
6647 }
6648}
6649
6650fn run_typed_merge_preferred<T>(
6651 preferred: Vec<T>,
6652 secondary_inputs: Vec<Vec<T>>,
6653 config: FusedExecutionConfig,
6654) -> StreamResult<FusedExecutionReport<T>>
6655where
6656 T: Send + 'static,
6657{
6658 let output_capacity = preferred.len() + secondary_inputs.iter().map(Vec::len).sum::<usize>();
6659 let input_count = secondary_inputs.len() + 1;
6660 let events = typed_fan_in_success_events(output_capacity, input_count)?;
6661 let mut checked_events = 0;
6662 add_typed_helper_events(&mut checked_events, config, events)?;
6663
6664 let mut output = Vec::with_capacity(output_capacity);
6665 output.extend(preferred);
6666
6667 let mut secondary: Vec<_> = secondary_inputs.into_iter().map(Vec::into_iter).collect();
6668 let secondary_schedule = (0..secondary.len()).collect::<Vec<_>>();
6669 let mut secondary_index = 0;
6670 while output.len() < output_capacity {
6671 let input_index =
6672 next_scheduled_input(&secondary, &secondary_schedule, &mut secondary_index)
6673 .ok_or_else(|| {
6674 StreamError::GraphValidation("no runnable secondary input".into())
6675 })?;
6676 let item = secondary[input_index]
6677 .next()
6678 .expect("scheduled secondary input had an item");
6679 output.push(item);
6680 }
6681
6682 Ok(FusedExecutionReport {
6683 output,
6684 events,
6685 async_boundary_crossings: 0,
6686 })
6687}
6688
6689fn next_scheduled_input<I>(
6690 queues: &[I],
6691 schedule: &[usize],
6692 schedule_index: &mut usize,
6693) -> Option<usize>
6694where
6695 I: ExactSizeIterator,
6696{
6697 if schedule.is_empty() {
6698 return queues.iter().position(|queue| queue.len() > 0);
6699 }
6700
6701 for _ in 0..schedule.len() {
6702 let index = schedule[*schedule_index % schedule.len()];
6703 *schedule_index += 1;
6704 if queues.get(index).is_some_and(|queue| queue.len() > 0) {
6705 return Some(index);
6706 }
6707 }
6708
6709 queues.iter().position(|queue| queue.len() > 0)
6710}
6711
6712fn next_open_index(completed: &[bool], current: usize) -> Option<usize> {
6713 if completed.is_empty() {
6714 return None;
6715 }
6716 let len = completed.len();
6717 for offset in 1..=len {
6718 let index = (current + offset) % len;
6719 if !completed[index] {
6720 return Some(index);
6721 }
6722 }
6723 None
6724}
6725
6726pub(crate) trait FusedOutputSink<Out> {
6735 fn emit(&mut self, value: Out) -> StreamResult<()>;
6736}
6737
6738pub(crate) struct VecOutputSink<'a, Out> {
6739 pub(crate) output: &'a mut Vec<Out>,
6740}
6741
6742impl<Out> FusedOutputSink<Out> for VecOutputSink<'_, Out> {
6743 fn emit(&mut self, value: Out) -> StreamResult<()> {
6744 self.output.push(value);
6745 Ok(())
6746 }
6747}
6748
6749pub(crate) struct CountOutputSink {
6750 pub(crate) count: usize,
6751}
6752
6753impl<Out> FusedOutputSink<Out> for CountOutputSink {
6754 fn emit(&mut self, _value: Out) -> StreamResult<()> {
6755 self.count += 1;
6756 Ok(())
6757 }
6758}
6759
6760pub(crate) struct FoldOutputSink<Acc, F> {
6761 pub(crate) accumulator: Option<Acc>,
6762 pub(crate) fold: F,
6763}
6764
6765impl<Out, Acc, F> FusedOutputSink<Out> for FoldOutputSink<Acc, F>
6766where
6767 F: FnMut(Acc, Out) -> Acc,
6768{
6769 fn emit(&mut self, value: Out) -> StreamResult<()> {
6770 let accumulator = self
6771 .accumulator
6772 .take()
6773 .expect("fold accumulator is present while executor is running");
6774 self.accumulator = Some((self.fold)(accumulator, value));
6775 Ok(())
6776 }
6777}
6778
6779impl<Acc, F> FoldOutputSink<Acc, F> {
6780 pub(crate) fn finish(mut self) -> Acc {
6781 self.accumulator
6782 .take()
6783 .expect("fold accumulator is present after executor finishes")
6784 }
6785}
6786
6787#[derive(Debug)]
6799pub(crate) struct FusedExecutor<'a, S: Shape> {
6800 graph: &'a GraphBlueprint<S>,
6801 pub(crate) edge_by_outlet: HashMap<PortId, PortId>,
6803 pub(crate) edge_by_inlet: HashMap<PortId, PortId>,
6805 pub(crate) stage_by_inlet: HashMap<PortId, usize>,
6807 pub(crate) stage_by_outlet: HashMap<PortId, usize>,
6809 stage_states: Vec<StageState>,
6810 pub(crate) opaque_logics: Vec<Option<GraphStageLogic>>,
6811 timer_mailbox: Arc<StageTimerMailbox>,
6812 timers_enabled: bool,
6813 config: FusedExecutionConfig,
6814 pub(crate) events: usize,
6815 pub(crate) async_boundary_crossings: usize,
6816 cancelled_outlets: HashSet<PortId>,
6817 event_stack: Vec<FusedEvent>,
6818 draining: bool,
6819 has_cycle: bool,
6820}
6821
6822#[derive(Debug)]
6823enum StageState {
6824 Stateless,
6825 Broadcast {
6826 cancelled_outlets: Vec<bool>,
6827 live_outlets: usize,
6828 },
6829 Balance {
6830 next: usize,
6831 cancelled_outlets: Vec<bool>,
6832 live_outlets: usize,
6833 },
6834 Merge {
6835 open_inputs: usize,
6836 eager_complete: bool,
6837 completed: bool,
6838 },
6839 OrElse {
6840 primary_emitted: bool,
6841 buffer: VecDeque<DatumValue>,
6842 primary_closed: bool,
6843 secondary_closed: bool,
6844 completed: bool,
6845 },
6846 Zip {
6847 left_inlet: PortId,
6848 right_inlet: PortId,
6849 left: VecDeque<DatumValue>,
6850 right: VecDeque<DatumValue>,
6851 left_pending_complete: bool,
6852 right_pending_complete: bool,
6853 completed: bool,
6854 },
6855 Unzip {
6856 fast_path: Option<UnzipFanInFastPath>,
6857 zip_fast_path: Option<UnzipZipFastPath>,
6858 demand: [bool; 2],
6859 cancelled: [bool; 2],
6860 upstream_closed: bool,
6861 },
6862 MergeSorted {
6863 left: VecDeque<DatumValue>,
6864 right: VecDeque<DatumValue>,
6865 left_closed: bool,
6866 right_closed: bool,
6867 pending: VecDeque<DatumValue>,
6868 completed: bool,
6869 },
6870 MergeSequence {
6871 next_sequence: u64,
6872 pending: Vec<(u64, DatumValue)>,
6873 completed_count: usize,
6874 output_buffer: VecDeque<DatumValue>,
6875 completed: bool,
6876 },
6877 MergeLatest {
6878 latest: Vec<Option<DatumValue>>,
6879 seen_count: usize,
6880 completed_count: usize,
6881 pending: VecDeque<DatumValue>,
6882 completed: bool,
6883 },
6884 Partition {
6885 pending: Option<(usize, DatumValue)>,
6886 upstream_closed: bool,
6887 demand: Vec<bool>,
6888 cancelled: Vec<bool>,
6889 output_count: usize,
6890 completed: bool,
6891 },
6892}
6893
6894#[derive(Clone, Copy, Debug)]
6895struct UnzipFanInFastPath {
6896 fan_in_stage_index: usize,
6897 target_inlet_indices: [usize; 2],
6901}
6902
6903#[derive(Clone, Copy, Debug)]
6904struct UnzipZipFastPath {
6905 zip_stage_index: usize,
6906}
6907
6908enum StageEmissions {
6909 None,
6910 One(PortId, DatumValue),
6911 Two((PortId, DatumValue), (PortId, DatumValue)),
6912 Many(Vec<(PortId, DatumValue)>),
6913}
6914
6915struct StageTransition {
6916 emissions: StageEmissions,
6917 completed_outlets: Vec<PortId>,
6918 cancelled_inlets: Vec<PortId>,
6919}
6920
6921impl StageTransition {
6922 fn none() -> Self {
6923 Self {
6924 emissions: StageEmissions::None,
6925 completed_outlets: Vec::new(),
6926 cancelled_inlets: Vec::new(),
6927 }
6928 }
6929
6930 fn emit(emissions: StageEmissions) -> Self {
6931 Self {
6932 emissions,
6933 completed_outlets: Vec::new(),
6934 cancelled_inlets: Vec::new(),
6935 }
6936 }
6937
6938 fn with_completion(mut self, completed_outlets: Vec<PortId>) -> Self {
6939 self.completed_outlets = completed_outlets;
6940 self
6941 }
6942
6943 fn with_cancellations(mut self, cancelled_inlets: Vec<PortId>) -> Self {
6944 self.cancelled_inlets = cancelled_inlets;
6945 self
6946 }
6947}
6948
6949#[derive(Debug)]
6950enum FusedEvent {
6951 Deliver { inlet: PortId, value: DatumValue },
6952 CompleteInlet { inlet: PortId },
6953 Request { outlet: PortId },
6954 DownstreamFinish { outlet: PortId },
6955 Emit { outlet: PortId, value: DatumValue },
6956 CompleteOutlet { outlet: PortId },
6957 CancelInlet { inlet: PortId },
6958}
6959
6960fn graph_has_cycle<S: Shape>(
6961 graph: &GraphBlueprint<S>,
6962 stage_by_inlet: &HashMap<PortId, usize>,
6963 stage_by_outlet: &HashMap<PortId, usize>,
6964) -> bool {
6965 let mut adjacency = vec![Vec::new(); graph.stages.len()];
6966 for edge in &graph.edges {
6967 let Some(from) = stage_by_outlet.get(&edge.outlet).copied() else {
6968 continue;
6969 };
6970 let Some(to) = stage_by_inlet.get(&edge.inlet).copied() else {
6971 continue;
6972 };
6973 adjacency[from].push(to);
6974 }
6975
6976 #[derive(Clone, Copy, PartialEq, Eq)]
6977 enum Visit {
6978 New,
6979 Active,
6980 Done,
6981 }
6982
6983 fn visit(node: usize, adjacency: &[Vec<usize>], marks: &mut [Visit]) -> bool {
6984 marks[node] = Visit::Active;
6985 for &next in &adjacency[node] {
6986 if marks[next] == Visit::Active {
6987 return true;
6988 }
6989 if marks[next] == Visit::New && visit(next, adjacency, marks) {
6990 return true;
6991 }
6992 }
6993 marks[node] = Visit::Done;
6994 false
6995 }
6996
6997 let mut marks = vec![Visit::New; graph.stages.len()];
6998 for node in 0..graph.stages.len() {
6999 if marks[node] == Visit::New && visit(node, &adjacency, &mut marks) {
7000 return true;
7001 }
7002 }
7003 false
7004}
7005
7006impl<'a, S: Shape> FusedExecutor<'a, S> {
7007 fn is_outlet_cancelled(&self, outlet: PortId) -> bool {
7008 !self.cancelled_outlets.is_empty() && self.cancelled_outlets.contains(&outlet)
7009 }
7010
7011 fn new(graph: &'a GraphBlueprint<S>, config: FusedExecutionConfig) -> Self {
7012 let edge_by_outlet = graph
7013 .edges
7014 .iter()
7015 .map(|edge| (edge.outlet, edge.inlet))
7016 .collect();
7017 let edge_by_inlet = graph
7018 .edges
7019 .iter()
7020 .map(|edge| (edge.inlet, edge.outlet))
7021 .collect();
7022 let mut stage_by_inlet = HashMap::new();
7023 let mut stage_by_outlet = HashMap::new();
7024 for (index, stage) in graph.stages.iter().enumerate() {
7025 for inlet in &stage.spec.inlets {
7026 stage_by_inlet.insert(inlet.id(), index);
7027 }
7028 for outlet in &stage.spec.outlets {
7029 stage_by_outlet.insert(outlet.id(), index);
7030 }
7031 }
7032
7033 let timer_mailbox = Arc::new(StageTimerMailbox::default());
7034 let timer_materializer = Arc::new(OnceLock::new());
7035 let mut opaque_logics: Vec<_> = graph
7036 .stages
7037 .iter()
7038 .map(|stage| stage.logic_factory.as_ref().map(|factory| factory()))
7039 .collect();
7040 for (stage_index, logic) in opaque_logics.iter_mut().enumerate() {
7041 if let Some(logic) = logic {
7042 logic.attach_timer_runtime(StageTimerRuntime::new(
7043 stage_index,
7044 Arc::clone(&timer_mailbox),
7045 Arc::clone(&timer_materializer),
7046 ));
7047 }
7048 }
7049 let timers_enabled = opaque_logics.iter().any(|logic| {
7050 logic
7051 .as_ref()
7052 .is_some_and(GraphStageLogic::has_timer_handler)
7053 });
7054
7055 let stage_states = graph
7056 .stages
7057 .iter()
7058 .map(|stage| match stage.spec.kind {
7059 StageKind::Broadcast => StageState::Broadcast {
7060 cancelled_outlets: vec![false; stage.spec.outlets.len()],
7061 live_outlets: stage.spec.outlets.len(),
7062 },
7063 StageKind::Balance => StageState::Balance {
7064 next: 0,
7065 cancelled_outlets: vec![false; stage.spec.outlets.len()],
7066 live_outlets: stage.spec.outlets.len(),
7067 },
7068 StageKind::Merge => StageState::Merge {
7069 open_inputs: stage.spec.inlets.len(),
7070 eager_complete: false,
7071 completed: false,
7072 },
7073 StageKind::MergePreferred => StageState::Merge {
7074 open_inputs: stage.spec.inlets.len(),
7075 eager_complete: false,
7076 completed: false,
7077 },
7078 StageKind::MergePrioritized { .. } => StageState::Merge {
7079 open_inputs: stage.spec.inlets.len(),
7080 eager_complete: false,
7081 completed: false,
7082 },
7083 StageKind::Concat => StageState::Merge {
7084 open_inputs: stage.spec.inlets.len(),
7085 eager_complete: false,
7086 completed: false,
7087 },
7088 StageKind::Interleave { eager_close, .. } => StageState::Merge {
7089 open_inputs: stage.spec.inlets.len(),
7090 eager_complete: eager_close,
7091 completed: false,
7092 },
7093 StageKind::OrElse { primary_inlet: _ } => StageState::OrElse {
7094 primary_emitted: false,
7095 buffer: VecDeque::new(),
7096 primary_closed: false,
7097 secondary_closed: false,
7098 completed: false,
7099 },
7100 StageKind::Zip(_) => {
7101 if let [left, right] = stage.spec.inlets.as_slice() {
7102 StageState::Zip {
7103 left_inlet: left.id(),
7104 right_inlet: right.id(),
7105 left: VecDeque::new(),
7106 right: VecDeque::new(),
7107 left_pending_complete: false,
7108 right_pending_complete: false,
7109 completed: false,
7110 }
7111 } else {
7112 StageState::Stateless
7113 }
7114 }
7115 StageKind::Unzip { .. } => StageState::Unzip {
7116 fast_path: unzip_fan_in_fast_path(
7117 stage,
7118 graph,
7119 &edge_by_outlet,
7120 &stage_by_inlet,
7121 ),
7122 zip_fast_path: unzip_zip_fast_path(
7123 stage,
7124 graph,
7125 &edge_by_outlet,
7126 &stage_by_inlet,
7127 ),
7128 demand: [false; 2],
7129 cancelled: [false; 2],
7130 upstream_closed: false,
7131 },
7132 StageKind::MergeSorted(_) => StageState::MergeSorted {
7133 left: VecDeque::new(),
7134 right: VecDeque::new(),
7135 left_closed: false,
7136 right_closed: false,
7137 pending: VecDeque::new(),
7138 completed: false,
7139 },
7140 StageKind::MergeSequence { .. } => StageState::MergeSequence {
7141 next_sequence: 0,
7142 pending: Vec::new(),
7143 completed_count: 0,
7144 output_buffer: VecDeque::new(),
7145 completed: false,
7146 },
7147 StageKind::MergeLatest { input_count, .. } => {
7148 let mut latest = Vec::with_capacity(input_count);
7149 for _ in 0..input_count {
7150 latest.push(None);
7151 }
7152 StageState::MergeLatest {
7153 latest,
7154 seen_count: 0,
7155 completed_count: 0,
7156 pending: VecDeque::new(),
7157 completed: false,
7158 }
7159 }
7160 StageKind::Partition { output_count, .. } => StageState::Partition {
7161 pending: None,
7162 upstream_closed: false,
7163 demand: vec![false; output_count],
7164 cancelled: vec![false; output_count],
7165 output_count,
7166 completed: false,
7167 },
7168 _ => StageState::Stateless,
7169 })
7170 .collect();
7171
7172 let has_cycle =
7173 !graph.edges.is_empty() && graph_has_cycle(graph, &stage_by_inlet, &stage_by_outlet);
7174
7175 let mut executor = Self {
7176 graph,
7177 edge_by_outlet,
7178 edge_by_inlet,
7179 stage_by_inlet,
7180 stage_by_outlet,
7181 stage_states,
7182 opaque_logics,
7183 timer_mailbox,
7184 timers_enabled,
7185 config,
7186 events: 0,
7187 async_boundary_crossings: 0,
7188 cancelled_outlets: HashSet::new(),
7189 event_stack: Vec::new(),
7190 draining: false,
7191 has_cycle,
7192 };
7193 executor.prime_connected_demands();
7194 executor
7195 }
7196
7197 fn deliver<Out>(
7198 &mut self,
7199 inlet: PortId,
7200 value: DatumValue,
7201 graph_outlet: PortId,
7202 output: &mut impl FusedOutputSink<Out>,
7203 ) -> StreamResult<()>
7204 where
7205 Out: Send + 'static,
7206 {
7207 if !self.has_cycle {
7208 return self.process_deliver_direct(inlet, value, graph_outlet, output);
7209 }
7210 self.schedule_event(FusedEvent::Deliver { inlet, value });
7211 self.drain_events(graph_outlet, output)
7212 }
7213
7214 fn complete<Out>(
7215 &mut self,
7216 inlet: PortId,
7217 graph_outlet: PortId,
7218 output: &mut impl FusedOutputSink<Out>,
7219 ) -> StreamResult<()>
7220 where
7221 Out: Send + 'static,
7222 {
7223 if !self.has_cycle {
7224 return self.process_complete_inlet_direct(inlet, graph_outlet, output);
7225 }
7226 self.schedule_event(FusedEvent::CompleteInlet { inlet });
7227 self.drain_events(graph_outlet, output)
7228 }
7229
7230 fn request<Out>(
7231 &mut self,
7232 outlet: PortId,
7233 graph_outlet: PortId,
7234 output: &mut impl FusedOutputSink<Out>,
7235 ) -> StreamResult<()>
7236 where
7237 Out: Send + 'static,
7238 {
7239 if !self.has_cycle {
7240 return self.process_request_direct(outlet, graph_outlet, output);
7241 }
7242 self.schedule_event(FusedEvent::Request { outlet });
7243 self.drain_events(graph_outlet, output)
7244 }
7245
7246 #[cfg_attr(not(test), allow(dead_code))]
7247 fn downstream_finish<Out>(
7248 &mut self,
7249 outlet: PortId,
7250 graph_outlet: PortId,
7251 output: &mut impl FusedOutputSink<Out>,
7252 ) -> StreamResult<()>
7253 where
7254 Out: Send + 'static,
7255 {
7256 if !self.has_cycle {
7257 return self.process_downstream_finish_direct(outlet, graph_outlet, output);
7258 }
7259 self.schedule_event(FusedEvent::DownstreamFinish { outlet });
7260 self.drain_events(graph_outlet, output)
7261 }
7262
7263 fn schedule_event(&mut self, event: FusedEvent) {
7264 self.event_stack.push(event);
7265 }
7266
7267 fn schedule_transition(&mut self, transition: StageTransition) {
7268 for inlet in transition.cancelled_inlets.into_iter().rev() {
7269 self.schedule_event(FusedEvent::CancelInlet { inlet });
7270 }
7271 for outlet in transition.completed_outlets.into_iter().rev() {
7272 if !self.is_outlet_cancelled(outlet) {
7273 self.schedule_event(FusedEvent::CompleteOutlet { outlet });
7274 }
7275 }
7276 self.schedule_emissions_in_order(transition.emissions);
7277 }
7278
7279 fn schedule_emissions_in_order(&mut self, emissions: StageEmissions) {
7280 match emissions {
7281 StageEmissions::None => {}
7282 StageEmissions::One(outlet, value) => {
7283 self.schedule_event(FusedEvent::Emit { outlet, value });
7284 }
7285 StageEmissions::Two((first_outlet, first_value), (second_outlet, second_value)) => {
7286 self.schedule_event(FusedEvent::Emit {
7287 outlet: second_outlet,
7288 value: second_value,
7289 });
7290 self.schedule_event(FusedEvent::Emit {
7291 outlet: first_outlet,
7292 value: first_value,
7293 });
7294 }
7295 StageEmissions::Many(emissions) => {
7296 for (outlet, value) in emissions.into_iter().rev() {
7297 self.schedule_event(FusedEvent::Emit { outlet, value });
7298 }
7299 }
7300 }
7301 }
7302
7303 fn drain_events<Out>(
7304 &mut self,
7305 graph_outlet: PortId,
7306 output: &mut impl FusedOutputSink<Out>,
7307 ) -> StreamResult<()>
7308 where
7309 Out: Send + 'static,
7310 {
7311 if self.draining {
7312 return Ok(());
7313 }
7314
7315 self.draining = true;
7316 let result = (|| {
7317 while let Some(event) = self.event_stack.pop() {
7318 self.process_event(event, graph_outlet, output)?;
7319 }
7320 Ok(())
7321 })();
7322 self.draining = false;
7323 if result.is_err() {
7324 self.event_stack.clear();
7325 }
7326 result
7327 }
7328
7329 fn drain_timer_events_nonblocking<Out>(
7330 &mut self,
7331 graph_outlet: PortId,
7332 output: &mut impl FusedOutputSink<Out>,
7333 ) -> StreamResult<()>
7334 where
7335 Out: Send + 'static,
7336 {
7337 if !self.timers_enabled {
7338 return Ok(());
7339 }
7340 if !self.timer_mailbox.has_pending() {
7341 return Ok(());
7342 }
7343 while let Some(stage_index) = self.timer_mailbox.pop() {
7344 self.process_timer_event(stage_index, graph_outlet, output)?;
7345 }
7346 Ok(())
7347 }
7348
7349 fn drain_timer_events_until_idle<Out>(
7350 &mut self,
7351 graph_outlet: PortId,
7352 output: &mut impl FusedOutputSink<Out>,
7353 ) -> StreamResult<()>
7354 where
7355 Out: Send + 'static,
7356 {
7357 if !self.timers_enabled {
7358 return Ok(());
7359 }
7360 self.drain_timer_events_nonblocking(graph_outlet, output)?;
7361 while self.has_timer_work() || self.timer_mailbox.has_pending() {
7362 let stage_index = self.timer_mailbox.wait();
7363 self.process_timer_event(stage_index, graph_outlet, output)?;
7364 self.drain_timer_events_nonblocking(graph_outlet, output)?;
7365 }
7366 Ok(())
7367 }
7368
7369 fn has_timer_work(&self) -> bool {
7370 self.opaque_logics
7371 .iter()
7372 .any(|logic| logic.as_ref().is_some_and(GraphStageLogic::has_timer_work))
7373 }
7374
7375 fn process_timer_event<Out>(
7376 &mut self,
7377 stage_index: usize,
7378 graph_outlet: PortId,
7379 output: &mut impl FusedOutputSink<Out>,
7380 ) -> StreamResult<()>
7381 where
7382 Out: Send + 'static,
7383 {
7384 let Some(transition) = self.process_stage_timer(stage_index)? else {
7385 return Ok(());
7386 };
7387 self.bump_event()?;
7388 if self.has_cycle {
7389 self.process_transition(transition);
7390 self.drain_events(graph_outlet, output)
7391 } else {
7392 self.process_transition_direct(transition, graph_outlet, output)
7393 }
7394 }
7395
7396 fn process_stage_timer(&mut self, stage_index: usize) -> StreamResult<Option<StageTransition>> {
7397 let Some(stage) = self.graph.stages.get(stage_index) else {
7398 return Ok(None);
7399 };
7400 if !matches!(stage.spec.kind, StageKind::Opaque) {
7401 return Ok(None);
7402 }
7403 let Some(logic) = self
7404 .opaque_logics
7405 .get_mut(stage_index)
7406 .and_then(|logic| logic.as_mut())
7407 else {
7408 return Ok(None);
7409 };
7410
7411 logic.drain_async_callbacks();
7412 let Some(key) = logic.pop_timer_event() else {
7413 return Ok(None);
7414 };
7415
7416 let mut handler = logic.take_timer_handler();
7417 let result = if let Some(ref mut handler) = handler {
7418 handler.on_timer(logic, &key)
7419 } else {
7420 Ok(())
7421 };
7422 if let Some(handler) = handler {
7423 logic.restore_timer_handler(handler);
7424 }
7425 if result.is_err() {
7426 logic.cancel_all_timers();
7427 }
7428 result?;
7429
7430 self.collect_opaque_emissions(stage, stage_index).map(Some)
7431 }
7432
7433 fn process_event<Out>(
7434 &mut self,
7435 event: FusedEvent,
7436 graph_outlet: PortId,
7437 output: &mut impl FusedOutputSink<Out>,
7438 ) -> StreamResult<()>
7439 where
7440 Out: Send + 'static,
7441 {
7442 match event {
7443 FusedEvent::Deliver { inlet, value } => {
7444 self.bump_event()?;
7445 let stage_index = *self.stage_by_inlet.get(&inlet).ok_or_else(|| {
7446 StreamError::GraphValidation(format!(
7447 "inlet {} has no owning stage",
7448 inlet.as_usize()
7449 ))
7450 })?;
7451 let transition = self.process_stage(stage_index, inlet, value)?;
7452 self.process_transition(transition);
7453 Ok(())
7454 }
7455 FusedEvent::CompleteInlet { inlet } => {
7456 self.bump_event()?;
7457 let stage_index = *self.stage_by_inlet.get(&inlet).ok_or_else(|| {
7458 StreamError::GraphValidation(format!(
7459 "inlet {} has no owning stage",
7460 inlet.as_usize()
7461 ))
7462 })?;
7463 let transition = self.process_completion(stage_index, inlet)?;
7464 self.process_transition(transition);
7465 Ok(())
7466 }
7467 FusedEvent::Request { outlet } => {
7468 if self.is_outlet_cancelled(outlet) {
7469 return Ok(());
7470 }
7471 self.bump_event()?;
7472 let Some(stage_index) = self.stage_by_outlet.get(&outlet).copied() else {
7473 return Ok(());
7474 };
7475 let transition = self.process_pull(stage_index, outlet)?;
7476 self.process_transition(transition);
7477 Ok(())
7478 }
7479 FusedEvent::DownstreamFinish { outlet } => {
7480 if !self.cancelled_outlets.insert(outlet) {
7481 return Ok(());
7482 }
7483 self.bump_event()?;
7484 let stage_index = *self.stage_by_outlet.get(&outlet).ok_or_else(|| {
7485 StreamError::GraphValidation(format!(
7486 "outlet {} has no owning stage",
7487 outlet.as_usize()
7488 ))
7489 })?;
7490 let transition = self.process_downstream_finish(stage_index, outlet)?;
7491 self.process_transition(transition);
7492 Ok(())
7493 }
7494 FusedEvent::Emit { outlet, value } => {
7495 self.process_emit_cyclic(outlet, value, graph_outlet, output)
7496 }
7497 FusedEvent::CompleteOutlet { outlet } => {
7498 self.process_complete_outlet_cyclic(outlet, graph_outlet, output)
7499 }
7500 FusedEvent::CancelInlet { inlet } => {
7501 if let Some(outlet) = self.edge_by_inlet.get(&inlet).copied() {
7502 self.schedule_event(FusedEvent::DownstreamFinish { outlet });
7503 }
7504 Ok(())
7505 }
7506 }
7507 }
7508
7509 fn process_transition(&mut self, transition: StageTransition) {
7510 self.schedule_transition(transition);
7511 }
7512
7513 fn process_transition_direct<Out>(
7514 &mut self,
7515 transition: StageTransition,
7516 graph_outlet: PortId,
7517 output: &mut impl FusedOutputSink<Out>,
7518 ) -> StreamResult<()>
7519 where
7520 Out: Send + 'static,
7521 {
7522 self.process_emissions_direct(transition.emissions, graph_outlet, output)?;
7523 for outlet in transition.completed_outlets {
7524 if !self.is_outlet_cancelled(outlet) {
7525 self.process_complete_outlet_direct(outlet, graph_outlet, output)?;
7526 }
7527 }
7528 for inlet in transition.cancelled_inlets {
7529 self.process_cancel_inlet_direct(inlet, graph_outlet, output)?;
7530 }
7531 Ok(())
7532 }
7533
7534 fn process_emissions_direct<Out>(
7535 &mut self,
7536 emissions: StageEmissions,
7537 graph_outlet: PortId,
7538 output: &mut impl FusedOutputSink<Out>,
7539 ) -> StreamResult<()>
7540 where
7541 Out: Send + 'static,
7542 {
7543 match emissions {
7544 StageEmissions::None => Ok(()),
7545 StageEmissions::One(outlet, value) => {
7546 self.process_emit_direct(outlet, value, graph_outlet, output)
7547 }
7548 StageEmissions::Two((first_outlet, first_value), (second_outlet, second_value)) => {
7549 self.process_emit_direct(first_outlet, first_value, graph_outlet, output)?;
7550 self.process_emit_direct(second_outlet, second_value, graph_outlet, output)
7551 }
7552 StageEmissions::Many(emissions) => {
7553 for (outlet, value) in emissions {
7554 self.process_emit_direct(outlet, value, graph_outlet, output)?;
7555 }
7556 Ok(())
7557 }
7558 }
7559 }
7560
7561 fn process_deliver_direct<Out>(
7562 &mut self,
7563 inlet: PortId,
7564 value: DatumValue,
7565 graph_outlet: PortId,
7566 output: &mut impl FusedOutputSink<Out>,
7567 ) -> StreamResult<()>
7568 where
7569 Out: Send + 'static,
7570 {
7571 self.bump_event()?;
7572 let stage_index = *self.stage_by_inlet.get(&inlet).ok_or_else(|| {
7573 StreamError::GraphValidation(format!("inlet {} has no owning stage", inlet.as_usize()))
7574 })?;
7575 let transition = self.process_stage(stage_index, inlet, value)?;
7576 self.process_transition_direct(transition, graph_outlet, output)
7577 }
7578
7579 fn process_complete_inlet_direct<Out>(
7580 &mut self,
7581 inlet: PortId,
7582 graph_outlet: PortId,
7583 output: &mut impl FusedOutputSink<Out>,
7584 ) -> StreamResult<()>
7585 where
7586 Out: Send + 'static,
7587 {
7588 self.bump_event()?;
7589 let stage_index = *self.stage_by_inlet.get(&inlet).ok_or_else(|| {
7590 StreamError::GraphValidation(format!("inlet {} has no owning stage", inlet.as_usize()))
7591 })?;
7592 let transition = self.process_completion(stage_index, inlet)?;
7593 self.process_transition_direct(transition, graph_outlet, output)
7594 }
7595
7596 fn process_request_direct<Out>(
7597 &mut self,
7598 outlet: PortId,
7599 graph_outlet: PortId,
7600 output: &mut impl FusedOutputSink<Out>,
7601 ) -> StreamResult<()>
7602 where
7603 Out: Send + 'static,
7604 {
7605 if self.is_outlet_cancelled(outlet) {
7606 return Ok(());
7607 }
7608 self.bump_event()?;
7609 let Some(stage_index) = self.stage_by_outlet.get(&outlet).copied() else {
7610 return Ok(());
7611 };
7612 let transition = self.process_pull(stage_index, outlet)?;
7613 self.process_transition_direct(transition, graph_outlet, output)
7614 }
7615
7616 fn process_downstream_finish_direct<Out>(
7617 &mut self,
7618 outlet: PortId,
7619 graph_outlet: PortId,
7620 output: &mut impl FusedOutputSink<Out>,
7621 ) -> StreamResult<()>
7622 where
7623 Out: Send + 'static,
7624 {
7625 if !self.cancelled_outlets.insert(outlet) {
7626 return Ok(());
7627 }
7628 self.bump_event()?;
7629 let stage_index = *self.stage_by_outlet.get(&outlet).ok_or_else(|| {
7630 StreamError::GraphValidation(format!(
7631 "outlet {} has no owning stage",
7632 outlet.as_usize()
7633 ))
7634 })?;
7635 let transition = self.process_downstream_finish(stage_index, outlet)?;
7636 self.process_transition_direct(transition, graph_outlet, output)
7637 }
7638
7639 fn process_cancel_inlet_direct<Out>(
7640 &mut self,
7641 inlet: PortId,
7642 graph_outlet: PortId,
7643 output: &mut impl FusedOutputSink<Out>,
7644 ) -> StreamResult<()>
7645 where
7646 Out: Send + 'static,
7647 {
7648 if let Some(outlet) = self.edge_by_inlet.get(&inlet).copied() {
7649 self.process_downstream_finish_direct(outlet, graph_outlet, output)
7650 } else {
7651 Ok(())
7652 }
7653 }
7654
7655 fn process_emit_direct<Out>(
7656 &mut self,
7657 outlet: PortId,
7658 value: DatumValue,
7659 graph_outlet: PortId,
7660 output: &mut impl FusedOutputSink<Out>,
7661 ) -> StreamResult<()>
7662 where
7663 Out: Send + 'static,
7664 {
7665 self.bump_event()?;
7666 if self.is_outlet_cancelled(outlet) {
7667 return Ok(());
7668 }
7669 if outlet == graph_outlet {
7670 output.emit(downcast_datum(value, "emit", || {
7671 format!("outlet#{}", outlet.as_usize())
7672 })?)?;
7673 return self.process_request_direct(outlet, graph_outlet, output);
7674 }
7675
7676 let Some(inlet) = self.edge_by_outlet.get(&outlet).copied() else {
7677 return Err(StreamError::GraphValidation(format!(
7678 "outlet {} is neither connected nor graph output",
7679 outlet.as_usize()
7680 )));
7681 };
7682 let should_repull = self
7683 .stage_by_outlet
7684 .get(&outlet)
7685 .copied()
7686 .is_some_and(|stage_index| {
7687 matches!(
7688 self.graph.stages[stage_index].spec.kind,
7689 StageKind::Opaque | StageKind::Unzip { .. } | StageKind::Partition { .. }
7690 )
7691 });
7692 self.process_deliver_direct(inlet, value, graph_outlet, output)?;
7693 if should_repull {
7694 self.process_request_direct(outlet, graph_outlet, output)?;
7695 }
7696 Ok(())
7697 }
7698
7699 fn process_emit_cyclic<Out>(
7700 &mut self,
7701 outlet: PortId,
7702 value: DatumValue,
7703 graph_outlet: PortId,
7704 output: &mut impl FusedOutputSink<Out>,
7705 ) -> StreamResult<()>
7706 where
7707 Out: Send + 'static,
7708 {
7709 self.bump_event()?;
7710 if self.is_outlet_cancelled(outlet) {
7711 return Ok(());
7712 }
7713 if outlet == graph_outlet {
7714 output.emit(downcast_datum(value, "emit", || {
7715 format!("outlet#{}", outlet.as_usize())
7716 })?)?;
7717 self.schedule_event(FusedEvent::Request { outlet });
7718 return Ok(());
7719 }
7720
7721 let Some(inlet) = self.edge_by_outlet.get(&outlet).copied() else {
7722 return Err(StreamError::GraphValidation(format!(
7723 "outlet {} is neither connected nor graph output",
7724 outlet.as_usize()
7725 )));
7726 };
7727 let should_repull = self
7728 .stage_by_outlet
7729 .get(&outlet)
7730 .copied()
7731 .is_some_and(|stage_index| {
7732 matches!(
7733 self.graph.stages[stage_index].spec.kind,
7734 StageKind::Opaque | StageKind::Unzip { .. } | StageKind::Partition { .. }
7735 )
7736 });
7737 if should_repull {
7738 self.schedule_event(FusedEvent::Request { outlet });
7739 }
7740 self.schedule_event(FusedEvent::Deliver { inlet, value });
7741 Ok(())
7742 }
7743
7744 fn process_complete_outlet_direct<Out>(
7745 &mut self,
7746 outlet: PortId,
7747 graph_outlet: PortId,
7748 output: &mut impl FusedOutputSink<Out>,
7749 ) -> StreamResult<()>
7750 where
7751 Out: Send + 'static,
7752 {
7753 self.bump_event()?;
7754 if outlet == graph_outlet {
7755 return Ok(());
7756 }
7757 let Some(inlet) = self.edge_by_outlet.get(&outlet).copied() else {
7758 return Err(StreamError::GraphValidation(format!(
7759 "outlet {} is neither connected nor graph output",
7760 outlet.as_usize()
7761 )));
7762 };
7763 self.process_complete_inlet_direct(inlet, graph_outlet, output)
7764 }
7765
7766 fn process_complete_outlet_cyclic<Out>(
7767 &mut self,
7768 outlet: PortId,
7769 graph_outlet: PortId,
7770 _output: &mut impl FusedOutputSink<Out>,
7771 ) -> StreamResult<()>
7772 where
7773 Out: Send + 'static,
7774 {
7775 self.bump_event()?;
7776 if outlet == graph_outlet {
7777 return Ok(());
7778 }
7779 let Some(inlet) = self.edge_by_outlet.get(&outlet).copied() else {
7780 return Err(StreamError::GraphValidation(format!(
7781 "outlet {} is neither connected nor graph output",
7782 outlet.as_usize()
7783 )));
7784 };
7785 self.schedule_event(FusedEvent::CompleteInlet { inlet });
7786 Ok(())
7787 }
7788
7789 fn process_stage(
7790 &mut self,
7791 stage_index: usize,
7792 inlet: PortId,
7793 value: DatumValue,
7794 ) -> StreamResult<StageTransition> {
7795 let stage = &self.graph.stages[stage_index];
7796 match &stage.spec.kind {
7797 StageKind::Identity => Ok(StageTransition::emit(StageEmissions::One(
7798 single_outlet(stage)?,
7799 value,
7800 ))),
7801 StageKind::Opaque => {
7802 if let Some(logic) = self
7803 .opaque_logics
7804 .get_mut(stage_index)
7805 .and_then(|l| l.as_mut())
7806 {
7807 logic.drain_async_callbacks();
7808 let inlet_ref = stage.spec.inlets.iter().find(|i| i.id() == inlet).cloned();
7809 if let Some(inlet_ref) = inlet_ref {
7810 logic.offer_datum(inlet, value)?;
7811 let mut handler = logic.take_in_handler(inlet);
7812 let result = if let Some(ref mut handler) = handler {
7813 let inlet_any = inlet_ref;
7814 handler.on_push(logic, inlet_any)
7815 } else {
7816 Ok(())
7817 };
7818 if let Some(handler) = handler {
7819 logic.restore_in_handler(inlet, handler);
7820 }
7821 if result.is_err() {
7822 logic.cancel_all_timers();
7823 }
7824 result?;
7825 }
7826 self.collect_opaque_emissions(stage, stage_index)
7827 } else {
7828 Ok(StageTransition::emit(StageEmissions::One(
7829 single_outlet(stage)?,
7830 value,
7831 )))
7832 }
7833 }
7834 StageKind::Map(map) => Ok(StageTransition::emit(StageEmissions::One(
7835 single_outlet(stage)?,
7836 (map.erased)(value)?,
7837 ))),
7838 StageKind::AsyncBoundary => {
7839 self.async_boundary_crossings += 1;
7840 Ok(StageTransition::emit(StageEmissions::One(
7841 single_outlet(stage)?,
7842 value,
7843 )))
7844 }
7845 StageKind::Broadcast => {
7846 broadcast_emissions(&stage.spec.outlets, value).map(StageTransition::emit)
7847 }
7848 StageKind::Balance => {
7849 let outlets = &stage.spec.outlets;
7850 if outlets.is_empty() {
7851 return Err(StreamError::GraphValidation(
7852 "balance has no outlets".into(),
7853 ));
7854 }
7855 let StageState::Balance {
7856 next,
7857 cancelled_outlets,
7858 live_outlets,
7859 } = &mut self.stage_states[stage_index]
7860 else {
7861 return Err(StreamError::GraphValidation(
7862 "balance state is missing".into(),
7863 ));
7864 };
7865 if *live_outlets == 0 {
7866 return Ok(StageTransition::none());
7867 }
7868 let mut selected = None;
7869 for offset in 0..outlets.len() {
7870 let index = (*next + offset) % outlets.len();
7871 if !cancelled_outlets[index] {
7872 selected = Some(index);
7873 break;
7874 }
7875 }
7876 let index = selected.ok_or_else(|| {
7877 StreamError::GraphValidation("balance has no live outlets".into())
7878 })?;
7879 let outlet = outlets[index].id();
7880 *next = (index + 1) % outlets.len();
7881 Ok(StageTransition::emit(StageEmissions::One(outlet, value)))
7882 }
7883 StageKind::Merge | StageKind::MergePreferred | StageKind::MergePrioritized { .. } => {
7884 let StageState::Merge { completed, .. } = &self.stage_states[stage_index] else {
7885 return Err(StreamError::GraphValidation(
7886 "merge state is missing".into(),
7887 ));
7888 };
7889 if *completed {
7890 return Ok(StageTransition::none());
7891 }
7892 Ok(StageTransition::emit(StageEmissions::One(
7893 single_outlet(stage)?,
7894 value,
7895 )))
7896 }
7897 StageKind::Concat | StageKind::Interleave { .. } => {
7898 let StageState::Merge { completed, .. } = &self.stage_states[stage_index] else {
7899 return Err(StreamError::GraphValidation(
7900 "fan-in state is missing".into(),
7901 ));
7902 };
7903 if *completed {
7904 return Ok(StageTransition::none());
7905 }
7906 Ok(StageTransition::emit(StageEmissions::One(
7907 single_outlet(stage)?,
7908 value,
7909 )))
7910 }
7911 StageKind::OrElse { primary_inlet } => {
7912 let StageState::OrElse {
7913 primary_emitted,
7914 buffer,
7915 primary_closed,
7916 completed,
7917 ..
7918 } = &mut self.stage_states[stage_index]
7919 else {
7920 return Err(StreamError::GraphValidation(
7921 "or-else state is missing".into(),
7922 ));
7923 };
7924 if *completed {
7925 return Ok(StageTransition::none());
7926 }
7927 if inlet == *primary_inlet {
7928 *primary_emitted = true;
7929 buffer.clear();
7930 Ok(StageTransition::emit(StageEmissions::One(
7931 single_outlet(stage)?,
7932 value,
7933 )))
7934 } else if *primary_emitted {
7935 Ok(StageTransition::none())
7936 } else if *primary_closed {
7937 Ok(StageTransition::emit(StageEmissions::One(
7938 single_outlet(stage)?,
7939 value,
7940 )))
7941 } else {
7942 buffer.push_back(value);
7943 Ok(StageTransition::none())
7944 }
7945 }
7946 StageKind::Zip(zip) => {
7947 if stage.spec.inlets.len() != 2 {
7948 return Err(StreamError::GraphValidation(format!(
7949 "zip stage {} expected 2 inlets, got {}",
7950 stage.spec.name(),
7951 stage.spec.inlets.len()
7952 )));
7953 }
7954
7955 let ready = {
7956 let StageState::Zip {
7957 left_inlet,
7958 right_inlet,
7959 left,
7960 right,
7961 left_pending_complete,
7962 right_pending_complete,
7963 completed,
7964 } = &mut self.stage_states[stage_index]
7965 else {
7966 return Err(StreamError::GraphValidation("zip state is missing".into()));
7967 };
7968
7969 if *completed {
7970 return Ok(StageTransition::none());
7971 }
7972
7973 if inlet == *left_inlet {
7977 left.push_back(value);
7978 } else if inlet == *right_inlet {
7979 right.push_back(value);
7980 } else {
7981 return Err(StreamError::GraphValidation(format!(
7982 "zip inlet {} is not part of the stage",
7983 inlet.as_usize()
7984 )));
7985 }
7986
7987 match (left.front().is_some(), right.front().is_some()) {
7988 (true, true) => {
7989 let left_item =
7990 left.pop_front().expect("zip left buffer had an element");
7991 let right_item =
7992 right.pop_front().expect("zip right buffer had an element");
7993 let should_complete = (*left_pending_complete && left.is_empty())
7994 || (*right_pending_complete && right.is_empty());
7995 Some((left_item, right_item, should_complete))
7996 }
7997 _ => None,
7998 }
7999 };
8000
8001 if let Some((left, right, should_complete)) = ready {
8002 let outlet = single_outlet(stage)?;
8003 if should_complete {
8004 let StageState::Zip { completed, .. } = &mut self.stage_states[stage_index]
8005 else {
8006 return Err(StreamError::GraphValidation(
8007 "zip state is missing".into(),
8008 ));
8009 };
8010 *completed = true;
8011 }
8012 let mut transition =
8013 StageTransition::emit(StageEmissions::One(outlet, zip(left, right)?));
8014 if should_complete {
8015 transition.completed_outlets.push(outlet);
8016 }
8017 Ok(transition)
8018 } else {
8019 Ok(StageTransition::none())
8020 }
8021 }
8022 StageKind::Unzip { split, .. } => {
8023 let (fan_in, zip_fast) = match &self.stage_states[stage_index] {
8024 StageState::Unzip {
8025 fast_path,
8026 zip_fast_path,
8027 ..
8028 } => (*fast_path, *zip_fast_path),
8029 _ => (None, None),
8030 };
8031 if let Some(zip_fast) = zip_fast {
8032 let (out0_val, out1_val) = split(value);
8033 let zip_stage = &self.graph.stages[zip_fast.zip_stage_index];
8034 let StageKind::Zip(zip) = &zip_stage.spec.kind else {
8035 return Err(StreamError::GraphValidation(
8036 "unzip-zip fast path references a non-zip stage".into(),
8037 ));
8038 };
8039 let outlet = single_outlet(zip_stage)?;
8040 let zipped = zip(out0_val, out1_val)?;
8041 return Ok(StageTransition::emit(StageEmissions::One(outlet, zipped)));
8042 }
8043 if let Some(fast_path) = fan_in {
8044 let (out0_val, out1_val) = split(value);
8045 let target = &self.graph.stages[fast_path.fan_in_stage_index];
8046 match &target.spec.kind {
8047 StageKind::MergeSorted(compare) => {
8048 let result = {
8049 let StageState::MergeSorted {
8050 left,
8051 right,
8052 left_closed,
8053 right_closed,
8054 pending,
8055 completed,
8056 } = &mut self.stage_states[fast_path.fan_in_stage_index]
8057 else {
8058 return Err(StreamError::GraphValidation(
8059 "merge-sorted state is missing".into(),
8060 ));
8061 };
8062 if *completed {
8063 return Ok(StageTransition::none());
8064 }
8065 if fast_path.target_inlet_indices[0] == 0 {
8068 left.push_back(out0_val);
8069 right.push_back(out1_val);
8070 } else {
8071 left.push_back(out1_val);
8072 right.push_back(out0_val);
8073 }
8074
8075 loop {
8076 let next = match (left.front(), right.front()) {
8077 (Some(l), Some(r)) => {
8078 if compare(l, r) != std::cmp::Ordering::Greater {
8079 left.pop_front()
8080 } else {
8081 right.pop_front()
8082 }
8083 }
8084 (Some(_), None) if *right_closed => left.pop_front(),
8085 (None, Some(_)) if *left_closed => right.pop_front(),
8086 _ => break,
8087 };
8088 if let Some(val) = next {
8089 pending.push_back(val);
8090 } else {
8091 break;
8092 }
8093 }
8094
8095 if let Some(output) = pending.pop_front() {
8096 let outlet = single_outlet(target)?;
8097 let all_done = *left_closed
8098 && *right_closed
8099 && left.is_empty()
8100 && right.is_empty()
8101 && pending.is_empty();
8102 if all_done {
8103 *completed = true;
8104 StageTransition::emit(StageEmissions::One(outlet, output))
8105 .with_completion(vec![outlet])
8106 } else {
8107 StageTransition::emit(StageEmissions::One(outlet, output))
8108 }
8109 } else {
8110 StageTransition::none()
8111 }
8112 };
8113 Ok(result)
8114 }
8115 StageKind::MergeSequence {
8116 extract_sequence,
8117 input_count,
8118 ..
8119 } => {
8120 let result = {
8121 let StageState::MergeSequence {
8122 next_sequence,
8123 pending,
8124 completed_count,
8125 output_buffer,
8126 completed,
8127 } = &mut self.stage_states[fast_path.fan_in_stage_index]
8128 else {
8129 return Err(StreamError::GraphValidation(
8130 "merge-sequence state is missing".into(),
8131 ));
8132 };
8133 if *completed {
8134 return Ok(StageTransition::none());
8135 }
8136 for val in [out0_val, out1_val] {
8137 let seq = extract_sequence(&val);
8138 if seq == *next_sequence {
8139 output_buffer.push_back(val);
8140 *next_sequence += 1;
8141 while let Some(index) =
8142 pending.iter().position(|(s, _)| *s == *next_sequence)
8143 {
8144 let (_, item) = pending.remove(index);
8145 output_buffer.push_back(item);
8146 *next_sequence += 1;
8147 }
8148 } else {
8149 if pending.iter().any(|(s, _)| *s == seq) {
8150 return Err(StreamError::Failed(format!(
8151 "duplicate sequence {seq} on merge sequence"
8152 )));
8153 }
8154 pending.push((seq, val));
8155 pending.sort_by_key(|(s, _)| *s);
8156 while let Some(index) =
8157 pending.iter().position(|(s, _)| *s == *next_sequence)
8158 {
8159 let (_, item) = pending.remove(index);
8160 output_buffer.push_back(item);
8161 *next_sequence += 1;
8162 }
8163 }
8164 }
8165
8166 if !output_buffer.is_empty() {
8167 let outlet = single_outlet(target)?;
8168 let all_done = *completed_count >= *input_count;
8169 let emissions: Vec<_> =
8170 output_buffer.drain(..).map(|v| (outlet, v)).collect();
8171 if all_done {
8172 *completed = true;
8173 StageTransition::emit(StageEmissions::Many(emissions))
8174 .with_completion(vec![outlet])
8175 } else {
8176 StageTransition::emit(StageEmissions::Many(emissions))
8177 }
8178 } else {
8179 StageTransition::none()
8180 }
8181 };
8182 Ok(result)
8183 }
8184 StageKind::MergeLatest {
8185 input_count,
8186 build_snapshot,
8187 ..
8188 } => {
8189 let result = {
8190 let StageState::MergeLatest {
8191 latest,
8192 seen_count,
8193 completed_count,
8194 pending,
8195 completed,
8196 } = &mut self.stage_states[fast_path.fan_in_stage_index]
8197 else {
8198 return Err(StreamError::GraphValidation(
8199 "merge-latest state is missing".into(),
8200 ));
8201 };
8202 if *completed {
8203 return Ok(StageTransition::none());
8204 }
8205 let inlets = &target.spec.inlets;
8206 for (idx, val) in fast_path
8207 .target_inlet_indices
8208 .into_iter()
8209 .zip([out0_val, out1_val])
8210 {
8211 if idx < inlets.len() && latest[idx].is_none() {
8212 *seen_count += 1;
8213 }
8214 latest[idx] = Some(val);
8215 if *seen_count >= *input_count {
8216 let values: Vec<&DatumValue> =
8217 latest.iter().filter_map(|v| v.as_ref()).collect();
8218 let snapshot = build_snapshot(&values);
8219 pending.push_back(snapshot);
8220 }
8221 }
8222
8223 if !pending.is_empty() {
8224 let outlet = single_outlet(target)?;
8225 let all_done = *completed_count >= *input_count;
8226 let emissions: Vec<_> =
8227 pending.drain(..).map(|v| (outlet, v)).collect();
8228 if all_done {
8229 *completed = true;
8230 StageTransition::emit(StageEmissions::Many(emissions))
8231 .with_completion(vec![outlet])
8232 } else {
8233 StageTransition::emit(StageEmissions::Many(emissions))
8234 }
8235 } else {
8236 StageTransition::none()
8237 }
8238 };
8239 Ok(result)
8240 }
8241 _ => {
8242 let transition = {
8243 let StageState::Unzip { cancelled, .. } =
8244 &self.stage_states[stage_index]
8245 else {
8246 return Err(StreamError::GraphValidation(
8247 "unzip state is missing".into(),
8248 ));
8249 };
8250 let out0 = stage.spec.outlets.first().map(AnyOutlet::id);
8251 let out1 = stage.spec.outlets.get(1).map(AnyOutlet::id);
8252 let live0 = out0.is_some() && !cancelled[0];
8253 let live1 = out1.is_some() && !cancelled[1];
8254 if live0 && live1 {
8255 StageTransition::emit(StageEmissions::Two(
8256 (out0.unwrap(), out0_val),
8257 (out1.unwrap(), out1_val),
8258 ))
8259 } else if live0 {
8260 StageTransition::emit(StageEmissions::One(
8261 out0.unwrap(),
8262 out0_val,
8263 ))
8264 } else if live1 {
8265 StageTransition::emit(StageEmissions::One(
8266 out1.unwrap(),
8267 out1_val,
8268 ))
8269 } else {
8270 StageTransition::none()
8271 }
8272 };
8273 Ok(transition)
8274 }
8275 }
8276 } else {
8277 let transition = {
8278 let StageState::Unzip { cancelled, .. } = &self.stage_states[stage_index]
8279 else {
8280 return Err(StreamError::GraphValidation(
8281 "unzip state is missing".into(),
8282 ));
8283 };
8284 let (out0_val, out1_val) = split(value);
8285 let out0 = stage.spec.outlets.first().map(AnyOutlet::id);
8286 let out1 = stage.spec.outlets.get(1).map(AnyOutlet::id);
8287 let live0 = out0.is_some() && !cancelled[0];
8288 let live1 = out1.is_some() && !cancelled[1];
8289 if live0 && live1 {
8290 StageTransition::emit(StageEmissions::Two(
8291 (out0.unwrap(), out0_val),
8292 (out1.unwrap(), out1_val),
8293 ))
8294 } else if live0 {
8295 StageTransition::emit(StageEmissions::One(out0.unwrap(), out0_val))
8296 } else if live1 {
8297 StageTransition::emit(StageEmissions::One(out1.unwrap(), out1_val))
8298 } else {
8299 StageTransition::none()
8300 }
8301 };
8302 Ok(transition)
8303 }
8304 }
8305 StageKind::MergeSorted(compare) => {
8306 let result = {
8307 let StageState::MergeSorted {
8308 left,
8309 right,
8310 left_closed,
8311 right_closed,
8312 pending,
8313 completed,
8314 } = &mut self.stage_states[stage_index]
8315 else {
8316 return Err(StreamError::GraphValidation(
8317 "merge-sorted state is missing".into(),
8318 ));
8319 };
8320 if *completed {
8321 return Ok(StageTransition::none());
8322 }
8323 let is_left = stage.spec.inlets.first().is_some_and(|i| i.id() == inlet);
8324 if is_left {
8325 left.push_back(value);
8326 } else {
8327 right.push_back(value);
8328 }
8329
8330 loop {
8331 let next = match (left.front(), right.front()) {
8332 (Some(l), Some(r)) => {
8333 if compare(l, r) != std::cmp::Ordering::Greater {
8334 left.pop_front()
8335 } else {
8336 right.pop_front()
8337 }
8338 }
8339 (Some(_), None) if *right_closed => left.pop_front(),
8340 (None, Some(_)) if *left_closed => right.pop_front(),
8341 _ => break,
8342 };
8343 if let Some(val) = next {
8344 pending.push_back(val);
8345 } else {
8346 break;
8347 }
8348 }
8349
8350 if let Some(output) = pending.pop_front() {
8351 let outlet = single_outlet(stage)?;
8352 let all_done = *left_closed
8353 && *right_closed
8354 && left.is_empty()
8355 && right.is_empty()
8356 && pending.is_empty();
8357 if all_done {
8358 *completed = true;
8359 StageTransition::emit(StageEmissions::One(outlet, output))
8360 .with_completion(vec![outlet])
8361 } else {
8362 StageTransition::emit(StageEmissions::One(outlet, output))
8363 }
8364 } else {
8365 StageTransition::none()
8366 }
8367 };
8368 Ok(result)
8369 }
8370 StageKind::MergeSequence {
8371 input_count,
8372 extract_sequence,
8373 ..
8374 } => {
8375 let result = {
8376 let StageState::MergeSequence {
8377 next_sequence,
8378 pending,
8379 completed_count,
8380 output_buffer,
8381 completed,
8382 } = &mut self.stage_states[stage_index]
8383 else {
8384 return Err(StreamError::GraphValidation(
8385 "merge-sequence state is missing".into(),
8386 ));
8387 };
8388 if *completed {
8389 return Ok(StageTransition::none());
8390 }
8391 let seq = extract_sequence(&value);
8392 if seq == *next_sequence {
8393 output_buffer.push_back(value);
8394 *next_sequence += 1;
8395 while let Some(index) =
8396 pending.iter().position(|(s, _)| *s == *next_sequence)
8397 {
8398 let (_, item) = pending.remove(index);
8399 output_buffer.push_back(item);
8400 *next_sequence += 1;
8401 }
8402 } else {
8403 if pending.iter().any(|(s, _)| *s == seq) {
8404 return Err(StreamError::Failed(format!(
8405 "duplicate sequence {seq} on merge sequence"
8406 )));
8407 }
8408 pending.push((seq, value));
8409 pending.sort_by_key(|(s, _)| *s);
8410 while let Some(index) =
8411 pending.iter().position(|(s, _)| *s == *next_sequence)
8412 {
8413 let (_, item) = pending.remove(index);
8414 output_buffer.push_back(item);
8415 *next_sequence += 1;
8416 }
8417 }
8418
8419 if !output_buffer.is_empty() {
8420 let outlet = single_outlet(stage)?;
8421 let all_done = *completed_count >= *input_count;
8422 let emissions: Vec<_> =
8423 output_buffer.drain(..).map(|v| (outlet, v)).collect();
8424 if all_done {
8425 *completed = true;
8426 StageTransition::emit(StageEmissions::Many(emissions))
8427 .with_completion(vec![outlet])
8428 } else {
8429 StageTransition::emit(StageEmissions::Many(emissions))
8430 }
8431 } else {
8432 StageTransition::none()
8433 }
8434 };
8435 Ok(result)
8436 }
8437 StageKind::MergeLatest {
8438 input_count,
8439 build_snapshot,
8440 ..
8441 } => {
8442 let result = {
8443 let StageState::MergeLatest {
8444 latest,
8445 seen_count,
8446 completed_count,
8447 pending,
8448 completed,
8449 } = &mut self.stage_states[stage_index]
8450 else {
8451 return Err(StreamError::GraphValidation(
8452 "merge-latest state is missing".into(),
8453 ));
8454 };
8455 if *completed {
8456 return Ok(StageTransition::none());
8457 }
8458 let inlet_index = stage
8459 .spec
8460 .inlets
8461 .iter()
8462 .position(|i| i.id() == inlet)
8463 .ok_or_else(|| {
8464 StreamError::GraphValidation(format!(
8465 "merge-latest inlet {} not part of stage",
8466 inlet.as_usize()
8467 ))
8468 })?;
8469 if latest[inlet_index].is_none() {
8470 *seen_count += 1;
8471 }
8472 latest[inlet_index] = Some(value);
8473 if *seen_count >= *input_count {
8474 let values: Vec<&DatumValue> =
8475 latest.iter().filter_map(|v| v.as_ref()).collect();
8476 let snapshot = build_snapshot(&values);
8477 pending.push_back(snapshot);
8478 }
8479
8480 if !pending.is_empty() {
8481 let outlet = single_outlet(stage)?;
8482 let all_done = *completed_count >= *input_count;
8483 let emissions: Vec<_> = pending.drain(..).map(|v| (outlet, v)).collect();
8484 if all_done {
8485 *completed = true;
8486 StageTransition::emit(StageEmissions::Many(emissions))
8487 .with_completion(vec![outlet])
8488 } else {
8489 StageTransition::emit(StageEmissions::Many(emissions))
8490 }
8491 } else {
8492 StageTransition::none()
8493 }
8494 };
8495 Ok(result)
8496 }
8497 StageKind::Partition {
8498 output_count,
8499 partitioner,
8500 ..
8501 } => {
8502 let result = {
8503 let StageState::Partition {
8504 pending,
8505 upstream_closed: _,
8506 demand,
8507 cancelled,
8508 output_count: _,
8509 completed,
8510 ..
8511 } = &mut self.stage_states[stage_index]
8512 else {
8513 return Err(StreamError::GraphValidation(
8514 "partition state is missing".into(),
8515 ));
8516 };
8517 if *completed {
8518 return Ok(StageTransition::none());
8519 }
8520 let idx = partitioner(&value);
8521 if idx >= *output_count {
8522 return Err(StreamError::Failed(format!(
8523 "partitioner returned out-of-bounds index {idx} for {output_count} outputs"
8524 )));
8525 }
8526 if cancelled[idx] {
8527 return Ok(StageTransition::none());
8528 }
8529 if demand[idx] {
8530 demand[idx] = false;
8531 let outlet = stage.spec.outlets[idx].id();
8532 StageTransition::emit(StageEmissions::One(outlet, value))
8533 } else {
8534 *pending = Some((idx, value));
8535 StageTransition::none()
8536 }
8537 };
8538 Ok(result)
8539 }
8540 }
8541 }
8542
8543 fn process_completion(
8544 &mut self,
8545 stage_index: usize,
8546 inlet: PortId,
8547 ) -> StreamResult<StageTransition> {
8548 let stage = &self.graph.stages[stage_index];
8549 match &stage.spec.kind {
8550 StageKind::Identity | StageKind::Map(_) | StageKind::AsyncBoundary => {
8551 Ok(StageTransition::emit(StageEmissions::None)
8552 .with_completion(vec![single_outlet(stage)?]))
8553 }
8554 StageKind::Opaque => {
8555 if let Some(logic) = self
8556 .opaque_logics
8557 .get_mut(stage_index)
8558 .and_then(|l| l.as_mut())
8559 {
8560 logic.drain_async_callbacks();
8561 logic.complete_inlet_by_id(inlet)?;
8562 let inlet_ref = stage.spec.inlets.iter().find(|i| i.id() == inlet).cloned();
8563 if let Some(inlet_ref) = inlet_ref {
8564 let mut handler = logic.take_in_handler(inlet);
8565 let result = if let Some(ref mut handler) = handler {
8566 let inlet_any = inlet_ref;
8567 handler.on_upstream_finish(logic, inlet_any)
8568 } else {
8569 Ok(())
8570 };
8571 if let Some(handler) = handler {
8572 logic.restore_in_handler(inlet, handler);
8573 }
8574 if result.is_err() {
8575 logic.cancel_all_timers();
8576 }
8577 result?;
8578 }
8579 self.collect_opaque_emissions(stage, stage_index)
8580 } else {
8581 Ok(StageTransition::emit(StageEmissions::None)
8582 .with_completion(vec![single_outlet(stage)?]))
8583 }
8584 }
8585 StageKind::Broadcast | StageKind::Balance => {
8586 Ok(StageTransition::emit(StageEmissions::None)
8587 .with_completion(stage.spec.outlets.iter().map(AnyOutlet::id).collect()))
8588 }
8589 StageKind::Merge | StageKind::MergePreferred | StageKind::MergePrioritized { .. } => {
8590 let StageState::Merge {
8591 open_inputs,
8592 eager_complete,
8593 completed,
8594 } = &mut self.stage_states[stage_index]
8595 else {
8596 return Err(StreamError::GraphValidation(
8597 "merge state is missing".into(),
8598 ));
8599 };
8600
8601 if *completed {
8602 return Ok(StageTransition::none());
8603 }
8604 if *open_inputs == 0 {
8605 return Ok(StageTransition::none());
8606 }
8607 *open_inputs -= 1;
8608 if *eager_complete || *open_inputs == 0 {
8609 *completed = true;
8610 Ok(StageTransition::emit(StageEmissions::None)
8611 .with_completion(vec![single_outlet(stage)?]))
8612 } else {
8613 Ok(StageTransition::none())
8614 }
8615 }
8616 StageKind::Concat | StageKind::Interleave { .. } => {
8617 let StageState::Merge {
8618 open_inputs,
8619 eager_complete,
8620 completed,
8621 } = &mut self.stage_states[stage_index]
8622 else {
8623 return Err(StreamError::GraphValidation(
8624 "fan-in state is missing".into(),
8625 ));
8626 };
8627
8628 if *completed {
8629 return Ok(StageTransition::none());
8630 }
8631 if *open_inputs == 0 {
8632 return Ok(StageTransition::none());
8633 }
8634 *open_inputs -= 1;
8635 if *eager_complete || *open_inputs == 0 {
8636 *completed = true;
8637 Ok(StageTransition::emit(StageEmissions::None)
8638 .with_completion(vec![single_outlet(stage)?]))
8639 } else {
8640 Ok(StageTransition::none())
8641 }
8642 }
8643 StageKind::OrElse { primary_inlet } => {
8644 let StageState::OrElse {
8645 primary_emitted,
8646 buffer,
8647 primary_closed,
8648 secondary_closed,
8649 completed,
8650 ..
8651 } = &mut self.stage_states[stage_index]
8652 else {
8653 return Err(StreamError::GraphValidation(
8654 "or-else state is missing".into(),
8655 ));
8656 };
8657 if *completed {
8658 return Ok(StageTransition::none());
8659 }
8660 if inlet == *primary_inlet {
8661 *primary_closed = true;
8662 if *primary_emitted {
8663 *completed = true;
8664 buffer.clear();
8665 Ok(StageTransition::emit(StageEmissions::None)
8666 .with_completion(vec![single_outlet(stage)?]))
8667 } else {
8668 let outlet = single_outlet(stage)?;
8669 let emissions: Vec<_> = buffer.drain(..).map(|v| (outlet, v)).collect();
8670 if *secondary_closed {
8671 *completed = true;
8672 let transition = if emissions.is_empty() {
8673 StageTransition::emit(StageEmissions::None)
8674 } else {
8675 StageTransition::emit(StageEmissions::Many(emissions))
8676 };
8677 Ok(transition.with_completion(vec![outlet]))
8678 } else {
8679 if emissions.is_empty() {
8680 Ok(StageTransition::none())
8681 } else {
8682 Ok(StageTransition::emit(StageEmissions::Many(emissions)))
8683 }
8684 }
8685 }
8686 } else {
8687 *secondary_closed = true;
8688 if *primary_closed && !*primary_emitted {
8689 let outlet = single_outlet(stage)?;
8690 let emissions: Vec<_> = buffer.drain(..).map(|v| (outlet, v)).collect();
8691 *completed = true;
8692 if emissions.is_empty() {
8693 Ok(StageTransition::emit(StageEmissions::None)
8694 .with_completion(vec![outlet]))
8695 } else {
8696 Ok(StageTransition::emit(StageEmissions::Many(emissions))
8697 .with_completion(vec![outlet]))
8698 }
8699 } else {
8700 Ok(StageTransition::none())
8701 }
8702 }
8703 }
8704 StageKind::Zip(_) => {
8705 let StageState::Zip {
8706 left_inlet,
8707 right_inlet,
8708 left,
8709 right,
8710 left_pending_complete,
8711 right_pending_complete,
8712 completed,
8713 } = &mut self.stage_states[stage_index]
8714 else {
8715 return Err(StreamError::GraphValidation("zip state is missing".into()));
8716 };
8717 if *completed {
8718 return Ok(StageTransition::none());
8719 }
8720 let finishes_left = inlet == *left_inlet;
8721 let finishes_right = inlet == *right_inlet;
8722 if (finishes_left && left.is_empty()) || (finishes_right && right.is_empty()) {
8723 *completed = true;
8724 Ok(StageTransition::emit(StageEmissions::None)
8725 .with_completion(vec![single_outlet(stage)?]))
8726 } else {
8727 if finishes_left {
8728 *left_pending_complete = true;
8729 }
8730 if finishes_right {
8731 *right_pending_complete = true;
8732 }
8733 Ok(StageTransition::none())
8734 }
8735 }
8736 StageKind::Unzip { .. } => {
8737 let (fan_in, zip_fast) = match &self.stage_states[stage_index] {
8738 StageState::Unzip {
8739 fast_path,
8740 zip_fast_path,
8741 ..
8742 } => (*fast_path, *zip_fast_path),
8743 _ => (None, None),
8744 };
8745 if let Some(zip_fast) = zip_fast {
8746 let StageState::Unzip {
8747 upstream_closed, ..
8748 } = &mut self.stage_states[stage_index]
8749 else {
8750 return Err(StreamError::GraphValidation(
8751 "unzip state is missing".into(),
8752 ));
8753 };
8754 *upstream_closed = true;
8755 let zip_stage = &self.graph.stages[zip_fast.zip_stage_index];
8756 return Ok(StageTransition::emit(StageEmissions::None)
8757 .with_completion(vec![single_outlet(zip_stage)?]));
8758 }
8759 if let Some(fast_path) = fan_in {
8760 let StageState::Unzip {
8761 upstream_closed, ..
8762 } = &mut self.stage_states[stage_index]
8763 else {
8764 return Err(StreamError::GraphValidation(
8765 "unzip state is missing".into(),
8766 ));
8767 };
8768 *upstream_closed = true;
8769 let target_stage = &self.graph.stages[fast_path.fan_in_stage_index];
8770 let target_inlets = [
8774 target_stage.spec.inlets[fast_path.target_inlet_indices[0]].id(),
8775 target_stage.spec.inlets[fast_path.target_inlet_indices[1]].id(),
8776 ];
8777 let mut combined = StageTransition::none();
8778 for target_inlet in target_inlets {
8779 let t =
8780 self.process_completion(fast_path.fan_in_stage_index, target_inlet)?;
8781 combined.emissions = merge_emissions(combined.emissions, t.emissions);
8782 combined.completed_outlets.extend(t.completed_outlets);
8783 combined.cancelled_inlets.extend(t.cancelled_inlets);
8784 }
8785 Ok(combined)
8786 } else {
8787 let StageState::Unzip {
8788 upstream_closed, ..
8789 } = &mut self.stage_states[stage_index]
8790 else {
8791 return Err(StreamError::GraphValidation(
8792 "unzip state is missing".into(),
8793 ));
8794 };
8795 *upstream_closed = true;
8796 Ok(StageTransition::emit(StageEmissions::None)
8797 .with_completion(stage.spec.outlets.iter().map(AnyOutlet::id).collect()))
8798 }
8799 }
8800 StageKind::MergeSorted(compare) => {
8801 let result = {
8802 let StageState::MergeSorted {
8803 left,
8804 right,
8805 left_closed,
8806 right_closed,
8807 pending,
8808 completed,
8809 } = &mut self.stage_states[stage_index]
8810 else {
8811 return Err(StreamError::GraphValidation(
8812 "merge-sorted state is missing".into(),
8813 ));
8814 };
8815 if *completed {
8816 return Ok(StageTransition::none());
8817 }
8818 let is_left = stage.spec.inlets.first().is_some_and(|i| i.id() == inlet);
8819 if is_left {
8820 *left_closed = true;
8821 } else {
8822 *right_closed = true;
8823 }
8824
8825 loop {
8826 let next = match (left.front(), right.front()) {
8827 (Some(l), Some(r)) => {
8828 if compare(l, r) != std::cmp::Ordering::Greater {
8829 left.pop_front()
8830 } else {
8831 right.pop_front()
8832 }
8833 }
8834 (Some(_), None) if *right_closed => left.pop_front(),
8835 (None, Some(_)) if *left_closed => right.pop_front(),
8836 _ => break,
8837 };
8838 if let Some(val) = next {
8839 pending.push_back(val);
8840 } else {
8841 break;
8842 }
8843 }
8844
8845 if pending.is_empty() {
8846 let all_done =
8847 *left_closed && *right_closed && left.is_empty() && right.is_empty();
8848 if all_done {
8849 *completed = true;
8850 StageTransition::emit(StageEmissions::None)
8851 .with_completion(vec![single_outlet(stage)?])
8852 } else {
8853 StageTransition::none()
8854 }
8855 } else {
8856 let outlet = single_outlet(stage)?;
8857 let emissions: Vec<_> = pending.drain(..).map(|v| (outlet, v)).collect();
8858 let all_done =
8859 *left_closed && *right_closed && left.is_empty() && right.is_empty();
8860 if all_done {
8861 *completed = true;
8862 StageTransition::emit(StageEmissions::Many(emissions))
8863 .with_completion(vec![outlet])
8864 } else {
8865 StageTransition::emit(StageEmissions::Many(emissions))
8866 }
8867 }
8868 };
8869 Ok(result)
8870 }
8871 StageKind::MergeSequence { input_count, .. } => {
8872 let result = {
8873 let StageState::MergeSequence {
8874 next_sequence,
8875 pending,
8876 completed_count,
8877 output_buffer,
8878 completed,
8879 } = &mut self.stage_states[stage_index]
8880 else {
8881 return Err(StreamError::GraphValidation(
8882 "merge-sequence state is missing".into(),
8883 ));
8884 };
8885 if *completed {
8886 return Ok(StageTransition::none());
8887 }
8888 *completed_count += 1;
8889 if *completed_count >= *input_count && output_buffer.is_empty() {
8890 if !pending.is_empty() {
8891 return Err(StreamError::Failed(format!(
8895 "expected sequence {next_sequence}, but all input ports have pushed or are complete",
8896 )));
8897 }
8898 *completed = true;
8899 StageTransition::emit(StageEmissions::None)
8900 .with_completion(vec![single_outlet(stage)?])
8901 } else {
8902 StageTransition::none()
8903 }
8904 };
8905 Ok(result)
8906 }
8907 StageKind::MergeLatest {
8908 input_count,
8909 eager_complete,
8910 ..
8911 } => {
8912 let result = {
8913 let StageState::MergeLatest {
8914 completed_count,
8915 pending,
8916 completed,
8917 ..
8918 } = &mut self.stage_states[stage_index]
8919 else {
8920 return Err(StreamError::GraphValidation(
8921 "merge-latest state is missing".into(),
8922 ));
8923 };
8924 if *completed {
8925 return Ok(StageTransition::none());
8926 }
8927 *completed_count += 1;
8928 let all_done = *completed_count >= *input_count;
8933 let eager_done = *eager_complete && pending.is_empty();
8934 if all_done || eager_done {
8935 *completed = true;
8936 StageTransition::emit(StageEmissions::None)
8937 .with_completion(vec![single_outlet(stage)?])
8938 } else {
8939 StageTransition::none()
8940 }
8941 };
8942 Ok(result)
8943 }
8944 StageKind::Partition { .. } => {
8945 let result = {
8946 let StageState::Partition {
8947 pending,
8948 upstream_closed,
8949 completed,
8950 ..
8951 } = &mut self.stage_states[stage_index]
8952 else {
8953 return Err(StreamError::GraphValidation(
8954 "partition state is missing".into(),
8955 ));
8956 };
8957 if *completed {
8958 return Ok(StageTransition::none());
8959 }
8960 *upstream_closed = true;
8961 if pending.is_none() {
8962 *completed = true;
8963 StageTransition::emit(StageEmissions::None)
8964 .with_completion(stage.spec.outlets.iter().map(AnyOutlet::id).collect())
8965 } else {
8966 StageTransition::none()
8967 }
8968 };
8969 Ok(result)
8970 }
8971 }
8972 }
8973
8974 fn process_pull(
8975 &mut self,
8976 stage_index: usize,
8977 outlet: PortId,
8978 ) -> StreamResult<StageTransition> {
8979 let stage = &self.graph.stages[stage_index];
8980 match &stage.spec.kind {
8981 StageKind::Opaque => {
8982 if let Some(logic) = self
8983 .opaque_logics
8984 .get_mut(stage_index)
8985 .and_then(|l| l.as_mut())
8986 {
8987 logic.drain_async_callbacks();
8988 logic.set_demand_by_id(outlet)?;
8989 let outlet_ref = stage
8990 .spec
8991 .outlets
8992 .iter()
8993 .find(|o| o.id() == outlet)
8994 .cloned();
8995 if let Some(outlet_ref) = outlet_ref {
8996 let mut handler = logic.take_out_handler(outlet);
8997 let result = if let Some(ref mut handler) = handler {
8998 handler.on_pull(logic, outlet_ref)
8999 } else {
9000 Ok(())
9001 };
9002 if let Some(handler) = handler
9003 && handler.keep_handler()
9004 && logic.get_out_handler_mut(outlet).is_none()
9005 {
9006 logic.restore_out_handler(outlet, handler);
9007 }
9008 if result.is_err() {
9009 logic.cancel_all_timers();
9010 }
9011 result?;
9012 }
9013 self.collect_opaque_emissions(stage, stage_index)
9014 } else {
9015 Ok(StageTransition::none())
9016 }
9017 }
9018 StageKind::Unzip { .. } => {
9019 let StageState::Unzip {
9020 demand, cancelled, ..
9021 } = &mut self.stage_states[stage_index]
9022 else {
9023 return Ok(StageTransition::none());
9024 };
9025 let Some(idx) = stage.spec.outlets.iter().position(|o| o.id() == outlet) else {
9026 return Ok(StageTransition::none());
9027 };
9028 if idx < 2 && !cancelled[idx] {
9029 demand[idx] = true;
9030 }
9031 Ok(StageTransition::none())
9032 }
9033 StageKind::Partition { .. } => {
9034 let result = {
9035 let StageState::Partition {
9036 pending,
9037 upstream_closed,
9038 demand,
9039 cancelled,
9040 completed,
9041 ..
9042 } = &mut self.stage_states[stage_index]
9043 else {
9044 return Ok(StageTransition::none());
9045 };
9046 if *completed {
9047 return Ok(StageTransition::none());
9048 }
9049 let Some(idx) = stage.spec.outlets.iter().position(|o| o.id() == outlet) else {
9050 return Ok(StageTransition::none());
9051 };
9052 if cancelled[idx] {
9053 return Ok(StageTransition::none());
9054 }
9055
9056 if let Some((p_idx, p_val)) = pending.take() {
9057 if p_idx == idx {
9058 let out = stage.spec.outlets[idx].id();
9059 if *upstream_closed {
9060 *completed = true;
9061 StageTransition::emit(StageEmissions::One(out, p_val))
9062 .with_completion(
9063 stage.spec.outlets.iter().map(AnyOutlet::id).collect(),
9064 )
9065 } else {
9066 StageTransition::emit(StageEmissions::One(out, p_val))
9067 }
9068 } else {
9069 *pending = Some((p_idx, p_val));
9070 demand[idx] = true;
9071 StageTransition::none()
9072 }
9073 } else {
9074 demand[idx] = true;
9075 StageTransition::none()
9076 }
9077 };
9078 Ok(result)
9079 }
9080 _ => Ok(StageTransition::none()),
9081 }
9082 }
9083
9084 fn process_downstream_finish(
9085 &mut self,
9086 stage_index: usize,
9087 outlet: PortId,
9088 ) -> StreamResult<StageTransition> {
9089 let stage = &self.graph.stages[stage_index];
9090 match &stage.spec.kind {
9091 StageKind::Broadcast => {
9092 let StageState::Broadcast {
9093 cancelled_outlets,
9094 live_outlets,
9095 ..
9096 } = &mut self.stage_states[stage_index]
9097 else {
9098 return Err(StreamError::GraphValidation(
9099 "broadcast state is missing".into(),
9100 ));
9101 };
9102 let index = stage
9103 .spec
9104 .outlets
9105 .iter()
9106 .position(|candidate| candidate.id() == outlet)
9107 .ok_or_else(|| {
9108 StreamError::GraphValidation(format!(
9109 "broadcast outlet {} is not part of the stage",
9110 outlet.as_usize()
9111 ))
9112 })?;
9113 if cancelled_outlets[index] {
9114 return Ok(StageTransition::none());
9115 }
9116 cancelled_outlets[index] = true;
9117 *live_outlets -= 1;
9118 if *live_outlets == 0 {
9119 Ok(StageTransition::none()
9120 .with_cancellations(stage.spec.inlets.iter().map(AnyInlet::id).collect()))
9121 } else {
9122 Ok(StageTransition::none())
9123 }
9124 }
9125 StageKind::Balance => {
9126 let StageState::Balance {
9127 cancelled_outlets,
9128 live_outlets,
9129 ..
9130 } = &mut self.stage_states[stage_index]
9131 else {
9132 return Err(StreamError::GraphValidation(
9133 "balance state is missing".into(),
9134 ));
9135 };
9136 let index = stage
9137 .spec
9138 .outlets
9139 .iter()
9140 .position(|candidate| candidate.id() == outlet)
9141 .ok_or_else(|| {
9142 StreamError::GraphValidation(format!(
9143 "balance outlet {} is not part of the stage",
9144 outlet.as_usize()
9145 ))
9146 })?;
9147 if cancelled_outlets[index] {
9148 return Ok(StageTransition::none());
9149 }
9150 cancelled_outlets[index] = true;
9151 *live_outlets -= 1;
9152 if *live_outlets == 0 {
9153 Ok(StageTransition::none()
9154 .with_cancellations(stage.spec.inlets.iter().map(AnyInlet::id).collect()))
9155 } else {
9156 Ok(StageTransition::none())
9157 }
9158 }
9159 StageKind::Unzip { .. } => {
9160 let StageState::Unzip { cancelled, .. } = &mut self.stage_states[stage_index]
9161 else {
9162 return Err(StreamError::GraphValidation(
9163 "unzip state is missing".into(),
9164 ));
9165 };
9166 let idx = stage
9167 .spec
9168 .outlets
9169 .iter()
9170 .position(|o| o.id() == outlet)
9171 .unwrap_or(0);
9172 if idx < 2 && !cancelled[idx] {
9173 cancelled[idx] = true;
9174 let all_cancelled = cancelled.iter().all(|c| *c);
9175 if all_cancelled {
9176 Ok(StageTransition::none().with_cancellations(
9177 stage.spec.inlets.iter().map(AnyInlet::id).collect(),
9178 ))
9179 } else {
9180 Ok(StageTransition::none())
9181 }
9182 } else {
9183 Ok(StageTransition::none())
9184 }
9185 }
9186 StageKind::MergeSorted(_)
9187 | StageKind::MergeSequence { .. }
9188 | StageKind::MergeLatest { .. } => Ok(StageTransition::none()
9189 .with_cancellations(stage.spec.inlets.iter().map(AnyInlet::id).collect())),
9190 StageKind::Partition { eager_cancel, .. } => {
9191 let result = {
9192 let StageState::Partition {
9193 pending,
9194 cancelled,
9195 completed,
9196 ..
9197 } = &mut self.stage_states[stage_index]
9198 else {
9199 return Err(StreamError::GraphValidation(
9200 "partition state is missing".into(),
9201 ));
9202 };
9203 if *completed {
9204 return Ok(StageTransition::none());
9205 }
9206 let Some(idx) = stage.spec.outlets.iter().position(|o| o.id() == outlet) else {
9207 return Ok(StageTransition::none());
9208 };
9209 if cancelled[idx] {
9210 return Ok(StageTransition::none());
9211 }
9212 cancelled[idx] = true;
9213 if let Some((p_idx, _)) = pending
9215 && *p_idx == idx
9216 {
9217 *pending = None;
9218 }
9219 let all_cancelled = cancelled.iter().all(|c| *c);
9220 if all_cancelled || *eager_cancel {
9221 *completed = true;
9222 StageTransition::none().with_cancellations(
9223 stage.spec.inlets.iter().map(AnyInlet::id).collect(),
9224 )
9225 } else {
9226 StageTransition::none()
9227 }
9228 };
9229 Ok(result)
9230 }
9231 StageKind::Opaque => {
9232 let no_cancelled_outlets = self.cancelled_outlets.is_empty();
9233 if let Some(logic) = self
9234 .opaque_logics
9235 .get_mut(stage_index)
9236 .and_then(|l| l.as_mut())
9237 {
9238 logic.drain_async_callbacks();
9239 logic.downstream_finish_by_id(outlet, "downstream_finish")?;
9240 let outlet_ref = stage
9241 .spec
9242 .outlets
9243 .iter()
9244 .find(|o| o.id() == outlet)
9245 .cloned();
9246 if let Some(outlet_ref) = outlet_ref {
9247 let mut handler = logic.take_out_handler(outlet);
9248 let result = if let Some(ref mut handler) = handler {
9249 handler.on_downstream_finish(logic, outlet_ref)
9250 } else {
9251 Ok(())
9252 };
9253 if let Some(handler) = handler
9254 && handler.keep_handler()
9255 && logic.get_out_handler_mut(outlet).is_none()
9256 {
9257 logic.restore_out_handler(outlet, handler);
9258 }
9259 if result.is_err() {
9260 logic.cancel_all_timers();
9261 }
9262 result?;
9263 }
9264 let all_outlets_closed = stage.spec.outlets.iter().all(|candidate| {
9265 logic.is_closed_by_id(candidate.id())
9266 || (!no_cancelled_outlets
9267 && self.cancelled_outlets.contains(&candidate.id()))
9268 });
9269 let mut transition = self.collect_opaque_emissions(stage, stage_index)?;
9270 if all_outlets_closed {
9271 transition.cancelled_inlets =
9272 stage.spec.inlets.iter().map(AnyInlet::id).collect();
9273 }
9274 Ok(transition)
9275 } else {
9276 Ok(StageTransition::none()
9277 .with_cancellations(stage.spec.inlets.iter().map(AnyInlet::id).collect()))
9278 }
9279 }
9280 _ => Ok(StageTransition::none()
9281 .with_cancellations(stage.spec.inlets.iter().map(AnyInlet::id).collect())),
9282 }
9283 }
9284
9285 fn collect_opaque_emissions(
9286 &mut self,
9287 stage: &StageRecord,
9288 stage_index: usize,
9289 ) -> StreamResult<StageTransition> {
9290 if let Some(logic) = self
9291 .opaque_logics
9292 .get_mut(stage_index)
9293 .and_then(|l| l.as_mut())
9294 {
9295 let emissions_slots = std::mem::take(&mut logic.pending_emissions);
9296 let completions = std::mem::take(&mut logic.pending_completions);
9297 let has_stage_failed = logic.stage_error().is_some();
9298
9299 let emissions = if emissions_slots.is_empty() {
9300 StageEmissions::None
9301 } else if emissions_slots.len() == 1 {
9302 let (port, val) = emissions_slots.into_iter().next().unwrap();
9303 StageEmissions::One(port, val)
9304 } else {
9305 StageEmissions::Many(emissions_slots)
9306 };
9307
9308 if has_stage_failed {
9309 let _ = has_stage_failed;
9310 }
9311
9312 Ok(StageTransition {
9313 emissions,
9314 completed_outlets: completions,
9315 cancelled_inlets: Vec::new(),
9316 })
9317 } else {
9318 Ok(StageTransition::emit(StageEmissions::None)
9319 .with_completion(vec![single_outlet(stage)?]))
9320 }
9321 }
9322
9323 fn bump_event(&mut self) -> StreamResult<()> {
9324 bump_fused_event(&mut self.events, self.config)
9325 }
9326
9327 fn prime_connected_demands(&mut self) {
9328 for (stage_index, stage) in self.graph.stages.iter().enumerate() {
9329 match &stage.spec.kind {
9330 StageKind::Opaque => {
9331 let Some(logic) = self
9332 .opaque_logics
9333 .get_mut(stage_index)
9334 .and_then(|logic| logic.as_mut())
9335 else {
9336 continue;
9337 };
9338 for outlet in &stage.spec.outlets {
9339 if self.edge_by_outlet.contains_key(&outlet.id()) {
9340 let _ = logic.set_demand_by_id(outlet.id());
9341 }
9342 }
9343 }
9344 StageKind::Unzip { .. } => {
9345 let StageState::Unzip { demand, .. } = &mut self.stage_states[stage_index]
9346 else {
9347 continue;
9348 };
9349 for (idx, outlet) in stage.spec.outlets.iter().enumerate() {
9350 if self.edge_by_outlet.contains_key(&outlet.id()) {
9351 demand[idx] = true;
9352 }
9353 }
9354 }
9355 StageKind::Partition { .. } => {
9356 let StageState::Partition {
9357 demand,
9358 output_count,
9359 ..
9360 } = &mut self.stage_states[stage_index]
9361 else {
9362 continue;
9363 };
9364 for (idx, demand_slot) in demand.iter_mut().enumerate().take(*output_count) {
9365 if idx < stage.spec.outlets.len()
9366 && self
9367 .edge_by_outlet
9368 .contains_key(&stage.spec.outlets[idx].id())
9369 {
9370 *demand_slot = true;
9371 }
9372 }
9373 }
9374 _ => {}
9375 }
9376 }
9377 }
9378}
9379
9380impl<Left, Right> GraphBlueprint<ZipShape<Left, Right>>
9381where
9382 Left: Clone + Send + 'static,
9383 Right: Clone + Send + 'static,
9384{
9385 pub fn run_zip(&self, left: Vec<Left>, right: Vec<Right>) -> StreamResult<Vec<(Left, Right)>> {
9386 Ok(self
9387 .run_zip_report(left, right, FusedExecutionConfig::default())?
9388 .output)
9389 }
9390
9391 pub fn run_zip_report(
9392 &self,
9393 left: Vec<Left>,
9394 right: Vec<Right>,
9395 config: FusedExecutionConfig,
9396 ) -> StreamResult<FusedExecutionReport<(Left, Right)>> {
9397 let mut left = left.into_iter();
9398 let mut right = right.into_iter();
9399 let left_inlet = self.shape.in0().id();
9400 let right_inlet = self.shape.in1().id();
9401 let outlet = self.shape.outlet().id();
9402 let mut executor = FusedExecutor::new(self, config);
9403 let mut output = Vec::with_capacity(left.len().min(right.len()));
9404 let mut left_completed = false;
9405 let mut right_completed = false;
9406
9407 {
9408 let mut output_sink = VecOutputSink {
9409 output: &mut output,
9410 };
9411 if left.len() == 0 {
9412 executor.complete(left_inlet, outlet, &mut output_sink)?;
9413 left_completed = true;
9414 }
9415 if right.len() == 0 {
9416 executor.complete(right_inlet, outlet, &mut output_sink)?;
9417 right_completed = true;
9418 }
9419
9420 while left.len() > 0 || right.len() > 0 {
9421 if let Some(item) = left.next() {
9422 executor.deliver(left_inlet, datum(item), outlet, &mut output_sink)?;
9423 if left.len() == 0 && !left_completed {
9424 executor.complete(left_inlet, outlet, &mut output_sink)?;
9425 left_completed = true;
9426 }
9427 }
9428 if let Some(item) = right.next() {
9429 executor.deliver(right_inlet, datum(item), outlet, &mut output_sink)?;
9430 if right.len() == 0 && !right_completed {
9431 executor.complete(right_inlet, outlet, &mut output_sink)?;
9432 right_completed = true;
9433 }
9434 }
9435 }
9436 }
9437
9438 Ok(FusedExecutionReport {
9439 output,
9440 events: executor.events,
9441 async_boundary_crossings: executor.async_boundary_crossings,
9442 })
9443 }
9444}
9445
9446fn unzip_fan_in_fast_path<S: Shape>(
9447 stage: &StageRecord,
9448 graph: &GraphBlueprint<S>,
9449 edge_by_outlet: &HashMap<PortId, PortId>,
9450 stage_by_inlet: &HashMap<PortId, usize>,
9451) -> Option<UnzipFanInFastPath> {
9452 let outlets = &stage.spec.outlets;
9453 if outlets.len() != 2 {
9454 return None;
9455 }
9456 let inlet0 = edge_by_outlet.get(&outlets[0].id()).copied()?;
9457 let inlet1 = edge_by_outlet.get(&outlets[1].id()).copied()?;
9458 let stage0 = *stage_by_inlet.get(&inlet0)?;
9459 let stage1 = *stage_by_inlet.get(&inlet1)?;
9460 if stage0 != stage1 {
9461 return None;
9462 }
9463 let target = graph.stages.get(stage0)?;
9464 if !matches!(
9465 target.spec.kind,
9466 StageKind::MergeSorted(_) | StageKind::MergeSequence { .. } | StageKind::MergeLatest { .. }
9467 ) {
9468 return None;
9469 }
9470 let idx0 = target.spec.inlets.iter().position(|i| i.id() == inlet0)?;
9473 let idx1 = target.spec.inlets.iter().position(|i| i.id() == inlet1)?;
9474 Some(UnzipFanInFastPath {
9475 fan_in_stage_index: stage0,
9476 target_inlet_indices: [idx0, idx1],
9477 })
9478}
9479
9480fn unzip_zip_fast_path<S: Shape>(
9481 stage: &StageRecord,
9482 graph: &GraphBlueprint<S>,
9483 edge_by_outlet: &HashMap<PortId, PortId>,
9484 stage_by_inlet: &HashMap<PortId, usize>,
9485) -> Option<UnzipZipFastPath> {
9486 let outlets = &stage.spec.outlets;
9487 if outlets.len() != 2 {
9488 return None;
9489 }
9490 let inlet0 = edge_by_outlet.get(&outlets[0].id()).copied()?;
9491 let inlet1 = edge_by_outlet.get(&outlets[1].id()).copied()?;
9492 let stage0 = *stage_by_inlet.get(&inlet0)?;
9493 let stage1 = *stage_by_inlet.get(&inlet1)?;
9494 if stage0 != stage1 {
9495 return None;
9496 }
9497 let target = graph.stages.get(stage0)?;
9498 if !matches!(target.spec.kind, StageKind::Zip(_)) {
9499 return None;
9500 }
9501 Some(UnzipZipFastPath {
9502 zip_stage_index: stage0,
9503 })
9504}
9505
9506pub(crate) fn bump_fused_event(
9512 events: &mut usize,
9513 config: FusedExecutionConfig,
9514) -> StreamResult<()> {
9515 *events += 1;
9516 if *events > config.event_limit {
9517 return Err(StreamError::EventLimitExceeded {
9518 limit: config.event_limit,
9519 });
9520 }
9521 Ok(())
9522}
9523
9524fn broadcast_emissions(outlets: &[AnyOutlet], value: DatumValue) -> StreamResult<StageEmissions> {
9525 match outlets {
9526 [] => Err(StreamError::GraphValidation(
9527 "broadcast has no outlets".into(),
9528 )),
9529 [outlet] => Ok(StageEmissions::One(outlet.id(), value)),
9530 [first, second] => Ok(StageEmissions::Two(
9531 (first.id(), value.clone_box()),
9532 (second.id(), value),
9533 )),
9534 outlets => {
9535 let mut emitted = Vec::with_capacity(outlets.len());
9536 for outlet in &outlets[..outlets.len() - 1] {
9537 emitted.push((outlet.id(), value.clone_box()));
9538 }
9539 emitted.push((outlets[outlets.len() - 1].id(), value));
9540 Ok(StageEmissions::Many(emitted))
9541 }
9542 }
9543}
9544
9545fn single_outlet(stage: &StageRecord) -> StreamResult<PortId> {
9546 stage
9547 .spec
9548 .outlets
9549 .first()
9550 .map(AnyOutlet::id)
9551 .ok_or_else(|| {
9552 StreamError::GraphValidation(format!("stage {} has no outlet", stage.spec.name()))
9553 })
9554}
9555
9556fn merge_emissions(first: StageEmissions, second: StageEmissions) -> StageEmissions {
9557 match (first, second) {
9558 (StageEmissions::None, other) | (other, StageEmissions::None) => other,
9559 (StageEmissions::One(p1, v1), StageEmissions::One(p2, v2)) => {
9560 StageEmissions::Many(vec![(p1, v1), (p2, v2)])
9561 }
9562 (StageEmissions::One(p, v), StageEmissions::Many(mut vec))
9563 | (StageEmissions::Many(mut vec), StageEmissions::One(p, v)) => {
9564 vec.push((p, v));
9565 StageEmissions::Many(vec)
9566 }
9567 (StageEmissions::Many(mut v1), StageEmissions::Many(v2)) => {
9568 v1.extend(v2);
9569 StageEmissions::Many(v1)
9570 }
9571 (a, b) => {
9572 let mut all = Vec::new();
9573 push_emissions(&mut all, a);
9574 push_emissions(&mut all, b);
9575 StageEmissions::Many(all)
9576 }
9577 }
9578}
9579
9580fn push_emissions(out: &mut Vec<(PortId, DatumValue)>, emissions: StageEmissions) {
9581 match emissions {
9582 StageEmissions::None => {}
9583 StageEmissions::One(p, v) => out.push((p, v)),
9584 StageEmissions::Two((p1, v1), (p2, v2)) => {
9585 out.push((p1, v1));
9586 out.push((p2, v2));
9587 }
9588 StageEmissions::Many(vec) => out.extend(vec),
9589 }
9590}