dynamo_runtime/pipeline/network/codec/
zero_copy_decoder.rs1use super::{
13 check_tcp_request_max_message_size, parse_tcp_request_frame_header, tcp_request_endpoint_len,
14 tcp_request_header_size, tcp_request_headers_len,
15};
16use crate::config::environment_names::request_plane::DYN_TCP_SHRINK_MESSAGE_SIZE;
17use crate::pipeline::network::get_tcp_max_message_size;
18use bytes::{Bytes, BytesMut};
19use std::io;
20use std::sync::OnceLock;
21use tokio::io::{AsyncRead, AsyncReadExt};
22
23const INITIAL_BUFFER_SIZE: usize = 262144; const DEFAULT_SHRINK_SIZE: usize = 8 * 1024 * 1024; static SHRINK_MESSAGE_SIZE: OnceLock<usize> = OnceLock::new();
27
28fn get_shrink_message_size() -> usize {
30 *SHRINK_MESSAGE_SIZE.get_or_init(|| {
31 let max_size = get_tcp_max_message_size();
32 let env_result = std::env::var(DYN_TCP_SHRINK_MESSAGE_SIZE);
34 let env_shrink_size = env_result.as_ref().ok().and_then(|s| {
35 s.parse::<usize>().ok().or_else(|| {
36 tracing::warn!(
37 env_var = DYN_TCP_SHRINK_MESSAGE_SIZE,
38 value = %s,
39 "Invalid value for DYN_TCP_SHRINK_MESSAGE_SIZE, using default"
40 );
41 None
42 })
43 });
44
45 let resolved = resolve_shrink_message_size(max_size, env_shrink_size);
46
47 if let Some(configured) = env_shrink_size
49 && configured != resolved
50 {
51 tracing::warn!(
52 configured_size = configured,
53 resolved_size = resolved,
54 max_size = max_size,
55 initial_buffer_size = INITIAL_BUFFER_SIZE,
56 "DYN_TCP_SHRINK_MESSAGE_SIZE was clamped to valid range. Note the size is in bytes."
57 );
58 }
59
60 resolved
61 })
62}
63
64fn resolve_shrink_message_size(max_size: usize, env_shrink_size: Option<usize>) -> usize {
67 let configured_size = env_shrink_size.unwrap_or(DEFAULT_SHRINK_SIZE);
68
69 configured_size
71 .min(max_size) .max(INITIAL_BUFFER_SIZE) }
74
75pub struct ZeroCopyTcpDecoder {
82 read_buffer: BytesMut,
84 max_message_size: usize,
86 shrink_threshold: usize,
88}
89
90impl ZeroCopyTcpDecoder {
91 pub fn new() -> Self {
93 Self::with_capacity(INITIAL_BUFFER_SIZE)
94 }
95
96 pub fn with_capacity(capacity: usize) -> Self {
98 Self {
99 read_buffer: BytesMut::with_capacity(capacity),
100 max_message_size: get_tcp_max_message_size(),
101 shrink_threshold: get_shrink_message_size(),
102 }
103 }
104
105 pub async fn read_message<R: AsyncRead + Unpin>(
114 &mut self,
115 reader: &mut R,
116 ) -> io::Result<TcpRequestMessageZeroCopy> {
117 while self.read_buffer.len() < super::TCP_REQUEST_ENDPOINT_LEN_WIDTH {
119 let n = reader.read_buf(&mut self.read_buffer).await?;
120 if n == 0 {
121 if self.read_buffer.is_empty() {
122 return Err(io::Error::new(
123 io::ErrorKind::UnexpectedEof,
124 "connection closed",
125 ));
126 } else {
127 return Err(io::Error::new(
128 io::ErrorKind::UnexpectedEof,
129 "incomplete message header",
130 ));
131 }
132 }
133 }
134
135 let path_len = tcp_request_endpoint_len(&self.read_buffer)?;
137
138 let initial_header_size =
140 super::TCP_REQUEST_ENDPOINT_LEN_WIDTH + path_len + super::TCP_REQUEST_HEADERS_LEN_WIDTH;
141 while self.read_buffer.len() < initial_header_size {
142 let n = reader.read_buf(&mut self.read_buffer).await?;
143 if n == 0 {
144 return Err(io::Error::new(
145 io::ErrorKind::UnexpectedEof,
146 "incomplete message header",
147 ));
148 }
149 }
150
151 let headers_len = tcp_request_headers_len(&self.read_buffer, path_len)?;
153
154 let full_header_size = tcp_request_header_size(path_len, headers_len);
156 while self.read_buffer.len() < full_header_size {
157 let n = reader.read_buf(&mut self.read_buffer).await?;
158 if n == 0 {
159 return Err(io::Error::new(
160 io::ErrorKind::UnexpectedEof,
161 "incomplete message header",
162 ));
163 }
164 }
165
166 let parsed = parse_tcp_request_frame_header(&self.read_buffer)?;
167
168 check_tcp_request_max_message_size(parsed.total_len, self.max_message_size)?;
170
171 while self.read_buffer.len() < parsed.total_len {
173 let n = reader.read_buf(&mut self.read_buffer).await?;
174 if n == 0 {
175 return Err(io::Error::new(
176 io::ErrorKind::UnexpectedEof,
177 format!(
178 "incomplete message: expected {} bytes, got {}",
179 parsed.total_len,
180 self.read_buffer.len()
181 ),
182 ));
183 }
184 }
185
186 let message_bytes = self.read_buffer.split_to(parsed.total_len).freeze();
189
190 if self.read_buffer.is_empty() && self.read_buffer.capacity() > self.shrink_threshold {
192 self.read_buffer = BytesMut::with_capacity(INITIAL_BUFFER_SIZE);
193 }
194
195 Ok(TcpRequestMessageZeroCopy::new(message_bytes, parsed))
197 }
198
199 pub fn buffer_capacity(&self) -> usize {
201 self.read_buffer.capacity()
202 }
203
204 pub fn buffered_len(&self) -> usize {
206 self.read_buffer.len()
207 }
208}
209
210impl Default for ZeroCopyTcpDecoder {
211 fn default() -> Self {
212 Self::new()
213 }
214}
215
216#[derive(Clone)]
221pub struct TcpRequestMessageZeroCopy {
222 raw: Bytes,
225 parsed: super::TcpRequestWireHeader,
226}
227
228impl TcpRequestMessageZeroCopy {
229 fn new(raw: Bytes, parsed: super::TcpRequestWireHeader) -> Self {
231 Self { raw, parsed }
232 }
233
234 pub fn endpoint_path(&self) -> Result<&str, std::str::Utf8Error> {
238 std::str::from_utf8(&self.raw[self.parsed.endpoint_start()..self.parsed.endpoint_end()])
239 }
240
241 pub fn endpoint_path_bytes(&self) -> &[u8] {
243 &self.raw[self.parsed.endpoint_start()..self.parsed.endpoint_end()]
244 }
245
246 pub fn headers_bytes(&self) -> &[u8] {
248 &self.raw[self.parsed.headers_start()..self.parsed.headers_end()]
249 }
250
251 pub fn headers(&self) -> std::collections::HashMap<String, String> {
253 let headers_bytes = self.headers_bytes();
254 if headers_bytes.is_empty() {
255 return std::collections::HashMap::new();
256 }
257
258 serde_json::from_slice(headers_bytes).unwrap_or_default()
260 }
261
262 #[inline]
264 fn payload_len(&self) -> usize {
265 self.parsed.payload_len
266 }
267
268 pub fn payload(&self) -> Bytes {
273 self.raw.slice(self.parsed.payload_start()..) }
275
276 pub fn total_size(&self) -> usize {
278 self.raw.len()
279 }
280
281 pub fn raw_bytes(&self) -> &Bytes {
283 &self.raw
284 }
285}
286
287impl std::fmt::Debug for TcpRequestMessageZeroCopy {
288 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
289 f.debug_struct("TcpRequestMessageZeroCopy")
290 .field("total_size", &self.total_size())
291 .field("endpoint_path", &self.endpoint_path().ok())
292 .field("payload_len", &self.payload_len())
293 .finish()
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300 use tokio::io::AsyncWriteExt;
301
302 #[test]
303 fn test_resolve_shrink_message_size_edge_cases() {
304 let max_size_10mb = 10 * 1024 * 1024;
307 let result = resolve_shrink_message_size(max_size_10mb, None);
308 assert_eq!(
309 result, DEFAULT_SHRINK_SIZE,
310 "10MB max should return default 8MB"
311 );
312
313 let max_size_1mb = 1024 * 1024;
316 let result = resolve_shrink_message_size(max_size_1mb, None);
317 assert_eq!(result, max_size_1mb, "1MB max should be capped to 1MB");
318
319 let result = resolve_shrink_message_size(DEFAULT_SHRINK_SIZE, None);
322 assert_eq!(
323 result, DEFAULT_SHRINK_SIZE,
324 "exact match should return default"
325 );
326
327 let env_size = 2 * 1024 * 1024; let result = resolve_shrink_message_size(max_size_10mb, Some(env_size));
330 assert_eq!(
331 result, env_size,
332 "env var should be used when within bounds"
333 );
334
335 let env_size_large = 20 * 1024 * 1024; let result = resolve_shrink_message_size(max_size_10mb, Some(env_size_large));
338 assert_eq!(
339 result, max_size_10mb,
340 "env var should be capped to max_size"
341 );
342
343 let env_size_small = 100 * 1024; let result = resolve_shrink_message_size(max_size_10mb, Some(env_size_small));
346 assert_eq!(
347 result, INITIAL_BUFFER_SIZE,
348 "env var should be clamped to INITIAL_BUFFER_SIZE"
349 );
350
351 let max_size_small = 100 * 1024; let result = resolve_shrink_message_size(max_size_small, None);
354 assert_eq!(
355 result, INITIAL_BUFFER_SIZE,
356 "result should be clamped to INITIAL_BUFFER_SIZE"
357 );
358 }
359
360 #[tokio::test]
361 async fn test_zero_copy_decoder_basic() {
362 let endpoint = "test/endpoint";
364 let payload = b"Hello, World!";
365 let headers: Vec<u8> = vec![]; let mut message = Vec::new();
368 message.extend_from_slice(&(endpoint.len() as u16).to_be_bytes());
370 message.extend_from_slice(endpoint.as_bytes());
371 message.extend_from_slice(&(headers.len() as u16).to_be_bytes());
373 message.extend_from_slice(&headers);
374 message.extend_from_slice(&(payload.len() as u32).to_be_bytes());
376 message.extend_from_slice(payload);
377
378 let mut reader = &message[..];
380
381 let mut decoder = ZeroCopyTcpDecoder::new();
383 let msg = decoder.read_message(&mut reader).await.unwrap();
384
385 assert_eq!(msg.endpoint_path().unwrap(), endpoint);
387 assert_eq!(msg.payload().as_ref(), payload);
388 assert_eq!(msg.total_size(), message.len());
389 assert_eq!(msg.headers().len(), 0); }
391
392 #[tokio::test]
393 async fn test_zero_copy_decoder_allows_empty_and_long_endpoint_paths() {
394 for endpoint in [String::new(), "x".repeat(2048)] {
395 let payload = b"payload";
396
397 let mut message = Vec::new();
398 message.extend_from_slice(&(endpoint.len() as u16).to_be_bytes());
399 message.extend_from_slice(endpoint.as_bytes());
400 message.extend_from_slice(&(0u16).to_be_bytes());
401 message.extend_from_slice(&(payload.len() as u32).to_be_bytes());
402 message.extend_from_slice(payload);
403
404 let mut reader = &message[..];
405 let mut decoder = ZeroCopyTcpDecoder::new();
406 let msg = decoder.read_message(&mut reader).await.unwrap();
407
408 assert_eq!(msg.endpoint_path().unwrap(), endpoint.as_str());
409 assert_eq!(msg.payload().as_ref(), payload);
410 }
411 }
412
413 #[tokio::test]
414 async fn test_zero_copy_decoder_large_payload() {
415 let endpoint = "large/endpoint";
417 let payload = vec![0x42u8; 200 * 1024];
418 let headers: Vec<u8> = vec![]; let mut message = Vec::new();
421 message.extend_from_slice(&(endpoint.len() as u16).to_be_bytes());
423 message.extend_from_slice(endpoint.as_bytes());
424 message.extend_from_slice(&(headers.len() as u16).to_be_bytes());
426 message.extend_from_slice(&headers);
427 message.extend_from_slice(&(payload.len() as u32).to_be_bytes());
429 message.extend_from_slice(&payload);
430
431 let mut reader = &message[..];
432 let mut decoder = ZeroCopyTcpDecoder::new();
433 let msg = decoder.read_message(&mut reader).await.unwrap();
434
435 assert_eq!(msg.endpoint_path().unwrap(), endpoint);
436 assert_eq!(msg.payload().len(), payload.len());
437 }
438
439 #[tokio::test]
440 async fn test_zero_copy_decoder_total_size_limit() {
441 let max_size = 1024; let mut decoder = ZeroCopyTcpDecoder::with_capacity(256);
445 decoder.max_message_size = max_size;
446
447 let endpoint = "test/endpoint";
449 let payload = vec![0x42u8; max_size]; let headers: Vec<u8> = vec![]; let mut message = Vec::new();
453 message.extend_from_slice(&(endpoint.len() as u16).to_be_bytes());
455 message.extend_from_slice(endpoint.as_bytes());
456 message.extend_from_slice(&(headers.len() as u16).to_be_bytes());
458 message.extend_from_slice(&headers);
459 message.extend_from_slice(&(payload.len() as u32).to_be_bytes());
461 message.extend_from_slice(&payload);
462
463 let mut reader = &message[..];
465 let result = decoder.read_message(&mut reader).await;
466
467 assert!(result.is_err());
469 let err = result.unwrap_err();
470 assert_eq!(err.kind(), io::ErrorKind::InvalidData);
471 assert!(err.to_string().contains("message too large"));
472 assert!(err.to_string().contains("1045")); assert!(err.to_string().contains("1024")); }
475
476 #[tokio::test]
477 async fn test_zero_copy_decoder_with_headers() {
478 let endpoint = "api/v1/inference";
480 let payload = b"Request payload data";
481
482 let mut headers_map = std::collections::HashMap::new();
484 headers_map.insert("traceparent".to_string(), "00-abc123-def456-01".to_string());
485 headers_map.insert("user-agent".to_string(), "test-client/1.0".to_string());
486 headers_map.insert("request-id".to_string(), "req-12345".to_string());
487
488 let headers_json = serde_json::to_vec(&headers_map).unwrap();
489
490 let mut message = Vec::new();
491 message.extend_from_slice(&(endpoint.len() as u16).to_be_bytes());
493 message.extend_from_slice(endpoint.as_bytes());
494 message.extend_from_slice(&(headers_json.len() as u16).to_be_bytes());
496 message.extend_from_slice(&headers_json);
497 message.extend_from_slice(&(payload.len() as u32).to_be_bytes());
499 message.extend_from_slice(payload);
500
501 let mut reader = &message[..];
503 let mut decoder = ZeroCopyTcpDecoder::new();
504 let msg = decoder.read_message(&mut reader).await.unwrap();
505
506 assert_eq!(msg.endpoint_path().unwrap(), endpoint);
508
509 assert_eq!(msg.payload().as_ref(), payload);
511
512 assert_eq!(msg.total_size(), message.len());
514
515 let decoded_headers = msg.headers();
517 assert_eq!(decoded_headers.len(), 3);
518 assert_eq!(
519 decoded_headers.get("traceparent").unwrap(),
520 "00-abc123-def456-01"
521 );
522 assert_eq!(
523 decoded_headers.get("user-agent").unwrap(),
524 "test-client/1.0"
525 );
526 assert_eq!(decoded_headers.get("request-id").unwrap(), "req-12345");
527
528 let headers_bytes = msg.headers_bytes();
530 assert_eq!(headers_bytes, &headers_json[..]);
531 }
532
533 #[tokio::test]
534 async fn test_zero_copy_decoder_empty_vs_populated_headers() {
535 let endpoint = "test/endpoint";
537 let payload = b"test data";
538
539 let mut message_empty = Vec::new();
541 message_empty.extend_from_slice(&(endpoint.len() as u16).to_be_bytes());
542 message_empty.extend_from_slice(endpoint.as_bytes());
543 message_empty.extend_from_slice(&(0u16).to_be_bytes()); message_empty.extend_from_slice(&(payload.len() as u32).to_be_bytes());
546 message_empty.extend_from_slice(payload);
547
548 let mut reader = &message_empty[..];
549 let mut decoder = ZeroCopyTcpDecoder::new();
550 let msg = decoder.read_message(&mut reader).await.unwrap();
551
552 assert_eq!(msg.endpoint_path().unwrap(), endpoint);
553 assert_eq!(msg.payload().as_ref(), payload);
554 assert_eq!(msg.headers().len(), 0);
555 assert_eq!(msg.headers_bytes().len(), 0);
556
557 let mut headers_map = std::collections::HashMap::new();
559 headers_map.insert("x-test-header".to_string(), "test-value".to_string());
560 let headers_json = serde_json::to_vec(&headers_map).unwrap();
561
562 let mut message_with_headers = Vec::new();
563 message_with_headers.extend_from_slice(&(endpoint.len() as u16).to_be_bytes());
564 message_with_headers.extend_from_slice(endpoint.as_bytes());
565 message_with_headers.extend_from_slice(&(headers_json.len() as u16).to_be_bytes());
566 message_with_headers.extend_from_slice(&headers_json);
567 message_with_headers.extend_from_slice(&(payload.len() as u32).to_be_bytes());
568 message_with_headers.extend_from_slice(payload);
569
570 let mut reader = &message_with_headers[..];
571 let msg = decoder.read_message(&mut reader).await.unwrap();
572
573 assert_eq!(msg.endpoint_path().unwrap(), endpoint);
574 assert_eq!(msg.payload().as_ref(), payload);
575 assert_eq!(msg.headers().len(), 1);
576 assert_eq!(msg.headers().get("x-test-header").unwrap(), "test-value");
577 }
578
579 #[tokio::test]
580 async fn test_zero_copy_decoder_buffer_shrinking() {
581 let endpoint = "test/endpoint";
584 let small_payload = b"small";
585 let large_payload = vec![0x42u8; 1024 * 1024]; fn make_message(endpoint: &str, payload: &[u8]) -> Vec<u8> {
589 let mut message = Vec::new();
590 message.extend_from_slice(&(endpoint.len() as u16).to_be_bytes());
591 message.extend_from_slice(endpoint.as_bytes());
592 message.extend_from_slice(&(0u16).to_be_bytes()); message.extend_from_slice(&(payload.len() as u32).to_be_bytes());
594 message.extend_from_slice(payload);
595 message
596 }
597
598 let mut decoder = ZeroCopyTcpDecoder::with_capacity(INITIAL_BUFFER_SIZE);
600 decoder.max_message_size = 2 * 1024 * 1024; decoder.shrink_threshold = 512 * 1024; assert!(decoder.buffer_capacity() <= INITIAL_BUFFER_SIZE);
604
605 let large_message = make_message(endpoint, &large_payload);
607 let mut reader = &large_message[..];
608 decoder.read_message(&mut reader).await.unwrap();
609
610 assert!(
614 decoder.buffer_capacity() <= INITIAL_BUFFER_SIZE,
615 "buffer should shrink after large message, got capacity {}",
616 decoder.buffer_capacity()
617 );
618 assert!(
619 decoder.buffered_len() == 0,
620 "buffer should be empty after read"
621 );
622
623 let small_message = make_message(endpoint, small_payload);
625 let mut reader = &small_message[..];
626 let msg = decoder.read_message(&mut reader).await.unwrap();
627 assert_eq!(msg.payload().as_ref(), small_payload);
628 }
629}