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<
159 T,
160 O: crate::live_collections::stream::Ordering,
161 R: crate::live_collections::stream::Retries,
162 >(
163 &self,
164 ) -> (
165 crate::sim::SimClusterSender<T, O, R>,
166 crate::live_collections::Stream<
167 T,
168 Self,
169 crate::live_collections::boundedness::Unbounded,
170 O,
171 R,
172 >,
173 )
174 where
175 T: serde::Serialize + serde::de::DeserializeOwned,
176 {
177 use crate::location::Location;
178
179 let external_location: crate::location::External<'a, ()> = crate::location::External {
180 key: LocationKey::FIRST,
181 flow_state: self.flow_state.clone(),
182 _phantom: PhantomData,
183 };
184
185 let (external, stream) = self.source_external_bincode(&external_location);
186
187 (
188 crate::sim::SimClusterSender(external.port_id, PhantomData),
189 stream,
190 )
191 }
192}
193
194pub struct ClusterIds<'a> {
199 pub key: LocationKey,
201 pub _phantom: PhantomData<&'a ()>,
203}
204
205impl<'a> Clone for ClusterIds<'a> {
206 fn clone(&self) -> Self {
207 Self {
208 key: self.key,
209 _phantom: Default::default(),
210 }
211 }
212}
213
214impl<'a, Ctx> FreeVariableWithContextWithProps<Ctx, ()> for ClusterIds<'a> {
215 type O = &'a [TaglessMemberId];
216
217 fn to_tokens(self, _ctx: &Ctx) -> (QuoteTokens, ())
218 where
219 Self: Sized,
220 {
221 let ident = syn::Ident::new(
222 &format!("__hydro_lang_cluster_ids_{}", self.key),
223 Span::call_site(),
224 );
225
226 (
227 QuoteTokens {
228 prelude: None,
229 expr: Some(quote! { #ident }),
230 },
231 (),
232 )
233 }
234}
235
236impl<'a, Ctx> QuotedWithContextWithProps<'a, &'a [TaglessMemberId], Ctx, ()> for ClusterIds<'a> {}
237
238pub trait IsCluster {
240 type Tag;
242}
243
244impl<C> IsCluster for Cluster<'_, C> {
245 type Tag = C;
246}
247
248pub static CLUSTER_SELF_ID: ClusterSelfId = ClusterSelfId { _private: &() };
251
252#[derive(Clone, Copy)]
257pub struct ClusterSelfId<'a> {
258 _private: &'a (),
259}
260
261impl<'a, Ctx> FreeVariableWithContextWithProps<Ctx, ()> for ClusterSelfId<'a>
262where
263 Ctx: crate::live_collections::ContextWithLocation<'a>,
264 <Ctx::Location as Location<'a>>::Root: IsCluster,
265{
266 type O = MemberId<<<Ctx::Location as Location<'a>>::Root as IsCluster>::Tag>;
267
268 fn to_tokens(self, ctx: &Ctx) -> (QuoteTokens, ())
269 where
270 Self: Sized,
271 {
272 let LocationId::Cluster(cluster_id) = ctx.context_location().root().id() else {
273 unreachable!()
274 };
275
276 let ident = syn::Ident::new(
277 &format!("__hydro_lang_cluster_self_id_{}", cluster_id),
278 Span::call_site(),
279 );
280 let root = get_this_crate();
281 let c_type: syn::Type =
282 quote_type::<<<Ctx::Location as Location<'a>>::Root as IsCluster>::Tag>();
283
284 (
285 QuoteTokens {
286 prelude: None,
287 expr: Some(
288 quote! { #root::__staged::location::MemberId::<#c_type>::from_tagless((#ident).clone()) },
289 ),
290 },
291 (),
292 )
293 }
294}
295
296impl<'a, Ctx>
297 QuotedWithContextWithProps<
298 'a,
299 MemberId<<<Ctx::Location as Location<'a>>::Root as IsCluster>::Tag>,
300 Ctx,
301 (),
302 > for ClusterSelfId<'a>
303where
304 Ctx: crate::live_collections::ContextWithLocation<'a>,
305 <Ctx::Location as Location<'a>>::Root: IsCluster,
306{
307}
308
309#[cfg(test)]
310mod tests {
311 #[cfg(feature = "sim")]
312 use stageleft::q;
313
314 #[cfg(feature = "sim")]
315 use super::CLUSTER_SELF_ID;
316 #[cfg(feature = "sim")]
317 use crate::location::{Location, MemberId, MembershipEvent};
318 #[cfg(feature = "sim")]
319 use crate::networking::TCP;
320 #[cfg(feature = "sim")]
321 use crate::nondet::nondet;
322 #[cfg(feature = "sim")]
323 use crate::prelude::FlowBuilder;
324
325 #[cfg(feature = "sim")]
326 #[test]
327 fn sim_cluster_self_id() {
328 let mut flow = FlowBuilder::new();
329 let cluster1 = flow.cluster::<()>();
330 let cluster2 = flow.cluster::<()>();
331
332 let node = flow.process::<()>();
333
334 let out_recv = cluster1
335 .source_iter(q!(vec![CLUSTER_SELF_ID]))
336 .send(&node, TCP.fail_stop().bincode())
337 .values()
338 .merge_unordered(
339 cluster2
340 .source_iter(q!(vec![CLUSTER_SELF_ID]))
341 .send(&node, TCP.fail_stop().bincode())
342 .values(),
343 )
344 .sim_output();
345
346 flow.sim()
347 .with_cluster_size(&cluster1, 3)
348 .with_cluster_size(&cluster2, 4)
349 .exhaustive(async || {
350 out_recv
351 .assert_yields_only_unordered([0, 1, 2, 0, 1, 2, 3].map(MemberId::from_raw_id))
352 .await
353 });
354 }
355
356 #[cfg(feature = "sim")]
357 #[test]
358 fn sim_cluster_with_tick() {
359 use std::collections::HashMap;
360
361 let mut flow = FlowBuilder::new();
362 let cluster = flow.cluster::<()>();
363 let node = flow.process::<()>();
364
365 let out_recv = cluster
366 .source_iter(q!(vec![1, 2, 3]))
367 .batch(&cluster.tick(), nondet!())
368 .count()
369 .all_ticks()
370 .send(&node, TCP.fail_stop().bincode())
371 .entries()
372 .map(q!(|(id, v)| (id, v)))
373 .sim_output();
374
375 let count = flow
376 .sim()
377 .with_cluster_size(&cluster, 2)
378 .exhaustive(async || {
379 let grouped = out_recv.collect_sorted::<Vec<_>>().await.into_iter().fold(
380 HashMap::new(),
381 |mut acc: HashMap<MemberId<()>, usize>, (id, v)| {
382 *acc.entry(id).or_default() += v;
383 acc
384 },
385 );
386
387 assert!(grouped.len() == 2);
388 for (_id, v) in grouped {
389 assert!(v == 3);
390 }
391 });
392
393 assert_eq!(count, 106);
394 }
398
399 #[cfg(feature = "sim")]
400 #[test]
401 fn sim_cluster_membership() {
402 let mut flow = FlowBuilder::new();
403 let cluster = flow.cluster::<()>();
404 let node = flow.process::<()>();
405
406 let out_recv = node
407 .source_cluster_membership_stream(&cluster, nondet!())
408 .entries()
409 .map(q!(|(id, v)| (id, v)))
410 .sim_output();
411
412 flow.sim()
413 .with_cluster_size(&cluster, 2)
414 .exhaustive(async || {
415 out_recv
416 .assert_yields_only_unordered(vec![
417 (MemberId::from_raw_id(0), MembershipEvent::Joined),
418 (MemberId::from_raw_id(1), MembershipEvent::Joined),
419 ])
420 .await;
421 });
422 }
423}