1use std::fmt::{Debug, Formatter};
13use std::marker::PhantomData;
14
15use proc_macro2::Span;
16use quote::quote;
17use stageleft::runtime_support::{FreeVariableWithContextWithProps, QuoteTokens};
18use stageleft::{QuotedWithContextWithProps, quote_type};
19
20use super::dynamic::LocationId;
21use super::{Location, MemberId};
22use crate::compile::builder::FlowState;
23use crate::location::dynamic::ClusterConsistency;
24use crate::location::member_id::TaglessMemberId;
25use crate::location::{LocationKey, TopLevel};
26use crate::staging_util::{Invariant, get_this_crate};
27
28pub trait Consistency {
31 fn consistency() -> ClusterConsistency;
33}
34
35pub enum NoConsistency {}
38impl Consistency for NoConsistency {
39 fn consistency() -> ClusterConsistency {
40 ClusterConsistency::NoConsistency
41 }
42}
43
44pub enum EventualConsistency {}
47impl Consistency for EventualConsistency {
48 fn consistency() -> ClusterConsistency {
49 ClusterConsistency::EventualConsistency
50 }
51}
52
53pub struct Cluster<'a, ClusterTag, Con: Consistency = NoConsistency> {
63 pub(crate) key: LocationKey,
64 pub(crate) flow_state: FlowState,
65 pub(crate) _phantom: Invariant<'a, (ClusterTag, Con)>,
66}
67
68impl<C, Con: Consistency> Debug for Cluster<'_, C, Con> {
69 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
70 write!(f, "Cluster({})", self.key)
71 }
72}
73
74impl<C, Con: Consistency> Eq for Cluster<'_, C, Con> {}
75impl<C, Con: Consistency> PartialEq for Cluster<'_, C, Con> {
76 fn eq(&self, other: &Self) -> bool {
77 self.key == other.key && FlowState::ptr_eq(&self.flow_state, &other.flow_state)
78 }
79}
80
81impl<C, Con: Consistency> Clone for Cluster<'_, C, Con> {
82 fn clone(&self) -> Self {
83 Cluster {
84 key: self.key,
85 flow_state: self.flow_state.clone(),
86 _phantom: PhantomData,
87 }
88 }
89}
90
91impl<'a, C, Con: Consistency> super::dynamic::DynLocation for Cluster<'a, C, Con> {
92 fn dyn_id(&self) -> LocationId {
93 LocationId::Cluster(self.key)
94 }
95
96 fn flow_state(&self) -> &FlowState {
97 &self.flow_state
98 }
99
100 fn is_top_level() -> bool {
101 true
102 }
103
104 fn multiversioned(&self) -> bool {
105 false }
107
108 fn cluster_consistency() -> Option<ClusterConsistency> {
109 Some(Con::consistency())
110 }
111}
112
113impl<'a, C, Con: Consistency> Location<'a> for Cluster<'a, C, Con> {
114 type Root = Cluster<'a, C, Con>;
115
116 type SimHookScope = crate::sim_hooks::OnCluster<C>;
117
118 type DropConsistency = Cluster<'a, C, NoConsistency>;
119
120 fn consistency() -> Option<ClusterConsistency> {
121 Some(Con::consistency())
122 }
123
124 fn root(&self) -> Self::Root {
125 self.clone()
126 }
127
128 fn drop_consistency(&self) -> Self::DropConsistency {
129 Cluster {
130 key: self.key,
131 flow_state: self.flow_state.clone(),
132 _phantom: PhantomData,
133 }
134 }
135
136 fn from_drop_consistency(l2: Self::DropConsistency) -> Self {
137 Cluster {
138 key: l2.key,
139 flow_state: l2.flow_state,
140 _phantom: PhantomData,
141 }
142 }
143}
144
145impl<'a, C, Con: Consistency> TopLevel<'a> for Cluster<'a, C, Con> {}
146
147#[cfg(feature = "sim")]
148impl<'a, C> Cluster<'a, C> {
149 pub fn sim_input<
162 T,
163 O: crate::live_collections::stream::Ordering,
164 R: crate::live_collections::stream::Retries,
165 >(
166 &self,
167 ) -> (
168 crate::sim::SimClusterSender<T, O, R>,
169 crate::live_collections::Stream<
170 T,
171 Self,
172 crate::live_collections::boundedness::Unbounded,
173 O,
174 R,
175 >,
176 )
177 where
178 T: serde::Serialize + serde::de::DeserializeOwned,
179 {
180 self.sim_input_with::<crate::sim::codec::BincodeCodec, T, O, R>()
181 }
182
183 pub fn sim_input_with<
192 Codec: crate::sim::codec::SimCodec<T>,
193 T,
194 O: crate::live_collections::stream::Ordering,
195 R: crate::live_collections::stream::Retries,
196 >(
197 &self,
198 ) -> (
199 crate::sim::SimClusterSender<T, O, R>,
200 crate::live_collections::Stream<
201 T,
202 Self,
203 crate::live_collections::boundedness::Unbounded,
204 O,
205 R,
206 >,
207 ) {
208 let external_location: crate::location::External<'a, ()> = crate::location::External {
209 key: LocationKey::FIRST,
210 flow_state: self.flow_state.clone(),
211 _phantom: PhantomData,
212 };
213
214 let (external_port_id, stream) = super::register_serialized_external_input(
215 self,
216 &external_location,
217 crate::sim::codec::staged_deserialize::<T, Codec>(),
218 );
219
220 (
221 crate::sim::SimClusterSender(external_port_id, PhantomData, Codec::encode),
222 stream.weaken_ordering().weaken_retries(),
223 )
224 }
225}
226
227pub struct ClusterIds<'a> {
232 pub key: LocationKey,
234 pub _phantom: PhantomData<&'a ()>,
236}
237
238impl<'a> Clone for ClusterIds<'a> {
239 fn clone(&self) -> Self {
240 Self {
241 key: self.key,
242 _phantom: Default::default(),
243 }
244 }
245}
246
247impl<'a, Ctx> FreeVariableWithContextWithProps<Ctx, ()> for ClusterIds<'a> {
248 type O = &'a [TaglessMemberId];
249
250 fn to_tokens(self, _ctx: &Ctx) -> (QuoteTokens, ())
251 where
252 Self: Sized,
253 {
254 let ident = syn::Ident::new(
255 &format!("__hydro_lang_cluster_ids_{}", self.key),
256 Span::call_site(),
257 );
258
259 (
260 QuoteTokens {
261 prelude: None,
262 expr: Some(quote! { #ident }),
263 },
264 (),
265 )
266 }
267}
268
269impl<'a, Ctx> QuotedWithContextWithProps<'a, &'a [TaglessMemberId], Ctx, ()> for ClusterIds<'a> {}
270
271pub trait IsCluster {
273 type Tag;
275}
276
277impl<C> IsCluster for Cluster<'_, C> {
278 type Tag = C;
279}
280
281pub static CLUSTER_SELF_ID: ClusterSelfId<'static> = ClusterSelfId { _private: &() };
284
285#[derive(Clone, Copy)]
290pub struct ClusterSelfId<'a> {
291 _private: &'a (),
292}
293
294impl<'a, Ctx> FreeVariableWithContextWithProps<Ctx, ()> for ClusterSelfId<'a>
295where
296 Ctx: crate::live_collections::ContextWithLocation<'a>,
297 <Ctx::Location as Location<'a>>::Root: IsCluster,
298{
299 type O = MemberId<<<Ctx::Location as Location<'a>>::Root as IsCluster>::Tag>;
300
301 fn to_tokens(self, ctx: &Ctx) -> (QuoteTokens, ())
302 where
303 Self: Sized,
304 {
305 let LocationId::Cluster(cluster_id) = ctx.context_location().root().id() else {
306 unreachable!()
307 };
308
309 let ident = syn::Ident::new(
310 &format!("__hydro_lang_cluster_self_id_{}", cluster_id),
311 Span::call_site(),
312 );
313 let root = get_this_crate();
314 let c_type: syn::Type =
315 quote_type::<<<Ctx::Location as Location<'a>>::Root as IsCluster>::Tag>();
316
317 (
318 QuoteTokens {
319 prelude: None,
320 expr: Some(
321 quote! { #root::__staged::location::MemberId::<#c_type>::from_tagless((#ident).clone()) },
322 ),
323 },
324 (),
325 )
326 }
327}
328
329impl<'a, Ctx>
330 QuotedWithContextWithProps<
331 'a,
332 MemberId<<<Ctx::Location as Location<'a>>::Root as IsCluster>::Tag>,
333 Ctx,
334 (),
335 > for ClusterSelfId<'a>
336where
337 Ctx: crate::live_collections::ContextWithLocation<'a>,
338 <Ctx::Location as Location<'a>>::Root: IsCluster,
339{
340}
341
342#[cfg(test)]
343mod tests {
344 #[cfg(feature = "sim")]
345 use stageleft::q;
346
347 #[cfg(feature = "sim")]
348 use super::CLUSTER_SELF_ID;
349 #[cfg(feature = "sim")]
350 use crate::location::{Location, MemberId, MembershipEvent};
351 #[cfg(feature = "sim")]
352 use crate::networking::TCP;
353 #[cfg(feature = "sim")]
354 use crate::nondet::nondet;
355 #[cfg(feature = "sim")]
356 use crate::prelude::FlowBuilder;
357
358 #[cfg(feature = "sim")]
359 #[test]
360 fn sim_cluster_self_id() {
361 let mut flow = FlowBuilder::new();
362 let cluster1 = flow.cluster::<()>();
363 let cluster2 = flow.cluster::<()>();
364
365 let node = flow.process::<()>();
366
367 let out_recv = cluster1
368 .source_iter(q!(vec![CLUSTER_SELF_ID]))
369 .send(&node, TCP.fail_stop().bincode())
370 .values()
371 .merge_unordered(
372 cluster2
373 .source_iter(q!(vec![CLUSTER_SELF_ID]))
374 .send(&node, TCP.fail_stop().bincode())
375 .values(),
376 )
377 .sim_output();
378
379 flow.sim()
380 .with_cluster_size(&cluster1, 3)
381 .with_cluster_size(&cluster2, 4)
382 .exhaustive(async || {
383 out_recv
384 .assert_yields_only_unordered([0, 1, 2, 0, 1, 2, 3].map(MemberId::from_raw_id))
385 .await
386 });
387 }
388
389 #[cfg(feature = "sim")]
390 #[test]
391 fn sim_cluster_with_tick() {
392 use std::collections::HashMap;
393
394 let mut flow = FlowBuilder::new();
395 let cluster = flow.cluster::<()>();
396 let node = flow.process::<()>();
397
398 let out_recv = cluster
399 .source_iter(q!(vec![1, 2, 3]))
400 .batch(&cluster.tick(), nondet!())
401 .count()
402 .all_ticks()
403 .send(&node, TCP.fail_stop().bincode())
404 .entries()
405 .map(q!(|(id, v)| (id, v)))
406 .sim_output();
407
408 let count = flow
409 .sim()
410 .with_cluster_size(&cluster, 2)
411 .exhaustive(async || {
412 let grouped = out_recv.collect_sorted::<Vec<_>>().await.into_iter().fold(
413 HashMap::new(),
414 |mut acc: HashMap<MemberId<()>, usize>, (id, v)| {
415 *acc.entry(id).or_default() += v;
416 acc
417 },
418 );
419
420 assert!(grouped.len() == 2);
421 for (_id, v) in grouped {
422 assert!(v == 3);
423 }
424 });
425
426 assert_eq!(count, 106);
427 }
431
432 #[cfg(feature = "sim")]
433 #[test]
434 fn sim_cluster_membership() {
435 let mut flow = FlowBuilder::new();
436 let cluster = flow.cluster::<()>();
437 let node = flow.process::<()>();
438
439 let out_recv = node
440 .source_cluster_membership_stream(&cluster, nondet!())
441 .entries()
442 .map(q!(|(id, v)| (id, v)))
443 .sim_output();
444
445 flow.sim()
446 .with_cluster_size(&cluster, 2)
447 .exhaustive(async || {
448 out_recv
449 .assert_yields_only_unordered(vec![
450 (MemberId::from_raw_id(0), MembershipEvent::Joined),
451 (MemberId::from_raw_id(1), MembershipEvent::Joined),
452 ])
453 .await;
454 });
455 }
456}