1pub mod codec;
12pub mod egress;
13pub mod ingress;
14pub mod manager;
15pub mod tcp;
16
17use crate::SystemHealth;
18use std::sync::{Arc, OnceLock};
19
20use anyhow::Result;
21use async_trait::async_trait;
22use bytes::Bytes;
23use codec::{TwoPartCodec, TwoPartMessage, TwoPartMessageType};
24use derive_builder::Builder;
25use futures::StreamExt;
26use super::{AsyncEngine, AsyncEngineContext, AsyncEngineContextProvider, ResponseStream};
28use serde::{Deserialize, Serialize, de::DeserializeOwned};
29
30use super::{
31 AsyncTransportEngine, Context, Data, Error, ManyIn, ManyOut, PipelineError, PipelineIO,
32 SegmentSource, ServiceBackend, ServiceEngine, SingleIn, Source, context,
33};
34use crate::metrics::MetricsHierarchy;
35use crate::metrics::prometheus_names::work_handler;
36use crate::protocols::maybe_error::MaybeError;
37use ingress::push_handler::WorkHandlerMetrics;
38use prometheus::{CounterVec, Histogram, IntCounter, IntCounterVec, IntGauge};
39
40pub(crate) const DEFAULT_TCP_MAX_MESSAGE_SIZE: usize = 32 * 1024 * 1024;
42
43static TCP_MAX_MESSAGE_SIZE: OnceLock<usize> = OnceLock::new();
44static REQUEST_PLANE_PAYLOAD_CODEC: OnceLock<RequestPlanePayloadCodec> = OnceLock::new();
45
46pub(crate) fn get_tcp_max_message_size() -> usize {
49 *TCP_MAX_MESSAGE_SIZE.get_or_init(|| {
50 std::env::var("DYN_TCP_MAX_MESSAGE_SIZE")
51 .ok()
52 .and_then(|s| s.parse::<usize>().ok())
53 .unwrap_or(DEFAULT_TCP_MAX_MESSAGE_SIZE)
54 })
55}
56
57#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
58#[serde(rename_all = "snake_case")]
59pub enum RequestPlanePayloadCodec {
60 #[default]
63 Json,
64 Msgpack,
65}
66
67impl RequestPlanePayloadCodec {
68 pub fn configured() -> Self {
69 *REQUEST_PLANE_PAYLOAD_CODEC.get_or_init(Self::from_env)
70 }
71
72 fn from_env() -> Self {
73 let value =
74 std::env::var(crate::config::environment_names::request_plane::DYN_REQUEST_PLANE_CODEC)
75 .ok();
76 Self::from_config_value(value.as_deref())
77 }
78
79 fn from_config_value(value: Option<&str>) -> Self {
80 match value {
81 None | Some("") | Some("msgpack") => Self::Msgpack,
82 Some("json") => Self::Json,
83 Some(other) => {
84 tracing::warn!(
85 env_var =
86 crate::config::environment_names::request_plane::DYN_REQUEST_PLANE_CODEC,
87 value = other,
88 "invalid request plane payload codec, defaulting to msgpack"
89 );
90 Self::Msgpack
91 }
92 }
93 }
94
95 pub fn name(&self) -> &'static str {
96 match self {
97 Self::Json => "json",
98 Self::Msgpack => "msgpack",
99 }
100 }
101
102 pub fn encode<T: Serialize>(&self, value: &T) -> Result<Vec<u8>> {
103 match self {
104 Self::Json => Ok(serde_json::to_vec(value)?),
105 Self::Msgpack => Ok(rmp_serde::to_vec_named(value)?),
106 }
107 }
108
109 pub fn decode<T: DeserializeOwned>(&self, bytes: &[u8]) -> Result<T> {
110 match self {
111 Self::Json => Ok(serde_json::from_slice(bytes)?),
112 Self::Msgpack => Ok(rmp_serde::from_slice(bytes)?),
113 }
114 }
115}
116
117pub trait Codable: PipelineIO + Serialize + for<'de> Deserialize<'de> {}
118impl<T: PipelineIO + Serialize + for<'de> Deserialize<'de>> Codable for T {}
119
120#[async_trait]
122pub trait WorkQueueConsumer {
123 async fn dequeue(&self) -> Result<Bytes, String>;
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
127#[serde(rename_all = "snake_case")]
128pub enum StreamType {
129 Request,
130 Response,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
134#[serde(rename_all = "snake_case")]
135pub(crate) enum RequestType {
136 SingleIn,
137 ManyIn,
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
141#[serde(rename_all = "snake_case")]
142pub(crate) enum ResponseType {
143 SingleOut,
144 ManyOut,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
148pub(crate) struct RequestControlMessage {
149 pub(crate) id: String,
150 pub(crate) request_type: RequestType,
151 pub(crate) response_type: ResponseType,
152 #[serde(default)]
153 pub(crate) payload_codec: RequestPlanePayloadCodec,
154 pub(crate) connection_info: ConnectionInfo,
155 #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
156 pub(crate) metadata: std::collections::BTreeMap<String, String>,
157 #[serde(default, skip_serializing_if = "Option::is_none")]
161 pub(crate) frontend_send_ts_ns: Option<u64>,
162 #[serde(default, skip_serializing_if = "Option::is_none")]
166 pub(crate) request_stream_connection_info: Option<ConnectionInfo>,
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
170#[serde(rename_all = "snake_case")]
171pub enum ControlMessage {
172 Stop,
173 Kill,
174 Sentinel,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
184pub struct ResponseStreamPrologue {
185 error: Option<String>,
186}
187
188pub type StreamProvider<T> = tokio::sync::oneshot::Receiver<Result<T, String>>;
189
190struct Cleanup(Option<Box<dyn FnOnce() + Send + 'static>>);
193
194impl Drop for Cleanup {
195 fn drop(&mut self) {
196 if let Some(f) = self.0.take() {
197 f();
198 }
199 }
200}
201
202pub struct RegisteredStream<T> {
206 pub connection_info: ConnectionInfo,
207 pub stream_provider: StreamProvider<T>,
208 cleanup: Cleanup,
209}
210
211impl<T> std::fmt::Debug for RegisteredStream<T> {
212 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213 f.debug_struct("RegisteredStream")
214 .field("connection_info", &self.connection_info)
215 .finish_non_exhaustive()
216 }
217}
218
219impl<T> RegisteredStream<T> {
220 pub(crate) fn new(connection_info: ConnectionInfo, stream_provider: StreamProvider<T>) -> Self {
221 Self {
222 connection_info,
223 stream_provider,
224 cleanup: Cleanup(None),
225 }
226 }
227
228 pub(crate) fn with_cleanup<F>(mut self, cleanup: F) -> Self
229 where
230 F: FnOnce() + Send + 'static,
231 {
232 self.cleanup.0 = Some(Box::new(cleanup));
233 self
234 }
235
236 pub fn into_parts(self) -> (ConnectionInfo, StreamProvider<T>) {
239 let Self {
240 connection_info,
241 stream_provider,
242 mut cleanup,
243 } = self;
244 cleanup.0.take();
245 (connection_info, stream_provider)
246 }
247}
248
249pub struct PendingConnections {
252 pub send_stream: Option<RegisteredStream<StreamSender>>,
253 pub recv_stream: Option<RegisteredStream<StreamReceiver>>,
254}
255
256impl PendingConnections {
257 pub fn into_parts(
258 self,
259 ) -> (
260 Option<RegisteredStream<StreamSender>>,
261 Option<RegisteredStream<StreamReceiver>>,
262 ) {
263 (self.send_stream, self.recv_stream)
264 }
265}
266
267#[async_trait::async_trait]
270pub trait ResponseService {
271 async fn register(&self, options: StreamOptions) -> PendingConnections;
272}
273
274#[cfg(test)]
275mod registered_stream_tests {
276 use super::*;
277 use std::sync::atomic::{AtomicBool, Ordering};
278
279 fn dummy_conn_info() -> ConnectionInfo {
280 ConnectionInfo {
281 transport: "test".to_string(),
282 info: "{}".to_string(),
283 }
284 }
285
286 #[test]
288 fn drop_runs_cleanup() {
289 let flag = Arc::new(AtomicBool::new(false));
290 let flag_clone = flag.clone();
291
292 let (_tx, rx) = tokio::sync::oneshot::channel::<Result<(), String>>();
293 let stream = RegisteredStream::new(dummy_conn_info(), rx).with_cleanup(move || {
294 flag_clone.store(true, Ordering::SeqCst);
295 });
296
297 drop(stream);
298 assert!(
299 flag.load(Ordering::SeqCst),
300 "cleanup must fire when RegisteredStream is dropped"
301 );
302 }
303
304 #[test]
308 fn into_parts_disarms_cleanup() {
309 let flag = Arc::new(AtomicBool::new(false));
310 let flag_clone = flag.clone();
311
312 let (_tx, rx) = tokio::sync::oneshot::channel::<Result<(), String>>();
313 let stream = RegisteredStream::new(dummy_conn_info(), rx).with_cleanup(move || {
314 flag_clone.store(true, Ordering::SeqCst);
315 });
316
317 let (conn, provider) = stream.into_parts();
318 drop(conn);
319 drop(provider);
320
321 assert!(
322 !flag.load(Ordering::SeqCst),
323 "into_parts() must disarm the cleanup closure"
324 );
325 }
326
327 #[test]
329 fn drop_without_cleanup_is_a_noop() {
330 let (_tx, rx) = tokio::sync::oneshot::channel::<Result<(), String>>();
331 let stream: RegisteredStream<()> = RegisteredStream::new(dummy_conn_info(), rx);
332 drop(stream); }
334}
335
336pub struct StreamSender {
361 tx: tokio::sync::mpsc::Sender<TwoPartMessage>,
362 prologue: Option<ResponseStreamPrologue>,
363}
364
365impl StreamSender {
366 pub async fn send(&self, data: Bytes) -> Result<()> {
367 Ok(self.tx.send(TwoPartMessage::from_data(data)).await?)
368 }
369
370 pub async fn send_control(&self, control: ControlMessage) -> Result<()> {
371 let bytes = serde_json::to_vec(&control)?;
372 Ok(self
373 .tx
374 .send(TwoPartMessage::from_header(bytes.into()))
375 .await?)
376 }
377
378 #[allow(clippy::needless_update)]
379 pub async fn send_prologue(&mut self, error: Option<String>) -> Result<(), String> {
380 if let Some(_prologue) = self.prologue.take() {
384 let prologue = ResponseStreamPrologue { error };
386 let header_bytes: Bytes = match serde_json::to_vec(&prologue) {
387 Ok(b) => b.into(),
388 Err(err) => {
389 tracing::error!(%err, "send_prologue: ResponseStreamPrologue did not serialize to a JSON array");
390 return Err("Invalid prologue".to_string());
391 }
392 };
393 self.tx
394 .send(TwoPartMessage::from_header(header_bytes))
395 .await
396 .map_err(|e| e.to_string())?;
397 } else {
398 panic!("Prologue already sent; or not set; logic error");
399 }
400 Ok(())
401 }
402}
403
404pub struct StreamReceiver {
405 rx: tokio::sync::mpsc::Receiver<Bytes>,
406}
407
408#[derive(Debug, Clone, Serialize, Deserialize)]
418pub struct ConnectionInfo {
419 pub transport: String,
420 pub info: String,
421}
422
423pub const DEFAULT_SEND_BUFFER_COUNT: usize = 64;
427
428#[derive(Clone, Builder)]
435pub struct StreamOptions {
436 pub context: Arc<dyn AsyncEngineContext>,
438
439 pub enable_request_stream: bool,
444
445 pub enable_response_stream: bool,
448
449 #[builder(default = "DEFAULT_SEND_BUFFER_COUNT")]
453 pub send_buffer_count: usize,
454
455 #[builder(default = "8")]
457 pub recv_buffer_count: usize,
458}
459
460impl StreamOptions {
461 pub fn builder() -> StreamOptionsBuilder {
462 StreamOptionsBuilder::default()
463 }
464}
465
466pub struct Egress<Req: PipelineIO, Resp: PipelineIO> {
467 transport_engine: Arc<dyn AsyncTransportEngine<Req, Resp>>,
468}
469
470#[cfg(test)]
471mod tests {
472 use super::{
473 DEFAULT_SEND_BUFFER_COUNT, NetworkStreamWrapper, RequestControlMessage,
474 RequestPlanePayloadCodec, RequestType, ResponseType, StreamOptions,
475 };
476 use crate::engine::AsyncEngineContextProvider;
477 use crate::pipeline::Context;
478 use serde::{Deserialize, Serialize};
479
480 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
481 struct TestPayload {
482 id: u64,
483 text: String,
484 tokens: Vec<u32>,
485 }
486
487 #[test]
488 fn stream_options_send_buffer_count_defaults_to_64() {
489 let context = Context::new(());
490 let options = StreamOptions::builder()
491 .context(context.context())
492 .enable_request_stream(true)
493 .enable_response_stream(true)
494 .build()
495 .expect("stream options should build");
496
497 assert_eq!(DEFAULT_SEND_BUFFER_COUNT, 64);
498 assert_eq!(options.send_buffer_count, DEFAULT_SEND_BUFFER_COUNT);
499 }
500
501 #[test]
502 fn stream_options_send_buffer_count_overrides_default() {
503 let context = Context::new(());
504 let options = StreamOptions::builder()
505 .context(context.context())
506 .enable_request_stream(true)
507 .enable_response_stream(true)
508 .send_buffer_count(128)
509 .build()
510 .expect("stream options should build");
511
512 assert_eq!(options.send_buffer_count, 128);
513 }
514
515 #[test]
516 fn legacy_frontend_control_message_defaults_payload_codec_to_json() {
517 let json = r#"{
518 "id": "request-123",
519 "request_type": "single_in",
520 "response_type": "many_out",
521 "connection_info": {
522 "transport": "tcp",
523 "info": "{}"
524 }
525 }"#;
526
527 let message: RequestControlMessage =
528 serde_json::from_str(json).expect("control message should deserialize");
529
530 assert_eq!(message.id, "request-123");
531 assert!(matches!(message.request_type, RequestType::SingleIn));
532 assert!(matches!(message.response_type, ResponseType::ManyOut));
533 assert_eq!(message.payload_codec, RequestPlanePayloadCodec::Json);
534 assert_eq!(message.connection_info.transport, "tcp");
535 assert_eq!(message.connection_info.info, "{}");
536 assert!(message.metadata.is_empty());
537 assert!(message.frontend_send_ts_ns.is_none());
538
539 let payload = br#"{"id":7,"text":"legacy","tokens":[1,2]}"#;
540 let decoded: TestPayload = message
541 .payload_codec
542 .decode(payload)
543 .expect("worker should decode the legacy frontend's JSON payload");
544 assert_eq!(
545 decoded,
546 TestPayload {
547 id: 7,
548 text: "legacy".to_string(),
549 tokens: vec![1, 2],
550 }
551 );
552 }
553
554 #[test]
555 fn request_control_message_decodes_msgpack_payload_codec() {
556 let json = r#"{
557 "id": "request-123",
558 "request_type": "single_in",
559 "response_type": "many_out",
560 "payload_codec": "msgpack",
561 "connection_info": {
562 "transport": "tcp",
563 "info": "{}"
564 }
565 }"#;
566
567 let message: RequestControlMessage =
568 serde_json::from_str(json).expect("control message should deserialize");
569
570 assert_eq!(message.payload_codec, RequestPlanePayloadCodec::Msgpack);
571 }
572
573 #[test]
574 fn request_plane_payload_codec_configuration_defaults_to_msgpack() {
575 assert_eq!(
576 RequestPlanePayloadCodec::from_config_value(None),
577 RequestPlanePayloadCodec::Msgpack
578 );
579 assert_eq!(
580 RequestPlanePayloadCodec::from_config_value(Some("")),
581 RequestPlanePayloadCodec::Msgpack
582 );
583 assert_eq!(
584 RequestPlanePayloadCodec::from_config_value(Some("invalid")),
585 RequestPlanePayloadCodec::Msgpack
586 );
587 }
588
589 #[test]
590 fn request_plane_payload_codec_configuration_honors_explicit_overrides() {
591 assert_eq!(
592 RequestPlanePayloadCodec::from_config_value(Some("json")),
593 RequestPlanePayloadCodec::Json
594 );
595 assert_eq!(
596 RequestPlanePayloadCodec::from_config_value(Some("msgpack")),
597 RequestPlanePayloadCodec::Msgpack
598 );
599 }
600
601 #[test]
602 fn request_plane_payload_codec_round_trips_response_wrapper_json_and_msgpack() {
603 let wrapper = NetworkStreamWrapper {
604 data: Some(TestPayload {
605 id: 42,
606 text: "line\nquote\"slash\\unicode δΈ".to_string(),
607 tokens: vec![1, 2, 3, 65535],
608 }),
609 complete_final: false,
610 };
611
612 for codec in [
613 RequestPlanePayloadCodec::Json,
614 RequestPlanePayloadCodec::Msgpack,
615 ] {
616 let encoded = codec.encode(&wrapper).expect("wrapper should encode");
617 let decoded: NetworkStreamWrapper<TestPayload> =
618 codec.decode(&encoded).expect("wrapper should decode");
619 assert_eq!(decoded, wrapper);
620 }
621 }
622}
623
624#[async_trait]
625impl<T: Data, U: Data> AsyncEngine<SingleIn<T>, ManyOut<U>, Error>
626 for Egress<SingleIn<T>, ManyOut<U>>
627where
628 T: Data + Serialize,
629 U: for<'de> Deserialize<'de> + Data,
630{
631 async fn generate(&self, request: SingleIn<T>) -> Result<ManyOut<U>, Error> {
632 self.transport_engine.generate(request).await
633 }
634}
635
636pub struct EncodedResponseFrame {
638 pub bytes: Bytes,
639 pub is_error: bool,
640 pub stop_stream: bool,
643}
644
645pub trait IngressRequestDecoder<T>: Send + Sync + 'static
647where
648 T: Data,
649{
650 fn decode_request(
651 &self,
652 payload_codec: RequestPlanePayloadCodec,
653 bytes: Bytes,
654 ) -> impl std::future::Future<Output = std::result::Result<T, PipelineError>> + Send;
655}
656
657pub trait IngressResponseEncoder<U>: Send + Sync + 'static
659where
660 U: Data,
661{
662 fn encode_response(
663 &self,
664 payload_codec: RequestPlanePayloadCodec,
665 response: Option<U>,
666 complete_final: bool,
667 ) -> impl std::future::Future<Output = std::result::Result<EncodedResponseFrame, PipelineError>> + Send;
668}
669
670pub trait IngressPayloadAdapter<T, U>:
672 IngressRequestDecoder<T> + IngressResponseEncoder<U>
673where
674 T: Data,
675 U: Data,
676{
677}
678
679impl<T, U, Adapter> IngressPayloadAdapter<T, U> for Adapter
680where
681 T: Data,
682 U: Data,
683 Adapter: IngressRequestDecoder<T> + IngressResponseEncoder<U>,
684{
685}
686
687#[derive(Debug, Default)]
689pub struct SerdeIngressPayloadAdapter;
690
691impl<T> IngressRequestDecoder<T> for SerdeIngressPayloadAdapter
692where
693 T: Data + DeserializeOwned,
694{
695 #[inline]
696 fn decode_request(
697 &self,
698 payload_codec: RequestPlanePayloadCodec,
699 bytes: Bytes,
700 ) -> impl std::future::Future<Output = std::result::Result<T, PipelineError>> + Send {
701 let decoded = payload_codec.decode(&bytes).map_err(|err| {
702 PipelineError::DeserializationError(format!(
703 "Failed deserializing {} request payload: {}",
704 payload_codec.name(),
705 err
706 ))
707 });
708 std::future::ready(decoded)
709 }
710}
711
712impl<U> IngressResponseEncoder<U> for SerdeIngressPayloadAdapter
713where
714 U: Data + Serialize + MaybeError,
715{
716 #[inline]
717 fn encode_response(
718 &self,
719 payload_codec: RequestPlanePayloadCodec,
720 response: Option<U>,
721 complete_final: bool,
722 ) -> impl std::future::Future<Output = std::result::Result<EncodedResponseFrame, PipelineError>> + Send
723 {
724 let is_error = response
725 .as_ref()
726 .is_some_and(|response| response.err().is_some());
727 let wrapper = NetworkStreamWrapper {
728 data: response,
729 complete_final,
730 };
731 let encoded = payload_codec.encode(&wrapper).map_err(|err| {
732 PipelineError::SerializationError(format!(
733 "Failed serializing {} request-plane response: {}",
734 payload_codec.name(),
735 err
736 ))
737 });
738 std::future::ready(encoded.map(|bytes| EncodedResponseFrame {
739 bytes: bytes.into(),
740 is_error,
741 stop_stream: false,
742 }))
743 }
744}
745
746pub struct Ingress<Req: PipelineIO, Resp: PipelineIO, Adapter = SerdeIngressPayloadAdapter> {
747 segment: OnceLock<Arc<SegmentSource<Req, Resp>>>,
748 metrics: OnceLock<Arc<WorkHandlerMetrics>>,
749 endpoint_health_check_notifier: OnceLock<Arc<tokio::sync::Notify>>,
751 payload_adapter: Arc<Adapter>,
752}
753
754impl<Req: PipelineIO + Sync, Resp: PipelineIO> Ingress<Req, Resp> {
755 pub fn new() -> Arc<Self> {
756 Ingress::new_with_adapter(SerdeIngressPayloadAdapter)
757 }
758
759 pub fn link(segment: Arc<SegmentSource<Req, Resp>>) -> Result<Arc<Self>> {
760 let ingress = Ingress::new();
761 ingress.attach(segment)?;
762 Ok(ingress)
763 }
764
765 pub fn for_pipeline(segment: Arc<SegmentSource<Req, Resp>>) -> Result<Arc<Self>> {
766 let ingress = Ingress::new();
767 ingress.attach(segment)?;
768 Ok(ingress)
769 }
770
771 pub fn for_engine(engine: ServiceEngine<Req, Resp>) -> Result<Arc<Self>> {
772 Self::for_engine_with_adapter(engine, SerdeIngressPayloadAdapter)
773 }
774}
775
776impl<Req, Resp, Adapter> Ingress<Req, Resp, Adapter>
777where
778 Req: PipelineIO + Sync,
779 Resp: PipelineIO,
780 Adapter: Send + Sync + 'static,
781{
782 pub fn new_with_adapter(payload_adapter: Adapter) -> Arc<Self> {
783 Arc::new(Self {
784 segment: OnceLock::new(),
785 metrics: OnceLock::new(),
786 endpoint_health_check_notifier: OnceLock::new(),
787 payload_adapter: Arc::new(payload_adapter),
788 })
789 }
790
791 pub fn attach(&self, segment: Arc<SegmentSource<Req, Resp>>) -> Result<()> {
792 self.segment
793 .set(segment)
794 .map_err(|_| anyhow::anyhow!("Segment already set"))
795 }
796
797 pub fn add_metrics(
798 &self,
799 endpoint: &crate::component::Endpoint,
800 metrics_labels: Option<&[(&str, &str)]>,
801 ) -> Result<()> {
802 let metrics = WorkHandlerMetrics::from_endpoint(endpoint, metrics_labels)
803 .map_err(|e| anyhow::anyhow!("Failed to create work handler metrics: {}", e))?;
804
805 crate::metrics::work_handler_perf::ensure_work_handler_perf_metrics_registered(
807 endpoint.get_metrics_registry(),
808 );
809
810 crate::metrics::work_handler_pool::ensure_work_handler_pool_metrics_registered(
814 endpoint.get_metrics_registry(),
815 );
816
817 self.metrics
818 .set(Arc::new(metrics))
819 .map_err(|_| anyhow::anyhow!("Metrics already set"))
820 }
821
822 pub fn for_engine_with_adapter(
823 engine: ServiceEngine<Req, Resp>,
824 payload_adapter: Adapter,
825 ) -> Result<Arc<Self>> {
826 let frontend = SegmentSource::<Req, Resp>::new();
827 let backend = ServiceBackend::from_engine(engine);
828
829 let pipeline = frontend.link(backend)?.link_terminal(frontend)?;
831
832 let ingress = Ingress::new_with_adapter(payload_adapter);
833 ingress.attach(pipeline)?;
834
835 Ok(ingress)
836 }
837
838 fn metrics(&self) -> Option<&Arc<WorkHandlerMetrics>> {
840 self.metrics.get()
841 }
842}
843
844#[async_trait]
845pub trait PushWorkHandler: Send + Sync {
846 async fn handle_payload(
847 &self,
848 payload: Bytes,
849 request_id: Option<String>,
850 ) -> Result<(), PipelineError>;
851
852 fn add_metrics(
854 &self,
855 endpoint: &crate::component::Endpoint,
856 metrics_labels: Option<&[(&str, &str)]>,
857 ) -> Result<()>;
858
859 fn set_endpoint_health_check_notifier(
861 &self,
862 _notifier: Arc<tokio::sync::Notify>,
863 ) -> Result<()> {
864 Ok(())
866 }
867}
868
869#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
914pub struct NetworkStreamWrapper<U> {
915 #[serde(skip_serializing_if = "Option::is_none")]
916 pub data: Option<U>,
917 pub complete_final: bool,
918}