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