Skip to main content

hydro_lang/compile/
embedded_runtime.rs

1//! Runtime helpers for the embedded deployment backend.
2//!
3//! This module is NOT gated on the `build` feature so that the staged
4//! re-exports are available at runtime in the generated code.
5
6use std::marker::PhantomData;
7
8use futures::Stream;
9use stageleft::{QuotedWithContext, RuntimeData, q};
10
11use crate::live_collections::stream::{
12    AtLeastOnce, ExactlyOnce, NoOrder, Ordering, Retries, TotalOrder,
13};
14use crate::location::MembershipEvent;
15use crate::location::member_id::TaglessMemberId;
16use crate::networking::NetworkingConfig;
17use crate::properties::ManualProof;
18
19/// Returns a [`QuotedWithContext`] that references the `__cluster_self_id` runtime variable.
20pub fn embedded_cluster_self_id<'a>() -> impl QuotedWithContext<'a, TaglessMemberId, ()> + Clone + 'a
21{
22    let self_id: RuntimeData<&TaglessMemberId> = RuntimeData::new("__cluster_self_id");
23    q!(self_id.clone())
24}
25
26/// Returns a [`QuotedWithContext`] that references a `__membership_{idx}` runtime variable.
27pub fn embedded_cluster_membership_stream<'a>(
28    idx: usize,
29) -> impl QuotedWithContext<'a, Box<dyn Stream<Item = (TaglessMemberId, MembershipEvent)> + Unpin>, ()>
30{
31    // TODO(shadaj): change `Deploy` trait to use `syn::Expr` to avoid leaking here
32    // TODO(shadaj): this will not work if the same location reads the same membership stream multiple times
33    let var_name: &'static str = Box::leak(format!("__membership_{}", idx).into_boxed_str());
34    let membership: RuntimeData<
35        Box<dyn Stream<Item = (TaglessMemberId, MembershipEvent)> + Unpin>,
36    > = RuntimeData::new(var_name);
37    q!(membership)
38}
39
40/// A channel crossing the boundary of an embedded dataflow: a network channel end, a stream
41/// input, a membership stream, or an output, with its guarantees as marker types (`Ordering`
42/// and `Retries`).
43///
44/// - For a stream input or membership stream, the guarantees are what the dataflow relies on,
45///   which the host's [`Promises`] must meet.
46/// - For the sending end of a network channel, they are what the dataflow sends, which the
47///   host's [`Acknowledgements`] must cover, as for an output.
48/// - For the receiving end of a network channel, they are what the dataflow relies on: the sent
49///   stream, weakened by the channel's configuration. The host's [`Promises`] must meet them, as
50///   for a stream input.
51/// - For an output, they are what the dataflow provides. The host's [`Acknowledgements`] must
52///   cover every way the output may be weaker than ordered and exactly-once.
53///
54/// Each location's generated module has a marker type implementing this for each
55/// channel crossing it, named after its field: under `incoming` / `outgoing` for
56/// network channel ends, `inputs` for stream inputs, `membership` for membership streams, and
57/// `outputs` for outputs. Each field of the generated structs is an [`EmbeddedInputChannel`] (a stream
58/// input or membership stream), a [`EmbeddedNetworkInputChannel`] (a receiving end), a [`EmbeddedNetworkOutputChannel`] (a
59/// sending end), or an [`EmbeddedOutputChannel`] (an output) over its marker. The marker is inferred from
60/// the field. The markers of network channel ends also implement [`EmbeddedNetworkChannel`].
61pub trait EmbeddedChannel {
62    /// The ordering the host must keep (for an output or the sending end of a network channel,
63    /// the ordering the dataflow provides): `TotalOrder` or `NoOrder`. For a network channel,
64    /// `TotalOrder` means each sender's messages in the order it sent them; for an input, that
65    /// the stream's order is the order to process; for a membership stream, each member's events
66    /// in the order they happened.
67    type Ordering: Ordering;
68
69    /// The retry guarantee the host must keep (for an output or the sending end of a network
70    /// channel, the one the dataflow provides): `ExactlyOnce` (nothing delivered twice) or
71    /// `AtLeastOnce`.
72    type Retries: Retries;
73}
74
75/// What a host promises about something it supplies, each guarantee promised separately with
76/// a [`manual_proof!`](crate::prelude::manual_proof) saying why it holds. An [`EmbeddedInputChannel`] or
77/// [`EmbeddedNetworkInputChannel`] is built from a stream and the `Promises` that meet its marker's
78/// guarantees.
79pub struct Promises<O, R> {
80    _promised: PhantomData<fn() -> (O, R)>,
81}
82
83impl Promises<NoOrder, AtLeastOnce> {
84    /// Promises nothing: elements may arrive in any order, and any may be delivered more than
85    /// once.
86    pub fn new() -> Self {
87        Promises {
88            _promised: PhantomData,
89        }
90    }
91}
92
93impl Default for Promises<NoOrder, AtLeastOnce> {
94    fn default() -> Self {
95        Self::new()
96    }
97}
98
99impl<O, R> Promises<O, R> {
100    fn retag<O2, R2>(self) -> Promises<O2, R2> {
101        Promises {
102            _promised: PhantomData,
103        }
104    }
105}
106
107impl<R> Promises<NoOrder, R> {
108    /// Promises the order: for a channel, each sender's messages are delivered in the order it
109    /// sent them; for an input, the stream's order is the order to process; for a membership
110    /// stream, each member's events arrive in the order they happened.
111    pub fn total_order(self, _proof: ManualProof) -> Promises<TotalOrder, R> {
112        self.retag()
113    }
114}
115
116impl<O> Promises<O, AtLeastOnce> {
117    /// Promises that nothing is delivered twice: a channel never duplicates a message (for
118    /// example by resending it after a reconnect), and every element of an input is a distinct
119    /// event.
120    pub fn exactly_once(self, _proof: ManualProof) -> Promises<O, ExactlyOnce> {
121        self.retag()
122    }
123}
124
125/// A channel whose guarantees promises of ordering `O` and retries `R` meet: each guarantee the
126/// channel needs is weaker than (or the same as) the one promised. It is what [`EmbeddedInputChannel::new`]
127/// requires, and lets a reusable transport state once what it provides:
128/// `fn source<C: SatisfiedBy<TotalOrder, ExactlyOnce>>()` serves every channel that needs at
129/// most that.
130pub trait SatisfiedBy<O, R>: EmbeddedChannel {}
131
132impl<K, O, R> SatisfiedBy<O, R> for K
133where
134    K: EmbeddedChannel,
135    K::Ordering: OrderPromisedBy<O, K>,
136    K::Retries: RetriesPromisedBy<R, K>,
137{
138}
139
140/// A needed order (`Self`) that the promised order `Promised` meets, for the channel end `End`.
141/// Only order is promised, so a promise is needed only for `TotalOrder`.
142#[diagnostic::on_unimplemented(
143    message = "`{End}` relies on its elements arriving in order, but the host does not promise it",
144    label = "does not promise `TotalOrder`",
145    note = "add `.total_order(manual_proof!(/** why the order is kept */))` to its `Promises`"
146)]
147trait OrderPromisedBy<Promised, End> {}
148
149impl<Promised: Ordering, End> OrderPromisedBy<Promised, End> for NoOrder {}
150impl<End> OrderPromisedBy<TotalOrder, End> for TotalOrder {}
151
152/// A needed retry guarantee (`Self`) that the promised one `Promised` meets, for `End`.
153#[diagnostic::on_unimplemented(
154    message = "`{End}` relies on each element arriving exactly once, but the host does not promise it",
155    label = "does not promise `ExactlyOnce`",
156    note = "add `.exactly_once(manual_proof!(/** why nothing is delivered twice */))` to its `Promises`"
157)]
158trait RetriesPromisedBy<Promised, End> {}
159
160impl<Promised: Retries, End> RetriesPromisedBy<Promised, End> for AtLeastOnce {}
161impl<End> RetriesPromisedBy<ExactlyOnce, End> for ExactlyOnce {}
162
163/// A channel flowing into an embedded dataflow, as the host provides it: a `Stream` for a stream
164/// input or a membership stream, for the channel `C` (its generated marker type, see
165/// [`EmbeddedChannel`]). The receiving end of a network channel is a [`EmbeddedNetworkInputChannel`] instead.
166///
167/// An `EmbeddedInputChannel` exists only with promises that meet `C`'s guarantees:
168/// [`EmbeddedInputChannel::new`] takes the [`Promises`] and checks them, so wiring a channel always
169/// states its guarantees and a mismatch with the dataflow is a type error at that call. Promising
170/// more than a channel needs is fine. `C` is inferred from the field the `EmbeddedInputChannel` fills.
171pub struct EmbeddedInputChannel<C, S> {
172    inner: S,
173    _channel: PhantomData<fn() -> C>,
174}
175
176impl<C: EmbeddedChannel, S> EmbeddedInputChannel<C, S> {
177    /// Wraps a host stream for channel `C`, with `promises` that must meet its guarantees.
178    pub fn new<O, R>(inner: S, promises: Promises<O, R>) -> Self
179    where
180        C: SatisfiedBy<O, R>,
181    {
182        let _ = promises;
183        EmbeddedInputChannel {
184            inner,
185            _channel: PhantomData,
186        }
187    }
188}
189
190impl<C, S> EmbeddedInputChannel<C, S> {
191    /// The host's stream. Used by generated code.
192    #[doc(hidden)]
193    pub fn into_inner(self) -> S {
194        self.inner
195    }
196}
197
198/// The configuration of a network channel crossing the boundary of an embedded dataflow: the
199/// transport type `Transport` of the network config the dataflow sends with (for example
200/// `Tcp<FailStop>` for `TCP.fail_stop()`, or `EmbeddedTransport<ExactlyOnce, NoOrder, FailStop>`
201/// for `EMBEDDED.may_reorder().never_duplicates().never_drops()`), and its serialization `Serialization` (`Bincode`, or `Embedded`
202/// for an unserialized `EMBEDDED` channel).
203///
204/// The markers of both ends of a network channel implement it, alongside [`EmbeddedChannel`]
205/// for the stream at that end. The host acknowledges the configuration exactly at each end with
206/// a [`ChannelConfiguration`], so that changing the channel's network config fails the host's
207/// build at both ends until it acknowledges the change.
208pub trait EmbeddedNetworkChannel: EmbeddedChannel {
209    /// The transport type of the channel's network config.
210    type Transport;
211
212    /// The serialization of the channel's network config.
213    type Serialization;
214}
215
216/// The configuration of a network channel as the host acknowledges it: the transport `Tr` and
217/// serialization `S` of its network config, which must match the channel's exactly (see
218/// [`EmbeddedNetworkChannel`]). It is built from the same network config value the dataflow
219/// sends with; the config's name is ignored.
220pub struct ChannelConfiguration<Tr, S> {
221    _acknowledged: PhantomData<fn() -> (Tr, S)>,
222}
223
224impl<Tr, S> ChannelConfiguration<Tr, S> {
225    /// Acknowledges a channel configured with `config`, the same network config the dataflow
226    /// sends with (for example `TCP.fail_stop().bincode()`).
227    pub fn new<Name>(config: NetworkingConfig<Tr, S, Name>) -> Self {
228        let _ = config;
229        ChannelConfiguration {
230            _acknowledged: PhantomData,
231        }
232    }
233}
234
235/// A network channel end whose configuration is exactly the acknowledged transport `Tr` and
236/// serialization `S`. Each part is checked separately, so a mismatch names the part the host
237/// acknowledges wrongly. It is what [`EmbeddedNetworkOutputChannel::new`] and [`EmbeddedNetworkInputChannel::new`] require.
238pub trait ConfiguredAs<Tr, S>: EmbeddedNetworkChannel {}
239
240impl<C, Tr, S> ConfiguredAs<Tr, S> for C
241where
242    C: EmbeddedNetworkChannel,
243    Tr: AcknowledgesTransport<C::Transport, C>,
244    S: AcknowledgesSerialization<C::Serialization, C>,
245{
246}
247
248/// Checks the acknowledged transport separately, so a mismatch names it.
249#[diagnostic::on_unimplemented(
250    message = "the channel end `{End}` is configured with `{Declared}`, but the host acknowledges `{Self}`",
251    label = "acknowledges `{Self}`",
252    note = "acknowledge the same network config the dataflow sends with; the marker's docs give it"
253)]
254trait AcknowledgesTransport<Declared, End> {}
255
256impl<X, End> AcknowledgesTransport<X, End> for X {}
257
258/// Checks the acknowledged serialization separately, so a mismatch names it.
259#[diagnostic::on_unimplemented(
260    message = "the channel end `{End}` is serialized with `{Declared}`, but the host acknowledges `{Self}`",
261    label = "acknowledges `{Self}`",
262    note = "acknowledge the same network config the dataflow sends with; the marker's docs give it"
263)]
264trait AcknowledgesSerialization<Declared, End> {}
265
266impl<X, End> AcknowledgesSerialization<X, End> for X {}
267
268/// The sending end of a network channel, as the host provides it: an `FnMut` sink that hands
269/// each message to the host's transport, for the channel end `C` (its generated marker type,
270/// under `outgoing`).
271///
272/// The host agrees to both inputs of the dataflow's send: [`EmbeddedNetworkOutputChannel::new`] takes the
273/// [`ChannelConfiguration`] the host acknowledges, which must match the channel's exactly, and the
274/// [`Acknowledgements`] of the stream the dataflow sends, which must cover every way it may be
275/// weaker than ordered and exactly-once, as for an [`EmbeddedOutputChannel`]. `C` is inferred from the
276/// field the `EmbeddedNetworkOutputChannel` fills.
277pub struct EmbeddedNetworkOutputChannel<C, S> {
278    inner: S,
279    _channel: PhantomData<fn() -> C>,
280}
281
282impl<C: EmbeddedNetworkChannel, S> EmbeddedNetworkOutputChannel<C, S> {
283    /// Wraps a host sink for the channel end `C`, with the acknowledgements of its
284    /// configuration (`channel`) and of the stream sent on it (`acknowledgements`).
285    pub fn new<Tr, Ser, O, R>(
286        inner: S,
287        channel: ChannelConfiguration<Tr, Ser>,
288        acknowledgements: Acknowledgements<O, R>,
289    ) -> Self
290    where
291        C: ConfiguredAs<Tr, Ser> + AcknowledgedBy<O, R>,
292    {
293        let _ = (channel, acknowledgements);
294        EmbeddedNetworkOutputChannel {
295            inner,
296            _channel: PhantomData,
297        }
298    }
299}
300
301impl<C, S> EmbeddedNetworkOutputChannel<C, S> {
302    /// The host's sink. Used by generated code.
303    #[doc(hidden)]
304    pub fn into_inner(self) -> S {
305        self.inner
306    }
307}
308
309/// The receiving end of a network channel, as the host provides it: a `Stream` of the messages
310/// its transport delivers, for the channel end `C` (its generated marker type, under
311/// `incoming`).
312///
313/// The host agrees to the channel's configuration and to the stream it delivers:
314/// [`EmbeddedNetworkInputChannel::new`] takes the [`ChannelConfiguration`] the host acknowledges,
315/// which must match exactly, and the [`Promises`] of the stream it delivers, which must meet what
316/// the dataflow relies on, as for an [`EmbeddedInputChannel`]. That stream is the sent stream weakened by
317/// the channel's configuration (a channel that may reorder delivers a `NoOrder` stream), so it
318/// may be weaker than what the sending end acknowledges. `C` is inferred from the field the
319/// `EmbeddedNetworkInputChannel` fills.
320pub struct EmbeddedNetworkInputChannel<C, S> {
321    inner: S,
322    _channel: PhantomData<fn() -> C>,
323}
324
325impl<C: EmbeddedNetworkChannel, S> EmbeddedNetworkInputChannel<C, S> {
326    /// Wraps a host stream for the channel end `C`, with the acknowledgement of its
327    /// configuration (`channel`) and the promises of the stream it delivers (`promises`).
328    pub fn new<Tr, Ser, O, R>(
329        inner: S,
330        channel: ChannelConfiguration<Tr, Ser>,
331        promises: Promises<O, R>,
332    ) -> Self
333    where
334        C: ConfiguredAs<Tr, Ser> + SatisfiedBy<O, R>,
335    {
336        let _ = (channel, promises);
337        EmbeddedNetworkInputChannel {
338            inner,
339            _channel: PhantomData,
340        }
341    }
342}
343
344impl<C, S> EmbeddedNetworkInputChannel<C, S> {
345    /// The host's stream. Used by generated code.
346    #[doc(hidden)]
347    pub fn into_inner(self) -> S {
348        self.inner
349    }
350}
351
352/// What a host acknowledges about a stream the dataflow provides (an output, or the stream sent
353/// on a network channel): each way it may be weaker than ordered and exactly-once. The stream's
354/// type already says what it may do, so an acknowledgement needs no justification: calling it is
355/// the host's agreement, and a stream that becomes weaker fails the host's build until the host
356/// acknowledges the change. An
357/// [`EmbeddedOutputChannel`] or [`EmbeddedNetworkOutputChannel`] is built from a sink and the `Acknowledgements` that
358/// cover what its marker's stream may do.
359pub struct Acknowledgements<O, R> {
360    _acknowledged: PhantomData<fn() -> (O, R)>,
361}
362
363impl Acknowledgements<TotalOrder, ExactlyOnce> {
364    /// Acknowledges nothing: the sink relies on receiving elements in the dataflow's order, each
365    /// exactly once.
366    pub fn new() -> Self {
367        Acknowledgements {
368            _acknowledged: PhantomData,
369        }
370    }
371}
372
373impl Default for Acknowledgements<TotalOrder, ExactlyOnce> {
374    fn default() -> Self {
375        Self::new()
376    }
377}
378
379impl<O, R> Acknowledgements<O, R> {
380    fn retag<O2, R2>(self) -> Acknowledgements<O2, R2> {
381        Acknowledgements {
382            _acknowledged: PhantomData,
383        }
384    }
385}
386
387impl<R> Acknowledgements<TotalOrder, R> {
388    /// Acknowledges that elements may arrive in any order.
389    pub fn no_order(self) -> Acknowledgements<NoOrder, R> {
390        self.retag()
391    }
392}
393
394impl<O> Acknowledgements<O, ExactlyOnce> {
395    /// Acknowledges that an element may arrive more than once.
396    pub fn at_least_once(self) -> Acknowledgements<O, AtLeastOnce> {
397        self.retag()
398    }
399}
400
401/// An output whose guarantees acknowledgements of ordering `O` and retries `R` cover: each
402/// acknowledged guarantee is weaker than (or the same as) the one the output provides. It is
403/// what [`EmbeddedOutputChannel::new`]
404/// requires.
405pub trait AcknowledgedBy<O, R>: EmbeddedChannel {}
406
407impl<K, O, R> AcknowledgedBy<O, R> for K
408where
409    K: EmbeddedChannel,
410    K::Ordering: OrderAcknowledgedBy<O, K>,
411    K::Retries: RetriesAcknowledgedBy<R, K>,
412{
413}
414
415/// A provided order (`Self`) that the acknowledged order `Acknowledged` covers, for `End`.
416/// Only a weaker order is acknowledged, so an acknowledgement is needed only for `NoOrder`.
417#[diagnostic::on_unimplemented(
418    message = "`{End}` may deliver its elements in any order, which the host must acknowledge",
419    label = "does not acknowledge `NoOrder`",
420    note = "add `.no_order()` to its `Acknowledgements`"
421)]
422trait OrderAcknowledgedBy<Acknowledged, End> {}
423
424impl<Acknowledged: Ordering, End> OrderAcknowledgedBy<Acknowledged, End> for TotalOrder {}
425impl<End> OrderAcknowledgedBy<NoOrder, End> for NoOrder {}
426
427/// A provided retry guarantee (`Self`) that the acknowledged one `Acknowledged` covers, for `End`.
428#[diagnostic::on_unimplemented(
429    message = "`{End}` may deliver an element more than once, which the host must acknowledge",
430    label = "does not acknowledge `AtLeastOnce`",
431    note = "add `.at_least_once()` to its `Acknowledgements`"
432)]
433trait RetriesAcknowledgedBy<Acknowledged, End> {}
434
435impl<Acknowledged: Retries, End> RetriesAcknowledgedBy<Acknowledged, End> for ExactlyOnce {}
436impl<End> RetriesAcknowledgedBy<AtLeastOnce, End> for AtLeastOnce {}
437
438/// An output of an embedded dataflow, as the host provides it: an `FnMut` sink for the channel
439/// `M` (its generated marker type, under `outputs`), whose [`EmbeddedChannel`] guarantees are
440/// what the dataflow's output stream provides.
441///
442/// An `EmbeddedOutputChannel` exists only with acknowledgements that cover what the output may do:
443/// [`EmbeddedOutputChannel::new`] takes the [`Acknowledgements`] and checks them, so a sink cannot
444/// silently receive elements out of order or more than once. Acknowledging more than an output
445/// may do is fine. `M` is inferred from the field the `EmbeddedOutputChannel` fills.
446pub struct EmbeddedOutputChannel<M, S> {
447    inner: S,
448    _output: PhantomData<fn() -> M>,
449}
450
451impl<M: EmbeddedChannel, S> EmbeddedOutputChannel<M, S> {
452    /// Wraps a host sink for output `M`, with `acknowledgements` that cover what `M` may do.
453    pub fn new<O, R>(inner: S, acknowledgements: Acknowledgements<O, R>) -> Self
454    where
455        M: AcknowledgedBy<O, R>,
456    {
457        let _ = acknowledgements;
458        EmbeddedOutputChannel {
459            inner,
460            _output: PhantomData,
461        }
462    }
463}
464
465impl<M, S> EmbeddedOutputChannel<M, S> {
466    /// The host's sink. Used by generated code.
467    #[doc(hidden)]
468    pub fn into_inner(self) -> S {
469        self.inner
470    }
471}