1pub mod codec;
12pub mod egress;
13pub mod ingress;
14pub mod manager;
15pub mod tcp;
16
17use crate::SystemHealth;
18use crate::error::DynamoError;
19use std::sync::{Arc, OnceLock};
20
21use anyhow::Result;
22use async_trait::async_trait;
23use bytes::Bytes;
24use codec::{TwoPartCodec, TwoPartMessage, TwoPartMessageType};
25use derive_builder::Builder;
26use futures::StreamExt;
27use super::{AsyncEngine, AsyncEngineContext, AsyncEngineContextProvider, ResponseStream};
29use serde::{Deserialize, Serialize, de::DeserializeOwned};
30
31use super::{
32 AsyncTransportEngine, Context, Data, Error, ManyIn, ManyOut, PipelineError, PipelineIO,
33 SegmentSource, ServiceBackend, ServiceEngine, SingleIn, Source, context,
34};
35use crate::metrics::MetricsHierarchy;
36use crate::metrics::prometheus_names::work_handler;
37use crate::protocols::maybe_error::MaybeError;
38use ingress::push_handler::WorkHandlerMetrics;
39use prometheus::{CounterVec, Histogram, IntCounter, IntCounterVec, IntGauge};
40
41pub(crate) const DEFAULT_TCP_MAX_MESSAGE_SIZE: usize = 32 * 1024 * 1024;
43
44static TCP_MAX_MESSAGE_SIZE: OnceLock<usize> = OnceLock::new();
45static REQUEST_PLANE_PAYLOAD_CODEC: OnceLock<RequestPlanePayloadCodec> = OnceLock::new();
46
47pub(crate) fn get_tcp_max_message_size() -> usize {
50 *TCP_MAX_MESSAGE_SIZE.get_or_init(|| {
51 std::env::var("DYN_TCP_MAX_MESSAGE_SIZE")
52 .ok()
53 .and_then(|s| s.parse::<usize>().ok())
54 .unwrap_or(DEFAULT_TCP_MAX_MESSAGE_SIZE)
55 })
56}
57
58#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
59#[serde(rename_all = "snake_case")]
60pub enum RequestPlanePayloadCodec {
61 #[default]
64 Json,
65 Msgpack,
66}
67
68impl RequestPlanePayloadCodec {
69 pub fn configured() -> Self {
70 *REQUEST_PLANE_PAYLOAD_CODEC.get_or_init(Self::from_env)
71 }
72
73 fn from_env() -> Self {
74 let value =
75 std::env::var(crate::config::environment_names::request_plane::DYN_REQUEST_PLANE_CODEC)
76 .ok();
77 Self::from_config_value(value.as_deref())
78 }
79
80 fn from_config_value(value: Option<&str>) -> Self {
81 match value {
82 None | Some("") | Some("msgpack") => Self::Msgpack,
83 Some("json") => Self::Json,
84 Some(other) => {
85 tracing::warn!(
86 env_var =
87 crate::config::environment_names::request_plane::DYN_REQUEST_PLANE_CODEC,
88 value = other,
89 "invalid request plane payload codec, defaulting to msgpack"
90 );
91 Self::Msgpack
92 }
93 }
94 }
95
96 pub fn name(&self) -> &'static str {
97 match self {
98 Self::Json => "json",
99 Self::Msgpack => "msgpack",
100 }
101 }
102
103 pub fn encode<T: Serialize>(&self, value: &T) -> Result<Vec<u8>> {
104 match self {
105 Self::Json => Ok(serde_json::to_vec(value)?),
106 Self::Msgpack => Ok(rmp_serde::to_vec_named(value)?),
107 }
108 }
109
110 pub fn encode_into<T, W>(&self, value: &T, writer: &mut W) -> Result<()>
116 where
117 T: Serialize + ?Sized,
118 W: std::io::Write,
119 {
120 match self {
121 Self::Json => Ok(serde_json::to_writer(writer, value)?),
122 Self::Msgpack => Ok(rmp_serde::encode::write_named(writer, value)?),
123 }
124 }
125
126 pub fn decode<T: DeserializeOwned>(&self, bytes: &[u8]) -> Result<T> {
127 match self {
128 Self::Json => Ok(serde_json::from_slice(bytes)?),
129 Self::Msgpack => Ok(rmp_serde::from_slice(bytes)?),
130 }
131 }
132}
133
134pub trait Codable: PipelineIO + Serialize + for<'de> Deserialize<'de> {}
135impl<T: PipelineIO + Serialize + for<'de> Deserialize<'de>> Codable for T {}
136
137#[async_trait]
139pub trait WorkQueueConsumer {
140 async fn dequeue(&self) -> Result<Bytes, String>;
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
144#[serde(rename_all = "snake_case")]
145pub enum StreamType {
146 Request,
147 Response,
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
151#[serde(rename_all = "snake_case")]
152pub(crate) enum RequestType {
153 SingleIn,
154 ManyIn,
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
158#[serde(rename_all = "snake_case")]
159pub(crate) enum ResponseType {
160 SingleOut,
161 ManyOut,
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
165pub(crate) struct RequestControlMessage {
166 pub(crate) id: String,
167 pub(crate) request_type: RequestType,
168 pub(crate) response_type: ResponseType,
169 #[serde(default)]
170 pub(crate) payload_codec: RequestPlanePayloadCodec,
171 pub(crate) connection_info: ConnectionInfo,
172 #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
173 pub(crate) metadata: std::collections::BTreeMap<String, String>,
174 #[serde(default, skip_serializing_if = "Option::is_none")]
178 pub(crate) frontend_send_ts_ns: Option<u64>,
179 #[serde(default, skip_serializing_if = "Option::is_none")]
183 pub(crate) request_stream_connection_info: Option<ConnectionInfo>,
184}
185
186#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
187#[serde(rename_all = "snake_case")]
188pub enum ControlMessage {
189 Stop,
190 Kill,
191 Sentinel,
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
201pub struct ResponseStreamPrologue {
202 error: Option<String>,
203
204 #[serde(
217 default,
218 deserialize_with = "deserialize_typed_error",
219 skip_serializing_if = "Option::is_none"
220 )]
221 typed_error: Option<DynamoError>,
222}
223
224fn deserialize_typed_error<'de, D>(deserializer: D) -> Result<Option<DynamoError>, D::Error>
225where
226 D: serde::Deserializer<'de>,
227{
228 let typed_error = Option::<serde_json::Value>::deserialize(deserializer)?;
229 Ok(typed_error.and_then(|typed_error| serde_json::from_value(typed_error).ok()))
230}
231
232#[derive(Debug, Clone, PartialEq, Eq)]
238pub struct StreamPrologueError {
239 pub message: String,
240 pub typed_error: Option<DynamoError>,
241}
242
243impl StreamPrologueError {
244 pub fn from_message(message: impl Into<String>) -> Self {
246 Self {
247 message: message.into(),
248 typed_error: None,
249 }
250 }
251
252 pub fn new(message: impl Into<String>, typed_error: DynamoError) -> Self {
255 Self {
256 message: message.into(),
257 typed_error: Some(typed_error),
258 }
259 }
260}
261
262impl std::fmt::Display for StreamPrologueError {
263 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
264 f.write_str(&self.message)
265 }
266}
267
268impl std::ops::Deref for StreamPrologueError {
272 type Target = str;
273
274 fn deref(&self) -> &Self::Target {
275 &self.message
276 }
277}
278
279impl AsRef<str> for StreamPrologueError {
280 fn as_ref(&self) -> &str {
281 &self.message
282 }
283}
284
285impl From<String> for StreamPrologueError {
286 fn from(message: String) -> Self {
287 Self::from_message(message)
288 }
289}
290
291impl From<&str> for StreamPrologueError {
292 fn from(message: &str) -> Self {
293 Self::from_message(message)
294 }
295}
296
297pub type StreamProvider<T> = tokio::sync::oneshot::Receiver<Result<T, StreamPrologueError>>;
302
303struct Cleanup(Option<Box<dyn FnOnce() + Send + 'static>>);
306
307impl Drop for Cleanup {
308 fn drop(&mut self) {
309 if let Some(f) = self.0.take() {
310 f();
311 }
312 }
313}
314
315pub struct RegisteredStream<T> {
319 pub connection_info: ConnectionInfo,
320 pub stream_provider: StreamProvider<T>,
321 cleanup: Cleanup,
322}
323
324impl<T> std::fmt::Debug for RegisteredStream<T> {
325 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
326 f.debug_struct("RegisteredStream")
327 .field("connection_info", &self.connection_info)
328 .finish_non_exhaustive()
329 }
330}
331
332impl<T> RegisteredStream<T> {
333 pub(crate) fn new(connection_info: ConnectionInfo, stream_provider: StreamProvider<T>) -> Self {
334 Self {
335 connection_info,
336 stream_provider,
337 cleanup: Cleanup(None),
338 }
339 }
340
341 pub(crate) fn with_cleanup<F>(mut self, cleanup: F) -> Self
342 where
343 F: FnOnce() + Send + 'static,
344 {
345 self.cleanup.0 = Some(Box::new(cleanup));
346 self
347 }
348
349 pub fn into_parts(self) -> (ConnectionInfo, StreamProvider<T>) {
352 let Self {
353 connection_info,
354 stream_provider,
355 mut cleanup,
356 } = self;
357 cleanup.0.take();
358 (connection_info, stream_provider)
359 }
360}
361
362pub struct PendingConnections {
365 pub send_stream: Option<RegisteredStream<StreamSender>>,
366 pub recv_stream: Option<RegisteredStream<StreamReceiver>>,
367}
368
369impl PendingConnections {
370 pub fn into_parts(
371 self,
372 ) -> (
373 Option<RegisteredStream<StreamSender>>,
374 Option<RegisteredStream<StreamReceiver>>,
375 ) {
376 (self.send_stream, self.recv_stream)
377 }
378}
379
380#[async_trait::async_trait]
383pub trait ResponseService {
384 async fn register(&self, options: StreamOptions) -> PendingConnections;
385}
386
387#[cfg(test)]
388mod registered_stream_tests {
389 use super::*;
390 use std::sync::atomic::{AtomicBool, Ordering};
391
392 fn dummy_conn_info() -> ConnectionInfo {
393 ConnectionInfo {
394 transport: "test".to_string(),
395 info: "{}".to_string(),
396 }
397 }
398
399 #[test]
401 fn drop_runs_cleanup() {
402 let flag = Arc::new(AtomicBool::new(false));
403 let flag_clone = flag.clone();
404
405 let (_tx, rx) = tokio::sync::oneshot::channel::<Result<(), StreamPrologueError>>();
406 let stream = RegisteredStream::new(dummy_conn_info(), rx).with_cleanup(move || {
407 flag_clone.store(true, Ordering::SeqCst);
408 });
409
410 drop(stream);
411 assert!(
412 flag.load(Ordering::SeqCst),
413 "cleanup must fire when RegisteredStream is dropped"
414 );
415 }
416
417 #[test]
421 fn into_parts_disarms_cleanup() {
422 let flag = Arc::new(AtomicBool::new(false));
423 let flag_clone = flag.clone();
424
425 let (_tx, rx) = tokio::sync::oneshot::channel::<Result<(), StreamPrologueError>>();
426 let stream = RegisteredStream::new(dummy_conn_info(), rx).with_cleanup(move || {
427 flag_clone.store(true, Ordering::SeqCst);
428 });
429
430 let (conn, provider) = stream.into_parts();
431 drop(conn);
432 drop(provider);
433
434 assert!(
435 !flag.load(Ordering::SeqCst),
436 "into_parts() must disarm the cleanup closure"
437 );
438 }
439
440 #[test]
442 fn drop_without_cleanup_is_a_noop() {
443 let (_tx, rx) = tokio::sync::oneshot::channel::<Result<(), StreamPrologueError>>();
444 let stream: RegisteredStream<()> = RegisteredStream::new(dummy_conn_info(), rx);
445 drop(stream); }
447}
448
449pub struct StreamSender {
474 tx: tokio::sync::mpsc::Sender<TwoPartMessage>,
475 prologue: Option<ResponseStreamPrologue>,
476}
477
478impl StreamSender {
479 pub async fn send(&self, data: Bytes) -> Result<()> {
480 Ok(self.tx.send(TwoPartMessage::from_data(data)).await?)
481 }
482
483 pub async fn send_control(&self, control: ControlMessage) -> Result<()> {
484 let bytes = serde_json::to_vec(&control)?;
485 Ok(self
486 .tx
487 .send(TwoPartMessage::from_header(bytes.into()))
488 .await?)
489 }
490
491 pub async fn send_prologue(&mut self, error: Option<String>) -> Result<(), String> {
496 self.send_prologue_typed(error.map(StreamPrologueError::from_message))
497 .await
498 }
499
500 pub async fn send_prologue_typed(
501 &mut self,
502 error: Option<StreamPrologueError>,
503 ) -> Result<(), String> {
504 if let Some(_prologue) = self.prologue.take() {
506 let (error, typed_error) = match error {
507 Some(StreamPrologueError {
508 message,
509 typed_error,
510 }) => (Some(message), typed_error),
511 None => (None, None),
512 };
513 let prologue = ResponseStreamPrologue { error, typed_error };
514 let header_bytes: Bytes = match serde_json::to_vec(&prologue) {
515 Ok(b) => b.into(),
516 Err(err) => {
517 tracing::error!(%err, "send_prologue: ResponseStreamPrologue did not serialize to a JSON array");
518 return Err("Invalid prologue".to_string());
519 }
520 };
521 self.tx
522 .send(TwoPartMessage::from_header(header_bytes))
523 .await
524 .map_err(|e| e.to_string())?;
525 } else {
526 panic!("Prologue already sent; or not set; logic error");
527 }
528 Ok(())
529 }
530}
531
532pub struct StreamReceiver {
533 rx: tokio::sync::mpsc::Receiver<Bytes>,
534}
535
536#[derive(Debug, Clone, Serialize, Deserialize)]
546pub struct ConnectionInfo {
547 pub transport: String,
548 pub info: String,
549}
550
551pub const DEFAULT_SEND_BUFFER_COUNT: usize = 64;
555
556#[derive(Clone, Builder)]
563pub struct StreamOptions {
564 pub context: Arc<dyn AsyncEngineContext>,
566
567 pub enable_request_stream: bool,
572
573 pub enable_response_stream: bool,
576
577 #[builder(default = "DEFAULT_SEND_BUFFER_COUNT")]
581 pub send_buffer_count: usize,
582
583 #[builder(default = "8")]
585 pub recv_buffer_count: usize,
586}
587
588impl StreamOptions {
589 pub fn builder() -> StreamOptionsBuilder {
590 StreamOptionsBuilder::default()
591 }
592}
593
594pub struct Egress<Req: PipelineIO, Resp: PipelineIO> {
595 transport_engine: Arc<dyn AsyncTransportEngine<Req, Resp>>,
596}
597
598#[cfg(test)]
599mod tests {
600 use super::{
601 DEFAULT_SEND_BUFFER_COUNT, NetworkStreamWrapper, RequestControlMessage,
602 RequestPlanePayloadCodec, RequestType, ResponseStreamPrologue, ResponseType, StreamOptions,
603 StreamPrologueError,
604 };
605 use crate::engine::AsyncEngineContextProvider;
606 use crate::error::{BackendError, DynamoError, ErrorType};
607 use crate::pipeline::Context;
608 use serde::{Deserialize, Serialize};
609
610 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
611 struct TestPayload {
612 id: u64,
613 text: String,
614 tokens: Vec<u32>,
615 }
616
617 #[test]
618 fn prologue_without_typed_error_field_still_decodes() {
619 let legacy = br#"{"error":"Generate Error: something went wrong"}"#;
620 let prologue: ResponseStreamPrologue =
621 serde_json::from_slice(legacy).expect("a prologue without the typed field must decode");
622
623 assert_eq!(
624 prologue.error.as_deref(),
625 Some("Generate Error: something went wrong")
626 );
627 assert!(
628 prologue.typed_error.is_none(),
629 "an absent typed error must decode to None, not fail"
630 );
631 }
632
633 #[test]
637 fn stream_prologue_error_substitutes_for_the_previous_string() {
638 let err = StreamPrologueError::from_message("malformed prologue: bad header");
639
640 assert!(err.contains("malformed prologue"));
641 assert!(err.starts_with("malformed"));
642 assert_eq!(err.len(), "malformed prologue: bad header".len());
643 assert_eq!(&err[..9], "malformed");
644 assert_eq!(err.as_ref() as &str, "malformed prologue: bad header");
645
646 let converted: StreamPrologueError = "from a &str".into();
647 assert_eq!(converted.message, "from a &str");
648 assert!(converted.typed_error.is_none());
649
650 let converted: StreamPrologueError = String::from("from a String").into();
651 assert_eq!(converted.message, "from a String");
652 }
653
654 #[test]
655 fn prologue_round_trips_the_typed_error() {
656 let prologue = ResponseStreamPrologue {
657 error: Some("Generate Error: unsupported input".to_string()),
658 typed_error: Some(
659 DynamoError::builder()
660 .error_type(ErrorType::Backend(BackendError::InvalidArgument))
661 .message("unsupported input")
662 .build(),
663 ),
664 };
665
666 let encoded = serde_json::to_vec(&prologue).expect("prologue should serialize");
667 let decoded: ResponseStreamPrologue =
668 serde_json::from_slice(&encoded).expect("prologue should deserialize");
669
670 assert_eq!(decoded, prologue);
671 assert_eq!(
672 decoded.typed_error.map(|e| e.error_type()),
673 Some(ErrorType::Backend(BackendError::InvalidArgument))
674 );
675 }
676
677 #[test]
678 fn encode_into_matches_encode_byte_for_byte() {
679 let payload = NetworkStreamWrapper {
680 data: Some(TestPayload {
681 id: 7,
682 text: "hello".to_string(),
683 tokens: vec![1, 200, 70_000],
684 }),
685 complete_final: false,
686 };
687
688 for codec in [
689 RequestPlanePayloadCodec::Json,
690 RequestPlanePayloadCodec::Msgpack,
691 ] {
692 let expected = codec.encode(&payload).expect("encode");
693 let mut actual = Vec::new();
694 codec
695 .encode_into(&payload, &mut actual)
696 .expect("encode_into");
697 assert_eq!(expected, actual, "codec={}", codec.name());
698 }
699 }
700
701 #[test]
702 fn stream_options_send_buffer_count_defaults_to_64() {
703 let context = Context::new(());
704 let options = StreamOptions::builder()
705 .context(context.context())
706 .enable_request_stream(true)
707 .enable_response_stream(true)
708 .build()
709 .expect("stream options should build");
710
711 assert_eq!(DEFAULT_SEND_BUFFER_COUNT, 64);
712 assert_eq!(options.send_buffer_count, DEFAULT_SEND_BUFFER_COUNT);
713 }
714
715 #[test]
716 fn stream_options_send_buffer_count_overrides_default() {
717 let context = Context::new(());
718 let options = StreamOptions::builder()
719 .context(context.context())
720 .enable_request_stream(true)
721 .enable_response_stream(true)
722 .send_buffer_count(128)
723 .build()
724 .expect("stream options should build");
725
726 assert_eq!(options.send_buffer_count, 128);
727 }
728
729 #[test]
730 fn legacy_frontend_control_message_defaults_payload_codec_to_json() {
731 let json = r#"{
732 "id": "request-123",
733 "request_type": "single_in",
734 "response_type": "many_out",
735 "connection_info": {
736 "transport": "tcp",
737 "info": "{}"
738 }
739 }"#;
740
741 let message: RequestControlMessage =
742 serde_json::from_str(json).expect("control message should deserialize");
743
744 assert_eq!(message.id, "request-123");
745 assert!(matches!(message.request_type, RequestType::SingleIn));
746 assert!(matches!(message.response_type, ResponseType::ManyOut));
747 assert_eq!(message.payload_codec, RequestPlanePayloadCodec::Json);
748 assert_eq!(message.connection_info.transport, "tcp");
749 assert_eq!(message.connection_info.info, "{}");
750 assert!(message.metadata.is_empty());
751 assert!(message.frontend_send_ts_ns.is_none());
752
753 let payload = br#"{"id":7,"text":"legacy","tokens":[1,2]}"#;
754 let decoded: TestPayload = message
755 .payload_codec
756 .decode(payload)
757 .expect("worker should decode the legacy frontend's JSON payload");
758 assert_eq!(
759 decoded,
760 TestPayload {
761 id: 7,
762 text: "legacy".to_string(),
763 tokens: vec![1, 2],
764 }
765 );
766 }
767
768 #[test]
769 fn request_control_message_decodes_msgpack_payload_codec() {
770 let json = r#"{
771 "id": "request-123",
772 "request_type": "single_in",
773 "response_type": "many_out",
774 "payload_codec": "msgpack",
775 "connection_info": {
776 "transport": "tcp",
777 "info": "{}"
778 }
779 }"#;
780
781 let message: RequestControlMessage =
782 serde_json::from_str(json).expect("control message should deserialize");
783
784 assert_eq!(message.payload_codec, RequestPlanePayloadCodec::Msgpack);
785 }
786
787 #[test]
788 fn request_plane_payload_codec_configuration_defaults_to_msgpack() {
789 assert_eq!(
790 RequestPlanePayloadCodec::from_config_value(None),
791 RequestPlanePayloadCodec::Msgpack
792 );
793 assert_eq!(
794 RequestPlanePayloadCodec::from_config_value(Some("")),
795 RequestPlanePayloadCodec::Msgpack
796 );
797 assert_eq!(
798 RequestPlanePayloadCodec::from_config_value(Some("invalid")),
799 RequestPlanePayloadCodec::Msgpack
800 );
801 }
802
803 #[test]
804 fn request_plane_payload_codec_configuration_honors_explicit_overrides() {
805 assert_eq!(
806 RequestPlanePayloadCodec::from_config_value(Some("json")),
807 RequestPlanePayloadCodec::Json
808 );
809 assert_eq!(
810 RequestPlanePayloadCodec::from_config_value(Some("msgpack")),
811 RequestPlanePayloadCodec::Msgpack
812 );
813 }
814
815 #[test]
816 fn request_plane_payload_codec_round_trips_response_wrapper_json_and_msgpack() {
817 let wrapper = NetworkStreamWrapper {
818 data: Some(TestPayload {
819 id: 42,
820 text: "line\nquote\"slash\\unicode δΈ".to_string(),
821 tokens: vec![1, 2, 3, 65535],
822 }),
823 complete_final: false,
824 };
825
826 for codec in [
827 RequestPlanePayloadCodec::Json,
828 RequestPlanePayloadCodec::Msgpack,
829 ] {
830 let encoded = codec.encode(&wrapper).expect("wrapper should encode");
831 let decoded: NetworkStreamWrapper<TestPayload> =
832 codec.decode(&encoded).expect("wrapper should decode");
833 assert_eq!(decoded, wrapper);
834 }
835 }
836}
837
838#[async_trait]
839impl<T: Data, U: Data> AsyncEngine<SingleIn<T>, ManyOut<U>, Error>
840 for Egress<SingleIn<T>, ManyOut<U>>
841where
842 T: Data + Serialize,
843 U: for<'de> Deserialize<'de> + Data,
844{
845 async fn generate(&self, request: SingleIn<T>) -> Result<ManyOut<U>, Error> {
846 self.transport_engine.generate(request).await
847 }
848}
849
850pub struct EncodedResponseFrame {
852 pub bytes: Bytes,
853 pub is_error: bool,
854 pub stop_stream: bool,
857}
858
859pub trait IngressRequestDecoder<T>: Send + Sync + 'static
861where
862 T: Data,
863{
864 fn decode_request(
865 &self,
866 payload_codec: RequestPlanePayloadCodec,
867 bytes: Bytes,
868 ) -> impl std::future::Future<Output = std::result::Result<T, PipelineError>> + Send;
869}
870
871pub trait IngressResponseEncoder<U>: Send + Sync + 'static
873where
874 U: Data,
875{
876 fn encode_response(
877 &self,
878 payload_codec: RequestPlanePayloadCodec,
879 response: Option<U>,
880 complete_final: bool,
881 ) -> impl std::future::Future<Output = std::result::Result<EncodedResponseFrame, PipelineError>> + Send;
882}
883
884pub trait IngressPayloadAdapter<T, U>:
886 IngressRequestDecoder<T> + IngressResponseEncoder<U>
887where
888 T: Data,
889 U: Data,
890{
891}
892
893impl<T, U, Adapter> IngressPayloadAdapter<T, U> for Adapter
894where
895 T: Data,
896 U: Data,
897 Adapter: IngressRequestDecoder<T> + IngressResponseEncoder<U>,
898{
899}
900
901#[derive(Debug, Default)]
903pub struct SerdeIngressPayloadAdapter;
904
905impl<T> IngressRequestDecoder<T> for SerdeIngressPayloadAdapter
906where
907 T: Data + DeserializeOwned,
908{
909 #[inline]
910 fn decode_request(
911 &self,
912 payload_codec: RequestPlanePayloadCodec,
913 bytes: Bytes,
914 ) -> impl std::future::Future<Output = std::result::Result<T, PipelineError>> + Send {
915 let decoded = payload_codec.decode(&bytes).map_err(|err| {
916 PipelineError::DeserializationError(format!(
917 "Failed deserializing {} request payload: {}",
918 payload_codec.name(),
919 err
920 ))
921 });
922 std::future::ready(decoded)
923 }
924}
925
926impl<U> IngressResponseEncoder<U> for SerdeIngressPayloadAdapter
927where
928 U: Data + Serialize + MaybeError,
929{
930 #[inline]
931 fn encode_response(
932 &self,
933 payload_codec: RequestPlanePayloadCodec,
934 response: Option<U>,
935 complete_final: bool,
936 ) -> impl std::future::Future<Output = std::result::Result<EncodedResponseFrame, PipelineError>> + Send
937 {
938 let is_error = response
939 .as_ref()
940 .is_some_and(|response| response.err().is_some());
941 let wrapper = NetworkStreamWrapper {
942 data: response,
943 complete_final,
944 };
945 let encoded = payload_codec.encode(&wrapper).map_err(|err| {
946 PipelineError::SerializationError(format!(
947 "Failed serializing {} request-plane response: {}",
948 payload_codec.name(),
949 err
950 ))
951 });
952 std::future::ready(encoded.map(|bytes| EncodedResponseFrame {
953 bytes: bytes.into(),
954 is_error,
955 stop_stream: false,
956 }))
957 }
958}
959
960pub struct Ingress<Req: PipelineIO, Resp: PipelineIO, Adapter = SerdeIngressPayloadAdapter> {
961 segment: OnceLock<Arc<SegmentSource<Req, Resp>>>,
962 metrics: OnceLock<Arc<WorkHandlerMetrics>>,
963 endpoint_health_check_notifier: OnceLock<Arc<tokio::sync::Notify>>,
965 payload_adapter: Arc<Adapter>,
966}
967
968impl<Req: PipelineIO + Sync, Resp: PipelineIO> Ingress<Req, Resp> {
969 pub fn new() -> Arc<Self> {
970 Ingress::new_with_adapter(SerdeIngressPayloadAdapter)
971 }
972
973 pub fn link(segment: Arc<SegmentSource<Req, Resp>>) -> Result<Arc<Self>> {
974 let ingress = Ingress::new();
975 ingress.attach(segment)?;
976 Ok(ingress)
977 }
978
979 pub fn for_pipeline(segment: Arc<SegmentSource<Req, Resp>>) -> Result<Arc<Self>> {
980 let ingress = Ingress::new();
981 ingress.attach(segment)?;
982 Ok(ingress)
983 }
984
985 pub fn for_engine(engine: ServiceEngine<Req, Resp>) -> Result<Arc<Self>> {
986 Self::for_engine_with_adapter(engine, SerdeIngressPayloadAdapter)
987 }
988}
989
990impl<Req, Resp, Adapter> Ingress<Req, Resp, Adapter>
991where
992 Req: PipelineIO + Sync,
993 Resp: PipelineIO,
994 Adapter: Send + Sync + 'static,
995{
996 pub fn new_with_adapter(payload_adapter: Adapter) -> Arc<Self> {
997 Arc::new(Self {
998 segment: OnceLock::new(),
999 metrics: OnceLock::new(),
1000 endpoint_health_check_notifier: OnceLock::new(),
1001 payload_adapter: Arc::new(payload_adapter),
1002 })
1003 }
1004
1005 pub fn attach(&self, segment: Arc<SegmentSource<Req, Resp>>) -> Result<()> {
1006 self.segment
1007 .set(segment)
1008 .map_err(|_| anyhow::anyhow!("Segment already set"))
1009 }
1010
1011 pub fn add_metrics(
1012 &self,
1013 endpoint: &crate::component::Endpoint,
1014 metrics_labels: Option<&[(&str, &str)]>,
1015 ) -> Result<()> {
1016 let metrics = WorkHandlerMetrics::from_endpoint(endpoint, metrics_labels)
1017 .map_err(|e| anyhow::anyhow!("Failed to create work handler metrics: {}", e))?;
1018
1019 crate::metrics::work_handler_perf::ensure_work_handler_perf_metrics_registered(
1021 endpoint.get_metrics_registry(),
1022 );
1023
1024 crate::metrics::work_handler_pool::ensure_work_handler_pool_metrics_registered(
1028 endpoint.get_metrics_registry(),
1029 );
1030
1031 self.metrics
1032 .set(Arc::new(metrics))
1033 .map_err(|_| anyhow::anyhow!("Metrics already set"))
1034 }
1035
1036 pub fn for_engine_with_adapter(
1037 engine: ServiceEngine<Req, Resp>,
1038 payload_adapter: Adapter,
1039 ) -> Result<Arc<Self>> {
1040 let frontend = SegmentSource::<Req, Resp>::new();
1041 let backend = ServiceBackend::from_engine(engine);
1042
1043 let pipeline = frontend.link(backend)?.link_terminal(frontend)?;
1045
1046 let ingress = Ingress::new_with_adapter(payload_adapter);
1047 ingress.attach(pipeline)?;
1048
1049 Ok(ingress)
1050 }
1051
1052 fn metrics(&self) -> Option<&Arc<WorkHandlerMetrics>> {
1054 self.metrics.get()
1055 }
1056}
1057
1058#[async_trait]
1059pub trait PushWorkHandler: Send + Sync {
1060 async fn handle_payload(
1061 &self,
1062 payload: Bytes,
1063 request_id: Option<String>,
1064 ) -> Result<(), PipelineError>;
1065
1066 fn add_metrics(
1068 &self,
1069 endpoint: &crate::component::Endpoint,
1070 metrics_labels: Option<&[(&str, &str)]>,
1071 ) -> Result<()>;
1072
1073 fn set_endpoint_health_check_notifier(
1075 &self,
1076 _notifier: Arc<tokio::sync::Notify>,
1077 ) -> Result<()> {
1078 Ok(())
1080 }
1081}
1082
1083#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
1128pub struct NetworkStreamWrapper<U> {
1129 #[serde(skip_serializing_if = "Option::is_none")]
1130 pub data: Option<U>,
1131 pub complete_final: bool,
1132}