1#![allow(
2 unused,
3 reason = "unused in trybuild but the __staged version is needed"
4)]
5#![allow(missing_docs, reason = "used internally")]
6
7use std::collections::HashMap;
8use std::future::Future;
9use std::net::SocketAddr;
10use std::ops::{Deref, DerefMut};
11use std::pin::Pin;
12use std::sync::Arc;
13use std::task::{Context, Poll};
14use std::time::Duration;
15
16use bytes::BytesMut;
17use futures::{FutureExt, Sink, SinkExt, Stream, StreamExt};
18use proc_macro2::Span;
19use sinktools::demux_map_lazy::LazyDemuxSink;
20use sinktools::lazy::{LazySink, LazySource};
21use sinktools::lazy_sink_source::LazySinkSource;
22use stageleft::runtime_support::{
23 FreeVariableWithContext, FreeVariableWithContextWithProps, QuoteTokens,
24};
25use stageleft::{QuotedWithContext, q};
26use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
27use tokio::net::{TcpListener, TcpStream};
28use tokio_util::codec::{FramedRead, FramedWrite, LengthDelimitedCodec};
29use tracing::{debug, instrument, warn};
30
31use crate::location::dynamic::LocationId;
32use crate::location::member_id::TaglessMemberId;
33use crate::location::{LocationKey, MemberId, MembershipEvent};
34
35pub const CHANNEL_MUX_PORT: u16 = 10000;
37
38pub const CHANNEL_MAGIC: u64 = 0x4859_4452_4F5F_4348;
40
41#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
46pub struct ChannelMagic {
47 pub magic: u64,
48}
49
50pub const CHANNEL_PROTOCOL_VERSION: u64 = 1;
52
53#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
58pub struct ChannelProtocolVersion {
59 pub version: u64,
60}
61
62#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
69pub struct ChannelHandshake {
70 pub channel_name: String,
72 pub sender_id: Option<String>,
76}
77
78type MuxConnection = (
80 Option<String>,
81 FramedRead<OwnedReadHalf, LengthDelimitedCodec>,
82);
83
84pub struct ChannelMux {
91 channels: std::sync::Mutex<HashMap<String, tokio::sync::mpsc::UnboundedSender<MuxConnection>>>,
93}
94
95impl Default for ChannelMux {
96 fn default() -> Self {
97 Self::new()
98 }
99}
100
101impl ChannelMux {
102 pub fn new() -> Self {
103 Self {
104 channels: std::sync::Mutex::new(HashMap::new()),
105 }
106 }
107
108 pub fn register(
109 &self,
110 channel_name: String,
111 ) -> tokio::sync::mpsc::UnboundedReceiver<MuxConnection> {
112 let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
113 let mut channels = self.channels.lock().unwrap();
114 channels.insert(channel_name, tx);
115 rx
116 }
117
118 pub async fn run_accept_loop(self: Arc<Self>, listener: TcpListener) {
119 loop {
120 let (stream, peer) = match listener.accept().await {
121 Ok(v) => v,
122 Err(e) => {
123 warn!(name: "accept_error", error = %e);
124 continue;
125 }
126 };
127 debug!(name: "mux_accepting", ?peer);
128
129 let mux = self.clone();
130 tokio::spawn(async move {
131 let (rx, _tx) = stream.into_split();
134 let mut source = FramedRead::new(rx, LengthDelimitedCodec::new());
135
136 let Some(Ok(magic_frame)) = source.next().await else {
137 warn!(name: "magic_failed", ?peer, "no magic frame");
138 return;
139 };
140
141 let magic: ChannelMagic = match bincode::deserialize(&magic_frame) {
142 Ok(m) => m,
143 Err(e) => {
144 warn!(name: "magic_deserialize_failed", ?peer, error = %e);
145 return;
146 }
147 };
148
149 if magic.magic != CHANNEL_MAGIC {
150 warn!(name: "bad_magic", ?peer, magic = magic.magic, expected = CHANNEL_MAGIC);
151 return;
152 }
153
154 let Some(Ok(version_frame)) = source.next().await else {
155 warn!(name: "version_failed", ?peer, "no version frame");
156 return;
157 };
158
159 let version: ChannelProtocolVersion = match bincode::deserialize(&version_frame) {
160 Ok(v) => v,
161 Err(e) => {
162 warn!(name: "version_deserialize_failed", ?peer, error = %e);
163 return;
164 }
165 };
166
167 if version.version != CHANNEL_PROTOCOL_VERSION {
168 warn!(name: "version_mismatch", ?peer, version = version.version, expected = CHANNEL_PROTOCOL_VERSION);
169 return;
170 }
171
172 let Some(Ok(handshake_frame)) = source.next().await else {
173 warn!(name: "handshake_failed", ?peer, "no handshake frame");
174 return;
175 };
176
177 let handshake: ChannelHandshake = match bincode::deserialize(&handshake_frame) {
178 Ok(h) => h,
179 Err(e) => {
180 warn!(name: "handshake_deserialize_failed", ?peer, error = %e);
181 return;
182 }
183 };
184
185 debug!(name: "handshake_received", ?peer, ?handshake);
186
187 let channels = mux.channels.lock().unwrap();
188 if let Some(tx_chan) = channels.get(&handshake.channel_name) {
189 let _ = tx_chan.send((handshake.sender_id, source));
190 } else {
191 warn!(
192 name: "unknown_channel",
193 channel_name = %handshake.channel_name,
194 ?peer,
195 "no registered consumer for channel"
196 );
197 }
198 });
199 }
200 }
201}
202
203pub fn get_or_init_channel_mux() -> Arc<ChannelMux> {
208 use std::sync::OnceLock;
209 static MUX: OnceLock<Arc<ChannelMux>> = OnceLock::new();
210
211 MUX.get_or_init(|| {
212 let mux = Arc::new(ChannelMux::new());
213 let mux_clone = mux.clone();
214
215 tokio::spawn(async move {
218 let bind_addr = format!("0.0.0.0:{}", CHANNEL_MUX_PORT);
219 debug!(name: "mux_listening", %bind_addr);
220 let listener = TcpListener::bind(&bind_addr)
221 .await
222 .expect("failed to bind channel mux listener");
223 mux_clone.run_accept_loop(listener).await;
224 });
225
226 mux
227 })
228 .clone()
229}
230
231pub async fn send_handshake(
234 sink: &mut FramedWrite<TcpStream, LengthDelimitedCodec>,
235 channel_name: &str,
236 sender_id: Option<&str>,
237) -> Result<(), std::io::Error> {
238 let magic = ChannelMagic {
239 magic: CHANNEL_MAGIC,
240 };
241 sink.send(bytes::Bytes::from(bincode::serialize(&magic).unwrap()))
242 .await?;
243
244 let version = ChannelProtocolVersion {
245 version: CHANNEL_PROTOCOL_VERSION,
246 };
247 sink.send(bytes::Bytes::from(bincode::serialize(&version).unwrap()))
248 .await?;
249
250 let handshake = ChannelHandshake {
251 channel_name: channel_name.to_owned(),
252 sender_id: sender_id.map(|s| s.to_owned()),
253 };
254 sink.send(bytes::Bytes::from(bincode::serialize(&handshake).unwrap()))
255 .await?;
256 Ok(())
257}
258
259pub async fn connect_channel(target: &str) -> Result<TcpStream, std::io::Error> {
263 let stream = TcpStream::connect(target).await?;
264 if let Err(e) = stream.set_nodelay(true) {
265 warn!(name: "set_nodelay_failed", %target, error = %e);
266 }
267 Ok(stream)
268}
269
270pub fn deploy_containerized_o2o(target: &str, channel_name: &str) -> (syn::Expr, syn::Expr) {
271 (
272 q!(LazySink::<_, _, _, bytes::Bytes>::new(move || Box::pin(
273 async move {
274 let channel_name = channel_name;
275 let target = format!("{}:{}", target, self::CHANNEL_MUX_PORT);
276 debug!(name: "connecting", %target, %channel_name);
277
278 let stream = self::connect_channel(&target).await?;
279 let mut sink = FramedWrite::new(stream, LengthDelimitedCodec::new());
280
281 self::send_handshake(&mut sink, channel_name, None).await?;
282
283 Result::<_, std::io::Error>::Ok(sink)
284 }
285 )))
286 .splice_untyped_ctx(&()),
287 q!(LazySource::new(move || Box::pin(async move {
288 let channel_name = channel_name;
289 let mux = self::get_or_init_channel_mux();
290 let mut rx = mux.register(channel_name.to_owned());
291
292 let (_sender_id, source) = rx.recv().await.ok_or_else(|| {
293 std::io::Error::new(std::io::ErrorKind::ConnectionReset, "channel mux closed")
294 })?;
295
296 debug!(name: "o2o_channel_connected", %channel_name);
297
298 Result::<_, std::io::Error>::Ok(source)
299 })))
300 .splice_untyped_ctx(&()),
301 )
302}
303
304pub fn deploy_containerized_o2m(channel_name: &str) -> (syn::Expr, syn::Expr) {
305 (
306 q!(sinktools::demux_map_lazy::<_, _, _, _>(
307 move |key: &TaglessMemberId| {
308 let key = key.clone();
309 let channel_name = channel_name.to_owned();
310
311 LazySink::<_, _, _, bytes::Bytes>::new(move || {
312 Box::pin(async move {
313 let target =
314 format!("{}:{}", key.get_container_name(), self::CHANNEL_MUX_PORT);
315 debug!(name: "connecting", %target, channel_name = %channel_name);
316
317 let stream = self::connect_channel(&target).await?;
318 let mut sink = FramedWrite::new(stream, LengthDelimitedCodec::new());
319
320 self::send_handshake(&mut sink, &channel_name, None).await?;
321
322 Result::<_, std::io::Error>::Ok(sink)
323 })
324 })
325 }
326 ))
327 .splice_untyped_ctx(&()),
328 q!(LazySource::new(move || Box::pin(async move {
329 let channel_name = channel_name;
330 let mux = self::get_or_init_channel_mux();
331 let mut rx = mux.register(channel_name.to_owned());
332
333 let (_sender_id, source) = rx.recv().await.ok_or_else(|| {
334 std::io::Error::new(std::io::ErrorKind::ConnectionReset, "channel mux closed")
335 })?;
336
337 debug!(name: "o2m_channel_connected", %channel_name);
338
339 Result::<_, std::io::Error>::Ok(source)
340 })))
341 .splice_untyped_ctx(&()),
342 )
343}
344
345pub fn deploy_containerized_m2o(target_host: &str, channel_name: &str) -> (syn::Expr, syn::Expr) {
346 (
347 q!(LazySink::<_, _, _, bytes::Bytes>::new(move || {
348 Box::pin(async move {
349 let channel_name = channel_name;
350 let target = format!("{}:{}", target_host, self::CHANNEL_MUX_PORT);
351 debug!(name: "connecting", %target, %channel_name);
352
353 let stream = self::connect_channel(&target).await?;
354 let mut sink = FramedWrite::new(stream, LengthDelimitedCodec::new());
355
356 let container_name = std::env::var("CONTAINER_NAME").unwrap();
357 self::send_handshake(&mut sink, channel_name, Some(&container_name)).await?;
358
359 Result::<_, std::io::Error>::Ok(sink)
360 })
361 }))
362 .splice_untyped_ctx(&()),
363 q!(LazySource::new(move || Box::pin(async move {
364 let channel_name = channel_name;
365 let mux = self::get_or_init_channel_mux();
366 let mut rx = mux.register(channel_name.to_owned());
367
368 Result::<_, std::io::Error>::Ok(
369 futures::stream::unfold(rx, |mut rx| {
370 Box::pin(async move {
371 let (sender_id, source) = rx.recv().await?;
372 let from = sender_id.expect("m2o sender must provide container name");
373
374 debug!(name: "m2o_channel_connected", %from);
375
376 Some((
377 source.map(move |v| {
378 v.map(|v| (TaglessMemberId::from_container_name(from.clone()), v))
379 }),
380 rx,
381 ))
382 })
383 })
384 .flatten_unordered(None),
385 )
386 })))
387 .splice_untyped_ctx(&()),
388 )
389}
390
391pub fn deploy_containerized_m2m(channel_name: &str) -> (syn::Expr, syn::Expr) {
392 (
393 q!(sinktools::demux_map_lazy::<_, _, _, _>(
394 move |key: &TaglessMemberId| {
395 let key = key.clone();
396 let channel_name = channel_name.to_owned();
397
398 LazySink::<_, _, _, bytes::Bytes>::new(move || {
399 Box::pin(async move {
400 let target =
401 format!("{}:{}", key.get_container_name(), self::CHANNEL_MUX_PORT);
402 debug!(name: "connecting", %target, channel_name = %channel_name);
403
404 let stream = self::connect_channel(&target).await?;
405 let mut sink = FramedWrite::new(stream, LengthDelimitedCodec::new());
406
407 let container_name = std::env::var("CONTAINER_NAME").unwrap();
408 self::send_handshake(&mut sink, &channel_name, Some(&container_name))
409 .await?;
410
411 Result::<_, std::io::Error>::Ok(sink)
412 })
413 })
414 }
415 ))
416 .splice_untyped_ctx(&()),
417 q!(LazySource::new(move || Box::pin(async move {
418 let channel_name = channel_name;
419 let mux = self::get_or_init_channel_mux();
420 let mut rx = mux.register(channel_name.to_owned());
421
422 Result::<_, std::io::Error>::Ok(
423 futures::stream::unfold(rx, |mut rx| {
424 Box::pin(async move {
425 let (sender_id, source) = rx.recv().await?;
426 let from = sender_id.expect("m2m sender must provide container name");
427
428 debug!(name: "m2m_channel_connected", %from);
429
430 Some((
431 source.map(move |v| {
432 v.map(|v| (TaglessMemberId::from_container_name(from.clone()), v))
433 }),
434 rx,
435 ))
436 })
437 })
438 .flatten_unordered(None),
439 )
440 })))
441 .splice_untyped_ctx(&()),
442 )
443}
444
445pub struct SocketIdent {
446 pub socket_ident: syn::Ident,
447}
448
449impl<Ctx> FreeVariableWithContextWithProps<Ctx, ()> for SocketIdent {
450 type O = TcpListener;
451
452 fn to_tokens(self, _ctx: &Ctx) -> (QuoteTokens, ())
453 where
454 Self: Sized,
455 {
456 let ident = self.socket_ident;
457
458 (
459 QuoteTokens {
460 prelude: None,
461 expr: Some(quote::quote! { #ident }),
462 },
463 (),
464 )
465 }
466}
467
468pub fn deploy_containerized_external_sink_source_ident(socket_ident: syn::Ident) -> syn::Expr {
469 let socket_ident = SocketIdent { socket_ident };
470
471 q!(LazySinkSource::<
472 _,
473 FramedRead<OwnedReadHalf, LengthDelimitedCodec>,
474 FramedWrite<OwnedWriteHalf, LengthDelimitedCodec>,
475 bytes::Bytes,
476 std::io::Error,
477 >::new(async move {
478 let (stream, peer) = socket_ident.accept().await?;
479 debug!(name: "external accepting", ?peer);
480 let (rx, tx) = stream.into_split();
481
482 let fr = FramedRead::new(rx, LengthDelimitedCodec::new());
483 let fw = FramedWrite::new(tx, LengthDelimitedCodec::new());
484
485 Result::<_, std::io::Error>::Ok((fr, fw))
486 },))
487 .splice_untyped_ctx(&())
488}
489
490pub fn cluster_ids<'a>() -> impl QuotedWithContext<'a, &'a [TaglessMemberId], ()> + Clone {
491 q!(Box::leak(Box::new([TaglessMemberId::from_container_name(
495 "INVALID CONTAINER NAME cluster_ids"
496 )]))
497 .as_slice())
498}
499
500#[cfg(feature = "docker_runtime")]
501pub fn cluster_self_id<'a>() -> impl QuotedWithContext<'a, TaglessMemberId, ()> + Clone + 'a {
502 q!(TaglessMemberId::from_container_name(
503 std::env::var("CONTAINER_NAME").unwrap()
504 ))
505}
506
507#[cfg(feature = "docker_runtime")]
508pub fn cluster_membership_stream<'a>(
509 location_id: &LocationId,
510) -> impl QuotedWithContext<'a, Box<dyn Stream<Item = (TaglessMemberId, MembershipEvent)> + Unpin>, ()>
511{
512 let key = location_id.key();
513
514 q!(Box::new(self::docker_membership_stream(
515 std::env::var("DEPLOYMENT_INSTANCE").unwrap(),
516 key
517 ))
518 as Box<
519 dyn Stream<Item = (TaglessMemberId, MembershipEvent)> + Unpin,
520 >)
521}
522
523#[cfg(feature = "docker_runtime")]
524#[instrument(skip_all, fields(%deployment_instance, %location_key))]
528fn docker_membership_stream(
529 deployment_instance: String,
530 location_key: LocationKey,
531) -> impl Stream<Item = (TaglessMemberId, MembershipEvent)> + Unpin {
532 use std::collections::HashSet;
533 use std::sync::{Arc, Mutex};
534
535 use bollard::Docker;
536 use bollard::query_parameters::{EventsOptions, ListContainersOptions};
537 use tokio::sync::mpsc;
538
539 let docker = Docker::connect_with_local_defaults()
540 .unwrap()
541 .with_timeout(Duration::from_secs(1));
542
543 let (event_tx, event_rx) = mpsc::unbounded_channel::<(String, MembershipEvent)>();
544
545 let events_docker = docker.clone();
547 let events_deployment_instance = deployment_instance.clone();
548 tokio::spawn(async move {
549 let mut filters = HashMap::new();
550 filters.insert("type".to_owned(), vec!["container".to_owned()]);
551 filters.insert(
552 "event".to_owned(),
553 vec!["start".to_owned(), "die".to_owned()],
554 );
555 let event_options = Some(EventsOptions {
556 filters: Some(filters),
557 ..Default::default()
558 });
559
560 let mut events = events_docker.events(event_options);
561 while let Some(event) = events.next().await {
562 if let Some((name, membership_event)) = event.ok().and_then(|e| {
563 let name = e
564 .actor
565 .as_ref()
566 .and_then(|a| a.attributes.as_ref())
567 .and_then(|attrs| attrs.get("name"))
568 .map(|s| &**s)?;
569
570 if name.contains(format!("{events_deployment_instance}-{location_key}").as_str()) {
571 match e.action.as_deref() {
572 Some("start") => Some((name.to_owned(), MembershipEvent::Joined)),
573 Some("die") => Some((name.to_owned(), MembershipEvent::Left)),
574 _ => None,
575 }
576 } else {
577 None
578 }
579 }) && event_tx.send((name, membership_event)).is_err()
580 {
581 break;
582 }
583 }
584 });
585
586 let seen_joined = Arc::new(Mutex::new(HashSet::<String>::new()));
588 let seen_joined_snapshot = seen_joined.clone();
589 let seen_joined_events = seen_joined;
590
591 let snapshot_stream = futures::stream::once(async move {
593 let mut filters = HashMap::new();
594 filters.insert(
595 "name".to_owned(),
596 vec![format!("{deployment_instance}-{location_key}")],
597 );
598 let options = Some(ListContainersOptions {
599 filters: Some(filters),
600 ..Default::default()
601 });
602
603 docker
604 .list_containers(options)
605 .await
606 .unwrap_or_default()
607 .iter()
608 .filter_map(|c| c.names.as_deref())
609 .filter_map(|names| names.first())
610 .map(|name| name.trim_start_matches('/'))
611 .filter(|&name| seen_joined_snapshot.lock().unwrap().insert(name.to_owned()))
612 .map(|name| (name.to_owned(), MembershipEvent::Joined))
613 .collect::<Vec<_>>()
614 })
615 .flat_map(futures::stream::iter);
616
617 let events_stream = tokio_stream::StreamExt::filter_map(
619 tokio_stream::wrappers::UnboundedReceiverStream::new(event_rx),
620 move |(name, event)| {
621 let mut seen = seen_joined_events.lock().unwrap();
622 match event {
623 MembershipEvent::Joined => {
624 if seen.insert(name.clone()) {
625 Some((name, MembershipEvent::Joined))
626 } else {
627 None
628 }
629 }
630 MembershipEvent::Left => seen.take(&name).map(|name| (name, MembershipEvent::Left)),
631 }
632 },
633 );
634
635 Box::pin(
637 snapshot_stream
638 .chain(events_stream)
639 .map(|(k, v)| (TaglessMemberId::from_container_name(k), v))
640 .inspect(|(member_id, event)| debug!(name: "membership_event", ?member_id, ?event)),
641 )
642}