hydro_lang/networking/mod.rs
1//! Types for configuring network channels with serialization formats, transports, etc.
2
3use std::marker::PhantomData;
4
5use serde::Serialize;
6use serde::de::DeserializeOwned;
7
8use crate::live_collections::stream::networking::{deserialize_bincode, serialize_bincode};
9use crate::live_collections::stream::{
10 AtLeastOnce, ExactlyOnce, NoOrder, Ordering, Retries, TotalOrder,
11};
12use crate::location::cluster::{Consistency, EventualConsistency, NoConsistency};
13use crate::nondet::NonDet;
14
15#[sealed::sealed]
16trait SerKind<T: ?Sized> {
17 fn serialize_thunk(is_demux: bool) -> syn::Expr;
18
19 fn deserialize_thunk(tagged: Option<&syn::Type>) -> syn::Expr;
20
21 /// Whether this serialization backend leaves serialization to code outside of Hydro (see
22 /// [`Embedded`]). When `true`, [`Self::serialize_thunk`] and [`Self::deserialize_thunk`] are
23 /// never called; the raw element type flows across the channel unserialized.
24 fn is_embedded() -> bool {
25 false
26 }
27}
28
29/// Serialize items using the [`bincode`] crate.
30pub enum Bincode {}
31
32#[sealed::sealed]
33impl<T: Serialize + DeserializeOwned> SerKind<T> for Bincode {
34 fn serialize_thunk(is_demux: bool) -> syn::Expr {
35 serialize_bincode::<T>(is_demux)
36 }
37
38 fn deserialize_thunk(tagged: Option<&syn::Type>) -> syn::Expr {
39 deserialize_bincode::<T>(tagged)
40 }
41}
42
43/// Leaves serialization of items to code outside of Hydro. Every [`EMBEDDED`] channel uses it.
44///
45/// This serialization backend is only supported by the embedded deployment backend (it will panic
46/// on all other backends). The generated network channel exposes the raw element type `T` to the
47/// developer (rather than serialized bytes), so they can perform custom serialization logic outside
48/// of the Hydro program for that channel.
49pub enum Embedded {}
50
51#[sealed::sealed]
52impl<T> SerKind<T> for Embedded {
53 fn serialize_thunk(_is_demux: bool) -> syn::Expr {
54 unreachable!("embedded serialization does not use a serialize thunk")
55 }
56
57 fn deserialize_thunk(_tagged: Option<&syn::Type>) -> syn::Expr {
58 unreachable!("embedded serialization does not use a deserialize thunk")
59 }
60
61 fn is_embedded() -> bool {
62 true
63 }
64}
65
66/// An unconfigured serialization backend.
67pub enum NoSer {}
68
69/// A transport backend for network channels.
70#[sealed::sealed]
71pub trait TransportKind {
72 /// The ordering guarantee provided by this transport.
73 type OrderingGuarantee: Ordering;
74
75 /// The retry guarantee provided by this transport: [`ExactlyOnce`] if it never delivers a
76 /// message more than once.
77 type RetriesGuarantee: Retries;
78
79 /// The consistency guarantee this transport can preserve for replicated outputs
80 /// (see [`NetworkFor::ConsistencyGuarantee`]).
81 type ConsistencyGuarantee: Consistency;
82
83 /// Returns the [`NetworkingInfo`] describing this transport's configuration.
84 fn networking_info() -> NetworkingInfo;
85}
86
87#[sealed::sealed]
88#[diagnostic::on_unimplemented(
89 message = "the TCP transport requires a failure policy",
90 note = "for example, `TCP.fail_stop()` stops delivering messages after a failed connection"
91)]
92/// A failure policy for TCP connections, determining how the transport handles
93/// connection failures and what ordering guarantees the output stream provides.
94pub trait TcpFailPolicy {
95 /// The ordering guarantee provided by this failure policy.
96 type OrderingGuarantee: Ordering;
97
98 /// The consistency guarantee this failure policy can preserve for replicated outputs
99 /// (see [`NetworkFor::ConsistencyGuarantee`]).
100 type ConsistencyGuarantee: Consistency;
101
102 /// Returns the [`TcpFault`] variant for this failure policy.
103 fn tcp_fault() -> TcpFault;
104}
105
106/// A TCP failure policy that stops sending messages after a failed connection.
107pub enum FailStop {}
108#[sealed::sealed]
109impl TcpFailPolicy for FailStop {
110 type OrderingGuarantee = TotalOrder;
111
112 // A failed connection stops *all* future deliveries to that recipient, which models the
113 // recipient as having failed. Consistency guarantees only apply to live members, so
114 // eventual consistency of replicated outputs is preserved.
115 type ConsistencyGuarantee = EventualConsistency;
116
117 fn tcp_fault() -> TcpFault {
118 TcpFault::FailStop
119 }
120}
121
122/// A failure policy that allows messages to be lost.
123pub enum Lossy {}
124#[sealed::sealed]
125impl TcpFailPolicy for Lossy {
126 type OrderingGuarantee = TotalOrder;
127
128 // A lossy channel can drop an arbitrary message for one recipient while continuing to
129 // deliver later messages, so replicated outputs can diverge across members forever.
130 type ConsistencyGuarantee = NoConsistency;
131
132 fn tcp_fault() -> TcpFault {
133 TcpFault::Lossy
134 }
135}
136
137/// A failure policy that treats dropped messages as indefinitely delayed.
138///
139/// Unlike [`Lossy`], this does not require a [`NonDet`] annotation because the output
140/// stream is always lower in the partial order than the ideal stream (dropped messages
141/// are modeled as infinite delays). The tradeoff is that the output has [`NoOrder`]
142/// guarantees, imposing stricter conditions on downstream consumers.
143///
144/// When using this mode in the Hydro simulator, you must call
145/// [`.test_safety_only()`](crate::sim::flow::SimFlow::test_safety_only): the simulator
146/// will not actually drop packets—it delays "dropped" messages until the end of the
147/// execution, which catches safety bugs but cannot test liveness.
148pub enum LossyDelayedForever {}
149#[sealed::sealed]
150impl TcpFailPolicy for LossyDelayedForever {
151 type OrderingGuarantee = NoOrder;
152
153 // Dropped messages are modeled as indefinitely delayed, so the output on each member is
154 // always a lower bound of the ideal stream that is eventually delivered in full; replicated
155 // outputs therefore remain eventually consistent.
156 type ConsistencyGuarantee = EventualConsistency;
157
158 fn tcp_fault() -> TcpFault {
159 TcpFault::LossyDelayedForever
160 }
161}
162
163#[sealed::sealed]
164#[diagnostic::on_unimplemented(
165 message = "the UDP transport requires a failure policy",
166 note = "for example, `UDP.lossy_delayed_forever()` treats dropped messages as indefinitely delayed"
167)]
168/// A failure policy for UDP channels, determining how the transport handles
169/// message loss. Because UDP provides no ordering guarantees, all policies
170/// produce [`NoOrder`] output streams, and there is no `fail_stop` option
171/// (UDP is connectionless, so there is no connection to fail).
172pub trait UdpFailPolicy {
173 /// The consistency guarantee this failure policy can preserve for replicated outputs
174 /// (see [`NetworkFor::ConsistencyGuarantee`]).
175 type ConsistencyGuarantee: Consistency;
176
177 /// Returns the [`UdpFault`] variant for this failure policy.
178 fn udp_fault() -> UdpFault;
179}
180
181#[sealed::sealed]
182impl UdpFailPolicy for Lossy {
183 // A lossy channel can drop an arbitrary message for one recipient while continuing to
184 // deliver later messages, so replicated outputs can diverge across members forever.
185 type ConsistencyGuarantee = NoConsistency;
186
187 fn udp_fault() -> UdpFault {
188 UdpFault::Lossy
189 }
190}
191
192#[sealed::sealed]
193impl UdpFailPolicy for LossyDelayedForever {
194 // Dropped messages are modeled as indefinitely delayed, so the output on each member is
195 // always a lower bound of the ideal stream that is eventually delivered in full; replicated
196 // outputs therefore remain eventually consistent.
197 type ConsistencyGuarantee = EventualConsistency;
198
199 fn udp_fault() -> UdpFault {
200 UdpFault::LossyDelayedForever
201 }
202}
203
204/// Send items across a length-delimited TCP channel.
205pub struct Tcp<F> {
206 _phantom: PhantomData<F>,
207}
208
209#[sealed::sealed]
210impl<F: TcpFailPolicy> TransportKind for Tcp<F> {
211 type OrderingGuarantee = F::OrderingGuarantee;
212
213 type RetriesGuarantee = ExactlyOnce;
214
215 type ConsistencyGuarantee = F::ConsistencyGuarantee;
216
217 fn networking_info() -> NetworkingInfo {
218 NetworkingInfo::Tcp {
219 fault: F::tcp_fault(),
220 }
221 }
222}
223
224/// Items cross a channel that the host of an embedded program implements however it likes (a
225/// network connection, an in-memory queue, ...), keeping the retries `R`, ordering `O`, and
226/// failure policy `F` that follow from what the channel declares about each anomaly (`()` until
227/// declared).
228pub struct EmbeddedTransport<R, O, F> {
229 _phantom: PhantomData<(R, O, F)>,
230}
231
232/// The retries an [`EMBEDDED`] channel declares: [`ExactlyOnce`]
233/// ([`never_duplicates`](NetworkingConfig::never_duplicates)) or [`AtLeastOnce`]
234/// ([`may_duplicate`](NetworkingConfig::may_duplicate)).
235#[sealed::sealed]
236#[diagnostic::on_unimplemented(
237 message = "this `EMBEDDED` channel does not declare whether it may duplicate messages",
238 note = "add `.never_duplicates()` or `.may_duplicate()` to the network config"
239)]
240pub trait EmbeddedRetries: Retries {}
241
242#[sealed::sealed]
243impl EmbeddedRetries for ExactlyOnce {}
244
245#[sealed::sealed]
246impl EmbeddedRetries for AtLeastOnce {}
247
248/// The ordering an [`EMBEDDED`] channel declares: [`TotalOrder`]
249/// ([`never_reorders`](NetworkingConfig::never_reorders)) or [`NoOrder`]
250/// ([`may_reorder`](NetworkingConfig::may_reorder)).
251#[sealed::sealed]
252#[diagnostic::on_unimplemented(
253 message = "this `EMBEDDED` channel does not declare whether it may reorder messages",
254 note = "add `.never_reorders()` or `.may_reorder()` to the network config"
255)]
256pub trait EmbeddedOrdering: Ordering {}
257
258#[sealed::sealed]
259impl EmbeddedOrdering for TotalOrder {}
260
261#[sealed::sealed]
262impl EmbeddedOrdering for NoOrder {}
263
264/// The failure policy an [`EMBEDDED`] channel declares: [`FailStop`]
265/// ([`never_drops`](NetworkingConfig::never_drops)), [`Lossy`]
266/// ([`may_drop`](NetworkingConfig::may_drop)), or [`LossyDelayedForever`]
267/// ([`may_delay_forever`](NetworkingConfig::may_delay_forever)).
268#[sealed::sealed]
269#[diagnostic::on_unimplemented(
270 message = "this `EMBEDDED` channel does not declare whether it may drop messages",
271 note = "add `.never_drops()`, `.may_drop(nondet!(..))`, or `.may_delay_forever()` to the network config"
272)]
273pub trait EmbeddedFailPolicy {
274 /// The ordering of the received stream when the channel declares ordering `O`.
275 type Ordering<O: Ordering>: Ordering;
276
277 /// The consistency guarantee this failure policy can preserve for replicated outputs
278 /// (see [`NetworkFor::ConsistencyGuarantee`]).
279 type ConsistencyGuarantee: Consistency;
280
281 /// Returns the [`EmbeddedFault`] variant for this failure policy.
282 fn embedded_fault() -> EmbeddedFault;
283}
284
285#[sealed::sealed]
286impl EmbeddedFailPolicy for FailStop {
287 type Ordering<O: Ordering> = O;
288
289 type ConsistencyGuarantee = EventualConsistency;
290
291 fn embedded_fault() -> EmbeddedFault {
292 EmbeddedFault::FailStop
293 }
294}
295
296#[sealed::sealed]
297impl EmbeddedFailPolicy for Lossy {
298 type Ordering<O: Ordering> = O;
299
300 type ConsistencyGuarantee = NoConsistency;
301
302 fn embedded_fault() -> EmbeddedFault {
303 EmbeddedFault::Lossy
304 }
305}
306
307#[sealed::sealed]
308impl EmbeddedFailPolicy for LossyDelayedForever {
309 type Ordering<O: Ordering> = NoOrder;
310
311 type ConsistencyGuarantee = EventualConsistency;
312
313 fn embedded_fault() -> EmbeddedFault {
314 EmbeddedFault::LossyDelayedForever
315 }
316}
317
318/// An [`EMBEDDED`] channel's guarantees are the ones its declarations leave, with the
319/// consistency guarantee of its failure policy (as for [`Tcp`] and [`Udp`]).
320#[sealed::sealed]
321impl<R: EmbeddedRetries, O: EmbeddedOrdering, F: EmbeddedFailPolicy> TransportKind
322 for EmbeddedTransport<R, O, F>
323{
324 type OrderingGuarantee = F::Ordering<O>;
325
326 type RetriesGuarantee = R;
327
328 type ConsistencyGuarantee = F::ConsistencyGuarantee;
329
330 fn networking_info() -> NetworkingInfo {
331 NetworkingInfo::Embedded {
332 retries: R::RETRIES_KIND,
333 ordering: O::ORDERING_KIND,
334 fault: F::embedded_fault(),
335 }
336 }
337}
338
339/// Send items across a UDP channel, which does not guarantee delivery or ordering.
340pub struct Udp<F> {
341 _phantom: PhantomData<F>,
342}
343
344#[sealed::sealed]
345impl<F: UdpFailPolicy> TransportKind for Udp<F> {
346 type OrderingGuarantee = NoOrder;
347
348 type RetriesGuarantee = ExactlyOnce;
349
350 type ConsistencyGuarantee = F::ConsistencyGuarantee;
351
352 fn networking_info() -> NetworkingInfo {
353 NetworkingInfo::Udp {
354 fault: F::udp_fault(),
355 }
356 }
357}
358
359/// A networking backend implementation that supports items of type `T`.
360#[sealed::sealed]
361pub trait NetworkFor<T: ?Sized> {
362 /// The ordering guarantee provided by this network configuration.
363 /// When combined with an input stream's ordering `O`, the output ordering
364 /// will be `<O as MinOrder<Self::OrderingGuarantee>>::Min`.
365 type OrderingGuarantee: Ordering;
366
367 /// The retry guarantee provided by this network configuration.
368 /// When combined with an input stream's retries `R`, the output retries
369 /// will be `<R as MinRetries<Self::RetriesGuarantee>>::Min`.
370 type RetriesGuarantee: Retries;
371
372 /// The consistency guarantee this network configuration can preserve when the same data is
373 /// replicated to several recipients (e.g. via
374 /// [`Stream::broadcast_closed`](crate::live_collections::stream::Stream::broadcast_closed)).
375 ///
376 /// Failure policies that guarantee each recipient eventually observes the full stream of
377 /// sent messages, or that model failures as the recipient stopping entirely (such as
378 /// `fail_stop` or `lossy_delayed_forever`), preserve
379 /// [`EventualConsistency`]. Plain `lossy`
380 /// channels can silently drop individual messages for some recipients while others receive
381 /// them, so replicated outputs can permanently diverge and only
382 /// [`NoConsistency`] is guaranteed.
383 type ConsistencyGuarantee: Consistency;
384
385 /// Generates serialization logic for sending `T`.
386 fn serialize_thunk(is_demux: bool) -> syn::Expr;
387
388 /// Generates deserialization logic for receiving `T`.
389 fn deserialize_thunk(tagged: Option<&syn::Type>) -> syn::Expr;
390
391 /// Whether this network channel leaves serialization to code outside of Hydro (an
392 /// [`EMBEDDED`] channel). When `true`, [`Self::serialize_thunk`] and
393 /// [`Self::deserialize_thunk`] are never called; the raw element type flows across the
394 /// channel unserialized.
395 fn is_embedded() -> bool {
396 false
397 }
398
399 /// Returns the optional name of the network channel.
400 fn name(&self) -> Option<&str>;
401
402 /// Returns the [`NetworkingInfo`] describing this network channel's transport and fault model.
403 fn networking_info() -> NetworkingInfo;
404}
405
406/// The fault model for a TCP connection.
407#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize)]
408pub enum TcpFault {
409 /// Stops sending messages after a failed connection.
410 FailStop,
411 /// Messages may be lost (e.g. due to network partitions).
412 Lossy,
413 /// Dropped messages are treated as indefinitely delayed with no ordering guarantee.
414 LossyDelayedForever,
415}
416
417/// The failure policy of an [`EMBEDDED`] channel.
418#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize)]
419pub enum EmbeddedFault {
420 /// Stops delivering a sender's messages once its channel fails.
421 FailStop,
422 /// Messages may be lost.
423 Lossy,
424 /// Undelivered messages are treated as indefinitely delayed.
425 LossyDelayedForever,
426}
427
428/// The fault model for a UDP channel.
429///
430/// UDP is connectionless and never guarantees delivery, so there is no
431/// `FailStop` variant — messages can always be dropped.
432#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize)]
433pub enum UdpFault {
434 /// Messages may be lost (e.g. due to network partitions or congestion).
435 Lossy,
436 /// Dropped messages are treated as indefinitely delayed.
437 LossyDelayedForever,
438}
439
440/// Describes the networking configuration for a network channel at the IR level.
441#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize)]
442pub enum NetworkingInfo {
443 /// A TCP-based network channel with a specific fault model.
444 Tcp {
445 /// The fault model for this TCP connection.
446 fault: TcpFault,
447 },
448 /// A UDP-based network channel with a specific fault model.
449 Udp {
450 /// The fault model for this UDP channel.
451 fault: UdpFault,
452 },
453 /// A channel implemented by the host of an embedded program ([`EMBEDDED`]), described by
454 /// the guarantees it is declared with, which the host's channel must keep.
455 Embedded {
456 /// Whether the channel may deliver a message more than once (`AtLeastOnce`) or not
457 /// (`ExactlyOnce`).
458 retries: crate::compile::ir::StreamRetry,
459 /// The order the channel must deliver each sender's messages in: `TotalOrder` (the order
460 /// they were sent) or `NoOrder` (any).
461 ordering: crate::compile::ir::StreamOrder,
462 /// What the channel may leave undelivered.
463 fault: EmbeddedFault,
464 },
465}
466
467/// A network channel configuration with `T` as transport backend and `S` as the serialization
468/// backend.
469pub struct NetworkingConfig<Tr: ?Sized, S: ?Sized, Name = ()> {
470 name: Option<Name>,
471 _phantom: (PhantomData<Tr>, PhantomData<S>),
472}
473
474impl<Tr: ?Sized, S: ?Sized> NetworkingConfig<Tr, S> {
475 /// Names the network channel and enables stable communication across multiple service versions.
476 pub fn name(self, name: impl Into<String>) -> NetworkingConfig<Tr, S, String> {
477 NetworkingConfig {
478 name: Some(name.into()),
479 _phantom: (PhantomData, PhantomData),
480 }
481 }
482}
483
484impl<Tr: ?Sized, N> NetworkingConfig<Tr, NoSer, N> {
485 /// Configures the network channel to use [`bincode`] to serialize items.
486 pub const fn bincode(mut self) -> NetworkingConfig<Tr, Bincode, N> {
487 let taken_name = self.name.take();
488 std::mem::forget(self); // nothing else is stored
489 NetworkingConfig {
490 name: taken_name,
491 _phantom: (PhantomData, PhantomData),
492 }
493 }
494}
495
496impl<S: ?Sized> NetworkingConfig<Tcp<()>, S> {
497 /// Configures the TCP transport to stop sending messages after a failed connection.
498 ///
499 /// Note that the Hydro simulator will not simulate connection failures that impact the
500 /// *liveness* of a program. If an output assertion depends on a `fail_stop` channel
501 /// making progress, that channel will not experience a failure that would cause the test to
502 /// block indefinitely. However, any *safety* issues caused by connection failures will still
503 /// be caught, such as a race condition between a failed connection and some other message.
504 #[must_use]
505 pub const fn fail_stop(self) -> NetworkingConfig<Tcp<FailStop>, S> {
506 NetworkingConfig {
507 name: self.name,
508 _phantom: (PhantomData, PhantomData),
509 }
510 }
511
512 /// Configures the TCP transport to allow messages to be lost.
513 ///
514 /// This is appropriate for networks where messages may be dropped, such as when
515 /// running under a Maelstrom partition nemesis. Unlike `fail_stop`, which guarantees
516 /// a prefix of messages is delivered, `lossy` makes no such guarantee.
517 ///
518 /// # Non-Determinism
519 /// A lossy TCP channel will non-deterministically drop messages during execution.
520 #[must_use]
521 pub const fn lossy(self, nondet: NonDet) -> NetworkingConfig<Tcp<Lossy>, S> {
522 let _ = nondet;
523 NetworkingConfig {
524 name: self.name,
525 _phantom: (PhantomData, PhantomData),
526 }
527 }
528
529 /// Configures the TCP transport to treat dropped messages as indefinitely delayed.
530 ///
531 /// This is appropriate for networks where messages may be dropped, such as when
532 /// running under a Maelstrom partition nemesis. Unlike [`Self::lossy`], this does
533 /// *not* require a [`NonDet`] annotation because the output is always lower in the
534 /// partial order than the ideal stream. However, the output stream will have
535 /// [`NoOrder`] guarantees, imposing stricter conditions on downstream consumers.
536 ///
537 /// Unlike [`Self::lossy`], this mode can easily be simulated in exhaustive mode
538 /// without running into fairness issues.
539 ///
540 /// When using this mode in the Hydro simulator, you must call
541 /// [`.test_safety_only()`](crate::sim::flow::SimFlow::test_safety_only) to opt in:
542 /// the simulator will not actually drop packets—it delays "dropped" messages until
543 /// the end of the execution, which catches safety bugs but cannot test liveness.
544 #[must_use]
545 pub const fn lossy_delayed_forever(self) -> NetworkingConfig<Tcp<LossyDelayedForever>, S> {
546 NetworkingConfig {
547 name: self.name,
548 _phantom: (PhantomData, PhantomData),
549 }
550 }
551}
552
553impl<O, F, S: ?Sized> NetworkingConfig<EmbeddedTransport<(), O, F>, S> {
554 /// Declares that the host's channel never delivers a message more than once, so the
555 /// received stream keeps the sent stream's retries.
556 #[must_use]
557 pub const fn never_duplicates(
558 self,
559 ) -> NetworkingConfig<EmbeddedTransport<ExactlyOnce, O, F>, S> {
560 NetworkingConfig {
561 name: self.name,
562 _phantom: (PhantomData, PhantomData),
563 }
564 }
565
566 /// Declares that the host's channel may deliver a message more than once, so the received
567 /// stream is [`AtLeastOnce`].
568 #[must_use]
569 pub const fn may_duplicate(self) -> NetworkingConfig<EmbeddedTransport<AtLeastOnce, O, F>, S> {
570 NetworkingConfig {
571 name: self.name,
572 _phantom: (PhantomData, PhantomData),
573 }
574 }
575}
576
577impl<R, F, S: ?Sized> NetworkingConfig<EmbeddedTransport<R, (), F>, S> {
578 /// Declares that the host's channel delivers each sender's messages in the order it sent
579 /// them, so the received stream keeps the sent stream's order.
580 #[must_use]
581 pub const fn never_reorders(self) -> NetworkingConfig<EmbeddedTransport<R, TotalOrder, F>, S> {
582 NetworkingConfig {
583 name: self.name,
584 _phantom: (PhantomData, PhantomData),
585 }
586 }
587
588 /// Declares that the host's channel may deliver a sender's messages in a different order
589 /// than it sent them, so the received stream is [`NoOrder`].
590 #[must_use]
591 pub const fn may_reorder(self) -> NetworkingConfig<EmbeddedTransport<R, NoOrder, F>, S> {
592 NetworkingConfig {
593 name: self.name,
594 _phantom: (PhantomData, PhantomData),
595 }
596 }
597}
598
599impl<R, O, S: ?Sized> NetworkingConfig<EmbeddedTransport<R, O, ()>, S> {
600 /// Declares that the host's channel never drops a message: while it stays live, it delivers
601 /// every message sent. Like [`never_reorders`](NetworkingConfig::never_reorders) and
602 /// [`never_duplicates`](NetworkingConfig::never_duplicates), this holds as long as the
603 /// channel remains live; if it fails, it delivers nothing more, like
604 /// [`TCP.fail_stop()`](NetworkingConfig::fail_stop).
605 #[must_use]
606 pub const fn never_drops(self) -> NetworkingConfig<EmbeddedTransport<R, O, FailStop>, S> {
607 NetworkingConfig {
608 name: self.name,
609 _phantom: (PhantomData, PhantomData),
610 }
611 }
612
613 /// Declares that the host's channel may lose messages and keep delivering later ones, like
614 /// [`TCP.lossy()`](NetworkingConfig::lossy).
615 ///
616 /// # Non-Determinism
617 /// A lossy channel will non-deterministically drop messages during execution.
618 #[must_use]
619 pub const fn may_drop(
620 self,
621 nondet: NonDet,
622 ) -> NetworkingConfig<EmbeddedTransport<R, O, Lossy>, S> {
623 let _ = nondet;
624 NetworkingConfig {
625 name: self.name,
626 _phantom: (PhantomData, PhantomData),
627 }
628 }
629
630 /// Declares that the host's channel may never deliver a message, which is treated as
631 /// indefinitely delayed, like
632 /// [`TCP.lossy_delayed_forever()`](NetworkingConfig::lossy_delayed_forever). The received
633 /// stream is [`NoOrder`].
634 ///
635 /// The Hydro simulator requires
636 /// [`.test_safety_only()`](crate::sim::flow::SimFlow::test_safety_only) for this mode.
637 #[must_use]
638 pub const fn may_delay_forever(
639 self,
640 ) -> NetworkingConfig<EmbeddedTransport<R, O, LossyDelayedForever>, S> {
641 NetworkingConfig {
642 name: self.name,
643 _phantom: (PhantomData, PhantomData),
644 }
645 }
646}
647
648impl<S: ?Sized> NetworkingConfig<Udp<()>, S> {
649 /// Configures the UDP transport to allow messages to be lost.
650 ///
651 /// UDP never guarantees delivery or ordering, so unlike TCP there is no `fail_stop`
652 /// policy — messages may always be dropped and the output stream always has
653 /// [`NoOrder`] guarantees.
654 ///
655 /// # Non-Determinism
656 /// A lossy UDP channel will non-deterministically drop messages during execution.
657 #[must_use]
658 pub const fn lossy(self, nondet: NonDet) -> NetworkingConfig<Udp<Lossy>, S> {
659 let _ = nondet;
660 NetworkingConfig {
661 name: self.name,
662 _phantom: (PhantomData, PhantomData),
663 }
664 }
665
666 /// Configures the UDP transport to treat dropped messages as indefinitely delayed.
667 ///
668 /// UDP never guarantees delivery or ordering, so unlike TCP there is no `fail_stop`
669 /// policy. Unlike [`Self::lossy`], this does *not* require a [`NonDet`] annotation
670 /// because the output is always lower in the partial order than the ideal stream
671 /// (dropped messages are modeled as infinite delays). The output stream has
672 /// [`NoOrder`] guarantees, imposing stricter conditions on downstream consumers.
673 ///
674 /// Unlike [`Self::lossy`], this mode can easily be simulated in exhaustive mode
675 /// without running into fairness issues.
676 ///
677 /// When using this mode in the Hydro simulator, you must call
678 /// [`.test_safety_only()`](crate::sim::flow::SimFlow::test_safety_only) to opt in:
679 /// the simulator will not actually drop packets—it delays "dropped" messages until
680 /// the end of the execution, which catches safety bugs but cannot test liveness.
681 #[must_use]
682 pub const fn lossy_delayed_forever(self) -> NetworkingConfig<Udp<LossyDelayedForever>, S> {
683 NetworkingConfig {
684 name: self.name,
685 _phantom: (PhantomData, PhantomData),
686 }
687 }
688}
689
690#[sealed::sealed]
691impl<Tr, S, T: ?Sized> NetworkFor<T> for NetworkingConfig<Tr, S>
692where
693 Tr: ?Sized + TransportKind,
694 S: ?Sized + SerKind<T>,
695{
696 type OrderingGuarantee = Tr::OrderingGuarantee;
697
698 type RetriesGuarantee = Tr::RetriesGuarantee;
699
700 type ConsistencyGuarantee = Tr::ConsistencyGuarantee;
701
702 fn serialize_thunk(is_demux: bool) -> syn::Expr {
703 S::serialize_thunk(is_demux)
704 }
705
706 fn deserialize_thunk(tagged: Option<&syn::Type>) -> syn::Expr {
707 S::deserialize_thunk(tagged)
708 }
709
710 fn is_embedded() -> bool {
711 S::is_embedded()
712 }
713
714 fn name(&self) -> Option<&str> {
715 None
716 }
717
718 fn networking_info() -> NetworkingInfo {
719 Tr::networking_info()
720 }
721}
722
723#[sealed::sealed]
724impl<Tr, S, T: ?Sized> NetworkFor<T> for NetworkingConfig<Tr, S, String>
725where
726 Tr: ?Sized + TransportKind,
727 S: ?Sized + SerKind<T>,
728{
729 type OrderingGuarantee = Tr::OrderingGuarantee;
730
731 type RetriesGuarantee = Tr::RetriesGuarantee;
732
733 type ConsistencyGuarantee = Tr::ConsistencyGuarantee;
734
735 fn serialize_thunk(is_demux: bool) -> syn::Expr {
736 S::serialize_thunk(is_demux)
737 }
738
739 fn deserialize_thunk(tagged: Option<&syn::Type>) -> syn::Expr {
740 S::deserialize_thunk(tagged)
741 }
742
743 fn is_embedded() -> bool {
744 S::is_embedded()
745 }
746
747 fn name(&self) -> Option<&str> {
748 self.name.as_deref()
749 }
750
751 fn networking_info() -> NetworkingInfo {
752 Tr::networking_info()
753 }
754}
755
756/// A network channel that uses length-delimited TCP for transport.
757pub const TCP: NetworkingConfig<Tcp<()>, NoSer> = NetworkingConfig {
758 name: None,
759 _phantom: (PhantomData, PhantomData),
760};
761
762/// A network channel implemented by the host of an embedded program, which chooses how messages
763/// travel: a network connection, an in-memory queue, or anything else. The program declares, for
764/// each anomaly, whether the host's channel may introduce it, and the received stream is weakened
765/// by each one it may:
766///
767/// - reordering: [`never_reorders`](NetworkingConfig::never_reorders) or
768/// [`may_reorder`](NetworkingConfig::may_reorder);
769/// - duplication: [`never_duplicates`](NetworkingConfig::never_duplicates) or
770/// [`may_duplicate`](NetworkingConfig::may_duplicate);
771/// - loss: [`never_drops`](NetworkingConfig::never_drops),
772/// [`may_drop`](NetworkingConfig::may_drop), or
773/// [`may_delay_forever`](NetworkingConfig::may_delay_forever).
774///
775/// Each axis must be declared (for example
776/// `EMBEDDED.may_reorder().never_duplicates().never_drops().name("messages")`). The `never_*`
777/// declarations hold as long as the channel remains live.
778///
779/// Items cross the channel unserialized: the generated channel ends carry the raw element type,
780/// and the host encodes it if its transport needs to. The program names only the anomalies, not
781/// a transport, so it is only supported by the embedded deployment backend and the Hydro
782/// simulator (which carries the raw values in memory); other backends use [`TCP`] or [`UDP`].
783pub const EMBEDDED: NetworkingConfig<EmbeddedTransport<(), (), ()>, Embedded> = NetworkingConfig {
784 name: None,
785 _phantom: (PhantomData, PhantomData),
786};
787
788/// A network channel that uses UDP for transport.
789///
790/// Unlike [`TCP`], UDP does not guarantee delivery or ordering, so output streams
791/// always have [`NoOrder`] guarantees. Because UDP is connectionless, there is no
792/// `fail_stop` policy; only [`lossy`](NetworkingConfig::lossy) and
793/// [`lossy_delayed_forever`](NetworkingConfig::lossy_delayed_forever) are available.
794///
795/// # Availability
796/// UDP is **not yet available** in "deploy" deployment mode (via Hydro Deploy,
797/// including Docker and ECS deployments); attempting to deploy a UDP channel there
798/// will panic at compile time. Both UDP modes are available for embedded
799/// deployments (the only production deployment option) and Maelstrom testing. In
800/// the Hydro simulator, only `lossy_delayed_forever` is supported, and it requires
801/// [`.test_safety_only()`](crate::sim::flow::SimFlow::test_safety_only): the
802/// simulator will not actually drop packets—it delays "dropped" messages until the
803/// end of the execution, which catches safety bugs but cannot test liveness.
804pub const UDP: NetworkingConfig<Udp<()>, NoSer> = NetworkingConfig {
805 name: None,
806 _phantom: (PhantomData, PhantomData),
807};
808
809#[cfg(test)]
810mod tests {
811 use super::*;
812
813 /// Compiles only if `K` gives retries `R`, ordering `O`, and consistency guarantee `C`.
814 fn guarantees<K, R, O, C>()
815 where
816 K: TransportKind<RetriesGuarantee = R, OrderingGuarantee = O, ConsistencyGuarantee = C>,
817 {
818 }
819
820 /// An `EMBEDDED` channel's retries and ordering are the declared ones (except that
821 /// `lossy_delayed_forever` is `NoOrder`), and its consistency guarantee follows from its
822 /// failure policy: only `lossy` drops to `NoConsistency`.
823 #[test]
824 fn embedded_guarantees_follow_declaration() {
825 type E<R, O, F> = EmbeddedTransport<R, O, F>;
826 guarantees::<
827 E<ExactlyOnce, TotalOrder, FailStop>,
828 ExactlyOnce,
829 TotalOrder,
830 EventualConsistency,
831 >();
832 guarantees::<E<AtLeastOnce, NoOrder, FailStop>, AtLeastOnce, NoOrder, EventualConsistency>(
833 );
834 guarantees::<E<ExactlyOnce, TotalOrder, Lossy>, ExactlyOnce, TotalOrder, NoConsistency>();
835 guarantees::<E<AtLeastOnce, NoOrder, Lossy>, AtLeastOnce, NoOrder, NoConsistency>();
836 guarantees::<
837 E<ExactlyOnce, TotalOrder, LossyDelayedForever>,
838 ExactlyOnce,
839 NoOrder,
840 EventualConsistency,
841 >();
842 guarantees::<
843 E<AtLeastOnce, NoOrder, LossyDelayedForever>,
844 AtLeastOnce,
845 NoOrder,
846 EventualConsistency,
847 >();
848 }
849}