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