1pub mod codec;
12pub mod egress;
13pub mod ingress;
14pub mod manager;
15pub mod quic_response;
16pub mod tcp;
17
18use crate::SystemHealth;
19use crate::error::{DynamoError, ErrorType};
20use crate::traits::DistributedRuntimeProvider;
21use std::sync::{Arc, OnceLock};
22
23use anyhow::Result;
24use async_trait::async_trait;
25use bytes::Bytes;
26use codec::{TwoPartCodec, TwoPartMessage, TwoPartMessageType};
27use derive_builder::Builder;
28use futures::StreamExt;
29use super::{AsyncEngine, AsyncEngineContext, AsyncEngineContextProvider, ResponseStream};
31use serde::{Deserialize, Serialize, de::DeserializeOwned};
32
33use super::{
34 AsyncTransportEngine, Context, Data, Error, ManyIn, ManyOut, PipelineError, PipelineIO,
35 SegmentSource, ServiceBackend, ServiceEngine, SingleIn, Source, context,
36};
37use crate::metrics::MetricsHierarchy;
38use crate::metrics::prometheus_names::work_handler;
39use crate::protocols::maybe_error::MaybeError;
40use crate::telemetry::LifecycleOperationRole;
41use ingress::push_handler::WorkHandlerMetrics;
42use prometheus::{CounterVec, Histogram, IntCounter, IntCounterVec, IntGauge};
43
44pub(crate) const DEFAULT_TCP_MAX_MESSAGE_SIZE: usize = 32 * 1024 * 1024;
46
47pub(crate) const ACK_UNAVAILABLE_PREFIX: &str = "Server unavailable:";
52
53static REQUEST_PLANE_PAYLOAD_CODEC: OnceLock<RequestPlanePayloadCodec> = OnceLock::new();
54static RESPONSE_PLANE_MODE: OnceLock<ResponsePlaneMode> = OnceLock::new();
55
56#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
58pub enum ResponsePlaneMode {
59 #[default]
60 Tcp,
61 Quic,
62}
63
64impl ResponsePlaneMode {
65 pub fn configured() -> Result<Self> {
66 if let Some(mode) = RESPONSE_PLANE_MODE.get() {
67 return Ok(*mode);
68 }
69 let value =
70 std::env::var(crate::config::environment_names::response_plane::DYN_RESPONSE_PLANE)
71 .ok();
72 let mode = Self::from_config_value(value.as_deref())?;
73 Ok(*RESPONSE_PLANE_MODE.get_or_init(|| mode))
74 }
75
76 fn from_config_value(value: Option<&str>) -> Result<Self> {
77 match value {
78 None | Some("tcp") => Ok(Self::Tcp),
79 Some("quic") => Ok(Self::Quic),
80 Some(other) => anyhow::bail!(
81 "invalid {} value '{other}'; expected 'tcp' or 'quic'",
82 crate::config::environment_names::response_plane::DYN_RESPONSE_PLANE
83 ),
84 }
85 }
86
87 pub fn from_transport_name(transport: &str) -> Result<Self> {
88 match transport {
89 "tcp_server" => Ok(Self::Tcp),
90 quic_response::TRANSPORT_NAME => Ok(Self::Quic),
91 other => anyhow::bail!("unsupported response transport '{other}'"),
92 }
93 }
94
95 pub const fn name(self) -> &'static str {
96 match self {
97 Self::Tcp => "tcp",
98 Self::Quic => "quic",
99 }
100 }
101}
102
103crate::env_config! {
104 pub(crate) fn get_tcp_max_message_size() -> usize =
107 crate::config::environment_names::request_plane::DYN_TCP_MAX_MESSAGE_SIZE,
108 default = DEFAULT_TCP_MAX_MESSAGE_SIZE;
109}
110
111#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
112#[serde(rename_all = "snake_case")]
113pub enum RequestPlanePayloadCodec {
114 #[default]
117 Json,
118 Msgpack,
119}
120
121impl RequestPlanePayloadCodec {
122 pub fn configured() -> Self {
123 *REQUEST_PLANE_PAYLOAD_CODEC.get_or_init(Self::from_env)
124 }
125
126 fn from_env() -> Self {
127 let value =
128 std::env::var(crate::config::environment_names::request_plane::DYN_REQUEST_PLANE_CODEC)
129 .ok();
130 Self::from_config_value(value.as_deref())
131 }
132
133 fn from_config_value(value: Option<&str>) -> Self {
134 match value {
135 None | Some("") | Some("msgpack") => Self::Msgpack,
136 Some("json") => Self::Json,
137 Some(other) => {
138 tracing::warn!(
139 env_var =
140 crate::config::environment_names::request_plane::DYN_REQUEST_PLANE_CODEC,
141 value = other,
142 "invalid request plane payload codec, defaulting to msgpack"
143 );
144 Self::Msgpack
145 }
146 }
147 }
148
149 pub fn name(&self) -> &'static str {
150 match self {
151 Self::Json => "json",
152 Self::Msgpack => "msgpack",
153 }
154 }
155
156 pub fn encode<T: Serialize>(&self, value: &T) -> Result<Vec<u8>> {
157 match self {
158 Self::Json => Ok(serde_json::to_vec(value)?),
159 Self::Msgpack => Ok(rmp_serde::to_vec_named(value)?),
160 }
161 }
162
163 pub fn encode_into<T, W>(&self, value: &T, writer: &mut W) -> Result<()>
169 where
170 T: Serialize + ?Sized,
171 W: std::io::Write,
172 {
173 match self {
174 Self::Json => Ok(serde_json::to_writer(writer, value)?),
175 Self::Msgpack => Ok(rmp_serde::encode::write_named(writer, value)?),
176 }
177 }
178
179 pub fn decode<T: DeserializeOwned>(&self, bytes: &[u8]) -> Result<T> {
180 match self {
181 Self::Json => Ok(serde_json::from_slice(bytes)?),
182 Self::Msgpack => Ok(rmp_serde::from_slice(bytes)?),
183 }
184 }
185}
186
187pub const RESPONSE_ENCODE_CAPACITY_HINT: usize = 128;
192
193pub trait Codable: PipelineIO + Serialize + for<'de> Deserialize<'de> {}
194impl<T: PipelineIO + Serialize + for<'de> Deserialize<'de>> Codable for T {}
195
196#[async_trait]
198pub trait WorkQueueConsumer {
199 async fn dequeue(&self) -> Result<Bytes, String>;
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
203#[serde(rename_all = "snake_case")]
204pub enum StreamType {
205 Request,
206 Response,
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize)]
210#[serde(rename_all = "snake_case")]
211pub(crate) enum RequestType {
212 SingleIn,
213 ManyIn,
214}
215
216#[derive(Debug, Clone, Serialize, Deserialize)]
217#[serde(rename_all = "snake_case")]
218pub(crate) enum ResponseType {
219 SingleOut,
220 ManyOut,
221}
222
223#[derive(Debug, Clone, Serialize, Deserialize)]
224pub(crate) struct RequestControlMessage {
225 pub(crate) id: String,
226 pub(crate) request_type: RequestType,
227 pub(crate) response_type: ResponseType,
228 #[serde(default)]
229 pub(crate) payload_codec: RequestPlanePayloadCodec,
230 pub(crate) connection_info: ConnectionInfo,
231 #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
232 pub(crate) metadata: std::collections::BTreeMap<String, String>,
233 #[serde(default, skip_serializing_if = "Option::is_none")]
237 pub(crate) frontend_send_ts_ns: Option<u64>,
238 #[serde(default, skip_serializing_if = "Option::is_none")]
242 pub(crate) request_stream_connection_info: Option<ConnectionInfo>,
243}
244
245#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
246#[serde(rename_all = "snake_case")]
247pub enum ControlMessage {
248 Stop,
249 Kill,
250 Sentinel,
251}
252
253#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
260pub struct ResponseStreamPrologue {
261 error: Option<String>,
262
263 #[serde(
277 default,
278 deserialize_with = "deserialize_typed_error",
279 skip_serializing_if = "Option::is_none"
280 )]
281 typed_error: Option<DynamoError>,
282}
283
284fn deserialize_typed_error<'de, D>(deserializer: D) -> Result<Option<DynamoError>, D::Error>
285where
286 D: serde::Deserializer<'de>,
287{
288 let typed_error = Option::<serde_json::Value>::deserialize(deserializer)?;
289 Ok(typed_error.and_then(|typed_error| {
290 if typed_error.get("class").is_none()
293 && let Some(wire_error_type) = typed_error.get("error_type")
294 {
295 let round_trip = serde_json::from_value::<ErrorType>(wire_error_type.clone())
296 .ok()
297 .and_then(|error_type| serde_json::to_value(error_type).ok());
298 if round_trip.as_ref() != Some(wire_error_type) {
299 return None;
300 }
301 }
302 serde_json::from_value(typed_error).ok()
303 }))
304}
305
306#[derive(Debug, Clone, PartialEq, Eq)]
312pub struct StreamPrologueError {
313 pub message: String,
314 pub typed_error: Option<DynamoError>,
315}
316
317impl StreamPrologueError {
318 pub fn from_message(message: impl Into<String>) -> Self {
320 Self {
321 message: message.into(),
322 typed_error: None,
323 }
324 }
325
326 pub fn new(message: impl Into<String>, typed_error: DynamoError) -> Self {
329 Self {
330 message: message.into(),
331 typed_error: Some(typed_error),
332 }
333 }
334}
335
336impl std::fmt::Display for StreamPrologueError {
337 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
338 f.write_str(&self.message)
339 }
340}
341
342impl std::ops::Deref for StreamPrologueError {
346 type Target = str;
347
348 fn deref(&self) -> &Self::Target {
349 &self.message
350 }
351}
352
353impl AsRef<str> for StreamPrologueError {
354 fn as_ref(&self) -> &str {
355 &self.message
356 }
357}
358
359impl From<String> for StreamPrologueError {
360 fn from(message: String) -> Self {
361 Self::from_message(message)
362 }
363}
364
365impl From<&str> for StreamPrologueError {
366 fn from(message: &str) -> Self {
367 Self::from_message(message)
368 }
369}
370
371pub type StreamProvider<T> = tokio::sync::oneshot::Receiver<Result<T, StreamPrologueError>>;
376
377struct Cleanup(Option<Box<dyn FnOnce() + Send + 'static>>);
380
381impl Drop for Cleanup {
382 fn drop(&mut self) {
383 if let Some(f) = self.0.take() {
384 f();
385 }
386 }
387}
388
389pub struct RegisteredStream<T> {
393 pub connection_info: ConnectionInfo,
394 pub stream_provider: StreamProvider<T>,
395 registration_id: Option<uuid::Uuid>,
396 cleanup: Cleanup,
397}
398
399impl<T> std::fmt::Debug for RegisteredStream<T> {
400 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
401 f.debug_struct("RegisteredStream")
402 .field("connection_info", &self.connection_info)
403 .finish_non_exhaustive()
404 }
405}
406
407impl<T> RegisteredStream<T> {
408 pub(crate) fn new(connection_info: ConnectionInfo, stream_provider: StreamProvider<T>) -> Self {
409 Self {
410 connection_info,
411 stream_provider,
412 registration_id: None,
413 cleanup: Cleanup(None),
414 }
415 }
416
417 pub(crate) fn with_registration_id(mut self, registration_id: uuid::Uuid) -> Self {
418 self.registration_id = Some(registration_id);
419 self
420 }
421
422 pub(crate) fn registration_id(&self) -> Option<uuid::Uuid> {
423 self.registration_id
424 }
425
426 pub(crate) fn with_cleanup<F>(mut self, cleanup: F) -> Self
427 where
428 F: FnOnce() + Send + 'static,
429 {
430 self.cleanup.0 = Some(Box::new(cleanup));
431 self
432 }
433
434 pub fn into_parts(self) -> (ConnectionInfo, StreamProvider<T>) {
437 let Self {
438 connection_info,
439 stream_provider,
440 registration_id: _,
441 mut cleanup,
442 } = self;
443 cleanup.0.take();
444 (connection_info, stream_provider)
445 }
446
447 pub async fn wait(
453 mut self,
454 ) -> Result<Result<T, StreamPrologueError>, tokio::sync::oneshot::error::RecvError> {
455 let result = (&mut self.stream_provider).await;
456 if matches!(result, Ok(Ok(_))) {
457 self.cleanup.0.take();
458 }
459 result
460 }
461}
462
463pub struct PendingConnections {
466 pub send_stream: Option<RegisteredStream<StreamSender>>,
467 pub recv_stream: Option<RegisteredStream<StreamReceiver>>,
468}
469
470impl PendingConnections {
471 pub fn into_parts(
472 self,
473 ) -> (
474 Option<RegisteredStream<StreamSender>>,
475 Option<RegisteredStream<StreamReceiver>>,
476 ) {
477 (self.send_stream, self.recv_stream)
478 }
479}
480
481#[async_trait::async_trait]
484pub trait ResponseService {
485 async fn register(&self, options: StreamOptions) -> PendingConnections;
486}
487
488#[cfg(test)]
489mod registered_stream_tests {
490 use super::*;
491 use std::sync::atomic::{AtomicBool, Ordering};
492
493 fn dummy_conn_info() -> ConnectionInfo {
494 ConnectionInfo {
495 transport: "test".to_string(),
496 info: "{}".to_string(),
497 }
498 }
499
500 #[test]
502 fn drop_runs_cleanup() {
503 let flag = Arc::new(AtomicBool::new(false));
504 let flag_clone = flag.clone();
505
506 let (_tx, rx) = tokio::sync::oneshot::channel::<Result<(), StreamPrologueError>>();
507 let stream = RegisteredStream::new(dummy_conn_info(), rx).with_cleanup(move || {
508 flag_clone.store(true, Ordering::SeqCst);
509 });
510
511 drop(stream);
512 assert!(
513 flag.load(Ordering::SeqCst),
514 "cleanup must fire when RegisteredStream is dropped"
515 );
516 }
517
518 #[test]
522 fn into_parts_disarms_cleanup() {
523 let flag = Arc::new(AtomicBool::new(false));
524 let flag_clone = flag.clone();
525
526 let (_tx, rx) = tokio::sync::oneshot::channel::<Result<(), StreamPrologueError>>();
527 let stream = RegisteredStream::new(dummy_conn_info(), rx).with_cleanup(move || {
528 flag_clone.store(true, Ordering::SeqCst);
529 });
530
531 let (conn, provider) = stream.into_parts();
532 drop(conn);
533 drop(provider);
534
535 assert!(
536 !flag.load(Ordering::SeqCst),
537 "into_parts() must disarm the cleanup closure"
538 );
539 }
540
541 #[tokio::test]
542 async fn successful_wait_disarms_cleanup() {
543 let flag = Arc::new(AtomicBool::new(false));
544 let flag_clone = flag.clone();
545 let (tx, rx) = tokio::sync::oneshot::channel();
546 let stream = RegisteredStream::new(dummy_conn_info(), rx).with_cleanup(move || {
547 flag_clone.store(true, Ordering::SeqCst);
548 });
549
550 tx.send(Ok(42)).unwrap();
551 assert_eq!(stream.wait().await.unwrap().unwrap(), 42);
552 assert!(
553 !flag.load(Ordering::SeqCst),
554 "successful wait must disarm cleanup"
555 );
556 }
557
558 #[tokio::test]
559 async fn failed_prologue_wait_runs_cleanup() {
560 let flag = Arc::new(AtomicBool::new(false));
561 let flag_clone = flag.clone();
562 let (tx, rx) = tokio::sync::oneshot::channel::<Result<(), StreamPrologueError>>();
563 let stream = RegisteredStream::new(dummy_conn_info(), rx).with_cleanup(move || {
564 flag_clone.store(true, Ordering::SeqCst);
565 });
566 let error = StreamPrologueError::from_message("worker rejected request");
567
568 tx.send(Err(error.clone())).unwrap();
569 assert_eq!(stream.wait().await.unwrap().unwrap_err(), error);
570 assert!(
571 flag.load(Ordering::SeqCst),
572 "failed prologue must run cleanup"
573 );
574 }
575
576 #[tokio::test]
577 async fn closed_provider_wait_runs_cleanup() {
578 let flag = Arc::new(AtomicBool::new(false));
579 let flag_clone = flag.clone();
580 let (tx, rx) = tokio::sync::oneshot::channel::<Result<(), StreamPrologueError>>();
581 let stream = RegisteredStream::new(dummy_conn_info(), rx).with_cleanup(move || {
582 flag_clone.store(true, Ordering::SeqCst);
583 });
584
585 drop(tx);
586 assert!(stream.wait().await.is_err());
587 assert!(
588 flag.load(Ordering::SeqCst),
589 "closed provider must run cleanup"
590 );
591 }
592
593 #[test]
595 fn drop_without_cleanup_is_a_noop() {
596 let (_tx, rx) = tokio::sync::oneshot::channel::<Result<(), StreamPrologueError>>();
597 let stream: RegisteredStream<()> = RegisteredStream::new(dummy_conn_info(), rx);
598 drop(stream); }
600}
601
602pub struct StreamSender {
627 tx: tokio::sync::mpsc::Sender<TwoPartMessage>,
628 prologue: Option<ResponseStreamPrologue>,
629}
630
631impl StreamSender {
632 pub async fn send(&self, data: Bytes) -> Result<()> {
633 Ok(self.tx.send(TwoPartMessage::from_data(data)).await?)
634 }
635
636 pub async fn send_control(&self, control: ControlMessage) -> Result<()> {
637 let bytes = serde_json::to_vec(&control)?;
638 Ok(self
639 .tx
640 .send(TwoPartMessage::from_header(bytes.into()))
641 .await?)
642 }
643
644 pub async fn send_prologue(&mut self, error: Option<String>) -> Result<(), String> {
649 self.send_prologue_typed(error.map(StreamPrologueError::from_message))
650 .await
651 }
652
653 pub async fn send_prologue_typed(
654 &mut self,
655 error: Option<StreamPrologueError>,
656 ) -> Result<(), String> {
657 if let Some(_prologue) = self.prologue.take() {
659 let (error, typed_error) = match error {
660 Some(StreamPrologueError {
661 message,
662 typed_error,
663 }) => (Some(message), typed_error),
664 None => (None, None),
665 };
666 let prologue = ResponseStreamPrologue { error, typed_error };
667 let header_bytes: Bytes = match serde_json::to_vec(&prologue) {
668 Ok(b) => b.into(),
669 Err(err) => {
670 tracing::error!(%err, "send_prologue: ResponseStreamPrologue did not serialize to a JSON array");
671 return Err("Invalid prologue".to_string());
672 }
673 };
674 self.tx
675 .send(TwoPartMessage::from_header(header_bytes))
676 .await
677 .map_err(|e| e.to_string())?;
678 } else {
679 panic!("Prologue already sent; or not set; logic error");
680 }
681 Ok(())
682 }
683}
684
685pub struct StreamReceiver {
686 rx: tokio::sync::mpsc::Receiver<Bytes>,
687}
688
689#[derive(Debug, Clone, Serialize, Deserialize)]
699pub struct ConnectionInfo {
700 pub transport: String,
701 pub info: String,
702}
703
704pub const DEFAULT_SEND_BUFFER_COUNT: usize = 64;
708
709#[derive(Clone, Builder)]
716pub struct StreamOptions {
717 pub context: Arc<dyn AsyncEngineContext>,
719
720 pub enable_request_stream: bool,
725
726 pub enable_response_stream: bool,
729
730 #[builder(default)]
732 pub defer_cancellation_until_prologue: bool,
733
734 #[builder(default = "DEFAULT_SEND_BUFFER_COUNT")]
738 pub send_buffer_count: usize,
739
740 #[builder(default = "8")]
742 pub recv_buffer_count: usize,
743}
744
745impl StreamOptions {
746 pub fn builder() -> StreamOptionsBuilder {
747 StreamOptionsBuilder::default()
748 }
749}
750
751pub struct Egress<Req: PipelineIO, Resp: PipelineIO> {
752 transport_engine: Arc<dyn AsyncTransportEngine<Req, Resp>>,
753}
754
755#[cfg(test)]
756mod tests {
757 use super::{
758 DEFAULT_SEND_BUFFER_COUNT, Ingress, IngressResponseEncoder, ManyOut, NetworkStreamWrapper,
759 RequestControlMessage, RequestPlanePayloadCodec, RequestType, ResponsePlaneMode,
760 ResponseStreamPrologue, ResponseType, SerdeIngressPayloadAdapter, SingleIn, StreamOptions,
761 StreamPrologueError,
762 };
763 use crate::engine::AsyncEngineContextProvider;
764 use crate::error::{BackendError, DynamoError, ErrorType};
765 use crate::pipeline::Context;
766 use crate::protocols::annotated::Annotated;
767 use crate::telemetry::LifecycleOperationRole;
768 use serde::{Deserialize, Serialize};
769 use std::sync::{Arc, OnceLock};
770
771 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
772 struct TestPayload {
773 id: u64,
774 text: String,
775 tokens: Vec<u32>,
776 }
777
778 #[test]
779 fn prologue_without_typed_error_field_still_decodes() {
780 let legacy = br#"{"error":"Generate Error: something went wrong"}"#;
781 let prologue: ResponseStreamPrologue =
782 serde_json::from_slice(legacy).expect("a prologue without the typed field must decode");
783
784 assert_eq!(
785 prologue.error.as_deref(),
786 Some("Generate Error: something went wrong")
787 );
788 assert!(
789 prologue.typed_error.is_none(),
790 "an absent typed error must decode to None, not fail"
791 );
792 }
793
794 #[test]
798 fn stream_prologue_error_substitutes_for_the_previous_string() {
799 let err = StreamPrologueError::from_message("malformed prologue: bad header");
800
801 assert!(err.contains("malformed prologue"));
802 assert!(err.starts_with("malformed"));
803 assert_eq!(err.len(), "malformed prologue: bad header".len());
804 assert_eq!(&err[..9], "malformed");
805 assert_eq!(err.as_ref() as &str, "malformed prologue: bad header");
806
807 let converted: StreamPrologueError = "from a &str".into();
808 assert_eq!(converted.message, "from a &str");
809 assert!(converted.typed_error.is_none());
810
811 let converted: StreamPrologueError = String::from("from a String").into();
812 assert_eq!(converted.message, "from a String");
813 }
814
815 #[test]
816 fn prologue_round_trips_the_typed_error() {
817 let prologue = ResponseStreamPrologue {
818 error: Some("Generate Error: unsupported input".to_string()),
819 typed_error: Some(
820 DynamoError::builder()
821 .error_type(ErrorType::Backend(BackendError::InvalidArgument))
822 .message("unsupported input")
823 .build(),
824 ),
825 };
826
827 let encoded = serde_json::to_vec(&prologue).expect("prologue should serialize");
828 let decoded: ResponseStreamPrologue =
829 serde_json::from_slice(&encoded).expect("prologue should deserialize");
830
831 assert_eq!(decoded, prologue);
832 assert_eq!(
833 decoded.typed_error.map(|e| e.error_type()),
834 Some(ErrorType::Backend(BackendError::InvalidArgument))
835 );
836 }
837
838 #[test]
839 fn prologue_accepts_a_known_legacy_typed_error() {
840 let legacy = br#"{
841 "error":"Generate Error: unsupported input",
842 "typed_error":{"error_type":"InvalidArgument","message":"unsupported input"}
843 }"#;
844 let prologue: ResponseStreamPrologue =
845 serde_json::from_slice(legacy).expect("a known legacy typed error must decode");
846 let error = prologue
847 .typed_error
848 .expect("a known legacy typed error must remain typed");
849
850 assert_eq!(error.error_type(), ErrorType::InvalidArgument);
851 assert_eq!(error.class(), ErrorType::InvalidRequest);
852 assert_eq!(error.reason().as_str(), "request.invalid_argument");
853 }
854
855 #[test]
856 fn prologue_ignores_a_future_legacy_typed_error() {
857 let future = br#"{
858 "error":"Generate Error: future failure",
859 "typed_error":{"error_type":"VariantFromTheFuture","message":"future failure"}
860 }"#;
861 let prologue: ResponseStreamPrologue = serde_json::from_slice(future)
862 .expect("a future legacy typed error must not reject the prologue");
863
864 assert!(prologue.typed_error.is_none());
865 }
866
867 #[test]
868 fn prologue_fails_closed_for_a_future_semantic_class() {
869 let future = br#"{
870 "error":"Generate Error: future failure",
871 "typed_error":{
872 "class":"FutureErrorClass",
873 "reason":"runtime.internal",
874 "public":{"type":"message","message":"must not escape"}
875 }
876 }"#;
877 let prologue: ResponseStreamPrologue = serde_json::from_slice(future)
878 .expect("a future semantic class must not reject the prologue");
879 let error = prologue
880 .typed_error
881 .expect("semantic errors use the DynamoError fail-closed identity");
882
883 assert_eq!(error.class(), ErrorType::Internal);
884 assert_eq!(error.reason().as_str(), "runtime.invalid_error");
885 assert!(error.public_details().is_none());
886 }
887
888 #[test]
889 fn response_plane_mode_parses_supported_values() {
890 assert_eq!(
891 ResponsePlaneMode::from_config_value(None).unwrap(),
892 ResponsePlaneMode::Tcp
893 );
894 assert_eq!(
895 ResponsePlaneMode::from_config_value(Some("tcp")).unwrap(),
896 ResponsePlaneMode::Tcp
897 );
898 assert_eq!(
899 ResponsePlaneMode::from_config_value(Some("quic")).unwrap(),
900 ResponsePlaneMode::Quic
901 );
902 assert!(ResponsePlaneMode::from_config_value(Some("")).is_err());
903 assert!(ResponsePlaneMode::from_config_value(Some("invalid")).is_err());
904 }
905
906 #[test]
907 fn encode_into_matches_encode_byte_for_byte() {
908 let payload = NetworkStreamWrapper {
909 data: Some(TestPayload {
910 id: 7,
911 text: "hello".to_string(),
912 tokens: vec![1, 200, 70_000],
913 }),
914 complete_final: false,
915 };
916
917 for codec in [
918 RequestPlanePayloadCodec::Json,
919 RequestPlanePayloadCodec::Msgpack,
920 ] {
921 let expected = codec.encode(&payload).expect("encode");
922 let mut actual = Vec::new();
923 codec
924 .encode_into(&payload, &mut actual)
925 .expect("encode_into");
926 assert_eq!(expected, actual, "codec={}", codec.name());
927 }
928 }
929
930 #[test]
931 fn stream_options_send_buffer_count_defaults_to_64() {
932 let context = Context::new(());
933 let options = StreamOptions::builder()
934 .context(context.context())
935 .enable_request_stream(true)
936 .enable_response_stream(true)
937 .build()
938 .expect("stream options should build");
939
940 assert_eq!(DEFAULT_SEND_BUFFER_COUNT, 64);
941 assert_eq!(options.send_buffer_count, DEFAULT_SEND_BUFFER_COUNT);
942 }
943
944 #[test]
945 fn stream_options_send_buffer_count_overrides_default() {
946 let context = Context::new(());
947 let options = StreamOptions::builder()
948 .context(context.context())
949 .enable_request_stream(true)
950 .enable_response_stream(true)
951 .send_buffer_count(128)
952 .build()
953 .expect("stream options should build");
954
955 assert_eq!(options.send_buffer_count, 128);
956 }
957
958 #[test]
959 fn ingress_observes_lifecycle_role_set_after_binding() {
960 let ingress = Ingress::<SingleIn<String>, ManyOut<String>>::new();
961 let endpoint_role = Arc::new(OnceLock::new());
962
963 ingress
964 .lifecycle_operation_role
965 .set(endpoint_role.clone())
966 .expect("new ingress lifecycle role source must be empty");
967 assert_eq!(ingress.registered_lifecycle_role(), None);
968
969 endpoint_role
970 .set(LifecycleOperationRole::Prefill)
971 .expect("new endpoint lifecycle role cell must be empty");
972 assert_eq!(
973 ingress.registered_lifecycle_role(),
974 Some(LifecycleOperationRole::Prefill)
975 );
976 }
977
978 #[test]
979 fn legacy_frontend_control_message_defaults_payload_codec_to_json() {
980 let json = r#"{
981 "id": "request-123",
982 "request_type": "single_in",
983 "response_type": "many_out",
984 "connection_info": {
985 "transport": "tcp",
986 "info": "{}"
987 }
988 }"#;
989
990 let message: RequestControlMessage =
991 serde_json::from_str(json).expect("control message should deserialize");
992
993 assert_eq!(message.id, "request-123");
994 assert!(matches!(message.request_type, RequestType::SingleIn));
995 assert!(matches!(message.response_type, ResponseType::ManyOut));
996 assert_eq!(message.payload_codec, RequestPlanePayloadCodec::Json);
997 assert_eq!(message.connection_info.transport, "tcp");
998 assert_eq!(message.connection_info.info, "{}");
999 assert!(message.metadata.is_empty());
1000 assert!(message.frontend_send_ts_ns.is_none());
1001
1002 let payload = br#"{"id":7,"text":"legacy","tokens":[1,2]}"#;
1003 let decoded: TestPayload = message
1004 .payload_codec
1005 .decode(payload)
1006 .expect("worker should decode the legacy frontend's JSON payload");
1007 assert_eq!(
1008 decoded,
1009 TestPayload {
1010 id: 7,
1011 text: "legacy".to_string(),
1012 tokens: vec![1, 2],
1013 }
1014 );
1015 }
1016
1017 #[test]
1018 fn request_control_message_decodes_msgpack_payload_codec() {
1019 let json = r#"{
1020 "id": "request-123",
1021 "request_type": "single_in",
1022 "response_type": "many_out",
1023 "payload_codec": "msgpack",
1024 "connection_info": {
1025 "transport": "tcp",
1026 "info": "{}"
1027 }
1028 }"#;
1029
1030 let message: RequestControlMessage =
1031 serde_json::from_str(json).expect("control message should deserialize");
1032
1033 assert_eq!(message.payload_codec, RequestPlanePayloadCodec::Msgpack);
1034 }
1035
1036 #[test]
1037 fn request_plane_payload_codec_configuration_defaults_to_msgpack() {
1038 assert_eq!(
1039 RequestPlanePayloadCodec::from_config_value(None),
1040 RequestPlanePayloadCodec::Msgpack
1041 );
1042 assert_eq!(
1043 RequestPlanePayloadCodec::from_config_value(Some("")),
1044 RequestPlanePayloadCodec::Msgpack
1045 );
1046 assert_eq!(
1047 RequestPlanePayloadCodec::from_config_value(Some("invalid")),
1048 RequestPlanePayloadCodec::Msgpack
1049 );
1050 }
1051
1052 #[test]
1053 fn request_plane_payload_codec_configuration_honors_explicit_overrides() {
1054 assert_eq!(
1055 RequestPlanePayloadCodec::from_config_value(Some("json")),
1056 RequestPlanePayloadCodec::Json
1057 );
1058 assert_eq!(
1059 RequestPlanePayloadCodec::from_config_value(Some("msgpack")),
1060 RequestPlanePayloadCodec::Msgpack
1061 );
1062 }
1063
1064 #[test]
1065 fn request_plane_payload_codec_round_trips_response_wrapper_json_and_msgpack() {
1066 let wrapper = NetworkStreamWrapper {
1067 data: Some(TestPayload {
1068 id: 42,
1069 text: "line\nquote\"slash\\unicode 中".to_string(),
1070 tokens: vec![1, 2, 3, 65535],
1071 }),
1072 complete_final: false,
1073 };
1074
1075 for codec in [
1076 RequestPlanePayloadCodec::Json,
1077 RequestPlanePayloadCodec::Msgpack,
1078 ] {
1079 let encoded = codec.encode(&wrapper).expect("wrapper should encode");
1080 let decoded: NetworkStreamWrapper<TestPayload> =
1081 codec.decode(&encoded).expect("wrapper should decode");
1082 assert_eq!(decoded, wrapper);
1083 }
1084 }
1085
1086 #[tokio::test]
1088 async fn serde_ingress_encoder_matches_encode_byte_for_byte() {
1089 let data = Annotated::from_data(serde_json::json!({
1090 "token_ids": [128, 9001],
1091 "index": 3,
1092 }));
1093 let error = Annotated::<serde_json::Value>::from_error("engine failed");
1094
1095 for codec in [
1096 RequestPlanePayloadCodec::Json,
1097 RequestPlanePayloadCodec::Msgpack,
1098 ] {
1099 for (case, response, complete_final, expect_error) in [
1100 ("data frame", Some(data.clone()), false, false),
1101 ("error frame", Some(error.clone()), false, true),
1102 ("complete final", None, true, false),
1103 ] {
1104 let expected = codec
1105 .encode(&NetworkStreamWrapper {
1106 data: response.clone(),
1107 complete_final,
1108 })
1109 .expect("reference encode");
1110
1111 let frame = SerdeIngressPayloadAdapter
1112 .encode_response(codec, response, complete_final)
1113 .await
1114 .expect("adapter encode");
1115
1116 assert_eq!(
1117 frame.bytes.as_ref(),
1118 expected.as_slice(),
1119 "codec={} case={case}",
1120 codec.name()
1121 );
1122 assert_eq!(
1123 frame.is_error,
1124 expect_error,
1125 "codec={} case={case}",
1126 codec.name()
1127 );
1128 assert!(!frame.stop_stream, "codec={} case={case}", codec.name());
1129 }
1130 }
1131 }
1132}
1133
1134#[async_trait]
1135impl<T: Data, U: Data> AsyncEngine<SingleIn<T>, ManyOut<U>, Error>
1136 for Egress<SingleIn<T>, ManyOut<U>>
1137where
1138 T: Data + Serialize,
1139 U: for<'de> Deserialize<'de> + Data,
1140{
1141 async fn generate(&self, request: SingleIn<T>) -> Result<ManyOut<U>, Error> {
1142 self.transport_engine.generate(request).await
1143 }
1144}
1145
1146pub struct EncodedResponseFrame {
1148 pub bytes: Bytes,
1149 pub is_error: bool,
1150 pub stop_stream: bool,
1153}
1154
1155pub trait IngressRequestDecoder<T>: Send + Sync + 'static
1157where
1158 T: Data,
1159{
1160 fn decode_request(
1161 &self,
1162 payload_codec: RequestPlanePayloadCodec,
1163 bytes: Bytes,
1164 ) -> impl std::future::Future<Output = std::result::Result<T, PipelineError>> + Send;
1165}
1166
1167pub trait IngressResponseEncoder<U>: Send + Sync + 'static
1169where
1170 U: Data,
1171{
1172 fn encode_response(
1173 &self,
1174 payload_codec: RequestPlanePayloadCodec,
1175 response: Option<U>,
1176 complete_final: bool,
1177 ) -> impl std::future::Future<Output = std::result::Result<EncodedResponseFrame, PipelineError>> + Send;
1178}
1179
1180pub trait IngressPayloadAdapter<T, U>:
1182 IngressRequestDecoder<T> + IngressResponseEncoder<U>
1183where
1184 T: Data,
1185 U: Data,
1186{
1187}
1188
1189impl<T, U, Adapter> IngressPayloadAdapter<T, U> for Adapter
1190where
1191 T: Data,
1192 U: Data,
1193 Adapter: IngressRequestDecoder<T> + IngressResponseEncoder<U>,
1194{
1195}
1196
1197#[derive(Debug, Default)]
1199pub struct SerdeIngressPayloadAdapter;
1200
1201impl<T> IngressRequestDecoder<T> for SerdeIngressPayloadAdapter
1202where
1203 T: Data + DeserializeOwned,
1204{
1205 #[inline]
1206 fn decode_request(
1207 &self,
1208 payload_codec: RequestPlanePayloadCodec,
1209 bytes: Bytes,
1210 ) -> impl std::future::Future<Output = std::result::Result<T, PipelineError>> + Send {
1211 let decoded = payload_codec.decode(&bytes).map_err(|err| {
1212 PipelineError::DeserializationError(format!(
1213 "Failed deserializing {} request payload: {}",
1214 payload_codec.name(),
1215 err
1216 ))
1217 });
1218 std::future::ready(decoded)
1219 }
1220}
1221
1222impl<U> IngressResponseEncoder<U> for SerdeIngressPayloadAdapter
1223where
1224 U: Data + Serialize + MaybeError,
1225{
1226 #[inline]
1227 fn encode_response(
1228 &self,
1229 payload_codec: RequestPlanePayloadCodec,
1230 response: Option<U>,
1231 complete_final: bool,
1232 ) -> impl std::future::Future<Output = std::result::Result<EncodedResponseFrame, PipelineError>> + Send
1233 {
1234 let is_error = response
1235 .as_ref()
1236 .is_some_and(|response| response.err().is_some());
1237 let wrapper = NetworkStreamWrapper {
1238 data: response,
1239 complete_final,
1240 };
1241 let mut bytes = Vec::with_capacity(RESPONSE_ENCODE_CAPACITY_HINT);
1242 let encoded = payload_codec
1243 .encode_into(&wrapper, &mut bytes)
1244 .map(|()| bytes)
1245 .map_err(|err| {
1246 PipelineError::SerializationError(format!(
1247 "Failed serializing {} request-plane response: {}",
1248 payload_codec.name(),
1249 err
1250 ))
1251 });
1252 std::future::ready(encoded.map(|bytes| EncodedResponseFrame {
1253 bytes: bytes.into(),
1254 is_error,
1255 stop_stream: false,
1256 }))
1257 }
1258}
1259
1260pub struct Ingress<Req: PipelineIO, Resp: PipelineIO, Adapter = SerdeIngressPayloadAdapter> {
1261 segment: OnceLock<Arc<SegmentSource<Req, Resp>>>,
1262 metrics: OnceLock<Arc<WorkHandlerMetrics>>,
1263 endpoint_health_check_notifier: OnceLock<Arc<tokio::sync::Notify>>,
1265 response_plane: OnceLock<ResponsePlaneMode>,
1267 quic_response_client_pool: OnceLock<Arc<quic_response::QuicResponseClientPool>>,
1268 payload_adapter: Arc<Adapter>,
1269 lifecycle_operation_role: OnceLock<Arc<OnceLock<LifecycleOperationRole>>>,
1270}
1271
1272impl<Req: PipelineIO + Sync, Resp: PipelineIO> Ingress<Req, Resp> {
1273 pub fn new() -> Arc<Self> {
1274 Ingress::new_with_adapter(SerdeIngressPayloadAdapter)
1275 }
1276
1277 pub fn link(segment: Arc<SegmentSource<Req, Resp>>) -> Result<Arc<Self>> {
1278 let ingress = Ingress::new();
1279 ingress.attach(segment)?;
1280 Ok(ingress)
1281 }
1282
1283 pub fn for_pipeline(segment: Arc<SegmentSource<Req, Resp>>) -> Result<Arc<Self>> {
1284 let ingress = Ingress::new();
1285 ingress.attach(segment)?;
1286 Ok(ingress)
1287 }
1288
1289 pub fn for_engine(engine: ServiceEngine<Req, Resp>) -> Result<Arc<Self>> {
1290 Self::for_engine_with_adapter(engine, SerdeIngressPayloadAdapter)
1291 }
1292
1293 pub fn for_engine_with_lifecycle_role(
1295 engine: ServiceEngine<Req, Resp>,
1296 role: LifecycleOperationRole,
1297 ) -> Result<Arc<Self>> {
1298 Self::for_engine_with_adapter_and_lifecycle_role(engine, SerdeIngressPayloadAdapter, role)
1299 }
1300}
1301
1302impl<Req, Resp, Adapter> Ingress<Req, Resp, Adapter>
1303where
1304 Req: PipelineIO + Sync,
1305 Resp: PipelineIO,
1306 Adapter: Send + Sync + 'static,
1307{
1308 pub fn new_with_adapter(payload_adapter: Adapter) -> Arc<Self> {
1309 Arc::new(Self {
1310 segment: OnceLock::new(),
1311 metrics: OnceLock::new(),
1312 endpoint_health_check_notifier: OnceLock::new(),
1313 response_plane: OnceLock::new(),
1314 quic_response_client_pool: OnceLock::new(),
1315 payload_adapter: Arc::new(payload_adapter),
1316 lifecycle_operation_role: OnceLock::new(),
1317 })
1318 }
1319
1320 fn new_with_adapter_and_lifecycle_role(
1321 payload_adapter: Adapter,
1322 lifecycle_operation_role: LifecycleOperationRole,
1323 ) -> Arc<Self> {
1324 let ingress = Self::new_with_adapter(payload_adapter);
1325 let role = Arc::new(OnceLock::new());
1326 role.set(lifecycle_operation_role)
1327 .expect("new lifecycle role cell must be empty");
1328 ingress
1329 .lifecycle_operation_role
1330 .set(role)
1331 .expect("new ingress lifecycle role source must be empty");
1332 ingress
1333 }
1334
1335 pub fn attach(&self, segment: Arc<SegmentSource<Req, Resp>>) -> Result<()> {
1336 self.segment
1337 .set(segment)
1338 .map_err(|_| anyhow::anyhow!("Segment already set"))
1339 }
1340
1341 pub(crate) fn set_quic_response_client_pool(
1342 &self,
1343 pool: Arc<quic_response::QuicResponseClientPool>,
1344 ) -> Result<()> {
1345 self.quic_response_client_pool
1346 .set(pool)
1347 .map_err(|_| anyhow::anyhow!("QUIC response client pool already set"))
1348 }
1349
1350 pub(crate) fn quic_response_client_pool(
1351 &self,
1352 ) -> Result<Arc<quic_response::QuicResponseClientPool>, PipelineError> {
1353 if let Some(pool) = self.quic_response_client_pool.get() {
1354 return Ok(pool.clone());
1355 }
1356 let pool = quic_response::process_client_pool_from_env()?;
1357 Ok(self.quic_response_client_pool.get_or_init(|| pool).clone())
1358 }
1359
1360 pub fn add_metrics(
1361 &self,
1362 endpoint: &crate::component::Endpoint,
1363 metrics_labels: Option<&[(&str, &str)]>,
1364 ) -> Result<()> {
1365 let metrics = WorkHandlerMetrics::from_endpoint(endpoint, metrics_labels)
1366 .map_err(|e| anyhow::anyhow!("Failed to create work handler metrics: {}", e))?;
1367
1368 crate::metrics::work_handler_perf::ensure_work_handler_perf_metrics_registered(
1370 endpoint.get_metrics_registry(),
1371 );
1372
1373 crate::metrics::work_handler_pool::ensure_work_handler_pool_metrics_registered(
1377 endpoint.get_metrics_registry(),
1378 );
1379
1380 self.metrics
1381 .set(Arc::new(metrics))
1382 .map_err(|_| anyhow::anyhow!("Metrics already set"))
1383 }
1384
1385 pub fn for_engine_with_adapter(
1386 engine: ServiceEngine<Req, Resp>,
1387 payload_adapter: Adapter,
1388 ) -> Result<Arc<Self>> {
1389 let frontend = SegmentSource::<Req, Resp>::new();
1390 let backend = ServiceBackend::from_engine(engine);
1391 let pipeline = frontend.link(backend)?.link_terminal(frontend)?;
1392 let ingress = Ingress::new_with_adapter(payload_adapter);
1393 ingress.attach(pipeline)?;
1394 Ok(ingress)
1395 }
1396
1397 pub fn for_engine_with_adapter_and_lifecycle_role(
1398 engine: ServiceEngine<Req, Resp>,
1399 payload_adapter: Adapter,
1400 role: LifecycleOperationRole,
1401 ) -> Result<Arc<Self>> {
1402 let frontend = SegmentSource::<Req, Resp>::new();
1403 let backend = ServiceBackend::from_engine(engine);
1404
1405 let pipeline = frontend.link(backend)?.link_terminal(frontend)?;
1406
1407 let ingress = Ingress::new_with_adapter_and_lifecycle_role(payload_adapter, role);
1408 ingress.attach(pipeline)?;
1409
1410 Ok(ingress)
1411 }
1412
1413 fn metrics(&self) -> Option<&Arc<WorkHandlerMetrics>> {
1415 self.metrics.get()
1416 }
1417
1418 fn registered_lifecycle_role(&self) -> Option<LifecycleOperationRole> {
1419 self.lifecycle_operation_role
1420 .get()
1421 .and_then(|role| role.get())
1422 .copied()
1423 }
1424
1425 fn bind_endpoint_config(&self, endpoint: &crate::component::Endpoint) {
1426 let _ = self.response_plane.set(endpoint.drt().response_plane());
1427 let _ = self
1430 .lifecycle_operation_role
1431 .set(endpoint.lifecycle_operation_role());
1432 }
1433}
1434
1435#[async_trait]
1436pub trait PushWorkHandler: Send + Sync {
1437 fn bind_endpoint(&self, _endpoint: &crate::component::Endpoint) {}
1439
1440 async fn handle_payload(
1441 &self,
1442 payload: Bytes,
1443 request_id: Option<String>,
1444 ) -> Result<(), PipelineError>;
1445
1446 fn add_metrics(
1448 &self,
1449 endpoint: &crate::component::Endpoint,
1450 metrics_labels: Option<&[(&str, &str)]>,
1451 ) -> Result<()>;
1452
1453 fn set_endpoint_health_check_notifier(
1455 &self,
1456 _notifier: Arc<tokio::sync::Notify>,
1457 ) -> Result<()> {
1458 Ok(())
1460 }
1461}
1462
1463#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
1508pub struct NetworkStreamWrapper<U> {
1509 #[serde(skip_serializing_if = "Option::is_none")]
1510 pub data: Option<U>,
1511 pub complete_final: bool,
1512}