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::{ExactlyOnce, NoOrder, Stream, TotalOrder};
58#[cfg(feature = "tokio")]
59use crate::live_collections::stream::{Ordering, Retries};
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 DropConsistency: Location<'a, DropConsistency = Self::DropConsistency>;
247
248 fn root(&self) -> Self::Root;
253
254 fn drop_consistency(&self) -> Self::DropConsistency;
256 fn consistency() -> Option<ClusterConsistency>;
258
259 fn with_consistency_of<L2: Location<'a, DropConsistency = Self::DropConsistency>>(&self) -> L2 {
261 L2::from_drop_consistency(self.drop_consistency())
262 }
263
264 #[doc(hidden)]
265 fn from_drop_consistency(l2: Self::DropConsistency) -> Self;
266
267 fn try_tick(&self) -> Option<Tick<Self>> {
274 if Self::is_top_level() {
275 let id = if let LocationId::Atomic { .. } = self.id() {
276 None
277 } else {
278 Some(self.flow_state().borrow_mut().next_clock_id())
279 };
280 Some(Tick {
281 id,
282 l: self.clone(),
283 })
284 } else {
285 None
286 }
287 }
288
289 fn id(&self) -> LocationId {
291 DynLocation::dyn_id(self)
292 }
293
294 fn tick(&self) -> Tick<Self> {
320 self.try_tick().expect("cannot create nested ticks")
321 }
322
323 fn spin(&self) -> Stream<(), Self, Unbounded, TotalOrder, ExactlyOnce>
348 where
349 Self: TopLevel<'a> + Sized,
350 {
351 Stream::new(
352 self.clone(),
353 HydroNode::Source {
354 source: HydroSource::Spin(),
355 metadata: self.new_node_metadata(Stream::<
356 (),
357 Self,
358 Unbounded,
359 TotalOrder,
360 ExactlyOnce,
361 >::collection_kind()),
362 },
363 )
364 }
365
366 fn source_stream<T, E>(
387 &self,
388 e: impl QuotedWithContext<'a, E, Self>,
389 ) -> Stream<T, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce>
390 where
391 E: FuturesStream<Item = T> + Unpin,
392 Self: TopLevel<'a> + Sized,
393 {
394 let e = e.splice_untyped_ctx(self);
395
396 let target_location = self.drop_consistency();
397 Stream::new(
398 target_location.clone(),
399 HydroNode::Source {
400 source: HydroSource::Stream(e.into()),
401 metadata: target_location.new_node_metadata(Stream::<
402 T,
403 Self::DropConsistency,
404 Unbounded,
405 TotalOrder,
406 ExactlyOnce,
407 >::collection_kind()),
408 },
409 )
410 }
411
412 fn source_iter<T, E>(
434 &self,
435 e: impl QuotedWithContext<'a, E, Self>,
436 ) -> Stream<T, Self::DropConsistency, Bounded, TotalOrder, ExactlyOnce>
437 where
438 E: IntoIterator<Item = T>,
439 Self: Sized,
440 {
441 let e = e.splice_typed_ctx(self);
442
443 let target_location = self.drop_consistency();
444 Stream::new(
445 target_location.clone(),
446 HydroNode::Source {
447 source: HydroSource::Iter(e.into()),
448 metadata: target_location.new_node_metadata(Stream::<
449 T,
450 Self::DropConsistency,
451 Bounded,
452 TotalOrder,
453 ExactlyOnce,
454 >::collection_kind()),
455 },
456 )
457 }
458
459 #[deprecated(note = "use .source_cluster_membership_stream(...) instead")]
460 fn source_cluster_members<C: 'a>(
499 &self,
500 cluster: &Cluster<'a, C>,
501 nondet_start: NonDet,
502 ) -> KeyedStream<MemberId<C>, MembershipEvent, Self::DropConsistency, Unbounded>
503 where
504 Self: TopLevel<'a> + Sized,
505 {
506 self.source_cluster_membership_stream(cluster, nondet_start)
507 }
508
509 fn source_cluster_membership_stream<C: 'a>(
548 &self,
549 cluster: &Cluster<'a, C>,
550 _nondet_start: NonDet,
551 ) -> KeyedStream<MemberId<C>, MembershipEvent, Self::DropConsistency, Unbounded>
552 where
553 Self: TopLevel<'a> + Sized,
554 {
555 let target_consistency = self.drop_consistency();
556 Stream::new(
557 target_consistency.clone(),
558 HydroNode::Source {
559 source: HydroSource::ClusterMembers(cluster.id(), ClusterMembersState::Uninit),
560 metadata: target_consistency.new_node_metadata(Stream::<
561 (TaglessMemberId, MembershipEvent),
562 Self,
563 Unbounded,
564 TotalOrder,
565 ExactlyOnce,
566 >::collection_kind(
567 )),
568 },
569 )
570 .map(q!(|(k, v)| (MemberId::from_tagless(k), v)))
571 .into_keyed()
572 }
573
574 #[cfg(feature = "tokio")]
582 fn source_external_bytes<L>(
583 &self,
584 from: &External<'_, L>,
585 ) -> (
586 ExternalBytesPort,
587 Stream<BytesMut, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce>,
588 )
589 where
590 Self: TopLevel<'a> + Sized,
591 {
592 let (port, stream, sink) =
593 self.bind_single_client::<_, Bytes, LengthDelimitedCodec>(from, NetworkHint::Auto);
594
595 sink.complete(stream.location().source_iter(q!([])));
596
597 (port, stream)
598 }
599
600 #[cfg(feature = "tokio")]
607 fn source_external_bincode<L, T, O: Ordering, R: Retries>(
608 &self,
609 from: &External<'_, L>,
610 ) -> (
611 ExternalBincodeSink<T, NotMany, O, R>,
612 Stream<T, Self::DropConsistency, Unbounded, O, R>,
613 )
614 where
615 Self: TopLevel<'a> + Sized,
616 T: Serialize + DeserializeOwned,
617 {
618 let (port, stream, sink) = self.bind_single_client_bincode::<_, T, ()>(from);
619 sink.complete(stream.location().source_iter(q!([])));
620
621 (
622 ExternalBincodeSink {
623 process_key: from.key,
624 port_id: port.port_id,
625 _phantom: PhantomData,
626 },
627 stream.weaken_ordering().weaken_retries(),
628 )
629 }
630
631 #[cfg(feature = "sim")]
637 fn sim_input<T, O: Ordering, R: Retries>(
638 &self,
639 ) -> (
640 SimSender<T, O, R>,
641 Stream<T, Self::DropConsistency, Unbounded, O, R>,
642 )
643 where
644 Self: TopLevel<'a> + Sized,
645 T: Serialize + DeserializeOwned,
646 {
647 self.sim_input_with(crate::sim::codec::BincodeCodec)
648 }
649
650 #[cfg(feature = "sim")]
657 fn sim_input_with<T, O: Ordering, R: Retries, C: crate::sim::codec::SimCodec<T>>(
658 &self,
659 _codec: C,
660 ) -> (
661 SimSender<T, O, R>,
662 Stream<T, Self::DropConsistency, Unbounded, O, R>,
663 )
664 where
665 Self: TopLevel<'a> + Sized,
666 {
667 let external_location: External<'a, ()> = External {
668 key: LocationKey::FIRST,
669 flow_state: self.flow_state().clone(),
670 _phantom: PhantomData,
671 };
672
673 let (external_port_id, stream) = register_serialized_external_input(
674 self,
675 &external_location,
676 crate::sim::codec::staged_deserialize::<T, C>(),
677 );
678
679 (
680 SimSender(external_port_id, PhantomData, C::encode),
681 stream.weaken_ordering().weaken_retries(),
682 )
683 }
684
685 fn embedded_input<T>(
691 &self,
692 name: impl Into<String>,
693 ) -> Stream<T, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce>
694 where
695 Self: TopLevel<'a> + Sized,
696 {
697 let ident = syn::Ident::new(&name.into(), Span::call_site());
698
699 let target_location = self.drop_consistency();
700 Stream::new(
701 target_location.clone(),
702 HydroNode::Source {
703 source: HydroSource::Embedded(ident),
704 metadata: target_location.new_node_metadata(Stream::<
705 T,
706 Self,
707 Unbounded,
708 TotalOrder,
709 ExactlyOnce,
710 >::collection_kind()),
711 },
712 )
713 }
714
715 fn embedded_singleton_input<T>(
721 &self,
722 name: impl Into<String>,
723 ) -> Singleton<T, Self::DropConsistency, Bounded>
724 where
725 Self: TopLevel<'a> + Sized,
726 {
727 let ident = syn::Ident::new(&name.into(), Span::call_site());
728
729 let target_location = self.drop_consistency();
730 Singleton::new(
731 target_location.clone(),
732 HydroNode::Source {
733 source: HydroSource::EmbeddedSingleton(ident),
734 metadata: target_location
735 .new_node_metadata(Singleton::<T, Self, Bounded>::collection_kind()),
736 },
737 )
738 }
739
740 #[cfg(feature = "tokio")]
785 #[expect(clippy::type_complexity, reason = "stream markers")]
786 fn bind_single_client<L, T, Codec: Encoder<T> + Decoder>(
787 &self,
788 from: &External<'_, L>,
789 port_hint: NetworkHint,
790 ) -> (
791 ExternalBytesPort<NotMany>,
792 Stream<<Codec as Decoder>::Item, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce>,
793 ForwardHandle<'a, Stream<T, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce>>,
794 )
795 where
796 Self: TopLevel<'a> + Sized,
797 {
798 let next_external_port_id = from.flow_state.borrow_mut().next_external_port();
799 let target_consistency = self.drop_consistency();
800
801 let (fwd_ref, to_sink) = target_consistency.forward_ref::<Stream<
802 T,
803 Self::DropConsistency,
804 Unbounded,
805 TotalOrder,
806 ExactlyOnce,
807 >>();
808 let mut flow_state_borrow = self.flow_state().borrow_mut();
809
810 flow_state_borrow.push_root(HydroRoot::SendExternal {
811 to_external_key: from.key,
812 to_port_id: next_external_port_id,
813 to_many: false,
814 unpaired: false,
815 serialize_fn: None,
816 instantiate_fn: DebugInstantiate::Building,
817 input: Box::new(to_sink.ir_node.replace(HydroNode::Placeholder)),
818 op_metadata: HydroIrOpMetadata::new(),
819 });
820 drop(flow_state_borrow);
821
822 let raw_stream: Stream<
823 Result<<Codec as Decoder>::Item, <Codec as Decoder>::Error>,
824 Self::DropConsistency,
825 Unbounded,
826 TotalOrder,
827 ExactlyOnce,
828 > = Stream::new(
829 target_consistency.clone(),
830 HydroNode::ExternalInput {
831 from_external_key: from.key,
832 from_port_id: next_external_port_id,
833 from_many: false,
834 codec_type: quote_type::<Codec>().into(),
835 port_hint,
836 instantiate_fn: DebugInstantiate::Building,
837 deserialize_fn: None,
838 metadata: target_consistency.new_node_metadata(Stream::<
839 Result<<Codec as Decoder>::Item, <Codec as Decoder>::Error>,
840 Self::DropConsistency,
841 Unbounded,
842 TotalOrder,
843 ExactlyOnce,
844 >::collection_kind(
845 )),
846 },
847 );
848
849 (
850 ExternalBytesPort {
851 process_key: from.key,
852 port_id: next_external_port_id,
853 _phantom: PhantomData,
854 },
855 raw_stream.flatten_ordered(),
856 fwd_ref,
857 )
858 }
859
860 #[doc(hidden)]
862 #[cfg(feature = "tokio")]
863 #[expect(clippy::type_complexity, reason = "stream markers")]
864 fn register_serialized_single_client<L, InT, OutT>(
865 &self,
866 from: &External<'_, L>,
867 serialize_fn: syn::Expr,
868 deserialize_fn: syn::Expr,
869 ) -> (
870 ExternalPortId,
871 Stream<InT, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce>,
872 ForwardHandle<'a, Stream<OutT, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce>>,
873 )
874 where
875 Self: TopLevel<'a> + Sized,
876 {
877 let next_external_port_id = from.flow_state.borrow_mut().next_external_port();
878
879 let target_consistency = self.drop_consistency();
880 let (fwd_ref, to_sink) = target_consistency.forward_ref::<Stream<
881 OutT,
882 Self::DropConsistency,
883 Unbounded,
884 TotalOrder,
885 ExactlyOnce,
886 >>();
887 let mut flow_state_borrow = self.flow_state().borrow_mut();
888
889 flow_state_borrow.push_root(HydroRoot::SendExternal {
890 to_external_key: from.key,
891 to_port_id: next_external_port_id,
892 to_many: false,
893 unpaired: false,
894 serialize_fn: Some(serialize_fn.into()),
895 instantiate_fn: DebugInstantiate::Building,
896 input: Box::new(to_sink.ir_node.replace(HydroNode::Placeholder)),
897 op_metadata: HydroIrOpMetadata::new(),
898 });
899 drop(flow_state_borrow);
900
901 let raw_stream: Stream<InT, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce> =
902 Stream::new(
903 target_consistency.clone(),
904 HydroNode::ExternalInput {
905 from_external_key: from.key,
906 from_port_id: next_external_port_id,
907 from_many: false,
908 codec_type: quote_type::<LengthDelimitedCodec>().into(),
909 port_hint: NetworkHint::Auto,
910 instantiate_fn: DebugInstantiate::Building,
911 deserialize_fn: Some(deserialize_fn.into()),
912 metadata: target_consistency.new_node_metadata(Stream::<
913 InT,
914 Self::DropConsistency,
915 Unbounded,
916 TotalOrder,
917 ExactlyOnce,
918 >::collection_kind(
919 )),
920 },
921 );
922
923 (next_external_port_id, raw_stream, fwd_ref)
924 }
925
926 #[cfg(feature = "tokio")]
936 #[expect(clippy::type_complexity, reason = "stream markers")]
937 fn bind_single_client_bincode<L, InT: DeserializeOwned, OutT: Serialize>(
938 &self,
939 from: &External<'_, L>,
940 ) -> (
941 ExternalBincodeBidi<InT, OutT, NotMany>,
942 Stream<InT, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce>,
943 ForwardHandle<'a, Stream<OutT, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce>>,
944 )
945 where
946 Self: TopLevel<'a> + Sized,
947 {
948 let root = get_this_crate();
949
950 let out_t_type = quote_type::<OutT>();
951 let ser_fn: syn::Expr = syn::parse_quote! {
952 #root::runtime_support::stageleft::runtime_support::fn1_type_hint::<#out_t_type, _>(
953 |b| #root::runtime_support::bincode::serialize(&b).unwrap().into()
954 )
955 };
956
957 let in_t_type = quote_type::<InT>();
958 let deser_fn: syn::Expr = syn::parse_quote! {
959 |res| {
960 let b = res.unwrap();
961 #root::runtime_support::bincode::deserialize::<#in_t_type>(&b).unwrap()
962 }
963 };
964
965 let (port_id, raw_stream, fwd_ref) =
966 self.register_serialized_single_client::<_, InT, OutT>(from, ser_fn, deser_fn);
967
968 (
969 ExternalBincodeBidi {
970 process_key: from.key,
971 port_id,
972 _phantom: PhantomData,
973 },
974 raw_stream,
975 fwd_ref,
976 )
977 }
978
979 #[cfg(feature = "tokio")]
991 #[expect(clippy::type_complexity, reason = "stream markers")]
992 fn bidi_external_many_bytes<L, T, Codec: Encoder<T> + Decoder>(
993 &self,
994 from: &External<'_, L>,
995 port_hint: NetworkHint,
996 ) -> (
997 ExternalBytesPort<Many>,
998 KeyedStream<
999 u64,
1000 <Codec as Decoder>::Item,
1001 Self::DropConsistency,
1002 Unbounded,
1003 TotalOrder,
1004 ExactlyOnce,
1005 >,
1006 KeyedStream<
1007 u64,
1008 MembershipEvent,
1009 Self::DropConsistency,
1010 Unbounded,
1011 TotalOrder,
1012 ExactlyOnce,
1013 >,
1014 ForwardHandle<
1015 'a,
1016 KeyedStream<u64, T, Self::DropConsistency, Unbounded, NoOrder, ExactlyOnce>,
1017 >,
1018 )
1019 where
1020 Self: TopLevel<'a> + Sized,
1021 {
1022 let next_external_port_id = from.flow_state.borrow_mut().next_external_port();
1023
1024 let target_consistency = self.drop_consistency();
1025 let (fwd_ref, to_sink) = target_consistency.forward_ref::<KeyedStream<
1026 u64,
1027 T,
1028 Self::DropConsistency,
1029 Unbounded,
1030 NoOrder,
1031 ExactlyOnce,
1032 >>();
1033 let to_sink_input = Box::new(to_sink.entries().ir_node.replace(HydroNode::Placeholder));
1034 let mut flow_state_borrow = self.flow_state().borrow_mut();
1035
1036 flow_state_borrow.push_root(HydroRoot::SendExternal {
1037 to_external_key: from.key,
1038 to_port_id: next_external_port_id,
1039 to_many: true,
1040 unpaired: false,
1041 serialize_fn: None,
1042 instantiate_fn: DebugInstantiate::Building,
1043 input: to_sink_input,
1044 op_metadata: HydroIrOpMetadata::new(),
1045 });
1046 drop(flow_state_borrow);
1047
1048 let raw_stream: Stream<
1049 Result<(u64, <Codec as Decoder>::Item), <Codec as Decoder>::Error>,
1050 Self::DropConsistency,
1051 Unbounded,
1052 TotalOrder,
1053 ExactlyOnce,
1054 > = Stream::new(
1055 target_consistency.clone(),
1056 HydroNode::ExternalInput {
1057 from_external_key: from.key,
1058 from_port_id: next_external_port_id,
1059 from_many: true,
1060 codec_type: quote_type::<Codec>().into(),
1061 port_hint,
1062 instantiate_fn: DebugInstantiate::Building,
1063 deserialize_fn: None,
1064 metadata: target_consistency.new_node_metadata(Stream::<
1065 Result<(u64, <Codec as Decoder>::Item), <Codec as Decoder>::Error>,
1066 Self::DropConsistency,
1067 Unbounded,
1068 TotalOrder,
1069 ExactlyOnce,
1070 >::collection_kind(
1071 )),
1072 },
1073 );
1074
1075 let membership_stream_ident = syn::Ident::new(
1076 &format!(
1077 "__hydro_deploy_many_{}_{}_membership",
1078 from.key, next_external_port_id
1079 ),
1080 Span::call_site(),
1081 );
1082 let membership_stream_expr: syn::Expr = parse_quote!(#membership_stream_ident);
1083 let raw_membership_stream: KeyedStream<
1084 u64,
1085 bool,
1086 Self::DropConsistency,
1087 Unbounded,
1088 TotalOrder,
1089 ExactlyOnce,
1090 > = KeyedStream::new(
1091 target_consistency.clone(),
1092 HydroNode::Source {
1093 source: HydroSource::Stream(membership_stream_expr.into()),
1094 metadata: target_consistency.new_node_metadata(KeyedStream::<
1095 u64,
1096 bool,
1097 Self::DropConsistency,
1098 Unbounded,
1099 TotalOrder,
1100 ExactlyOnce,
1101 >::collection_kind(
1102 )),
1103 },
1104 );
1105
1106 (
1107 ExternalBytesPort {
1108 process_key: from.key,
1109 port_id: next_external_port_id,
1110 _phantom: PhantomData,
1111 },
1112 raw_stream
1113 .flatten_ordered() .into_keyed(),
1115 raw_membership_stream.map(q!(|join| {
1116 if join {
1117 MembershipEvent::Joined
1118 } else {
1119 MembershipEvent::Left
1120 }
1121 })),
1122 fwd_ref,
1123 )
1124 }
1125
1126 #[cfg(feature = "tokio")]
1142 #[expect(clippy::type_complexity, reason = "stream markers")]
1143 fn bidi_external_many_bincode<L, InT: DeserializeOwned, OutT: Serialize>(
1144 &self,
1145 from: &External<'_, L>,
1146 ) -> (
1147 ExternalBincodeBidi<InT, OutT, Many>,
1148 KeyedStream<u64, InT, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce>,
1149 KeyedStream<
1150 u64,
1151 MembershipEvent,
1152 Self::DropConsistency,
1153 Unbounded,
1154 TotalOrder,
1155 ExactlyOnce,
1156 >,
1157 ForwardHandle<
1158 'a,
1159 KeyedStream<u64, OutT, Self::DropConsistency, Unbounded, NoOrder, ExactlyOnce>,
1160 >,
1161 )
1162 where
1163 Self: TopLevel<'a> + Sized,
1164 {
1165 let next_external_port_id = from.flow_state.borrow_mut().next_external_port();
1166
1167 let target_consistency = self.drop_consistency();
1168 let (fwd_ref, to_sink) = target_consistency.forward_ref::<KeyedStream<
1169 u64,
1170 OutT,
1171 Self::DropConsistency,
1172 Unbounded,
1173 NoOrder,
1174 ExactlyOnce,
1175 >>();
1176 let to_sink_input = Box::new(to_sink.entries().ir_node.replace(HydroNode::Placeholder));
1177 let mut flow_state_borrow = self.flow_state().borrow_mut();
1178
1179 let root = get_this_crate();
1180
1181 let out_t_type = quote_type::<OutT>();
1182 let ser_fn: syn::Expr = syn::parse_quote! {
1183 #root::runtime_support::stageleft::runtime_support::fn1_type_hint::<(u64, #out_t_type), _>(
1184 |(id, b)| (id, #root::runtime_support::bincode::serialize(&b).unwrap().into())
1185 )
1186 };
1187
1188 flow_state_borrow.push_root(HydroRoot::SendExternal {
1189 to_external_key: from.key,
1190 to_port_id: next_external_port_id,
1191 to_many: true,
1192 unpaired: false,
1193 serialize_fn: Some(ser_fn.into()),
1194 instantiate_fn: DebugInstantiate::Building,
1195 input: to_sink_input,
1196 op_metadata: HydroIrOpMetadata::new(),
1197 });
1198 drop(flow_state_borrow);
1199
1200 let in_t_type = quote_type::<InT>();
1201
1202 let deser_fn: syn::Expr = syn::parse_quote! {
1203 |res| {
1204 let (id, b) = res.unwrap();
1205 (id, #root::runtime_support::bincode::deserialize::<#in_t_type>(&b).unwrap())
1206 }
1207 };
1208
1209 let raw_stream: KeyedStream<
1210 u64,
1211 InT,
1212 Self::DropConsistency,
1213 Unbounded,
1214 TotalOrder,
1215 ExactlyOnce,
1216 > = KeyedStream::new(
1217 target_consistency.clone(),
1218 HydroNode::ExternalInput {
1219 from_external_key: from.key,
1220 from_port_id: next_external_port_id,
1221 from_many: true,
1222 codec_type: quote_type::<LengthDelimitedCodec>().into(),
1223 port_hint: NetworkHint::Auto,
1224 instantiate_fn: DebugInstantiate::Building,
1225 deserialize_fn: Some(deser_fn.into()),
1226 metadata: target_consistency.new_node_metadata(KeyedStream::<
1227 u64,
1228 InT,
1229 Self::DropConsistency,
1230 Unbounded,
1231 TotalOrder,
1232 ExactlyOnce,
1233 >::collection_kind(
1234 )),
1235 },
1236 );
1237
1238 let membership_stream_ident = syn::Ident::new(
1239 &format!(
1240 "__hydro_deploy_many_{}_{}_membership",
1241 from.key, next_external_port_id
1242 ),
1243 Span::call_site(),
1244 );
1245 let membership_stream_expr: syn::Expr = parse_quote!(#membership_stream_ident);
1246 let raw_membership_stream: KeyedStream<
1247 u64,
1248 bool,
1249 Self::DropConsistency,
1250 Unbounded,
1251 TotalOrder,
1252 ExactlyOnce,
1253 > = KeyedStream::new(
1254 target_consistency.clone(),
1255 HydroNode::Source {
1256 source: HydroSource::Stream(membership_stream_expr.into()),
1257 metadata: target_consistency.new_node_metadata(KeyedStream::<
1258 u64,
1259 bool,
1260 Self::DropConsistency,
1261 Unbounded,
1262 TotalOrder,
1263 ExactlyOnce,
1264 >::collection_kind(
1265 )),
1266 },
1267 );
1268
1269 (
1270 ExternalBincodeBidi {
1271 process_key: from.key,
1272 port_id: next_external_port_id,
1273 _phantom: PhantomData,
1274 },
1275 raw_stream,
1276 raw_membership_stream.map(q!(|join| {
1277 if join {
1278 MembershipEvent::Joined
1279 } else {
1280 MembershipEvent::Left
1281 }
1282 })),
1283 fwd_ref,
1284 )
1285 }
1286
1287 fn sidecar_bidi<InT: 'static, OutT: 'static, F>(
1340 &self,
1341 sidecar: impl QuotedWithContext<'a, F, Self>,
1342 ) -> (
1343 Stream<InT, Self, Unbounded, TotalOrder, ExactlyOnce>,
1344 ForwardHandle<'a, Stream<OutT, Self, Unbounded, NoOrder, ExactlyOnce>>,
1345 )
1346 where
1347 Self: Sized + TopLevel<'a>,
1348 {
1349 let location_key = Location::id(self).key();
1350
1351 let sidecar_id = self.flow_state().borrow_mut().next_sidecar_id();
1352 let (stream_ident, sink_ident) = sidecar_id.idents();
1353
1354 let sidecar_closure: syn::Expr = sidecar.splice_untyped_ctx(self);
1355 self.flow_state()
1356 .borrow_mut()
1357 .sidecars
1358 .push(crate::compile::builder::Sidecar::Bidi {
1359 location_key,
1360 sidecar_id,
1361 sidecar_closure: Box::new(sidecar_closure),
1362 });
1363
1364 let source_expr: syn::Expr = parse_quote! {
1366 #stream_ident
1367 };
1368 let inbound: Stream<InT, Self, Unbounded, TotalOrder, ExactlyOnce> = Stream::new(
1369 self.clone(),
1370 HydroNode::Source {
1371 source: HydroSource::Stream(source_expr.into()),
1372 metadata: self.new_node_metadata(Stream::<
1373 InT,
1374 Self,
1375 Unbounded, TotalOrder, ExactlyOnce,
1378 >::collection_kind()),
1379 },
1380 );
1381
1382 let (fwd_ref, to_sink): (
1384 ForwardHandle<'a, Stream<OutT, Self, Unbounded, NoOrder, ExactlyOnce>>,
1385 Stream<OutT, Self, Unbounded, NoOrder, ExactlyOnce>,
1386 ) = self.forward_ref();
1387
1388 let sink_expr: syn::Expr = parse_quote! {
1389 #sink_ident
1390 };
1391
1392 let sink_input_ir = to_sink.ir_node.replace(HydroNode::Placeholder);
1393 self.flow_state()
1394 .borrow_mut()
1395 .try_push_root(HydroRoot::DestSink {
1396 sink: sink_expr.into(),
1397 input: Box::new(sink_input_ir),
1398 op_metadata: HydroIrOpMetadata::new(),
1399 });
1400
1401 (inbound, fwd_ref)
1402 }
1403
1404 fn singleton<T>(
1424 &self,
1425 e: impl QuotedWithContext<'a, T, Self>,
1426 ) -> Singleton<T, Self::DropConsistency, Bounded>
1427 where
1428 Self: Sized,
1429 {
1430 let e = e.splice_untyped_ctx(self);
1431
1432 let target_location = self.drop_consistency();
1433 Singleton::new(
1434 target_location.clone(),
1435 HydroNode::SingletonSource {
1436 value: e.into(),
1437 first_tick_only: false,
1438 metadata: target_location.new_node_metadata(Singleton::<
1439 T,
1440 Self::DropConsistency,
1441 Bounded,
1442 >::collection_kind()),
1443 },
1444 )
1445 }
1446
1447 fn singleton_future<F>(
1470 &self,
1471 e: impl QuotedWithContext<'a, F, Self>,
1472 ) -> Singleton<F::Output, Self::DropConsistency, Bounded>
1473 where
1474 F: Future,
1475 Self: Sized,
1476 {
1477 self.singleton(e).resolve_future_blocking()
1478 }
1479
1480 #[cfg(feature = "tokio")]
1489 fn source_interval(
1490 &self,
1491 interval: impl QuotedWithContext<'a, Duration, Self> + Copy + 'a,
1492 ) -> Stream<(), Self, Unbounded, TotalOrder, ExactlyOnce>
1493 where
1494 Self: TopLevel<'a> + Sized,
1495 {
1496 self.source_stream(q!(tokio_stream::StreamExt::map(
1497 tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(interval)),
1498 |_| ()
1499 )))
1500 .assert_has_consistency_of_trusted(
1501 manual_proof!(),
1502 )
1503 }
1504
1505 #[cfg(feature = "tokio")]
1512 fn source_interval_delayed(
1513 &self,
1514 delay: impl QuotedWithContext<'a, Duration, Self> + Copy + 'a,
1515 interval: impl QuotedWithContext<'a, Duration, Self> + Copy + 'a,
1516 ) -> Stream<(), Self, Unbounded, TotalOrder, ExactlyOnce>
1517 where
1518 Self: TopLevel<'a> + Sized,
1519 {
1520 self.source_stream(q!(tokio_stream::StreamExt::map(
1521 tokio_stream::wrappers::IntervalStream::new(tokio::time::interval_at(
1522 tokio::time::Instant::now() + delay,
1523 interval,
1524 )),
1525 |_| ()
1526 )))
1527 .assert_has_consistency_of_trusted(
1528 manual_proof!(),
1529 )
1530 }
1531
1532 fn forward_ref<S>(&self) -> (ForwardHandle<'a, S>, S)
1572 where
1573 S: CycleCollection<'a, ForwardRef, Location = Self>,
1574 {
1575 let cycle_id = self.flow_state().borrow_mut().next_cycle_id();
1576 (
1577 ForwardHandle::new(cycle_id, Location::id(self)),
1578 S::create_source(cycle_id, self.clone()),
1579 )
1580 }
1581}
1582
1583#[cfg(feature = "deploy")]
1584#[cfg(test)]
1585mod tests {
1586 use std::collections::HashSet;
1587
1588 use futures::{SinkExt, StreamExt};
1589 use hydro_deploy::Deployment;
1590 use stageleft::q;
1591 use tokio_util::codec::LengthDelimitedCodec;
1592
1593 use crate::compile::builder::FlowBuilder;
1594 use crate::live_collections::stream::{ExactlyOnce, TotalOrder};
1595 use crate::location::{Location, NetworkHint};
1596 use crate::nondet::nondet;
1597
1598 #[tokio::test]
1599 async fn top_level_singleton_replay_cardinality() {
1600 let mut deployment = Deployment::new();
1601
1602 let mut flow = FlowBuilder::new();
1603 let node = flow.process::<()>();
1604 let external = flow.external::<()>();
1605
1606 let (in_port, input) =
1607 node.source_external_bincode::<_, _, TotalOrder, ExactlyOnce>(&external);
1608 let singleton = node.singleton(q!(123));
1609 let tick = node.tick();
1610 let out = input
1611 .batch(&tick, nondet!())
1612 .cross_singleton(singleton.clone().snapshot(&tick, nondet!()))
1613 .cross_singleton(
1614 singleton
1615 .snapshot(&tick, nondet!())
1616 .into_stream()
1617 .count(),
1618 )
1619 .all_ticks()
1620 .send_bincode_external(&external);
1621
1622 let nodes = flow
1623 .with_process(&node, deployment.Localhost())
1624 .with_external(&external, deployment.Localhost())
1625 .deploy(&mut deployment);
1626
1627 deployment.deploy().await.unwrap();
1628
1629 let mut external_in = nodes.connect(in_port).await;
1630 let mut external_out = nodes.connect(out).await;
1631
1632 deployment.start().await.unwrap();
1633
1634 external_in.send(1).await.unwrap();
1635 assert_eq!(external_out.next().await.unwrap(), ((1, 123), 1));
1636
1637 external_in.send(2).await.unwrap();
1638 assert_eq!(external_out.next().await.unwrap(), ((2, 123), 1));
1639 }
1640
1641 #[tokio::test]
1642 async fn tick_singleton_replay_cardinality() {
1643 let mut deployment = Deployment::new();
1644
1645 let mut flow = FlowBuilder::new();
1646 let node = flow.process::<()>();
1647 let external = flow.external::<()>();
1648
1649 let (in_port, input) =
1650 node.source_external_bincode::<_, _, TotalOrder, ExactlyOnce>(&external);
1651 let tick = node.tick();
1652 let singleton = tick.singleton(q!(123));
1653 let out = input
1654 .batch(&tick, nondet!())
1655 .cross_singleton(singleton.clone())
1656 .cross_singleton(singleton.into_stream().count())
1657 .all_ticks()
1658 .send_bincode_external(&external);
1659
1660 let nodes = flow
1661 .with_process(&node, deployment.Localhost())
1662 .with_external(&external, deployment.Localhost())
1663 .deploy(&mut deployment);
1664
1665 deployment.deploy().await.unwrap();
1666
1667 let mut external_in = nodes.connect(in_port).await;
1668 let mut external_out = nodes.connect(out).await;
1669
1670 deployment.start().await.unwrap();
1671
1672 external_in.send(1).await.unwrap();
1673 assert_eq!(external_out.next().await.unwrap(), ((1, 123), 1));
1674
1675 external_in.send(2).await.unwrap();
1676 assert_eq!(external_out.next().await.unwrap(), ((2, 123), 1));
1677 }
1678
1679 #[tokio::test]
1680 async fn external_bytes() {
1681 let mut deployment = Deployment::new();
1682
1683 let mut flow = FlowBuilder::new();
1684 let first_node = flow.process::<()>();
1685 let external = flow.external::<()>();
1686
1687 let (in_port, input) = first_node.source_external_bytes(&external);
1688 let out = input.send_bincode_external(&external);
1689
1690 let nodes = flow
1691 .with_process(&first_node, deployment.Localhost())
1692 .with_external(&external, deployment.Localhost())
1693 .deploy(&mut deployment);
1694
1695 deployment.deploy().await.unwrap();
1696
1697 let mut external_in = nodes.connect(in_port).await.1;
1698 let mut external_out = nodes.connect(out).await;
1699
1700 deployment.start().await.unwrap();
1701
1702 external_in.send(vec![1, 2, 3].into()).await.unwrap();
1703
1704 assert_eq!(external_out.next().await.unwrap(), vec![1, 2, 3]);
1705 }
1706
1707 #[tokio::test]
1708 async fn multi_external_source() {
1709 let mut deployment = Deployment::new();
1710
1711 let mut flow = FlowBuilder::new();
1712 let first_node = flow.process::<()>();
1713 let external = flow.external::<()>();
1714
1715 let (in_port, input, _membership, complete_sink) =
1716 first_node.bidi_external_many_bincode(&external);
1717 let out = input.entries().send_bincode_external(&external);
1718 complete_sink.complete(
1719 first_node
1720 .source_iter::<(u64, ()), _>(q!([]))
1721 .into_keyed()
1722 .weaken_ordering(),
1723 );
1724
1725 let nodes = flow
1726 .with_process(&first_node, deployment.Localhost())
1727 .with_external(&external, deployment.Localhost())
1728 .deploy(&mut deployment);
1729
1730 deployment.deploy().await.unwrap();
1731
1732 let (_, mut external_in_1) = nodes.connect_bincode(in_port.clone()).await;
1733 let (_, mut external_in_2) = nodes.connect_bincode(in_port).await;
1734 let external_out = nodes.connect(out).await;
1735
1736 deployment.start().await.unwrap();
1737
1738 external_in_1.send(123).await.unwrap();
1739 external_in_2.send(456).await.unwrap();
1740
1741 assert_eq!(
1742 external_out.take(2).collect::<HashSet<_>>().await,
1743 vec![(0, 123), (1, 456)].into_iter().collect()
1744 );
1745 }
1746
1747 #[tokio::test]
1748 async fn second_connection_only_multi_source() {
1749 let mut deployment = Deployment::new();
1750
1751 let mut flow = FlowBuilder::new();
1752 let first_node = flow.process::<()>();
1753 let external = flow.external::<()>();
1754
1755 let (in_port, input, _membership, complete_sink) =
1756 first_node.bidi_external_many_bincode(&external);
1757 let out = input.entries().send_bincode_external(&external);
1758 complete_sink.complete(
1759 first_node
1760 .source_iter::<(u64, ()), _>(q!([]))
1761 .into_keyed()
1762 .weaken_ordering(),
1763 );
1764
1765 let nodes = flow
1766 .with_process(&first_node, deployment.Localhost())
1767 .with_external(&external, deployment.Localhost())
1768 .deploy(&mut deployment);
1769
1770 deployment.deploy().await.unwrap();
1771
1772 let (_, mut _external_in_1) = nodes.connect_bincode(in_port.clone()).await;
1774 let (_, mut external_in_2) = nodes.connect_bincode(in_port).await;
1775 let mut external_out = nodes.connect(out).await;
1776
1777 deployment.start().await.unwrap();
1778
1779 external_in_2.send(456).await.unwrap();
1780
1781 assert_eq!(external_out.next().await.unwrap(), (1, 456));
1782 }
1783
1784 #[tokio::test]
1785 async fn multi_external_bytes() {
1786 let mut deployment = Deployment::new();
1787
1788 let mut flow = FlowBuilder::new();
1789 let first_node = flow.process::<()>();
1790 let external = flow.external::<()>();
1791
1792 let (in_port, input, _membership, complete_sink) = first_node
1793 .bidi_external_many_bytes::<_, _, LengthDelimitedCodec>(&external, NetworkHint::Auto);
1794 let out = input.entries().send_bincode_external(&external);
1795 complete_sink.complete(
1796 first_node
1797 .source_iter(q!([]))
1798 .into_keyed()
1799 .weaken_ordering(),
1800 );
1801
1802 let nodes = flow
1803 .with_process(&first_node, deployment.Localhost())
1804 .with_external(&external, deployment.Localhost())
1805 .deploy(&mut deployment);
1806
1807 deployment.deploy().await.unwrap();
1808
1809 let mut external_in_1 = nodes.connect(in_port.clone()).await.1;
1810 let mut external_in_2 = nodes.connect(in_port).await.1;
1811 let external_out = nodes.connect(out).await;
1812
1813 deployment.start().await.unwrap();
1814
1815 external_in_1.send(vec![1, 2, 3].into()).await.unwrap();
1816 external_in_2.send(vec![4, 5].into()).await.unwrap();
1817
1818 assert_eq!(
1819 external_out.take(2).collect::<HashSet<_>>().await,
1820 vec![
1821 (0, (&[1u8, 2, 3] as &[u8]).into()),
1822 (1, (&[4u8, 5] as &[u8]).into())
1823 ]
1824 .into_iter()
1825 .collect()
1826 );
1827 }
1828
1829 #[tokio::test]
1830 async fn single_client_external_bytes() {
1831 let mut deployment = Deployment::new();
1832 let mut flow = FlowBuilder::new();
1833 let first_node = flow.process::<()>();
1834 let external = flow.external::<()>();
1835 let (port, input, complete_sink) = first_node
1836 .bind_single_client::<_, _, LengthDelimitedCodec>(&external, NetworkHint::Auto);
1837 complete_sink.complete(input.map(q!(|data| {
1838 let mut resp: Vec<u8> = data.into();
1839 resp.push(42);
1840 resp.into() })));
1842
1843 let nodes = flow
1844 .with_process(&first_node, deployment.Localhost())
1845 .with_external(&external, deployment.Localhost())
1846 .deploy(&mut deployment);
1847
1848 deployment.deploy().await.unwrap();
1849 deployment.start().await.unwrap();
1850
1851 let (mut external_out, mut external_in) = nodes.connect(port).await;
1852
1853 external_in.send(vec![1, 2, 3].into()).await.unwrap();
1854 assert_eq!(
1855 external_out.next().await.unwrap().unwrap(),
1856 vec![1, 2, 3, 42]
1857 );
1858 }
1859
1860 #[tokio::test]
1861 async fn echo_external_bytes() {
1862 let mut deployment = Deployment::new();
1863
1864 let mut flow = FlowBuilder::new();
1865 let first_node = flow.process::<()>();
1866 let external = flow.external::<()>();
1867
1868 let (port, input, _membership, complete_sink) = first_node
1869 .bidi_external_many_bytes::<_, _, LengthDelimitedCodec>(&external, NetworkHint::Auto);
1870 complete_sink
1871 .complete(input.map(q!(|bytes| { bytes.into_iter().map(|x| x + 1).collect() })));
1872
1873 let nodes = flow
1874 .with_process(&first_node, deployment.Localhost())
1875 .with_external(&external, deployment.Localhost())
1876 .deploy(&mut deployment);
1877
1878 deployment.deploy().await.unwrap();
1879
1880 let (mut external_out_1, mut external_in_1) = nodes.connect(port.clone()).await;
1881 let (mut external_out_2, mut external_in_2) = nodes.connect(port).await;
1882
1883 deployment.start().await.unwrap();
1884
1885 external_in_1.send(vec![1, 2, 3].into()).await.unwrap();
1886 external_in_2.send(vec![4, 5].into()).await.unwrap();
1887
1888 assert_eq!(external_out_1.next().await.unwrap().unwrap(), vec![2, 3, 4]);
1889 assert_eq!(external_out_2.next().await.unwrap().unwrap(), vec![5, 6]);
1890 }
1891
1892 #[tokio::test]
1893 async fn echo_external_bincode() {
1894 let mut deployment = Deployment::new();
1895
1896 let mut flow = FlowBuilder::new();
1897 let first_node = flow.process::<()>();
1898 let external = flow.external::<()>();
1899
1900 let (port, input, _membership, complete_sink) =
1901 first_node.bidi_external_many_bincode(&external);
1902 complete_sink.complete(input.map(q!(|text: String| { text.to_uppercase() })));
1903
1904 let nodes = flow
1905 .with_process(&first_node, deployment.Localhost())
1906 .with_external(&external, deployment.Localhost())
1907 .deploy(&mut deployment);
1908
1909 deployment.deploy().await.unwrap();
1910
1911 let (mut external_out_1, mut external_in_1) = nodes.connect_bincode(port.clone()).await;
1912 let (mut external_out_2, mut external_in_2) = nodes.connect_bincode(port).await;
1913
1914 deployment.start().await.unwrap();
1915
1916 external_in_1.send("hi".to_owned()).await.unwrap();
1917 external_in_2.send("hello".to_owned()).await.unwrap();
1918
1919 assert_eq!(external_out_1.next().await.unwrap(), "HI");
1920 assert_eq!(external_out_2.next().await.unwrap(), "HELLO");
1921 }
1922
1923 #[tokio::test]
1924 async fn closure_location_name() {
1925 let mut deployment = Deployment::new();
1926 let mut flow = FlowBuilder::new();
1927
1928 enum ClosureProcess {}
1929
1930 let node = flow.process::<ClosureProcess>();
1931 let external = flow.external::<()>();
1932
1933 let (in_port, input) =
1934 node.source_external_bincode::<_, i32, TotalOrder, ExactlyOnce>(&external);
1935 let out = input.send_bincode_external(&external);
1936
1937 let nodes = flow
1938 .with_process(&node, deployment.Localhost())
1939 .with_external(&external, deployment.Localhost())
1940 .deploy(&mut deployment);
1941
1942 deployment.deploy().await.unwrap();
1943
1944 let mut external_in = nodes.connect(in_port).await;
1945 let mut external_out = nodes.connect(out).await;
1946
1947 deployment.start().await.unwrap();
1948
1949 external_in.send(42).await.unwrap();
1950 assert_eq!(external_out.next().await.unwrap(), 42);
1951 }
1952}