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