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::ir::DebugInstantiate;
43use crate::compile::ir::{
44 ClusterMembersState, HydroIrOpMetadata, HydroNode, HydroRoot, HydroSource,
45};
46use crate::forward_handle::ForwardRef;
47#[cfg(stageleft_runtime)]
48use crate::forward_handle::{CycleCollection, ForwardHandle};
49use crate::live_collections::boundedness::{Bounded, Unbounded};
50use crate::live_collections::keyed_stream::KeyedStream;
51use crate::live_collections::singleton::Singleton;
52use crate::live_collections::stream::{ExactlyOnce, NoOrder, Stream, TotalOrder};
53#[cfg(feature = "tokio")]
54use crate::live_collections::stream::{Ordering, Retries};
55#[cfg(stageleft_runtime)]
56use crate::location::dynamic::DynLocation;
57use crate::location::dynamic::{ClusterConsistency, LocationId};
58#[cfg(feature = "tokio")]
59use crate::location::external_process::{
60 ExternalBincodeBidi, ExternalBincodeSink, ExternalBytesPort, Many, NotMany,
61};
62use crate::nondet::NonDet;
63#[cfg(feature = "tokio")]
64use crate::properties::manual_proof;
65#[cfg(feature = "sim")]
66use crate::sim::SimSender;
67use crate::staging_util::get_this_crate;
68
69pub mod dynamic;
70
71pub mod external_process;
72pub use external_process::External;
73
74pub mod process;
75pub use process::Process;
76
77pub mod cluster;
78pub use cluster::Cluster;
79
80pub mod member_id;
81pub use member_id::{MemberId, TaglessMemberId};
82
83pub mod tick;
84pub use tick::{Atomic, Tick};
85
86#[derive(PartialEq, Eq, Clone, Debug, Hash, Serialize, Deserialize)]
89pub enum MembershipEvent {
90 Joined,
92 Left,
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
102pub enum NetworkHint {
103 Auto,
105 TcpPort(Option<u16>),
110}
111
112pub(crate) fn check_matching_location<'a, L: Location<'a>>(l1: &L, l2: &L) {
113 assert_eq!(Location::id(l1), Location::id(l2), "locations do not match");
114}
115
116#[stageleft::export(LocationKey)]
117new_key_type! {
118 pub struct LocationKey;
120}
121
122impl std::fmt::Display for LocationKey {
123 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124 write!(f, "loc{:?}", self.data()) }
126}
127
128impl std::str::FromStr for LocationKey {
131 type Err = Option<ParseIntError>;
132
133 fn from_str(s: &str) -> Result<Self, Self::Err> {
134 let nvn = s.strip_prefix("loc").ok_or(None)?;
135 let (idx, ver) = nvn.split_once("v").ok_or(None)?;
136 let idx: u64 = idx.parse()?;
137 let ver: u64 = ver.parse()?;
138 Ok(slotmap::KeyData::from_ffi((ver << 32) | idx).into())
139 }
140}
141
142impl LocationKey {
143 pub const FIRST: Self = Self(slotmap::KeyData::from_ffi(0x0000000100000001)); #[cfg(test)]
149 pub const TEST_KEY_1: Self = Self(slotmap::KeyData::from_ffi(0x000000FF00000001)); #[cfg(test)]
153 pub const TEST_KEY_2: Self = Self(slotmap::KeyData::from_ffi(0x000000FF00000002)); }
155
156impl<Ctx> FreeVariableWithContextWithProps<Ctx, ()> for LocationKey {
158 type O = LocationKey;
159
160 fn to_tokens(self, _ctx: &Ctx) -> (QuoteTokens, ())
161 where
162 Self: Sized,
163 {
164 let root = get_this_crate();
165 let n = Key::data(&self).as_ffi();
166 (
167 QuoteTokens {
168 prelude: None,
169 expr: Some(quote! {
170 #root::location::LocationKey::from(#root::runtime_support::slotmap::KeyData::from_ffi(#n))
171 }),
172 },
173 (),
174 )
175 }
176}
177
178#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize)]
180pub enum LocationType {
181 Process,
183 Cluster,
185 External,
187}
188
189pub trait TopLevel<'a>: Location<'a> {}
191
192#[expect(
206 private_bounds,
207 reason = "only internal Hydro code can define location types"
208)]
209pub trait Location<'a>: DynLocation {
210 type Root: Location<'a>;
215
216 type DropConsistency: Location<'a, DropConsistency = Self::DropConsistency>;
218
219 fn root(&self) -> Self::Root;
224
225 fn drop_consistency(&self) -> Self::DropConsistency;
227 fn consistency() -> Option<ClusterConsistency>;
229
230 fn with_consistency_of<L2: Location<'a, DropConsistency = Self::DropConsistency>>(&self) -> L2 {
232 L2::from_drop_consistency(self.drop_consistency())
233 }
234
235 #[doc(hidden)]
236 fn from_drop_consistency(l2: Self::DropConsistency) -> Self;
237
238 fn try_tick(&self) -> Option<Tick<Self>> {
245 if Self::is_top_level() {
246 let id = self.flow_state().borrow_mut().next_clock_id();
247 Some(Tick {
248 id,
249 l: self.clone(),
250 })
251 } else {
252 None
253 }
254 }
255
256 fn id(&self) -> LocationId {
258 DynLocation::dyn_id(self)
259 }
260
261 fn tick(&self) -> Tick<Self> {
287 if let LocationId::Tick(_, _) = self.id() {
288 panic!("cannot create nested ticks");
289 }
290
291 let id = self.flow_state().borrow_mut().next_clock_id();
292 Tick {
293 id,
294 l: self.clone(),
295 }
296 }
297
298 fn spin(&self) -> Stream<(), Self, Unbounded, TotalOrder, ExactlyOnce>
323 where
324 Self: TopLevel<'a> + Sized,
325 {
326 Stream::new(
327 self.clone(),
328 HydroNode::Source {
329 source: HydroSource::Spin(),
330 metadata: self.new_node_metadata(Stream::<
331 (),
332 Self,
333 Unbounded,
334 TotalOrder,
335 ExactlyOnce,
336 >::collection_kind()),
337 },
338 )
339 }
340
341 fn source_stream<T, E>(
362 &self,
363 e: impl QuotedWithContext<'a, E, Self>,
364 ) -> Stream<T, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce>
365 where
366 E: FuturesStream<Item = T> + Unpin,
367 Self: TopLevel<'a> + Sized,
368 {
369 let e = e.splice_untyped_ctx(self);
370
371 let target_location = self.drop_consistency();
372 Stream::new(
373 target_location.clone(),
374 HydroNode::Source {
375 source: HydroSource::Stream(e.into()),
376 metadata: target_location.new_node_metadata(Stream::<
377 T,
378 Self::DropConsistency,
379 Unbounded,
380 TotalOrder,
381 ExactlyOnce,
382 >::collection_kind()),
383 },
384 )
385 }
386
387 fn source_iter<T, E>(
409 &self,
410 e: impl QuotedWithContext<'a, E, Self>,
411 ) -> Stream<T, Self::DropConsistency, Bounded, TotalOrder, ExactlyOnce>
412 where
413 E: IntoIterator<Item = T>,
414 Self: Sized,
415 {
416 let e = e.splice_typed_ctx(self);
417
418 let target_location = self.drop_consistency();
419 Stream::new(
420 target_location.clone(),
421 HydroNode::Source {
422 source: HydroSource::Iter(e.into()),
423 metadata: target_location.new_node_metadata(Stream::<
424 T,
425 Self::DropConsistency,
426 Bounded,
427 TotalOrder,
428 ExactlyOnce,
429 >::collection_kind()),
430 },
431 )
432 }
433
434 #[deprecated(note = "use .source_cluster_membership_stream(...) instead")]
435 fn source_cluster_members<C: 'a>(
474 &self,
475 cluster: &Cluster<'a, C>,
476 nondet_start: NonDet,
477 ) -> KeyedStream<MemberId<C>, MembershipEvent, Self::DropConsistency, Unbounded>
478 where
479 Self: TopLevel<'a> + Sized,
480 {
481 self.source_cluster_membership_stream(cluster, nondet_start)
482 }
483
484 fn source_cluster_membership_stream<C: 'a>(
523 &self,
524 cluster: &Cluster<'a, C>,
525 _nondet_start: NonDet,
526 ) -> KeyedStream<MemberId<C>, MembershipEvent, Self::DropConsistency, Unbounded>
527 where
528 Self: TopLevel<'a> + Sized,
529 {
530 let target_consistency = self.drop_consistency();
531 Stream::new(
532 target_consistency.clone(),
533 HydroNode::Source {
534 source: HydroSource::ClusterMembers(cluster.id(), ClusterMembersState::Uninit),
535 metadata: target_consistency.new_node_metadata(Stream::<
536 (TaglessMemberId, MembershipEvent),
537 Self,
538 Unbounded,
539 TotalOrder,
540 ExactlyOnce,
541 >::collection_kind(
542 )),
543 },
544 )
545 .map(q!(|(k, v)| (MemberId::from_tagless(k), v)))
546 .into_keyed()
547 }
548
549 #[cfg(feature = "tokio")]
557 fn source_external_bytes<L>(
558 &self,
559 from: &External<L>,
560 ) -> (
561 ExternalBytesPort,
562 Stream<BytesMut, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce>,
563 )
564 where
565 Self: TopLevel<'a> + Sized,
566 {
567 let (port, stream, sink) =
568 self.bind_single_client::<_, Bytes, LengthDelimitedCodec>(from, NetworkHint::Auto);
569
570 sink.complete(stream.location().source_iter(q!([])));
571
572 (port, stream)
573 }
574
575 #[cfg(feature = "tokio")]
582 fn source_external_bincode<L, T, O: Ordering, R: Retries>(
583 &self,
584 from: &External<L>,
585 ) -> (
586 ExternalBincodeSink<T, NotMany, O, R>,
587 Stream<T, Self::DropConsistency, Unbounded, O, R>,
588 )
589 where
590 Self: TopLevel<'a> + Sized,
591 T: Serialize + DeserializeOwned,
592 {
593 let (port, stream, sink) = self.bind_single_client_bincode::<_, T, ()>(from);
594 sink.complete(stream.location().source_iter(q!([])));
595
596 (
597 ExternalBincodeSink {
598 process_key: from.key,
599 port_id: port.port_id,
600 _phantom: PhantomData,
601 },
602 stream.weaken_ordering().weaken_retries(),
603 )
604 }
605
606 #[cfg(feature = "sim")]
611 fn sim_input<T, O: Ordering, R: Retries>(
612 &self,
613 ) -> (
614 SimSender<T, O, R>,
615 Stream<T, Self::DropConsistency, Unbounded, O, R>,
616 )
617 where
618 Self: TopLevel<'a> + Sized,
619 T: Serialize + DeserializeOwned,
620 {
621 let external_location: External<'a, ()> = External {
622 key: LocationKey::FIRST,
623 flow_state: self.flow_state().clone(),
624 _phantom: PhantomData,
625 };
626
627 let (external, stream) = self.source_external_bincode(&external_location);
628
629 (SimSender(external.port_id, PhantomData), stream)
630 }
631
632 fn embedded_input<T>(
638 &self,
639 name: impl Into<String>,
640 ) -> Stream<T, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce>
641 where
642 Self: TopLevel<'a> + Sized,
643 {
644 let ident = syn::Ident::new(&name.into(), Span::call_site());
645
646 let target_location = self.drop_consistency();
647 Stream::new(
648 target_location.clone(),
649 HydroNode::Source {
650 source: HydroSource::Embedded(ident),
651 metadata: target_location.new_node_metadata(Stream::<
652 T,
653 Self,
654 Unbounded,
655 TotalOrder,
656 ExactlyOnce,
657 >::collection_kind()),
658 },
659 )
660 }
661
662 fn embedded_singleton_input<T>(
668 &self,
669 name: impl Into<String>,
670 ) -> Singleton<T, Self::DropConsistency, Bounded>
671 where
672 Self: TopLevel<'a> + Sized,
673 {
674 let ident = syn::Ident::new(&name.into(), Span::call_site());
675
676 let target_location = self.drop_consistency();
677 Singleton::new(
678 target_location.clone(),
679 HydroNode::Source {
680 source: HydroSource::EmbeddedSingleton(ident),
681 metadata: target_location
682 .new_node_metadata(Singleton::<T, Self, Bounded>::collection_kind()),
683 },
684 )
685 }
686
687 #[cfg(feature = "tokio")]
732 #[expect(clippy::type_complexity, reason = "stream markers")]
733 fn bind_single_client<L, T, Codec: Encoder<T> + Decoder>(
734 &self,
735 from: &External<L>,
736 port_hint: NetworkHint,
737 ) -> (
738 ExternalBytesPort<NotMany>,
739 Stream<<Codec as Decoder>::Item, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce>,
740 ForwardHandle<'a, Stream<T, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce>>,
741 )
742 where
743 Self: TopLevel<'a> + Sized,
744 {
745 let next_external_port_id = from.flow_state.borrow_mut().next_external_port();
746 let target_consistency = self.drop_consistency();
747
748 let (fwd_ref, to_sink) = target_consistency.forward_ref::<Stream<
749 T,
750 Self::DropConsistency,
751 Unbounded,
752 TotalOrder,
753 ExactlyOnce,
754 >>();
755 let mut flow_state_borrow = self.flow_state().borrow_mut();
756
757 flow_state_borrow.push_root(HydroRoot::SendExternal {
758 to_external_key: from.key,
759 to_port_id: next_external_port_id,
760 to_many: false,
761 unpaired: false,
762 serialize_fn: None,
763 instantiate_fn: DebugInstantiate::Building,
764 input: Box::new(to_sink.ir_node.replace(HydroNode::Placeholder)),
765 op_metadata: HydroIrOpMetadata::new(),
766 });
767 drop(flow_state_borrow);
768
769 let raw_stream: Stream<
770 Result<<Codec as Decoder>::Item, <Codec as Decoder>::Error>,
771 Self::DropConsistency,
772 Unbounded,
773 TotalOrder,
774 ExactlyOnce,
775 > = Stream::new(
776 target_consistency.clone(),
777 HydroNode::ExternalInput {
778 from_external_key: from.key,
779 from_port_id: next_external_port_id,
780 from_many: false,
781 codec_type: quote_type::<Codec>().into(),
782 port_hint,
783 instantiate_fn: DebugInstantiate::Building,
784 deserialize_fn: None,
785 metadata: target_consistency.new_node_metadata(Stream::<
786 Result<<Codec as Decoder>::Item, <Codec as Decoder>::Error>,
787 Self::DropConsistency,
788 Unbounded,
789 TotalOrder,
790 ExactlyOnce,
791 >::collection_kind(
792 )),
793 },
794 );
795
796 (
797 ExternalBytesPort {
798 process_key: from.key,
799 port_id: next_external_port_id,
800 _phantom: PhantomData,
801 },
802 raw_stream.flatten_ordered(),
803 fwd_ref,
804 )
805 }
806
807 #[cfg(feature = "tokio")]
817 #[expect(clippy::type_complexity, reason = "stream markers")]
818 fn bind_single_client_bincode<L, InT: DeserializeOwned, OutT: Serialize>(
819 &self,
820 from: &External<L>,
821 ) -> (
822 ExternalBincodeBidi<InT, OutT, NotMany>,
823 Stream<InT, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce>,
824 ForwardHandle<'a, Stream<OutT, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce>>,
825 )
826 where
827 Self: TopLevel<'a> + Sized,
828 {
829 let next_external_port_id = from.flow_state.borrow_mut().next_external_port();
830
831 let target_consistency = self.drop_consistency();
832 let (fwd_ref, to_sink) = target_consistency.forward_ref::<Stream<
833 OutT,
834 Self::DropConsistency,
835 Unbounded,
836 TotalOrder,
837 ExactlyOnce,
838 >>();
839 let mut flow_state_borrow = self.flow_state().borrow_mut();
840
841 let root = get_this_crate();
842
843 let out_t_type = quote_type::<OutT>();
844 let ser_fn: syn::Expr = syn::parse_quote! {
845 #root::runtime_support::stageleft::runtime_support::fn1_type_hint::<#out_t_type, _>(
846 |b| #root::runtime_support::bincode::serialize(&b).unwrap().into()
847 )
848 };
849
850 flow_state_borrow.push_root(HydroRoot::SendExternal {
851 to_external_key: from.key,
852 to_port_id: next_external_port_id,
853 to_many: false,
854 unpaired: false,
855 serialize_fn: Some(ser_fn.into()),
856 instantiate_fn: DebugInstantiate::Building,
857 input: Box::new(to_sink.ir_node.replace(HydroNode::Placeholder)),
858 op_metadata: HydroIrOpMetadata::new(),
859 });
860 drop(flow_state_borrow);
861
862 let in_t_type = quote_type::<InT>();
863
864 let deser_fn: syn::Expr = syn::parse_quote! {
865 |res| {
866 let b = res.unwrap();
867 #root::runtime_support::bincode::deserialize::<#in_t_type>(&b).unwrap()
868 }
869 };
870
871 let raw_stream: Stream<InT, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce> =
872 Stream::new(
873 target_consistency.clone(),
874 HydroNode::ExternalInput {
875 from_external_key: from.key,
876 from_port_id: next_external_port_id,
877 from_many: false,
878 codec_type: quote_type::<LengthDelimitedCodec>().into(),
879 port_hint: NetworkHint::Auto,
880 instantiate_fn: DebugInstantiate::Building,
881 deserialize_fn: Some(deser_fn.into()),
882 metadata: target_consistency.new_node_metadata(Stream::<
883 InT,
884 Self::DropConsistency,
885 Unbounded,
886 TotalOrder,
887 ExactlyOnce,
888 >::collection_kind(
889 )),
890 },
891 );
892
893 (
894 ExternalBincodeBidi {
895 process_key: from.key,
896 port_id: next_external_port_id,
897 _phantom: PhantomData,
898 },
899 raw_stream,
900 fwd_ref,
901 )
902 }
903
904 #[cfg(feature = "tokio")]
916 #[expect(clippy::type_complexity, reason = "stream markers")]
917 fn bidi_external_many_bytes<L, T, Codec: Encoder<T> + Decoder>(
918 &self,
919 from: &External<L>,
920 port_hint: NetworkHint,
921 ) -> (
922 ExternalBytesPort<Many>,
923 KeyedStream<
924 u64,
925 <Codec as Decoder>::Item,
926 Self::DropConsistency,
927 Unbounded,
928 TotalOrder,
929 ExactlyOnce,
930 >,
931 KeyedStream<
932 u64,
933 MembershipEvent,
934 Self::DropConsistency,
935 Unbounded,
936 TotalOrder,
937 ExactlyOnce,
938 >,
939 ForwardHandle<
940 'a,
941 KeyedStream<u64, T, Self::DropConsistency, Unbounded, NoOrder, ExactlyOnce>,
942 >,
943 )
944 where
945 Self: TopLevel<'a> + Sized,
946 {
947 let next_external_port_id = from.flow_state.borrow_mut().next_external_port();
948
949 let target_consistency = self.drop_consistency();
950 let (fwd_ref, to_sink) = target_consistency.forward_ref::<KeyedStream<
951 u64,
952 T,
953 Self::DropConsistency,
954 Unbounded,
955 NoOrder,
956 ExactlyOnce,
957 >>();
958 let to_sink_input = Box::new(to_sink.entries().ir_node.replace(HydroNode::Placeholder));
959 let mut flow_state_borrow = self.flow_state().borrow_mut();
960
961 flow_state_borrow.push_root(HydroRoot::SendExternal {
962 to_external_key: from.key,
963 to_port_id: next_external_port_id,
964 to_many: true,
965 unpaired: false,
966 serialize_fn: None,
967 instantiate_fn: DebugInstantiate::Building,
968 input: to_sink_input,
969 op_metadata: HydroIrOpMetadata::new(),
970 });
971 drop(flow_state_borrow);
972
973 let raw_stream: Stream<
974 Result<(u64, <Codec as Decoder>::Item), <Codec as Decoder>::Error>,
975 Self::DropConsistency,
976 Unbounded,
977 TotalOrder,
978 ExactlyOnce,
979 > = Stream::new(
980 target_consistency.clone(),
981 HydroNode::ExternalInput {
982 from_external_key: from.key,
983 from_port_id: next_external_port_id,
984 from_many: true,
985 codec_type: quote_type::<Codec>().into(),
986 port_hint,
987 instantiate_fn: DebugInstantiate::Building,
988 deserialize_fn: None,
989 metadata: target_consistency.new_node_metadata(Stream::<
990 Result<(u64, <Codec as Decoder>::Item), <Codec as Decoder>::Error>,
991 Self::DropConsistency,
992 Unbounded,
993 TotalOrder,
994 ExactlyOnce,
995 >::collection_kind(
996 )),
997 },
998 );
999
1000 let membership_stream_ident = syn::Ident::new(
1001 &format!(
1002 "__hydro_deploy_many_{}_{}_membership",
1003 from.key, next_external_port_id
1004 ),
1005 Span::call_site(),
1006 );
1007 let membership_stream_expr: syn::Expr = parse_quote!(#membership_stream_ident);
1008 let raw_membership_stream: KeyedStream<
1009 u64,
1010 bool,
1011 Self::DropConsistency,
1012 Unbounded,
1013 TotalOrder,
1014 ExactlyOnce,
1015 > = KeyedStream::new(
1016 target_consistency.clone(),
1017 HydroNode::Source {
1018 source: HydroSource::Stream(membership_stream_expr.into()),
1019 metadata: target_consistency.new_node_metadata(KeyedStream::<
1020 u64,
1021 bool,
1022 Self::DropConsistency,
1023 Unbounded,
1024 TotalOrder,
1025 ExactlyOnce,
1026 >::collection_kind(
1027 )),
1028 },
1029 );
1030
1031 (
1032 ExternalBytesPort {
1033 process_key: from.key,
1034 port_id: next_external_port_id,
1035 _phantom: PhantomData,
1036 },
1037 raw_stream
1038 .flatten_ordered() .into_keyed(),
1040 raw_membership_stream.map(q!(|join| {
1041 if join {
1042 MembershipEvent::Joined
1043 } else {
1044 MembershipEvent::Left
1045 }
1046 })),
1047 fwd_ref,
1048 )
1049 }
1050
1051 #[cfg(feature = "tokio")]
1067 #[expect(clippy::type_complexity, reason = "stream markers")]
1068 fn bidi_external_many_bincode<L, InT: DeserializeOwned, OutT: Serialize>(
1069 &self,
1070 from: &External<L>,
1071 ) -> (
1072 ExternalBincodeBidi<InT, OutT, Many>,
1073 KeyedStream<u64, InT, Self::DropConsistency, Unbounded, TotalOrder, ExactlyOnce>,
1074 KeyedStream<
1075 u64,
1076 MembershipEvent,
1077 Self::DropConsistency,
1078 Unbounded,
1079 TotalOrder,
1080 ExactlyOnce,
1081 >,
1082 ForwardHandle<
1083 'a,
1084 KeyedStream<u64, OutT, Self::DropConsistency, Unbounded, NoOrder, ExactlyOnce>,
1085 >,
1086 )
1087 where
1088 Self: TopLevel<'a> + Sized,
1089 {
1090 let next_external_port_id = from.flow_state.borrow_mut().next_external_port();
1091
1092 let target_consistency = self.drop_consistency();
1093 let (fwd_ref, to_sink) = target_consistency.forward_ref::<KeyedStream<
1094 u64,
1095 OutT,
1096 Self::DropConsistency,
1097 Unbounded,
1098 NoOrder,
1099 ExactlyOnce,
1100 >>();
1101 let to_sink_input = Box::new(to_sink.entries().ir_node.replace(HydroNode::Placeholder));
1102 let mut flow_state_borrow = self.flow_state().borrow_mut();
1103
1104 let root = get_this_crate();
1105
1106 let out_t_type = quote_type::<OutT>();
1107 let ser_fn: syn::Expr = syn::parse_quote! {
1108 #root::runtime_support::stageleft::runtime_support::fn1_type_hint::<(u64, #out_t_type), _>(
1109 |(id, b)| (id, #root::runtime_support::bincode::serialize(&b).unwrap().into())
1110 )
1111 };
1112
1113 flow_state_borrow.push_root(HydroRoot::SendExternal {
1114 to_external_key: from.key,
1115 to_port_id: next_external_port_id,
1116 to_many: true,
1117 unpaired: false,
1118 serialize_fn: Some(ser_fn.into()),
1119 instantiate_fn: DebugInstantiate::Building,
1120 input: to_sink_input,
1121 op_metadata: HydroIrOpMetadata::new(),
1122 });
1123 drop(flow_state_borrow);
1124
1125 let in_t_type = quote_type::<InT>();
1126
1127 let deser_fn: syn::Expr = syn::parse_quote! {
1128 |res| {
1129 let (id, b) = res.unwrap();
1130 (id, #root::runtime_support::bincode::deserialize::<#in_t_type>(&b).unwrap())
1131 }
1132 };
1133
1134 let raw_stream: KeyedStream<
1135 u64,
1136 InT,
1137 Self::DropConsistency,
1138 Unbounded,
1139 TotalOrder,
1140 ExactlyOnce,
1141 > = KeyedStream::new(
1142 target_consistency.clone(),
1143 HydroNode::ExternalInput {
1144 from_external_key: from.key,
1145 from_port_id: next_external_port_id,
1146 from_many: true,
1147 codec_type: quote_type::<LengthDelimitedCodec>().into(),
1148 port_hint: NetworkHint::Auto,
1149 instantiate_fn: DebugInstantiate::Building,
1150 deserialize_fn: Some(deser_fn.into()),
1151 metadata: target_consistency.new_node_metadata(KeyedStream::<
1152 u64,
1153 InT,
1154 Self::DropConsistency,
1155 Unbounded,
1156 TotalOrder,
1157 ExactlyOnce,
1158 >::collection_kind(
1159 )),
1160 },
1161 );
1162
1163 let membership_stream_ident = syn::Ident::new(
1164 &format!(
1165 "__hydro_deploy_many_{}_{}_membership",
1166 from.key, next_external_port_id
1167 ),
1168 Span::call_site(),
1169 );
1170 let membership_stream_expr: syn::Expr = parse_quote!(#membership_stream_ident);
1171 let raw_membership_stream: KeyedStream<
1172 u64,
1173 bool,
1174 Self::DropConsistency,
1175 Unbounded,
1176 TotalOrder,
1177 ExactlyOnce,
1178 > = KeyedStream::new(
1179 target_consistency.clone(),
1180 HydroNode::Source {
1181 source: HydroSource::Stream(membership_stream_expr.into()),
1182 metadata: target_consistency.new_node_metadata(KeyedStream::<
1183 u64,
1184 bool,
1185 Self::DropConsistency,
1186 Unbounded,
1187 TotalOrder,
1188 ExactlyOnce,
1189 >::collection_kind(
1190 )),
1191 },
1192 );
1193
1194 (
1195 ExternalBincodeBidi {
1196 process_key: from.key,
1197 port_id: next_external_port_id,
1198 _phantom: PhantomData,
1199 },
1200 raw_stream,
1201 raw_membership_stream.map(q!(|join| {
1202 if join {
1203 MembershipEvent::Joined
1204 } else {
1205 MembershipEvent::Left
1206 }
1207 })),
1208 fwd_ref,
1209 )
1210 }
1211
1212 fn sidecar_bidi<InT: 'static, OutT: 'static, F>(
1265 &self,
1266 sidecar: impl QuotedWithContext<'a, F, Self>,
1267 ) -> (
1268 Stream<InT, Self, Unbounded, TotalOrder, ExactlyOnce>,
1269 ForwardHandle<'a, Stream<OutT, Self, Unbounded, NoOrder, ExactlyOnce>>,
1270 )
1271 where
1272 Self: Sized + TopLevel<'a>,
1273 {
1274 let location_key = Location::id(self).key();
1275
1276 let sidecar_id = self.flow_state().borrow_mut().next_sidecar_id();
1277 let (stream_ident, sink_ident) = sidecar_id.idents();
1278
1279 let sidecar_closure: syn::Expr = sidecar.splice_untyped_ctx(self);
1280 self.flow_state()
1281 .borrow_mut()
1282 .sidecars
1283 .push(crate::compile::builder::Sidecar::Bidi {
1284 location_key,
1285 sidecar_id,
1286 sidecar_closure: Box::new(sidecar_closure),
1287 });
1288
1289 let source_expr: syn::Expr = parse_quote! {
1291 #stream_ident
1292 };
1293 let inbound: Stream<InT, Self, Unbounded, TotalOrder, ExactlyOnce> = Stream::new(
1294 self.clone(),
1295 HydroNode::Source {
1296 source: HydroSource::Stream(source_expr.into()),
1297 metadata: self.new_node_metadata(Stream::<
1298 InT,
1299 Self,
1300 Unbounded, TotalOrder, ExactlyOnce,
1303 >::collection_kind()),
1304 },
1305 );
1306
1307 let (fwd_ref, to_sink): (
1309 ForwardHandle<'a, Stream<OutT, Self, Unbounded, NoOrder, ExactlyOnce>>,
1310 Stream<OutT, Self, Unbounded, NoOrder, ExactlyOnce>,
1311 ) = self.forward_ref();
1312
1313 let sink_expr: syn::Expr = parse_quote! {
1314 #sink_ident
1315 };
1316
1317 let sink_input_ir = to_sink.ir_node.replace(HydroNode::Placeholder);
1318 self.flow_state()
1319 .borrow_mut()
1320 .try_push_root(HydroRoot::DestSink {
1321 sink: sink_expr.into(),
1322 input: Box::new(sink_input_ir),
1323 op_metadata: HydroIrOpMetadata::new(),
1324 });
1325
1326 (inbound, fwd_ref)
1327 }
1328
1329 fn singleton<T>(
1349 &self,
1350 e: impl QuotedWithContext<'a, T, Self>,
1351 ) -> Singleton<T, Self::DropConsistency, Bounded>
1352 where
1353 Self: Sized,
1354 {
1355 let e = e.splice_untyped_ctx(self);
1356
1357 let target_location = self.drop_consistency();
1358 Singleton::new(
1359 target_location.clone(),
1360 HydroNode::SingletonSource {
1361 value: e.into(),
1362 first_tick_only: false,
1363 metadata: target_location.new_node_metadata(Singleton::<
1364 T,
1365 Self::DropConsistency,
1366 Bounded,
1367 >::collection_kind()),
1368 },
1369 )
1370 }
1371
1372 fn singleton_future<F>(
1395 &self,
1396 e: impl QuotedWithContext<'a, F, Self>,
1397 ) -> Singleton<F::Output, Self::DropConsistency, Bounded>
1398 where
1399 F: Future,
1400 Self: Sized,
1401 {
1402 self.singleton(e).resolve_future_blocking()
1403 }
1404
1405 #[cfg(feature = "tokio")]
1414 fn source_interval(
1415 &self,
1416 interval: impl QuotedWithContext<'a, Duration, Self> + Copy + 'a,
1417 ) -> Stream<(), Self, Unbounded, TotalOrder, ExactlyOnce>
1418 where
1419 Self: TopLevel<'a> + Sized,
1420 {
1421 self.source_stream(q!(tokio_stream::StreamExt::map(
1422 tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(interval)),
1423 |_| ()
1424 )))
1425 .assert_has_consistency_of_trusted(
1426 manual_proof!(),
1427 )
1428 }
1429
1430 #[cfg(feature = "tokio")]
1437 fn source_interval_delayed(
1438 &self,
1439 delay: impl QuotedWithContext<'a, Duration, Self> + Copy + 'a,
1440 interval: impl QuotedWithContext<'a, Duration, Self> + Copy + 'a,
1441 ) -> Stream<(), Self, Unbounded, TotalOrder, ExactlyOnce>
1442 where
1443 Self: TopLevel<'a> + Sized,
1444 {
1445 self.source_stream(q!(tokio_stream::StreamExt::map(
1446 tokio_stream::wrappers::IntervalStream::new(tokio::time::interval_at(
1447 tokio::time::Instant::now() + delay,
1448 interval,
1449 )),
1450 |_| ()
1451 )))
1452 .assert_has_consistency_of_trusted(
1453 manual_proof!(),
1454 )
1455 }
1456
1457 fn forward_ref<S>(&self) -> (ForwardHandle<'a, S>, S)
1497 where
1498 S: CycleCollection<'a, ForwardRef, Location = Self>,
1499 {
1500 let cycle_id = self.flow_state().borrow_mut().next_cycle_id();
1501 (
1502 ForwardHandle::new(cycle_id, Location::id(self)),
1503 S::create_source(cycle_id, self.clone()),
1504 )
1505 }
1506}
1507
1508#[cfg(feature = "deploy")]
1509#[cfg(test)]
1510mod tests {
1511 use std::collections::HashSet;
1512
1513 use futures::{SinkExt, StreamExt};
1514 use hydro_deploy::Deployment;
1515 use stageleft::q;
1516 use tokio_util::codec::LengthDelimitedCodec;
1517
1518 use crate::compile::builder::FlowBuilder;
1519 use crate::live_collections::stream::{ExactlyOnce, TotalOrder};
1520 use crate::location::{Location, NetworkHint};
1521 use crate::nondet::nondet;
1522
1523 #[tokio::test]
1524 async fn top_level_singleton_replay_cardinality() {
1525 let mut deployment = Deployment::new();
1526
1527 let mut flow = FlowBuilder::new();
1528 let node = flow.process::<()>();
1529 let external = flow.external::<()>();
1530
1531 let (in_port, input) =
1532 node.source_external_bincode::<_, _, TotalOrder, ExactlyOnce>(&external);
1533 let singleton = node.singleton(q!(123));
1534 let tick = node.tick();
1535 let out = input
1536 .batch(&tick, nondet!())
1537 .cross_singleton(singleton.clone().snapshot(&tick, nondet!()))
1538 .cross_singleton(
1539 singleton
1540 .snapshot(&tick, nondet!())
1541 .into_stream()
1542 .count(),
1543 )
1544 .all_ticks()
1545 .send_bincode_external(&external);
1546
1547 let nodes = flow
1548 .with_process(&node, deployment.Localhost())
1549 .with_external(&external, deployment.Localhost())
1550 .deploy(&mut deployment);
1551
1552 deployment.deploy().await.unwrap();
1553
1554 let mut external_in = nodes.connect(in_port).await;
1555 let mut external_out = nodes.connect(out).await;
1556
1557 deployment.start().await.unwrap();
1558
1559 external_in.send(1).await.unwrap();
1560 assert_eq!(external_out.next().await.unwrap(), ((1, 123), 1));
1561
1562 external_in.send(2).await.unwrap();
1563 assert_eq!(external_out.next().await.unwrap(), ((2, 123), 1));
1564 }
1565
1566 #[tokio::test]
1567 async fn tick_singleton_replay_cardinality() {
1568 let mut deployment = Deployment::new();
1569
1570 let mut flow = FlowBuilder::new();
1571 let node = flow.process::<()>();
1572 let external = flow.external::<()>();
1573
1574 let (in_port, input) =
1575 node.source_external_bincode::<_, _, TotalOrder, ExactlyOnce>(&external);
1576 let tick = node.tick();
1577 let singleton = tick.singleton(q!(123));
1578 let out = input
1579 .batch(&tick, nondet!())
1580 .cross_singleton(singleton.clone())
1581 .cross_singleton(singleton.into_stream().count())
1582 .all_ticks()
1583 .send_bincode_external(&external);
1584
1585 let nodes = flow
1586 .with_process(&node, deployment.Localhost())
1587 .with_external(&external, deployment.Localhost())
1588 .deploy(&mut deployment);
1589
1590 deployment.deploy().await.unwrap();
1591
1592 let mut external_in = nodes.connect(in_port).await;
1593 let mut external_out = nodes.connect(out).await;
1594
1595 deployment.start().await.unwrap();
1596
1597 external_in.send(1).await.unwrap();
1598 assert_eq!(external_out.next().await.unwrap(), ((1, 123), 1));
1599
1600 external_in.send(2).await.unwrap();
1601 assert_eq!(external_out.next().await.unwrap(), ((2, 123), 1));
1602 }
1603
1604 #[tokio::test]
1605 async fn external_bytes() {
1606 let mut deployment = Deployment::new();
1607
1608 let mut flow = FlowBuilder::new();
1609 let first_node = flow.process::<()>();
1610 let external = flow.external::<()>();
1611
1612 let (in_port, input) = first_node.source_external_bytes(&external);
1613 let out = input.send_bincode_external(&external);
1614
1615 let nodes = flow
1616 .with_process(&first_node, deployment.Localhost())
1617 .with_external(&external, deployment.Localhost())
1618 .deploy(&mut deployment);
1619
1620 deployment.deploy().await.unwrap();
1621
1622 let mut external_in = nodes.connect(in_port).await.1;
1623 let mut external_out = nodes.connect(out).await;
1624
1625 deployment.start().await.unwrap();
1626
1627 external_in.send(vec![1, 2, 3].into()).await.unwrap();
1628
1629 assert_eq!(external_out.next().await.unwrap(), vec![1, 2, 3]);
1630 }
1631
1632 #[tokio::test]
1633 async fn multi_external_source() {
1634 let mut deployment = Deployment::new();
1635
1636 let mut flow = FlowBuilder::new();
1637 let first_node = flow.process::<()>();
1638 let external = flow.external::<()>();
1639
1640 let (in_port, input, _membership, complete_sink) =
1641 first_node.bidi_external_many_bincode(&external);
1642 let out = input.entries().send_bincode_external(&external);
1643 complete_sink.complete(
1644 first_node
1645 .source_iter::<(u64, ()), _>(q!([]))
1646 .into_keyed()
1647 .weaken_ordering(),
1648 );
1649
1650 let nodes = flow
1651 .with_process(&first_node, deployment.Localhost())
1652 .with_external(&external, deployment.Localhost())
1653 .deploy(&mut deployment);
1654
1655 deployment.deploy().await.unwrap();
1656
1657 let (_, mut external_in_1) = nodes.connect_bincode(in_port.clone()).await;
1658 let (_, mut external_in_2) = nodes.connect_bincode(in_port).await;
1659 let external_out = nodes.connect(out).await;
1660
1661 deployment.start().await.unwrap();
1662
1663 external_in_1.send(123).await.unwrap();
1664 external_in_2.send(456).await.unwrap();
1665
1666 assert_eq!(
1667 external_out.take(2).collect::<HashSet<_>>().await,
1668 vec![(0, 123), (1, 456)].into_iter().collect()
1669 );
1670 }
1671
1672 #[tokio::test]
1673 async fn second_connection_only_multi_source() {
1674 let mut deployment = Deployment::new();
1675
1676 let mut flow = FlowBuilder::new();
1677 let first_node = flow.process::<()>();
1678 let external = flow.external::<()>();
1679
1680 let (in_port, input, _membership, complete_sink) =
1681 first_node.bidi_external_many_bincode(&external);
1682 let out = input.entries().send_bincode_external(&external);
1683 complete_sink.complete(
1684 first_node
1685 .source_iter::<(u64, ()), _>(q!([]))
1686 .into_keyed()
1687 .weaken_ordering(),
1688 );
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_1) = nodes.connect_bincode(in_port.clone()).await;
1699 let (_, mut external_in_2) = nodes.connect_bincode(in_port).await;
1700 let mut external_out = nodes.connect(out).await;
1701
1702 deployment.start().await.unwrap();
1703
1704 external_in_2.send(456).await.unwrap();
1705
1706 assert_eq!(external_out.next().await.unwrap(), (1, 456));
1707 }
1708
1709 #[tokio::test]
1710 async fn multi_external_bytes() {
1711 let mut deployment = Deployment::new();
1712
1713 let mut flow = FlowBuilder::new();
1714 let first_node = flow.process::<()>();
1715 let external = flow.external::<()>();
1716
1717 let (in_port, input, _membership, complete_sink) = first_node
1718 .bidi_external_many_bytes::<_, _, LengthDelimitedCodec>(&external, NetworkHint::Auto);
1719 let out = input.entries().send_bincode_external(&external);
1720 complete_sink.complete(
1721 first_node
1722 .source_iter(q!([]))
1723 .into_keyed()
1724 .weaken_ordering(),
1725 );
1726
1727 let nodes = flow
1728 .with_process(&first_node, deployment.Localhost())
1729 .with_external(&external, deployment.Localhost())
1730 .deploy(&mut deployment);
1731
1732 deployment.deploy().await.unwrap();
1733
1734 let mut external_in_1 = nodes.connect(in_port.clone()).await.1;
1735 let mut external_in_2 = nodes.connect(in_port).await.1;
1736 let external_out = nodes.connect(out).await;
1737
1738 deployment.start().await.unwrap();
1739
1740 external_in_1.send(vec![1, 2, 3].into()).await.unwrap();
1741 external_in_2.send(vec![4, 5].into()).await.unwrap();
1742
1743 assert_eq!(
1744 external_out.take(2).collect::<HashSet<_>>().await,
1745 vec![
1746 (0, (&[1u8, 2, 3] as &[u8]).into()),
1747 (1, (&[4u8, 5] as &[u8]).into())
1748 ]
1749 .into_iter()
1750 .collect()
1751 );
1752 }
1753
1754 #[tokio::test]
1755 async fn single_client_external_bytes() {
1756 let mut deployment = Deployment::new();
1757 let mut flow = FlowBuilder::new();
1758 let first_node = flow.process::<()>();
1759 let external = flow.external::<()>();
1760 let (port, input, complete_sink) = first_node
1761 .bind_single_client::<_, _, LengthDelimitedCodec>(&external, NetworkHint::Auto);
1762 complete_sink.complete(input.map(q!(|data| {
1763 let mut resp: Vec<u8> = data.into();
1764 resp.push(42);
1765 resp.into() })));
1767
1768 let nodes = flow
1769 .with_process(&first_node, deployment.Localhost())
1770 .with_external(&external, deployment.Localhost())
1771 .deploy(&mut deployment);
1772
1773 deployment.deploy().await.unwrap();
1774 deployment.start().await.unwrap();
1775
1776 let (mut external_out, mut external_in) = nodes.connect(port).await;
1777
1778 external_in.send(vec![1, 2, 3].into()).await.unwrap();
1779 assert_eq!(
1780 external_out.next().await.unwrap().unwrap(),
1781 vec![1, 2, 3, 42]
1782 );
1783 }
1784
1785 #[tokio::test]
1786 async fn echo_external_bytes() {
1787 let mut deployment = Deployment::new();
1788
1789 let mut flow = FlowBuilder::new();
1790 let first_node = flow.process::<()>();
1791 let external = flow.external::<()>();
1792
1793 let (port, input, _membership, complete_sink) = first_node
1794 .bidi_external_many_bytes::<_, _, LengthDelimitedCodec>(&external, NetworkHint::Auto);
1795 complete_sink
1796 .complete(input.map(q!(|bytes| { bytes.into_iter().map(|x| x + 1).collect() })));
1797
1798 let nodes = flow
1799 .with_process(&first_node, deployment.Localhost())
1800 .with_external(&external, deployment.Localhost())
1801 .deploy(&mut deployment);
1802
1803 deployment.deploy().await.unwrap();
1804
1805 let (mut external_out_1, mut external_in_1) = nodes.connect(port.clone()).await;
1806 let (mut external_out_2, mut external_in_2) = nodes.connect(port).await;
1807
1808 deployment.start().await.unwrap();
1809
1810 external_in_1.send(vec![1, 2, 3].into()).await.unwrap();
1811 external_in_2.send(vec![4, 5].into()).await.unwrap();
1812
1813 assert_eq!(external_out_1.next().await.unwrap().unwrap(), vec![2, 3, 4]);
1814 assert_eq!(external_out_2.next().await.unwrap().unwrap(), vec![5, 6]);
1815 }
1816
1817 #[tokio::test]
1818 async fn echo_external_bincode() {
1819 let mut deployment = Deployment::new();
1820
1821 let mut flow = FlowBuilder::new();
1822 let first_node = flow.process::<()>();
1823 let external = flow.external::<()>();
1824
1825 let (port, input, _membership, complete_sink) =
1826 first_node.bidi_external_many_bincode(&external);
1827 complete_sink.complete(input.map(q!(|text: String| { text.to_uppercase() })));
1828
1829 let nodes = flow
1830 .with_process(&first_node, deployment.Localhost())
1831 .with_external(&external, deployment.Localhost())
1832 .deploy(&mut deployment);
1833
1834 deployment.deploy().await.unwrap();
1835
1836 let (mut external_out_1, mut external_in_1) = nodes.connect_bincode(port.clone()).await;
1837 let (mut external_out_2, mut external_in_2) = nodes.connect_bincode(port).await;
1838
1839 deployment.start().await.unwrap();
1840
1841 external_in_1.send("hi".to_owned()).await.unwrap();
1842 external_in_2.send("hello".to_owned()).await.unwrap();
1843
1844 assert_eq!(external_out_1.next().await.unwrap(), "HI");
1845 assert_eq!(external_out_2.next().await.unwrap(), "HELLO");
1846 }
1847
1848 #[tokio::test]
1849 async fn closure_location_name() {
1850 let mut deployment = Deployment::new();
1851 let mut flow = FlowBuilder::new();
1852
1853 enum ClosureProcess {}
1854
1855 let node = flow.process::<ClosureProcess>();
1856 let external = flow.external::<()>();
1857
1858 let (in_port, input) =
1859 node.source_external_bincode::<_, i32, TotalOrder, ExactlyOnce>(&external);
1860 let out = input.send_bincode_external(&external);
1861
1862 let nodes = flow
1863 .with_process(&node, deployment.Localhost())
1864 .with_external(&external, deployment.Localhost())
1865 .deploy(&mut deployment);
1866
1867 deployment.deploy().await.unwrap();
1868
1869 let mut external_in = nodes.connect(in_port).await;
1870 let mut external_out = nodes.connect(out).await;
1871
1872 deployment.start().await.unwrap();
1873
1874 external_in.send(42).await.unwrap();
1875 assert_eq!(external_out.next().await.unwrap(), 42);
1876 }
1877}