1use std::fmt::Debug;
17use std::future::Future;
18#[cfg(feature = "tokio")]
19use std::marker::PhantomData;
20use std::num::ParseIntError;
21#[cfg(feature = "tokio")]
22use std::time::Duration;
23
24#[cfg(feature = "tokio")]
25use bytes::{Bytes, BytesMut};
26use futures::stream::Stream as FuturesStream;
27use proc_macro2::Span;
28use quote::quote;
29#[cfg(feature = "tokio")]
30use serde::de::DeserializeOwned;
31use serde::{Deserialize, Serialize};
32use slotmap::{Key, new_key_type};
33#[cfg(feature = "tokio")]
34use stageleft::quote_type;
35use stageleft::runtime_support::{FreeVariableWithContextWithProps, QuoteTokens};
36use stageleft::{QuotedWithContext, q};
37use syn::parse_quote;
38#[cfg(feature = "tokio")]
39use tokio_util::codec::{Decoder, Encoder, LengthDelimitedCodec};
40
41#[cfg(feature = "tokio")]
42use crate::compile::builder::ExternalPortId;
43#[cfg(feature = "tokio")]
44use crate::compile::ir::DebugInstantiate;
45use crate::compile::ir::{
46 ClusterMembersState, HydroIrOpMetadata, HydroNode, HydroRoot, HydroSource,
47};
48use crate::forward_handle::ForwardRef;
49#[cfg(stageleft_runtime)]
50use crate::forward_handle::{CycleCollection, ForwardHandle};
51use crate::live_collections::boundedness::{Bounded, Unbounded};
52use crate::live_collections::keyed_stream::KeyedStream;
53use crate::live_collections::singleton::Singleton;
54#[cfg(feature = "sim")]
55#[cfg(stageleft_runtime)]
56use crate::live_collections::stream::networking::serialize_bincode;
57use crate::live_collections::stream::{
58 ExactlyOnce, NoOrder, Ordering, Retries, Stream, TotalOrder,
59};
60#[cfg(stageleft_runtime)]
61use crate::location::dynamic::DynLocation;
62use crate::location::dynamic::{ClusterConsistency, LocationId};
63#[cfg(feature = "tokio")]
64use crate::location::external_process::{
65 ExternalBincodeBidi, ExternalBincodeSink, ExternalBytesPort, Many, NotMany,
66};
67use crate::nondet::NonDet;
68#[cfg(feature = "tokio")]
69use crate::properties::manual_proof;
70#[cfg(feature = "sim")]
71use crate::sim::SimSender;
72use crate::staging_util::get_this_crate;
73
74pub mod dynamic;
75
76pub mod external_process;
77pub use external_process::External;
78
79pub mod process;
80pub use process::Process;
81
82pub mod cluster;
83pub use cluster::Cluster;
84
85pub mod member_id;
86pub use member_id::{MemberId, TaglessMemberId};
87
88pub mod tick;
89pub use tick::{Atomic, Tick};
90
91#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
94pub enum MembershipEvent {
95 Joined,
97 Left,
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
107pub enum NetworkHint {
108 Auto,
110 TcpPort(Option<u16>),
115}
116
117#[track_caller]
118pub(crate) fn check_matching_location<'a, L: Location<'a>>(l1: &L, l2: &L) {
119 assert_eq!(Location::id(l1), Location::id(l2), "locations do not match");
120}
121
122#[stageleft::export(LocationKey)]
123new_key_type! {
124 pub struct LocationKey;
126}
127
128impl std::fmt::Display for LocationKey {
129 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130 write!(f, "loc{:?}", self.data()) }
132}
133
134impl std::str::FromStr for LocationKey {
137 type Err = Option<ParseIntError>;
138
139 fn from_str(s: &str) -> Result<Self, Self::Err> {
140 let nvn = s.strip_prefix("loc").ok_or(None)?;
141 let (idx, ver) = nvn.split_once('v').ok_or(None)?;
142 let idx: u64 = idx.parse()?;
143 let ver: u64 = ver.parse()?;
144 Ok(slotmap::KeyData::from_ffi((ver << 32) | idx).into())
145 }
146}
147
148impl LocationKey {
149 pub const FIRST: Self = Self(slotmap::KeyData::from_ffi(0x0000000100000001)); #[cfg(test)]
155 pub const TEST_KEY_1: Self = Self(slotmap::KeyData::from_ffi(0x000000FF00000001)); #[cfg(test)]
159 pub const TEST_KEY_2: Self = Self(slotmap::KeyData::from_ffi(0x000000FF00000002)); }
161
162impl<Ctx> FreeVariableWithContextWithProps<Ctx, ()> for LocationKey {
164 type O = LocationKey;
165
166 fn to_tokens(self, _ctx: &Ctx) -> (QuoteTokens, ())
167 where
168 Self: Sized,
169 {
170 let root = get_this_crate();
171 let n = Key::data(&self).as_ffi();
172 (
173 QuoteTokens {
174 prelude: None,
175 expr: Some(quote! {
176 #root::location::LocationKey::from(#root::runtime_support::slotmap::KeyData::from_ffi(#n))
177 }),
178 },
179 (),
180 )
181 }
182}
183
184#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize)]
186pub enum LocationType {
187 Process,
189 Cluster,
191 External,
193}
194
195pub trait TopLevel<'a>: Location<'a> {}
197
198#[cfg(feature = "sim")]
199#[cfg(stageleft_runtime)]
200fn register_serialized_external_input<'a, At, L, T>(
201 at: &At,
202 from: &External<'_, L>,
203 deserialize_fn: syn::Expr,
204) -> (
205 ExternalPortId,
206 Stream<T, At::DropConsistency, Unbounded, TotalOrder, ExactlyOnce>,
207)
208where
209 At: TopLevel<'a> + Sized,
210{
211 let (port_id, stream, sink) = at.register_serialized_single_client::<_, T, ()>(
212 from,
213 serialize_bincode::<()>(false),
214 deserialize_fn,
215 );
216 sink.complete(stream.location().source_iter(q!([])));
217
218 (port_id, stream)
219}
220
221#[expect(
235 private_bounds,
236 reason = "only internal Hydro code can define location types"
237)]
238pub trait Location<'a>: DynLocation {
239 type Root: Location<'a>;
244
245 type SimHookScope: crate::sim_hooks::BindableHookScope;
254
255 type DropConsistency: Location<'a, DropConsistency = Self::DropConsistency, SimHookScope = Self::SimHookScope>;
257
258 fn root(&self) -> Self::Root;
263
264 fn drop_consistency(&self) -> Self::DropConsistency;
266 fn consistency() -> Option<ClusterConsistency>;
268
269 fn with_consistency_of<L2: Location<'a, DropConsistency = Self::DropConsistency>>(&self) -> L2 {
271 L2::from_drop_consistency(self.drop_consistency())
272 }
273
274 #[doc(hidden)]
275 fn from_drop_consistency(l2: Self::DropConsistency) -> Self;
276
277 fn try_tick(&self) -> Option<Tick<Self>> {
284 if Self::is_top_level() {
285 let id = if let LocationId::Atomic { .. } = self.id() {
286 None
287 } else {
288 Some(self.flow_state().borrow_mut().next_clock_id())
289 };
290 Some(Tick {
291 id,
292 l: self.clone(),
293 })
294 } else {
295 None
296 }
297 }
298
299 fn id(&self) -> LocationId {
301 DynLocation::dyn_id(self)
302 }
303
304 fn tick(&self) -> Tick<Self> {
330 self.try_tick().expect("cannot create nested ticks")
331 }
332
333 fn spin(&self) -> Stream<(), Self, Unbounded, TotalOrder, ExactlyOnce>
358 where
359 Self: TopLevel<'a> + Sized,
360 {
361 Stream::new(
362 self.clone(),
363 HydroNode::Source {
364 source: HydroSource::Spin(),
365 metadata: self.new_node_metadata(Stream::<
366 (),
367 Self,
368 Unbounded,
369 TotalOrder,
370 ExactlyOnce,
371 >::collection_kind()),
372 },
373 )
374 }
375
376 fn source_stream<T, E>(
397 &self,
398 e: impl QuotedWithContext<'a, E, Self>,
399 ) -> Stream<T, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce>
400 where
401 E: FuturesStream<Item = T> + Unpin,
402 Self: TopLevel<'a> + Sized,
403 {
404 let e = e.splice_untyped_ctx(self);
405
406 let target_location = self.drop_consistency();
407 Stream::new(
408 target_location.clone(),
409 HydroNode::Source {
410 source: HydroSource::Stream(e.into()),
411 metadata: target_location.new_node_metadata(Stream::<
412 T,
413 Self::DropConsistency,
414 Unbounded,
415 TotalOrder,
416 ExactlyOnce,
417 >::collection_kind()),
418 },
419 )
420 }
421
422 fn source_iter<T, E>(
444 &self,
445 e: impl QuotedWithContext<'a, E, Self>,
446 ) -> Stream<T, Self::DropConsistency, Bounded, TotalOrder, ExactlyOnce>
447 where
448 E: IntoIterator<Item = T>,
449 Self: Sized,
450 {
451 let e = e.splice_typed_ctx(self);
452
453 let target_location = self.drop_consistency();
454 Stream::new(
455 target_location.clone(),
456 HydroNode::Source {
457 source: HydroSource::Iter(e.into()),
458 metadata: target_location.new_node_metadata(Stream::<
459 T,
460 Self::DropConsistency,
461 Bounded,
462 TotalOrder,
463 ExactlyOnce,
464 >::collection_kind()),
465 },
466 )
467 }
468
469 #[deprecated(note = "use .source_cluster_membership_stream(...) instead")]
470 fn source_cluster_members<C: 'a>(
509 &self,
510 cluster: &Cluster<'a, C>,
511 nondet_start: NonDet,
512 ) -> KeyedStream<MemberId<C>, MembershipEvent, Self::DropConsistency, Unbounded>
513 where
514 Self: TopLevel<'a> + Sized,
515 {
516 self.source_cluster_membership_stream(cluster, nondet_start)
517 }
518
519 fn source_cluster_membership_stream<C: 'a>(
558 &self,
559 cluster: &Cluster<'a, C>,
560 _nondet_start: NonDet,
561 ) -> KeyedStream<MemberId<C>, MembershipEvent, Self::DropConsistency, Unbounded>
562 where
563 Self: TopLevel<'a> + Sized,
564 {
565 let target_consistency = self.drop_consistency();
566 Stream::new(
567 target_consistency.clone(),
568 HydroNode::Source {
569 source: HydroSource::ClusterMembers(cluster.id(), ClusterMembersState::Uninit),
570 metadata: target_consistency.new_node_metadata(Stream::<
571 (TaglessMemberId, MembershipEvent),
572 Self,
573 Unbounded,
574 TotalOrder,
575 ExactlyOnce,
576 >::collection_kind(
577 )),
578 },
579 )
580 .map(q!(|(k, v)| (MemberId::from_tagless(k), v)))
581 .into_keyed()
582 }
583
584 #[cfg(feature = "tokio")]
592 fn source_external_bytes<L>(
593 &self,
594 from: &External<'_, L>,
595 ) -> (
596 ExternalBytesPort,
597 Stream<BytesMut, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce>,
598 )
599 where
600 Self: TopLevel<'a> + Sized,
601 {
602 let (port, stream, sink) =
603 self.bind_single_client::<_, Bytes, LengthDelimitedCodec>(from, NetworkHint::Auto);
604
605 sink.complete(stream.location().source_iter(q!([])));
606
607 (port, stream)
608 }
609
610 #[cfg(feature = "tokio")]
617 fn source_external_bincode<L, T, O: Ordering, R: Retries>(
618 &self,
619 from: &External<'_, L>,
620 ) -> (
621 ExternalBincodeSink<T, NotMany, O, R>,
622 Stream<T, Self::DropConsistency, Unbounded, O, R>,
623 )
624 where
625 Self: TopLevel<'a> + Sized,
626 T: Serialize + DeserializeOwned,
627 {
628 let (port, stream, sink) = self.bind_single_client_bincode::<_, T, ()>(from);
629 sink.complete(stream.location().source_iter(q!([])));
630
631 (
632 ExternalBincodeSink {
633 process_key: from.key,
634 port_id: port.port_id,
635 _phantom: PhantomData,
636 },
637 stream.weaken_ordering().weaken_retries(),
638 )
639 }
640
641 #[cfg(feature = "sim")]
647 fn sim_input<T, O: Ordering, R: Retries>(
648 &self,
649 ) -> (
650 SimSender<T, O, R>,
651 Stream<T, Self::DropConsistency, Unbounded, O, R>,
652 )
653 where
654 Self: TopLevel<'a> + Sized,
655 T: Serialize + DeserializeOwned,
656 {
657 self.sim_input_with::<crate::sim::codec::RmpCodec, T, O, R>()
658 }
659
660 #[cfg(feature = "sim")]
668 fn sim_input_with<C: crate::sim::codec::SimCodec<T>, T, O: Ordering, R: Retries>(
669 &self,
670 ) -> (
671 SimSender<T, O, R>,
672 Stream<T, Self::DropConsistency, Unbounded, O, R>,
673 )
674 where
675 Self: TopLevel<'a> + Sized,
676 {
677 let external_location: External<'a, ()> = External {
678 key: LocationKey::FIRST,
679 flow_state: self.flow_state().clone(),
680 _phantom: PhantomData,
681 };
682
683 let (external_port_id, stream) = register_serialized_external_input(
684 self,
685 &external_location,
686 crate::sim::codec::staged_deserialize::<T, C>(),
687 );
688
689 (
690 SimSender(external_port_id, PhantomData, C::encode),
691 stream.weaken_ordering().weaken_retries(),
692 )
693 }
694
695 fn embedded_input<T, O: Ordering, R: Retries>(
705 &self,
706 name: impl Into<String>,
707 ) -> Stream<T, Self::DropConsistency, Unbounded, O, R>
708 where
709 Self: TopLevel<'a> + Sized,
710 {
711 let ident = syn::Ident::new(&name.into(), Span::call_site());
712
713 let target_location = self.drop_consistency();
714 Stream::new(
715 target_location.clone(),
716 HydroNode::Source {
717 source: HydroSource::Embedded(ident),
718 metadata: target_location
719 .new_node_metadata(Stream::<T, Self, Unbounded, O, R>::collection_kind()),
720 },
721 )
722 }
723
724 fn embedded_singleton_input<T>(
730 &self,
731 name: impl Into<String>,
732 ) -> Singleton<T, Self::DropConsistency, Bounded>
733 where
734 Self: TopLevel<'a> + Sized,
735 {
736 let ident = syn::Ident::new(&name.into(), Span::call_site());
737
738 let target_location = self.drop_consistency();
739 Singleton::new(
740 target_location.clone(),
741 HydroNode::Source {
742 source: HydroSource::EmbeddedSingleton(ident),
743 metadata: target_location
744 .new_node_metadata(Singleton::<T, Self, Bounded>::collection_kind()),
745 },
746 )
747 }
748
749 #[cfg(feature = "tokio")]
794 #[expect(clippy::type_complexity, reason = "stream markers")]
795 fn bind_single_client<L, T, Codec: Encoder<T> + Decoder>(
796 &self,
797 from: &External<'_, L>,
798 port_hint: NetworkHint,
799 ) -> (
800 ExternalBytesPort<NotMany>,
801 Stream<<Codec as Decoder>::Item, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce>,
802 ForwardHandle<'a, Stream<T, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce>>,
803 )
804 where
805 Self: TopLevel<'a> + Sized,
806 {
807 let next_external_port_id = from.flow_state.borrow_mut().next_external_port();
808 let target_consistency = self.drop_consistency();
809
810 let (fwd_ref, to_sink) = target_consistency.forward_ref::<Stream<
811 T,
812 Self::DropConsistency,
813 Unbounded,
814 TotalOrder,
815 ExactlyOnce,
816 >>();
817 let mut flow_state_borrow = self.flow_state().borrow_mut();
818
819 flow_state_borrow.push_root(HydroRoot::SendExternal {
820 to_external_key: from.key,
821 to_port_id: next_external_port_id,
822 to_many: false,
823 unpaired: false,
824 serialize_fn: None,
825 instantiate_fn: DebugInstantiate::Building,
826 input: Box::new(to_sink.ir_node.replace(HydroNode::Placeholder)),
827 op_metadata: HydroIrOpMetadata::new(),
828 });
829 drop(flow_state_borrow);
830
831 let raw_stream: Stream<
832 Result<<Codec as Decoder>::Item, <Codec as Decoder>::Error>,
833 Self::DropConsistency,
834 Unbounded,
835 TotalOrder,
836 ExactlyOnce,
837 > = Stream::new(
838 target_consistency.clone(),
839 HydroNode::ExternalInput {
840 from_external_key: from.key,
841 from_port_id: next_external_port_id,
842 from_many: false,
843 codec_type: quote_type::<Codec>().into(),
844 port_hint,
845 instantiate_fn: DebugInstantiate::Building,
846 deserialize_fn: None,
847 metadata: target_consistency.new_node_metadata(Stream::<
848 Result<<Codec as Decoder>::Item, <Codec as Decoder>::Error>,
849 Self::DropConsistency,
850 Unbounded,
851 TotalOrder,
852 ExactlyOnce,
853 >::collection_kind(
854 )),
855 },
856 );
857
858 (
859 ExternalBytesPort {
860 process_key: from.key,
861 port_id: next_external_port_id,
862 _phantom: PhantomData,
863 },
864 raw_stream.flatten_ordered(),
865 fwd_ref,
866 )
867 }
868
869 #[doc(hidden)]
871 #[cfg(feature = "tokio")]
872 #[expect(clippy::type_complexity, reason = "stream markers")]
873 fn register_serialized_single_client<L, InT, OutT>(
874 &self,
875 from: &External<'_, L>,
876 serialize_fn: syn::Expr,
877 deserialize_fn: syn::Expr,
878 ) -> (
879 ExternalPortId,
880 Stream<InT, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce>,
881 ForwardHandle<'a, Stream<OutT, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce>>,
882 )
883 where
884 Self: TopLevel<'a> + Sized,
885 {
886 let next_external_port_id = from.flow_state.borrow_mut().next_external_port();
887
888 let target_consistency = self.drop_consistency();
889 let (fwd_ref, to_sink) = target_consistency.forward_ref::<Stream<
890 OutT,
891 Self::DropConsistency,
892 Unbounded,
893 TotalOrder,
894 ExactlyOnce,
895 >>();
896 let mut flow_state_borrow = self.flow_state().borrow_mut();
897
898 flow_state_borrow.push_root(HydroRoot::SendExternal {
899 to_external_key: from.key,
900 to_port_id: next_external_port_id,
901 to_many: false,
902 unpaired: false,
903 serialize_fn: Some(serialize_fn.into()),
904 instantiate_fn: DebugInstantiate::Building,
905 input: Box::new(to_sink.ir_node.replace(HydroNode::Placeholder)),
906 op_metadata: HydroIrOpMetadata::new(),
907 });
908 drop(flow_state_borrow);
909
910 let raw_stream: Stream<InT, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce> =
911 Stream::new(
912 target_consistency.clone(),
913 HydroNode::ExternalInput {
914 from_external_key: from.key,
915 from_port_id: next_external_port_id,
916 from_many: false,
917 codec_type: quote_type::<LengthDelimitedCodec>().into(),
918 port_hint: NetworkHint::Auto,
919 instantiate_fn: DebugInstantiate::Building,
920 deserialize_fn: Some(deserialize_fn.into()),
921 metadata: target_consistency.new_node_metadata(Stream::<
922 InT,
923 Self::DropConsistency,
924 Unbounded,
925 TotalOrder,
926 ExactlyOnce,
927 >::collection_kind(
928 )),
929 },
930 );
931
932 (next_external_port_id, raw_stream, fwd_ref)
933 }
934
935 #[cfg(feature = "tokio")]
945 #[expect(clippy::type_complexity, reason = "stream markers")]
946 fn bind_single_client_bincode<L, InT: DeserializeOwned, OutT: Serialize>(
947 &self,
948 from: &External<'_, L>,
949 ) -> (
950 ExternalBincodeBidi<InT, OutT, NotMany>,
951 Stream<InT, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce>,
952 ForwardHandle<'a, Stream<OutT, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce>>,
953 )
954 where
955 Self: TopLevel<'a> + Sized,
956 {
957 let root = get_this_crate();
958
959 let out_t_type = quote_type::<OutT>();
960 let ser_fn: syn::Expr = syn::parse_quote! {
961 #root::runtime_support::stageleft::runtime_support::fn1_type_hint::<#out_t_type, _>(
962 |b| #root::runtime_support::bincode::serialize(&b).unwrap().into()
963 )
964 };
965
966 let in_t_type = quote_type::<InT>();
967 let deser_fn: syn::Expr = syn::parse_quote! {
968 |res| {
969 let b = res.unwrap();
970 #root::runtime_support::bincode::deserialize::<#in_t_type>(&b).unwrap()
971 }
972 };
973
974 let (port_id, raw_stream, fwd_ref) =
975 self.register_serialized_single_client::<_, InT, OutT>(from, ser_fn, deser_fn);
976
977 (
978 ExternalBincodeBidi {
979 process_key: from.key,
980 port_id,
981 _phantom: PhantomData,
982 },
983 raw_stream,
984 fwd_ref,
985 )
986 }
987
988 #[cfg(feature = "tokio")]
1000 #[expect(clippy::type_complexity, reason = "stream markers")]
1001 fn bidi_external_many_bytes<L, T, Codec: Encoder<T> + Decoder>(
1002 &self,
1003 from: &External<'_, L>,
1004 port_hint: NetworkHint,
1005 ) -> (
1006 ExternalBytesPort<Many>,
1007 KeyedStream<
1008 u64,
1009 <Codec as Decoder>::Item,
1010 Self::DropConsistency,
1011 Unbounded,
1012 TotalOrder,
1013 ExactlyOnce,
1014 >,
1015 KeyedStream<
1016 u64,
1017 MembershipEvent,
1018 Self::DropConsistency,
1019 Unbounded,
1020 TotalOrder,
1021 ExactlyOnce,
1022 >,
1023 ForwardHandle<
1024 'a,
1025 KeyedStream<u64, T, Self::DropConsistency, Unbounded, NoOrder, ExactlyOnce>,
1026 >,
1027 )
1028 where
1029 Self: TopLevel<'a> + Sized,
1030 {
1031 let next_external_port_id = from.flow_state.borrow_mut().next_external_port();
1032
1033 let target_consistency = self.drop_consistency();
1034 let (fwd_ref, to_sink) = target_consistency.forward_ref::<KeyedStream<
1035 u64,
1036 T,
1037 Self::DropConsistency,
1038 Unbounded,
1039 NoOrder,
1040 ExactlyOnce,
1041 >>();
1042 let to_sink_input = Box::new(to_sink.entries().ir_node.replace(HydroNode::Placeholder));
1043 let mut flow_state_borrow = self.flow_state().borrow_mut();
1044
1045 flow_state_borrow.push_root(HydroRoot::SendExternal {
1046 to_external_key: from.key,
1047 to_port_id: next_external_port_id,
1048 to_many: true,
1049 unpaired: false,
1050 serialize_fn: None,
1051 instantiate_fn: DebugInstantiate::Building,
1052 input: to_sink_input,
1053 op_metadata: HydroIrOpMetadata::new(),
1054 });
1055 drop(flow_state_borrow);
1056
1057 let raw_stream: Stream<
1058 Result<(u64, <Codec as Decoder>::Item), <Codec as Decoder>::Error>,
1059 Self::DropConsistency,
1060 Unbounded,
1061 TotalOrder,
1062 ExactlyOnce,
1063 > = Stream::new(
1064 target_consistency.clone(),
1065 HydroNode::ExternalInput {
1066 from_external_key: from.key,
1067 from_port_id: next_external_port_id,
1068 from_many: true,
1069 codec_type: quote_type::<Codec>().into(),
1070 port_hint,
1071 instantiate_fn: DebugInstantiate::Building,
1072 deserialize_fn: None,
1073 metadata: target_consistency.new_node_metadata(Stream::<
1074 Result<(u64, <Codec as Decoder>::Item), <Codec as Decoder>::Error>,
1075 Self::DropConsistency,
1076 Unbounded,
1077 TotalOrder,
1078 ExactlyOnce,
1079 >::collection_kind(
1080 )),
1081 },
1082 );
1083
1084 let membership_stream_ident = syn::Ident::new(
1085 &format!(
1086 "__hydro_deploy_many_{}_{}_membership",
1087 from.key, next_external_port_id
1088 ),
1089 Span::call_site(),
1090 );
1091 let membership_stream_expr: syn::Expr = parse_quote!(#membership_stream_ident);
1092 let raw_membership_stream: KeyedStream<
1093 u64,
1094 bool,
1095 Self::DropConsistency,
1096 Unbounded,
1097 TotalOrder,
1098 ExactlyOnce,
1099 > = KeyedStream::new(
1100 target_consistency.clone(),
1101 HydroNode::Source {
1102 source: HydroSource::Stream(membership_stream_expr.into()),
1103 metadata: target_consistency.new_node_metadata(KeyedStream::<
1104 u64,
1105 bool,
1106 Self::DropConsistency,
1107 Unbounded,
1108 TotalOrder,
1109 ExactlyOnce,
1110 >::collection_kind(
1111 )),
1112 },
1113 );
1114
1115 (
1116 ExternalBytesPort {
1117 process_key: from.key,
1118 port_id: next_external_port_id,
1119 _phantom: PhantomData,
1120 },
1121 raw_stream
1122 .flatten_ordered() .into_keyed(),
1124 raw_membership_stream.map(q!(|join| {
1125 if join {
1126 MembershipEvent::Joined
1127 } else {
1128 MembershipEvent::Left
1129 }
1130 })),
1131 fwd_ref,
1132 )
1133 }
1134
1135 #[cfg(feature = "tokio")]
1151 #[expect(clippy::type_complexity, reason = "stream markers")]
1152 fn bidi_external_many_bincode<L, InT: DeserializeOwned, OutT: Serialize>(
1153 &self,
1154 from: &External<'_, L>,
1155 ) -> (
1156 ExternalBincodeBidi<InT, OutT, Many>,
1157 KeyedStream<u64, InT, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce>,
1158 KeyedStream<
1159 u64,
1160 MembershipEvent,
1161 Self::DropConsistency,
1162 Unbounded,
1163 TotalOrder,
1164 ExactlyOnce,
1165 >,
1166 ForwardHandle<
1167 'a,
1168 KeyedStream<u64, OutT, Self::DropConsistency, Unbounded, NoOrder, ExactlyOnce>,
1169 >,
1170 )
1171 where
1172 Self: TopLevel<'a> + Sized,
1173 {
1174 let next_external_port_id = from.flow_state.borrow_mut().next_external_port();
1175
1176 let target_consistency = self.drop_consistency();
1177 let (fwd_ref, to_sink) = target_consistency.forward_ref::<KeyedStream<
1178 u64,
1179 OutT,
1180 Self::DropConsistency,
1181 Unbounded,
1182 NoOrder,
1183 ExactlyOnce,
1184 >>();
1185 let to_sink_input = Box::new(to_sink.entries().ir_node.replace(HydroNode::Placeholder));
1186 let mut flow_state_borrow = self.flow_state().borrow_mut();
1187
1188 let root = get_this_crate();
1189
1190 let out_t_type = quote_type::<OutT>();
1191 let ser_fn: syn::Expr = syn::parse_quote! {
1192 #root::runtime_support::stageleft::runtime_support::fn1_type_hint::<(u64, #out_t_type), _>(
1193 |(id, b)| (id, #root::runtime_support::bincode::serialize(&b).unwrap().into())
1194 )
1195 };
1196
1197 flow_state_borrow.push_root(HydroRoot::SendExternal {
1198 to_external_key: from.key,
1199 to_port_id: next_external_port_id,
1200 to_many: true,
1201 unpaired: false,
1202 serialize_fn: Some(ser_fn.into()),
1203 instantiate_fn: DebugInstantiate::Building,
1204 input: to_sink_input,
1205 op_metadata: HydroIrOpMetadata::new(),
1206 });
1207 drop(flow_state_borrow);
1208
1209 let in_t_type = quote_type::<InT>();
1210
1211 let deser_fn: syn::Expr = syn::parse_quote! {
1212 |res| {
1213 let (id, b) = res.unwrap();
1214 (id, #root::runtime_support::bincode::deserialize::<#in_t_type>(&b).unwrap())
1215 }
1216 };
1217
1218 let raw_stream: KeyedStream<
1219 u64,
1220 InT,
1221 Self::DropConsistency,
1222 Unbounded,
1223 TotalOrder,
1224 ExactlyOnce,
1225 > = KeyedStream::new(
1226 target_consistency.clone(),
1227 HydroNode::ExternalInput {
1228 from_external_key: from.key,
1229 from_port_id: next_external_port_id,
1230 from_many: true,
1231 codec_type: quote_type::<LengthDelimitedCodec>().into(),
1232 port_hint: NetworkHint::Auto,
1233 instantiate_fn: DebugInstantiate::Building,
1234 deserialize_fn: Some(deser_fn.into()),
1235 metadata: target_consistency.new_node_metadata(KeyedStream::<
1236 u64,
1237 InT,
1238 Self::DropConsistency,
1239 Unbounded,
1240 TotalOrder,
1241 ExactlyOnce,
1242 >::collection_kind(
1243 )),
1244 },
1245 );
1246
1247 let membership_stream_ident = syn::Ident::new(
1248 &format!(
1249 "__hydro_deploy_many_{}_{}_membership",
1250 from.key, next_external_port_id
1251 ),
1252 Span::call_site(),
1253 );
1254 let membership_stream_expr: syn::Expr = parse_quote!(#membership_stream_ident);
1255 let raw_membership_stream: KeyedStream<
1256 u64,
1257 bool,
1258 Self::DropConsistency,
1259 Unbounded,
1260 TotalOrder,
1261 ExactlyOnce,
1262 > = KeyedStream::new(
1263 target_consistency.clone(),
1264 HydroNode::Source {
1265 source: HydroSource::Stream(membership_stream_expr.into()),
1266 metadata: target_consistency.new_node_metadata(KeyedStream::<
1267 u64,
1268 bool,
1269 Self::DropConsistency,
1270 Unbounded,
1271 TotalOrder,
1272 ExactlyOnce,
1273 >::collection_kind(
1274 )),
1275 },
1276 );
1277
1278 (
1279 ExternalBincodeBidi {
1280 process_key: from.key,
1281 port_id: next_external_port_id,
1282 _phantom: PhantomData,
1283 },
1284 raw_stream,
1285 raw_membership_stream.map(q!(|join| {
1286 if join {
1287 MembershipEvent::Joined
1288 } else {
1289 MembershipEvent::Left
1290 }
1291 })),
1292 fwd_ref,
1293 )
1294 }
1295
1296 fn sidecar_bidi<InT: 'static, OutT: 'static, F>(
1349 &self,
1350 sidecar: impl QuotedWithContext<'a, F, Self>,
1351 ) -> (
1352 Stream<InT, Self, Unbounded, TotalOrder, ExactlyOnce>,
1353 ForwardHandle<'a, Stream<OutT, Self, Unbounded, NoOrder, ExactlyOnce>>,
1354 )
1355 where
1356 Self: Sized + TopLevel<'a>,
1357 {
1358 let location_key = Location::id(self).key();
1359
1360 let sidecar_id = self.flow_state().borrow_mut().next_sidecar_id();
1361 let (stream_ident, sink_ident) = sidecar_id.idents();
1362
1363 let sidecar_closure: syn::Expr = sidecar.splice_untyped_ctx(self);
1364 self.flow_state()
1365 .borrow_mut()
1366 .sidecars
1367 .push(crate::compile::builder::Sidecar::Bidi {
1368 location_key,
1369 sidecar_id,
1370 sidecar_closure: Box::new(sidecar_closure),
1371 });
1372
1373 let source_expr: syn::Expr = parse_quote! {
1375 #stream_ident
1376 };
1377 let inbound: Stream<InT, Self, Unbounded, TotalOrder, ExactlyOnce> = Stream::new(
1378 self.clone(),
1379 HydroNode::Source {
1380 source: HydroSource::Stream(source_expr.into()),
1381 metadata: self.new_node_metadata(Stream::<
1382 InT,
1383 Self,
1384 Unbounded, TotalOrder, ExactlyOnce,
1387 >::collection_kind()),
1388 },
1389 );
1390
1391 let (fwd_ref, to_sink): (
1393 ForwardHandle<'a, Stream<OutT, Self, Unbounded, NoOrder, ExactlyOnce>>,
1394 Stream<OutT, Self, Unbounded, NoOrder, ExactlyOnce>,
1395 ) = self.forward_ref();
1396
1397 let sink_expr: syn::Expr = parse_quote! {
1398 #sink_ident
1399 };
1400
1401 let sink_input_ir = to_sink.ir_node.replace(HydroNode::Placeholder);
1402 self.flow_state()
1403 .borrow_mut()
1404 .try_push_root(HydroRoot::DestSink {
1405 sink: sink_expr.into(),
1406 input: Box::new(sink_input_ir),
1407 op_metadata: HydroIrOpMetadata::new(),
1408 });
1409
1410 (inbound, fwd_ref)
1411 }
1412
1413 fn singleton<T>(
1433 &self,
1434 e: impl QuotedWithContext<'a, T, Self>,
1435 ) -> Singleton<T, Self::DropConsistency, Bounded>
1436 where
1437 Self: Sized,
1438 {
1439 let e = e.splice_untyped_ctx(self);
1440
1441 let target_location = self.drop_consistency();
1442 Singleton::new(
1443 target_location.clone(),
1444 HydroNode::SingletonSource {
1445 value: e.into(),
1446 first_tick_only: false,
1447 metadata: target_location.new_node_metadata(Singleton::<
1448 T,
1449 Self::DropConsistency,
1450 Bounded,
1451 >::collection_kind()),
1452 },
1453 )
1454 }
1455
1456 fn singleton_future<F>(
1479 &self,
1480 e: impl QuotedWithContext<'a, F, Self>,
1481 ) -> Singleton<F::Output, Self::DropConsistency, Bounded>
1482 where
1483 F: Future,
1484 Self: Sized,
1485 {
1486 self.singleton(e).resolve_future_blocking()
1487 }
1488
1489 #[cfg(feature = "tokio")]
1498 fn source_interval(
1499 &self,
1500 interval: impl QuotedWithContext<'a, Duration, Self> + Copy + 'a,
1501 ) -> Stream<(), Self, Unbounded, TotalOrder, ExactlyOnce>
1502 where
1503 Self: TopLevel<'a> + Sized,
1504 {
1505 self.source_stream(q!(tokio_stream::StreamExt::map(
1506 tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(interval)),
1507 |_| ()
1508 )))
1509 .assert_has_consistency_of_trusted(
1510 manual_proof!(),
1511 )
1512 }
1513
1514 #[cfg(feature = "tokio")]
1521 fn source_interval_delayed(
1522 &self,
1523 delay: impl QuotedWithContext<'a, Duration, Self> + Copy + 'a,
1524 interval: impl QuotedWithContext<'a, Duration, Self> + Copy + 'a,
1525 ) -> Stream<(), Self, Unbounded, TotalOrder, ExactlyOnce>
1526 where
1527 Self: TopLevel<'a> + Sized,
1528 {
1529 self.source_stream(q!(tokio_stream::StreamExt::map(
1530 tokio_stream::wrappers::IntervalStream::new(tokio::time::interval_at(
1531 tokio::time::Instant::now() + delay,
1532 interval,
1533 )),
1534 |_| ()
1535 )))
1536 .assert_has_consistency_of_trusted(
1537 manual_proof!(),
1538 )
1539 }
1540
1541 fn forward_ref<S>(&self) -> (ForwardHandle<'a, S>, S)
1581 where
1582 S: CycleCollection<'a, ForwardRef, Location = Self>,
1583 {
1584 let cycle_id = self.flow_state().borrow_mut().next_cycle_id();
1585 (
1586 ForwardHandle::new(cycle_id, Location::id(self)),
1587 S::create_source(cycle_id, self.clone()),
1588 )
1589 }
1590}
1591
1592#[cfg(feature = "deploy")]
1593#[cfg(test)]
1594mod tests {
1595 use std::collections::HashSet;
1596
1597 use futures::{SinkExt, StreamExt};
1598 use hydro_deploy::Deployment;
1599 use stageleft::q;
1600 use tokio_util::codec::LengthDelimitedCodec;
1601
1602 use crate::compile::builder::FlowBuilder;
1603 use crate::live_collections::stream::{ExactlyOnce, TotalOrder};
1604 use crate::location::{Location, NetworkHint};
1605 use crate::nondet::nondet;
1606
1607 #[tokio::test]
1608 async fn top_level_singleton_replay_cardinality() {
1609 let mut deployment = Deployment::new();
1610
1611 let mut flow = FlowBuilder::new();
1612 let node = flow.process::<()>();
1613 let external = flow.external::<()>();
1614
1615 let (in_port, input) =
1616 node.source_external_bincode::<_, _, TotalOrder, ExactlyOnce>(&external);
1617 let singleton = node.singleton(q!(123));
1618 let tick = node.tick();
1619 let out = input
1620 .batch(&tick, nondet!())
1621 .cross_singleton(singleton.clone().snapshot(&tick, nondet!()))
1622 .cross_singleton(
1623 singleton
1624 .snapshot(&tick, nondet!())
1625 .into_stream()
1626 .count(),
1627 )
1628 .all_ticks()
1629 .send_bincode_external(&external);
1630
1631 let nodes = flow
1632 .with_process(&node, deployment.Localhost())
1633 .with_external(&external, deployment.Localhost())
1634 .deploy(&mut deployment);
1635
1636 deployment.deploy().await.unwrap();
1637
1638 let mut external_in = nodes.connect(in_port).await;
1639 let mut external_out = nodes.connect(out).await;
1640
1641 deployment.start().await.unwrap();
1642
1643 external_in.send(1).await.unwrap();
1644 assert_eq!(external_out.next().await.unwrap(), ((1, 123), 1));
1645
1646 external_in.send(2).await.unwrap();
1647 assert_eq!(external_out.next().await.unwrap(), ((2, 123), 1));
1648 }
1649
1650 #[tokio::test]
1651 async fn tick_singleton_replay_cardinality() {
1652 let mut deployment = Deployment::new();
1653
1654 let mut flow = FlowBuilder::new();
1655 let node = flow.process::<()>();
1656 let external = flow.external::<()>();
1657
1658 let (in_port, input) =
1659 node.source_external_bincode::<_, _, TotalOrder, ExactlyOnce>(&external);
1660 let tick = node.tick();
1661 let singleton = tick.singleton(q!(123));
1662 let out = input
1663 .batch(&tick, nondet!())
1664 .cross_singleton(singleton.clone())
1665 .cross_singleton(singleton.into_stream().count())
1666 .all_ticks()
1667 .send_bincode_external(&external);
1668
1669 let nodes = flow
1670 .with_process(&node, deployment.Localhost())
1671 .with_external(&external, deployment.Localhost())
1672 .deploy(&mut deployment);
1673
1674 deployment.deploy().await.unwrap();
1675
1676 let mut external_in = nodes.connect(in_port).await;
1677 let mut external_out = nodes.connect(out).await;
1678
1679 deployment.start().await.unwrap();
1680
1681 external_in.send(1).await.unwrap();
1682 assert_eq!(external_out.next().await.unwrap(), ((1, 123), 1));
1683
1684 external_in.send(2).await.unwrap();
1685 assert_eq!(external_out.next().await.unwrap(), ((2, 123), 1));
1686 }
1687
1688 #[tokio::test]
1689 async fn external_bytes() {
1690 let mut deployment = Deployment::new();
1691
1692 let mut flow = FlowBuilder::new();
1693 let first_node = flow.process::<()>();
1694 let external = flow.external::<()>();
1695
1696 let (in_port, input) = first_node.source_external_bytes(&external);
1697 let out = input.send_bincode_external(&external);
1698
1699 let nodes = flow
1700 .with_process(&first_node, deployment.Localhost())
1701 .with_external(&external, deployment.Localhost())
1702 .deploy(&mut deployment);
1703
1704 deployment.deploy().await.unwrap();
1705
1706 let mut external_in = nodes.connect(in_port).await.1;
1707 let mut external_out = nodes.connect(out).await;
1708
1709 deployment.start().await.unwrap();
1710
1711 external_in.send(vec![1, 2, 3].into()).await.unwrap();
1712
1713 assert_eq!(external_out.next().await.unwrap(), vec![1, 2, 3]);
1714 }
1715
1716 #[tokio::test]
1717 async fn multi_external_source() {
1718 let mut deployment = Deployment::new();
1719
1720 let mut flow = FlowBuilder::new();
1721 let first_node = flow.process::<()>();
1722 let external = flow.external::<()>();
1723
1724 let (in_port, input, _membership, complete_sink) =
1725 first_node.bidi_external_many_bincode(&external);
1726 let out = input.entries().send_bincode_external(&external);
1727 complete_sink.complete(
1728 first_node
1729 .source_iter::<(u64, ()), _>(q!([]))
1730 .into_keyed()
1731 .weaken_ordering(),
1732 );
1733
1734 let nodes = flow
1735 .with_process(&first_node, deployment.Localhost())
1736 .with_external(&external, deployment.Localhost())
1737 .deploy(&mut deployment);
1738
1739 deployment.deploy().await.unwrap();
1740
1741 let (_, mut external_in_1) = nodes.connect_bincode(in_port.clone()).await;
1742 let (_, mut external_in_2) = nodes.connect_bincode(in_port).await;
1743 let external_out = nodes.connect(out).await;
1744
1745 deployment.start().await.unwrap();
1746
1747 external_in_1.send(123).await.unwrap();
1748 external_in_2.send(456).await.unwrap();
1749
1750 assert_eq!(
1751 external_out.take(2).collect::<HashSet<_>>().await,
1752 vec![(0, 123), (1, 456)].into_iter().collect()
1753 );
1754 }
1755
1756 #[tokio::test]
1757 async fn second_connection_only_multi_source() {
1758 let mut deployment = Deployment::new();
1759
1760 let mut flow = FlowBuilder::new();
1761 let first_node = flow.process::<()>();
1762 let external = flow.external::<()>();
1763
1764 let (in_port, input, _membership, complete_sink) =
1765 first_node.bidi_external_many_bincode(&external);
1766 let out = input.entries().send_bincode_external(&external);
1767 complete_sink.complete(
1768 first_node
1769 .source_iter::<(u64, ()), _>(q!([]))
1770 .into_keyed()
1771 .weaken_ordering(),
1772 );
1773
1774 let nodes = flow
1775 .with_process(&first_node, deployment.Localhost())
1776 .with_external(&external, deployment.Localhost())
1777 .deploy(&mut deployment);
1778
1779 deployment.deploy().await.unwrap();
1780
1781 let (_, mut _external_in_1) = nodes.connect_bincode(in_port.clone()).await;
1783 let (_, mut external_in_2) = nodes.connect_bincode(in_port).await;
1784 let mut external_out = nodes.connect(out).await;
1785
1786 deployment.start().await.unwrap();
1787
1788 external_in_2.send(456).await.unwrap();
1789
1790 assert_eq!(external_out.next().await.unwrap(), (1, 456));
1791 }
1792
1793 #[tokio::test]
1794 async fn multi_external_bytes() {
1795 let mut deployment = Deployment::new();
1796
1797 let mut flow = FlowBuilder::new();
1798 let first_node = flow.process::<()>();
1799 let external = flow.external::<()>();
1800
1801 let (in_port, input, _membership, complete_sink) = first_node
1802 .bidi_external_many_bytes::<_, _, LengthDelimitedCodec>(&external, NetworkHint::Auto);
1803 let out = input.entries().send_bincode_external(&external);
1804 complete_sink.complete(
1805 first_node
1806 .source_iter(q!([]))
1807 .into_keyed()
1808 .weaken_ordering(),
1809 );
1810
1811 let nodes = flow
1812 .with_process(&first_node, deployment.Localhost())
1813 .with_external(&external, deployment.Localhost())
1814 .deploy(&mut deployment);
1815
1816 deployment.deploy().await.unwrap();
1817
1818 let mut external_in_1 = nodes.connect(in_port.clone()).await.1;
1819 let mut external_in_2 = nodes.connect(in_port).await.1;
1820 let external_out = nodes.connect(out).await;
1821
1822 deployment.start().await.unwrap();
1823
1824 external_in_1.send(vec![1, 2, 3].into()).await.unwrap();
1825 external_in_2.send(vec![4, 5].into()).await.unwrap();
1826
1827 assert_eq!(
1828 external_out.take(2).collect::<HashSet<_>>().await,
1829 vec![
1830 (0, (&[1u8, 2, 3] as &[u8]).into()),
1831 (1, (&[4u8, 5] as &[u8]).into())
1832 ]
1833 .into_iter()
1834 .collect()
1835 );
1836 }
1837
1838 #[tokio::test]
1839 async fn single_client_external_bytes() {
1840 let mut deployment = Deployment::new();
1841 let mut flow = FlowBuilder::new();
1842 let first_node = flow.process::<()>();
1843 let external = flow.external::<()>();
1844 let (port, input, complete_sink) = first_node
1845 .bind_single_client::<_, _, LengthDelimitedCodec>(&external, NetworkHint::Auto);
1846 complete_sink.complete(input.map(q!(|data| {
1847 let mut resp: Vec<u8> = data.into();
1848 resp.push(42);
1849 resp.into() })));
1851
1852 let nodes = flow
1853 .with_process(&first_node, deployment.Localhost())
1854 .with_external(&external, deployment.Localhost())
1855 .deploy(&mut deployment);
1856
1857 deployment.deploy().await.unwrap();
1858 deployment.start().await.unwrap();
1859
1860 let (mut external_out, mut external_in) = nodes.connect(port).await;
1861
1862 external_in.send(vec![1, 2, 3].into()).await.unwrap();
1863 assert_eq!(
1864 external_out.next().await.unwrap().unwrap(),
1865 vec![1, 2, 3, 42]
1866 );
1867 }
1868
1869 #[tokio::test]
1870 async fn echo_external_bytes() {
1871 let mut deployment = Deployment::new();
1872
1873 let mut flow = FlowBuilder::new();
1874 let first_node = flow.process::<()>();
1875 let external = flow.external::<()>();
1876
1877 let (port, input, _membership, complete_sink) = first_node
1878 .bidi_external_many_bytes::<_, _, LengthDelimitedCodec>(&external, NetworkHint::Auto);
1879 complete_sink
1880 .complete(input.map(q!(|bytes| { bytes.into_iter().map(|x| x + 1).collect() })));
1881
1882 let nodes = flow
1883 .with_process(&first_node, deployment.Localhost())
1884 .with_external(&external, deployment.Localhost())
1885 .deploy(&mut deployment);
1886
1887 deployment.deploy().await.unwrap();
1888
1889 let (mut external_out_1, mut external_in_1) = nodes.connect(port.clone()).await;
1890 let (mut external_out_2, mut external_in_2) = nodes.connect(port).await;
1891
1892 deployment.start().await.unwrap();
1893
1894 external_in_1.send(vec![1, 2, 3].into()).await.unwrap();
1895 external_in_2.send(vec![4, 5].into()).await.unwrap();
1896
1897 assert_eq!(external_out_1.next().await.unwrap().unwrap(), vec![2, 3, 4]);
1898 assert_eq!(external_out_2.next().await.unwrap().unwrap(), vec![5, 6]);
1899 }
1900
1901 #[tokio::test]
1902 async fn echo_external_bincode() {
1903 let mut deployment = Deployment::new();
1904
1905 let mut flow = FlowBuilder::new();
1906 let first_node = flow.process::<()>();
1907 let external = flow.external::<()>();
1908
1909 let (port, input, _membership, complete_sink) =
1910 first_node.bidi_external_many_bincode(&external);
1911 complete_sink.complete(input.map(q!(|text: String| { text.to_uppercase() })));
1912
1913 let nodes = flow
1914 .with_process(&first_node, deployment.Localhost())
1915 .with_external(&external, deployment.Localhost())
1916 .deploy(&mut deployment);
1917
1918 deployment.deploy().await.unwrap();
1919
1920 let (mut external_out_1, mut external_in_1) = nodes.connect_bincode(port.clone()).await;
1921 let (mut external_out_2, mut external_in_2) = nodes.connect_bincode(port).await;
1922
1923 deployment.start().await.unwrap();
1924
1925 external_in_1.send("hi".to_owned()).await.unwrap();
1926 external_in_2.send("hello".to_owned()).await.unwrap();
1927
1928 assert_eq!(external_out_1.next().await.unwrap(), "HI");
1929 assert_eq!(external_out_2.next().await.unwrap(), "HELLO");
1930 }
1931
1932 #[tokio::test]
1933 async fn closure_location_name() {
1934 let mut deployment = Deployment::new();
1935 let mut flow = FlowBuilder::new();
1936
1937 enum ClosureProcess {}
1938
1939 let node = flow.process::<ClosureProcess>();
1940 let external = flow.external::<()>();
1941
1942 let (in_port, input) =
1943 node.source_external_bincode::<_, i32, TotalOrder, ExactlyOnce>(&external);
1944 let out = input.send_bincode_external(&external);
1945
1946 let nodes = flow
1947 .with_process(&node, deployment.Localhost())
1948 .with_external(&external, deployment.Localhost())
1949 .deploy(&mut deployment);
1950
1951 deployment.deploy().await.unwrap();
1952
1953 let mut external_in = nodes.connect(in_port).await;
1954 let mut external_out = nodes.connect(out).await;
1955
1956 deployment.start().await.unwrap();
1957
1958 external_in.send(42).await.unwrap();
1959 assert_eq!(external_out.next().await.unwrap(), 42);
1960 }
1961}