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 DropConsistency = Cluster<'a, C, NoConsistency>;
117
118 fn consistency() -> Option<ClusterConsistency> {
119 Some(Con::consistency())
120 }
121
122 fn root(&self) -> Self::Root {
123 self.clone()
124 }
125
126 fn drop_consistency(&self) -> Self::DropConsistency {
127 Cluster {
128 key: self.key,
129 flow_state: self.flow_state.clone(),
130 _phantom: PhantomData,
131 }
132 }
133
134 fn from_drop_consistency(l2: Self::DropConsistency) -> Self {
135 Cluster {
136 key: l2.key,
137 flow_state: l2.flow_state,
138 _phantom: PhantomData,
139 }
140 }
141}
142
143impl<'a, C, Con: Consistency> TopLevel<'a> for Cluster<'a, C, Con> {}
144
145#[cfg(feature = "sim")]
146impl<'a, C> Cluster<'a, C> {
147 pub fn sim_input<
160 T,
161 O: crate::live_collections::stream::Ordering,
162 R: crate::live_collections::stream::Retries,
163 >(
164 &self,
165 ) -> (
166 crate::sim::SimClusterSender<T, O, R>,
167 crate::live_collections::Stream<
168 T,
169 Self,
170 crate::live_collections::boundedness::Unbounded,
171 O,
172 R,
173 >,
174 )
175 where
176 T: serde::Serialize + serde::de::DeserializeOwned,
177 {
178 self.sim_input_with::<crate::sim::codec::BincodeCodec, T, O, R>()
179 }
180
181 pub fn sim_input_with<
190 Codec: crate::sim::codec::SimCodec<T>,
191 T,
192 O: crate::live_collections::stream::Ordering,
193 R: crate::live_collections::stream::Retries,
194 >(
195 &self,
196 ) -> (
197 crate::sim::SimClusterSender<T, O, R>,
198 crate::live_collections::Stream<
199 T,
200 Self,
201 crate::live_collections::boundedness::Unbounded,
202 O,
203 R,
204 >,
205 ) {
206 let external_location: crate::location::External<'a, ()> = crate::location::External {
207 key: LocationKey::FIRST,
208 flow_state: self.flow_state.clone(),
209 _phantom: PhantomData,
210 };
211
212 let (external_port_id, stream) = super::register_serialized_external_input(
213 self,
214 &external_location,
215 crate::sim::codec::staged_deserialize::<T, Codec>(),
216 );
217
218 (
219 crate::sim::SimClusterSender(external_port_id, PhantomData, Codec::encode),
220 stream.weaken_ordering().weaken_retries(),
221 )
222 }
223}
224
225pub struct ClusterIds<'a> {
230 pub key: LocationKey,
232 pub _phantom: PhantomData<&'a ()>,
234}
235
236impl<'a> Clone for ClusterIds<'a> {
237 fn clone(&self) -> Self {
238 Self {
239 key: self.key,
240 _phantom: Default::default(),
241 }
242 }
243}
244
245impl<'a, Ctx> FreeVariableWithContextWithProps<Ctx, ()> for ClusterIds<'a> {
246 type O = &'a [TaglessMemberId];
247
248 fn to_tokens(self, _ctx: &Ctx) -> (QuoteTokens, ())
249 where
250 Self: Sized,
251 {
252 let ident = syn::Ident::new(
253 &format!("__hydro_lang_cluster_ids_{}", self.key),
254 Span::call_site(),
255 );
256
257 (
258 QuoteTokens {
259 prelude: None,
260 expr: Some(quote! { #ident }),
261 },
262 (),
263 )
264 }
265}
266
267impl<'a, Ctx> QuotedWithContextWithProps<'a, &'a [TaglessMemberId], Ctx, ()> for ClusterIds<'a> {}
268
269pub trait IsCluster {
271 type Tag;
273}
274
275impl<C> IsCluster for Cluster<'_, C> {
276 type Tag = C;
277}
278
279pub static CLUSTER_SELF_ID: ClusterSelfId<'static> = ClusterSelfId { _private: &() };
282
283#[derive(Clone, Copy)]
288pub struct ClusterSelfId<'a> {
289 _private: &'a (),
290}
291
292impl<'a, Ctx> FreeVariableWithContextWithProps<Ctx, ()> for ClusterSelfId<'a>
293where
294 Ctx: crate::live_collections::ContextWithLocation<'a>,
295 <Ctx::Location as Location<'a>>::Root: IsCluster,
296{
297 type O = MemberId<<<Ctx::Location as Location<'a>>::Root as IsCluster>::Tag>;
298
299 fn to_tokens(self, ctx: &Ctx) -> (QuoteTokens, ())
300 where
301 Self: Sized,
302 {
303 let LocationId::Cluster(cluster_id) = ctx.context_location().root().id() else {
304 unreachable!()
305 };
306
307 let ident = syn::Ident::new(
308 &format!("__hydro_lang_cluster_self_id_{}", cluster_id),
309 Span::call_site(),
310 );
311 let root = get_this_crate();
312 let c_type: syn::Type =
313 quote_type::<<<Ctx::Location as Location<'a>>::Root as IsCluster>::Tag>();
314
315 (
316 QuoteTokens {
317 prelude: None,
318 expr: Some(
319 quote! { #root::__staged::location::MemberId::<#c_type>::from_tagless((#ident).clone()) },
320 ),
321 },
322 (),
323 )
324 }
325}
326
327impl<'a, Ctx>
328 QuotedWithContextWithProps<
329 'a,
330 MemberId<<<Ctx::Location as Location<'a>>::Root as IsCluster>::Tag>,
331 Ctx,
332 (),
333 > for ClusterSelfId<'a>
334where
335 Ctx: crate::live_collections::ContextWithLocation<'a>,
336 <Ctx::Location as Location<'a>>::Root: IsCluster,
337{
338}
339
340#[cfg(test)]
341mod tests {
342 #[cfg(feature = "sim")]
343 use stageleft::q;
344
345 #[cfg(feature = "sim")]
346 use super::CLUSTER_SELF_ID;
347 #[cfg(feature = "sim")]
348 use crate::location::{Location, MemberId, MembershipEvent};
349 #[cfg(feature = "sim")]
350 use crate::networking::TCP;
351 #[cfg(feature = "sim")]
352 use crate::nondet::nondet;
353 #[cfg(feature = "sim")]
354 use crate::prelude::FlowBuilder;
355
356 #[cfg(feature = "sim")]
357 #[test]
358 fn sim_cluster_self_id() {
359 let mut flow = FlowBuilder::new();
360 let cluster1 = flow.cluster::<()>();
361 let cluster2 = flow.cluster::<()>();
362
363 let node = flow.process::<()>();
364
365 let out_recv = cluster1
366 .source_iter(q!(vec![CLUSTER_SELF_ID]))
367 .send(&node, TCP.fail_stop().bincode())
368 .values()
369 .merge_unordered(
370 cluster2
371 .source_iter(q!(vec![CLUSTER_SELF_ID]))
372 .send(&node, TCP.fail_stop().bincode())
373 .values(),
374 )
375 .sim_output();
376
377 flow.sim()
378 .with_cluster_size(&cluster1, 3)
379 .with_cluster_size(&cluster2, 4)
380 .exhaustive(async || {
381 out_recv
382 .assert_yields_only_unordered([0, 1, 2, 0, 1, 2, 3].map(MemberId::from_raw_id))
383 .await
384 });
385 }
386
387 #[cfg(feature = "sim")]
388 #[test]
389 fn sim_cluster_with_tick() {
390 use std::collections::HashMap;
391
392 let mut flow = FlowBuilder::new();
393 let cluster = flow.cluster::<()>();
394 let node = flow.process::<()>();
395
396 let out_recv = cluster
397 .source_iter(q!(vec![1, 2, 3]))
398 .batch(&cluster.tick(), nondet!())
399 .count()
400 .all_ticks()
401 .send(&node, TCP.fail_stop().bincode())
402 .entries()
403 .map(q!(|(id, v)| (id, v)))
404 .sim_output();
405
406 let count = flow
407 .sim()
408 .with_cluster_size(&cluster, 2)
409 .exhaustive(async || {
410 let grouped = out_recv.collect_sorted::<Vec<_>>().await.into_iter().fold(
411 HashMap::new(),
412 |mut acc: HashMap<MemberId<()>, usize>, (id, v)| {
413 *acc.entry(id).or_default() += v;
414 acc
415 },
416 );
417
418 assert!(grouped.len() == 2);
419 for (_id, v) in grouped {
420 assert!(v == 3);
421 }
422 });
423
424 assert_eq!(count, 106);
425 }
429
430 #[cfg(feature = "sim")]
431 #[test]
432 fn sim_cluster_membership() {
433 let mut flow = FlowBuilder::new();
434 let cluster = flow.cluster::<()>();
435 let node = flow.process::<()>();
436
437 let out_recv = node
438 .source_cluster_membership_stream(&cluster, nondet!())
439 .entries()
440 .map(q!(|(id, v)| (id, v)))
441 .sim_output();
442
443 flow.sim()
444 .with_cluster_size(&cluster, 2)
445 .exhaustive(async || {
446 out_recv
447 .assert_yields_only_unordered(vec![
448 (MemberId::from_raw_id(0), MembershipEvent::Joined),
449 (MemberId::from_raw_id(1), MembershipEvent::Joined),
450 ])
451 .await;
452 });
453 }
454}