1use std::{cell::RefCell, net::SocketAddr, rc::Rc};
11
12use mio::{Token, net::TcpStream};
13use rusty_ulid::Ulid;
14use sozu_command::{
15 config::MAX_LOOP_ITERATIONS,
16 logging::{EndpointRecord, LogContext, ansi_palette},
17 state::ClusterId,
18};
19
20use crate::metrics::names;
21use crate::{
22 L7Proxy, ListenerHandler, Protocol, Readiness, SessionMetrics, SessionResult, StateResult,
23 backends::Backend,
24 pool::Checkout,
25 protocol::{SessionState, http::parser::Method},
26 socket::{SocketHandler, SocketResult, TransportProtocol, stats::socket_rtt},
27 sozu_command::ready::Ready,
28 timer::TimeoutContainer,
29};
30
31#[cfg(all(target_os = "linux", feature = "splice"))]
32use crate::splice::{self, SplicePipe};
33
34macro_rules! log_context {
41 ($self:expr) => {{
42 let (open, reset, grey, gray, white) = ansi_palette();
43 format!(
44 "{gray}{ctx}{reset}\t{open}PIPE{reset}\t{grey}Session{reset}({gray}address{reset}={white}{address}{reset}, {gray}frontend{reset}={white}{frontend}{reset}, {gray}frontend_readiness{reset}={white}{frontend_readiness}{reset}, {gray}frontend_status{reset}={white}{frontend_status:?}{reset}, {gray}backend{reset}={white}{backend}{reset}, {gray}backend_status{reset}={white}{backend_status:?}{reset}, {gray}backend_readiness{reset}={white}{backend_readiness}{reset})\t >>>",
45 open = open,
46 reset = reset,
47 grey = grey,
48 gray = gray,
49 white = white,
50 ctx = $self.log_context(),
51 address = $self.session_address.map(|addr| addr.to_string()).unwrap_or_else(|| "<none>".to_string()),
52 frontend = $self.frontend_token.0,
53 frontend_readiness = $self.frontend_readiness,
54 frontend_status = $self.frontend_status,
55 backend = $self.backend_token.map(|token| token.0.to_string()).unwrap_or_else(|| "<none>".to_string()),
56 backend_status = $self.backend_status,
57 backend_readiness = $self.backend_readiness,
58 )
59 }};
60}
61
62#[derive(PartialEq, Eq)]
63pub enum SessionStatus {
64 Normal,
65 DefaultAnswer,
66}
67
68#[derive(Copy, Clone, Debug)]
69enum ConnectionStatus {
70 Normal,
71 ReadOpen,
72 WriteOpen,
73 Closed,
74}
75
76pub enum WebSocketContext {
78 Http {
79 method: Option<Method>,
80 authority: Option<String>,
81 path: Option<String>,
82 status: Option<u16>,
83 reason: Option<String>,
84 },
85 Tcp,
86}
87
88pub struct Pipe<Front: SocketHandler, L: ListenerHandler> {
89 backend_buffer: Checkout,
90 backend_id: Option<String>,
91 cluster_metrics_incarnation: crate::metrics::ClusterMetricsCapture,
94 backend_address: Option<SocketAddr>,
100 pub backend_readiness: Readiness,
101 backend_socket: Option<TcpStream>,
102 backend_status: ConnectionStatus,
103 backend_token: Option<Token>,
104 pub backend: Option<Rc<RefCell<Backend>>>,
105 cluster_id: Option<ClusterId>,
106 pub container_backend_timeout: Option<TimeoutContainer>,
107 pub container_frontend_timeout: Option<TimeoutContainer>,
108 frontend_buffer: Checkout,
109 pub frontend_readiness: Readiness,
110 frontend_status: ConnectionStatus,
111 frontend_token: Token,
112 frontend: Front,
113 listener: Rc<RefCell<L>>,
114 protocol: Protocol,
115 session_id: Ulid,
118 request_id: Ulid,
119 session_address: Option<SocketAddr>,
120 websocket_context: WebSocketContext,
121 tls_version: Option<&'static str>,
126 tls_cipher: Option<&'static str>,
127 tls_sni: Option<String>,
130 tls_alpn: Option<&'static str>,
131 tags_key: Option<String>,
137 #[cfg(all(target_os = "linux", feature = "splice"))]
142 splice_pipe: Option<SplicePipe>,
143}
144
145impl<Front: SocketHandler, L: ListenerHandler> Pipe<Front, L> {
146 #[allow(clippy::too_many_arguments)]
155 pub fn new(
156 backend_buffer: Checkout,
157 backend_id: Option<String>,
158 backend_socket: Option<TcpStream>,
159 backend: Option<Rc<RefCell<Backend>>>,
160 container_backend_timeout: Option<TimeoutContainer>,
161 container_frontend_timeout: Option<TimeoutContainer>,
162 cluster_id: Option<ClusterId>,
163 frontend_buffer: Checkout,
164 frontend_token: Token,
165 frontend: Front,
166 listener: Rc<RefCell<L>>,
167 protocol: Protocol,
168 session_id: Ulid,
169 request_id: Ulid,
170 session_address: Option<SocketAddr>,
171 websocket_context: WebSocketContext,
172 ) -> Pipe<Front, L> {
173 let frontend_status = ConnectionStatus::Normal;
174 let backend_status = if backend_socket.is_none() {
175 ConnectionStatus::Closed
176 } else {
177 ConnectionStatus::Normal
178 };
179
180 let backend_address = backend.as_ref().map(|backend| backend.borrow().address);
181 let mut session = Pipe {
182 backend_buffer,
183 backend_id,
184 cluster_metrics_incarnation: Default::default(),
185 backend_address,
186 backend_readiness: Readiness {
187 interest: Ready::READABLE | Ready::WRITABLE | Ready::HUP | Ready::ERROR,
188 event: Ready::EMPTY,
189 },
190 backend_socket,
191 backend_status,
192 backend_token: None,
193 backend,
194 cluster_id,
195 container_backend_timeout,
196 container_frontend_timeout,
197 frontend_buffer,
198 frontend_readiness: Readiness {
199 interest: Ready::READABLE | Ready::WRITABLE | Ready::HUP | Ready::ERROR,
200 event: Ready::EMPTY,
201 },
202 frontend_status,
203 frontend_token,
204 frontend,
205 listener,
206 protocol,
207 session_id,
208 request_id,
209 session_address,
210 websocket_context,
211 tls_version: None,
212 tls_cipher: None,
213 tls_sni: None,
214 tls_alpn: None,
215 tags_key: None,
216 #[cfg(all(target_os = "linux", feature = "splice"))]
217 splice_pipe: if protocol == Protocol::TCP {
218 SplicePipe::new()
219 } else {
220 None
221 },
222 };
223
224 session.arm_inherited_buffer_writes();
225
226 trace!("{} created pipe", log_context!(session));
227 session
228 }
229
230 fn arm_inherited_buffer_writes(&mut self) {
231 if self.backend_buffer.available_data() > 0 {
232 self.frontend_readiness.arm_writable();
233 }
234 if self.frontend_buffer.available_data() > 0 && self.backend_socket.is_some() {
235 self.backend_readiness.arm_writable();
236 }
237 }
238
239 pub fn restore_readiness_events(&mut self, frontend_event: Ready, backend_event: Ready) {
240 self.frontend_readiness.event = frontend_event;
241 self.backend_readiness.event = backend_event;
242 self.arm_inherited_buffer_writes();
243 }
244
245 pub(crate) fn set_cluster_metrics_incarnation(
246 &mut self,
247 incarnation: crate::metrics::ClusterMetricsCapture,
248 ) {
249 self.cluster_metrics_incarnation = incarnation;
250 }
251
252 pub fn set_tls_metadata(
269 &mut self,
270 version: Option<&'static str>,
271 cipher: Option<&'static str>,
272 sni: Option<String>,
273 alpn: Option<&'static str>,
274 ) {
275 self.tls_version = version;
276 self.tls_cipher = cipher;
277 self.tls_sni = sni;
278 self.tls_alpn = alpn;
279 }
280
281 pub fn front_socket(&self) -> &TcpStream {
282 self.frontend.socket_ref()
283 }
284
285 pub fn front_socket_handler_mut(&mut self) -> &mut Front {
288 &mut self.frontend
289 }
290
291 pub fn front_socket_mut(&mut self) -> &mut TcpStream {
292 self.frontend.socket_mut()
293 }
294
295 pub fn back_socket(&self) -> Option<&TcpStream> {
296 self.backend_socket.as_ref()
297 }
298
299 pub fn back_socket_mut(&mut self) -> Option<&mut TcpStream> {
300 self.backend_socket.as_mut()
301 }
302
303 pub fn set_back_socket(&mut self, socket: TcpStream) {
304 self.backend_socket = Some(socket);
305 self.backend_status = ConnectionStatus::Normal;
306 }
307
308 pub fn back_token(&self) -> Vec<Token> {
309 self.backend_token.iter().cloned().collect()
310 }
311
312 fn reset_timeouts(&mut self) {
313 if let Some(t) = self.container_frontend_timeout.as_mut()
314 && !t.reset()
315 {
316 error!(
317 "{} Could not reset front timeout (pipe)",
318 log_context!(self)
319 );
320 }
321
322 if let Some(t) = self.container_backend_timeout.as_mut()
323 && !t.reset()
324 {
325 error!("{} Could not reset back timeout (pipe)", log_context!(self));
326 }
327 }
328
329 pub fn set_cluster_id(&mut self, cluster_id: Option<ClusterId>) {
330 self.cluster_id = cluster_id;
331 }
332
333 pub fn set_tags_key(&mut self, tags_key: Option<String>) {
338 self.tags_key = tags_key;
339 }
340
341 pub fn set_backend_id(&mut self, backend_id: Option<String>) {
342 self.backend_id = backend_id;
343 }
344
345 pub fn set_back_token(&mut self, token: Token) {
346 self.backend_token = Some(token);
347 }
348
349 pub fn set_backend_address(&mut self, backend_address: Option<SocketAddr>) {
353 self.backend_address = backend_address;
354 }
355
356 pub fn get_session_address(&self) -> Option<SocketAddr> {
357 self.session_address
358 .or_else(|| self.frontend.socket_ref().peer_addr().ok())
359 }
360
361 pub fn get_backend_address(&self) -> Option<SocketAddr> {
366 self.backend_address
367 }
368
369 fn protocol_string(&self) -> &'static str {
370 match self.protocol {
371 Protocol::TCP => "TCP",
372 Protocol::HTTP => "WS",
373 Protocol::HTTPS => match self.frontend.protocol() {
374 TransportProtocol::Ssl2 => "WSS-SSL2",
375 TransportProtocol::Ssl3 => "WSS-SSL3",
376 TransportProtocol::Tls1_0 => "WSS-TLS1.0",
377 TransportProtocol::Tls1_1 => "WSS-TLS1.1",
378 TransportProtocol::Tls1_2 => "WSS-TLS1.2",
379 TransportProtocol::Tls1_3 => "WSS-TLS1.3",
380 _ => unreachable!(),
381 },
382 _ => unreachable!(),
383 }
384 }
385
386 pub fn log_request(&self, metrics: &SessionMetrics, error: bool, message: Option<&str>) {
387 let listener = self.listener.borrow();
388 let context = self.log_context();
389 let endpoint = self.log_endpoint();
390 metrics.register_end_of_session_for_incarnation(&context, self.cluster_metrics_incarnation);
391 let address_key = listener.get_addr().to_string();
395 let tags_key = self.tags_key.as_deref().unwrap_or(&address_key);
396 log_access!(
397 error,
398 on_failure: { incr!(names::access_logs::UNSENT) },
399 message,
400 context,
401 session_address: self.get_session_address(),
402 backend_address: self.get_backend_address(),
403 protocol: self.protocol_string(),
404 endpoint,
405 tags: listener.get_tags(tags_key),
406 client_rtt: socket_rtt(self.front_socket()),
407 server_rtt: self.backend_socket.as_ref().and_then(socket_rtt),
408 service_time: metrics.service_time(),
409 response_time: metrics.backend_response_time(),
410 request_time: metrics.request_time(),
411 start_time_ns: metrics.start_wall_ns(),
412 bytes_in: metrics.bin,
413 bytes_out: metrics.bout,
414 user_agent: None,
415 x_request_id: None,
416 tls_version: self.tls_version,
421 tls_cipher: self.tls_cipher,
422 tls_sni: self.tls_sni.as_deref(),
423 tls_alpn: self.tls_alpn,
424 xff_chain: None,
425 otel: None,
426 );
427 }
428
429 pub fn log_request_success(&self, metrics: &SessionMetrics) {
430 self.log_request(metrics, false, None);
431 }
432
433 pub fn log_request_error(&self, metrics: &SessionMetrics, message: &str) {
434 incr!(names::pipe::ERRORS);
435 error!(
436 "{} Could not process request properly got: {}",
437 log_context!(self),
438 message
439 );
440 self.print_state(self.protocol_string());
441 self.log_request(metrics, true, Some(message));
442 }
443
444 pub fn log_request_timeout(&self, metrics: &SessionMetrics, message: &str) {
451 debug!("{} pipe timeout: {}", log_context!(self), message);
452 self.log_request(metrics, true, Some(message));
453 }
454
455 #[cfg(all(target_os = "linux", feature = "splice"))]
460 fn splice_in_pending(&self) -> usize {
461 self.splice_pipe
462 .as_ref()
463 .map(|p| p.in_pipe_pending)
464 .unwrap_or(0)
465 }
466 #[cfg(not(all(target_os = "linux", feature = "splice")))]
467 fn splice_in_pending(&self) -> usize {
468 0
469 }
470
471 #[cfg(all(target_os = "linux", feature = "splice"))]
475 fn splice_out_pending(&self) -> usize {
476 self.splice_pipe
477 .as_ref()
478 .map(|p| p.out_pipe_pending)
479 .unwrap_or(0)
480 }
481 #[cfg(not(all(target_os = "linux", feature = "splice")))]
482 fn splice_out_pending(&self) -> usize {
483 0
484 }
485
486 #[cfg(all(target_os = "linux", feature = "splice"))]
490 fn splice_capacity(&self) -> usize {
491 self.splice_pipe.as_ref().map(|p| p.capacity).unwrap_or(0)
492 }
493
494 fn reset_readiness_for_close(&mut self) {
504 self.frontend_readiness.reset();
505 self.backend_readiness.reset();
506 debug_assert!(
507 self.frontend_readiness.interest.is_empty() && self.frontend_readiness.event.is_empty(),
508 "frontend readiness must be fully cleared on close (write-only-shutdown discipline)"
509 );
510 debug_assert!(
511 self.backend_readiness.interest.is_empty() && self.backend_readiness.event.is_empty(),
512 "backend readiness must be fully cleared on close (write-only-shutdown discipline)"
513 );
514 }
515
516 pub fn check_connections(&self) -> bool {
519 debug_assert!(
527 self.frontend_buffer.available_data() <= self.frontend_buffer.capacity(),
528 "frontend buffered data exceeds its capacity"
529 );
530 debug_assert!(
531 self.backend_buffer.available_data() <= self.backend_buffer.capacity(),
532 "backend buffered data exceeds its capacity"
533 );
534
535 let request_is_inflight = self.frontend_buffer.available_data() > 0
536 || self.frontend_readiness.event.is_readable()
537 || self.splice_in_pending() > 0;
538 let response_is_inflight = self.backend_buffer.available_data() > 0
539 || self.backend_readiness.event.is_readable()
540 || self.splice_out_pending() > 0;
541 match (self.frontend_status, self.backend_status) {
542 (ConnectionStatus::Normal, ConnectionStatus::Normal) => true,
543 (ConnectionStatus::Normal, ConnectionStatus::ReadOpen) => true,
544 (ConnectionStatus::Normal, ConnectionStatus::WriteOpen) => {
545 request_is_inflight || response_is_inflight
550 }
551 (ConnectionStatus::Normal, ConnectionStatus::Closed) => response_is_inflight,
552
553 (ConnectionStatus::WriteOpen, ConnectionStatus::Normal) => {
554 request_is_inflight || response_is_inflight
557 }
558 (ConnectionStatus::WriteOpen, ConnectionStatus::ReadOpen) => true,
559 (ConnectionStatus::WriteOpen, ConnectionStatus::WriteOpen) => {
560 request_is_inflight || response_is_inflight
561 }
562 (ConnectionStatus::WriteOpen, ConnectionStatus::Closed) => response_is_inflight,
563
564 (ConnectionStatus::ReadOpen, ConnectionStatus::Normal) => true,
565 (ConnectionStatus::ReadOpen, ConnectionStatus::ReadOpen) => false,
566 (ConnectionStatus::ReadOpen, ConnectionStatus::WriteOpen) => true,
567 (ConnectionStatus::ReadOpen, ConnectionStatus::Closed) => false,
568
569 (ConnectionStatus::Closed, ConnectionStatus::Normal) => request_is_inflight,
570 (ConnectionStatus::Closed, ConnectionStatus::ReadOpen) => false,
571 (ConnectionStatus::Closed, ConnectionStatus::WriteOpen) => request_is_inflight,
572 (ConnectionStatus::Closed, ConnectionStatus::Closed) => false,
573 }
574 }
575
576 pub fn frontend_hup(&mut self, metrics: &mut SessionMetrics) -> SessionResult {
577 self.frontend_status = ConnectionStatus::Closed;
578 debug_assert!(
581 matches!(self.frontend_status, ConnectionStatus::Closed),
582 "frontend_hup must mark the frontend Closed"
583 );
584 let request_is_inflight = self.frontend_buffer.available_data() > 0
594 || self.frontend_readiness.event.is_readable()
595 || self.splice_in_pending() > 0;
596 if request_is_inflight && self.backend_socket.is_some() {
597 debug_assert!(
600 self.frontend_buffer.available_data() > 0
601 || self.frontend_readiness.event.is_readable()
602 || self.splice_in_pending() > 0,
603 "drain branch entered without any observable in-flight request bytes"
604 );
605 if self.frontend_readiness.event.is_readable() {
606 self.frontend_readiness.interest.insert(Ready::READABLE);
613 }
614 self.backend_readiness.arm_writable();
615 debug!(
616 "{} Pipe::frontend_hup: frontend connection closed, keeping alive due to inflight request data.",
617 log_context!(self)
618 );
619 SessionResult::Continue
620 } else {
621 debug_assert!(
624 self.backend_socket.is_none() || self.frontend_buffer.available_data() == 0,
625 "close branch entered with backend present but request bytes still queued"
626 );
627 self.log_request_success(metrics);
628 SessionResult::Close
629 }
630 }
631
632 pub fn backend_hup(&mut self, metrics: &mut SessionMetrics) -> SessionResult {
633 self.backend_status = ConnectionStatus::Closed;
634 debug_assert!(
637 matches!(self.backend_status, ConnectionStatus::Closed),
638 "backend_hup must mark the backend Closed"
639 );
640 let pipe_has_data = self.splice_out_pending() > 0;
641 if self.backend_buffer.available_data() == 0 && !pipe_has_data {
642 debug_assert_eq!(
645 self.backend_buffer.available_data(),
646 0,
647 "no-data branch entered with response bytes still buffered"
648 );
649 if self.backend_readiness.event.is_readable() {
650 self.backend_readiness.interest.insert(Ready::READABLE);
651 debug!(
652 "{} Pipe::backend_hup: backend connection closed, keeping alive due to inflight data in kernel.",
653 log_context!(self)
654 );
655 SessionResult::Continue
656 } else {
657 self.log_request_success(metrics);
658 SessionResult::Close
659 }
660 } else {
661 debug!(
662 "{} Pipe::backend_hup: backend connection closed, keeping alive due to inflight data in buffers.",
663 log_context!(self)
664 );
665 self.frontend_readiness.arm_writable();
666 if self.backend_readiness.event.is_readable() {
667 self.backend_readiness.interest.insert(Ready::READABLE);
668 }
669 SessionResult::Continue
670 }
671 }
672
673 fn close_frontend_read_side(
683 &mut self,
684 metrics: &mut SessionMetrics,
685 has_pending: bool,
686 ) -> SessionResult {
687 self.frontend_status = match self.frontend_status {
688 ConnectionStatus::Normal => ConnectionStatus::WriteOpen,
689 ConnectionStatus::ReadOpen => ConnectionStatus::Closed,
690 s => s,
691 };
692 self.frontend_readiness.event.remove(Ready::READABLE);
693 self.frontend_readiness.interest.remove(Ready::READABLE);
694 if has_pending && self.backend_socket.is_some() {
695 self.backend_readiness.arm_writable();
696 }
697 if !self.check_connections() {
698 self.reset_readiness_for_close();
699 self.log_request_success(metrics);
700 return SessionResult::Close;
701 }
702 SessionResult::Continue
703 }
704
705 pub fn readable(&mut self, metrics: &mut SessionMetrics) -> SessionResult {
707 #[cfg(all(target_os = "linux", feature = "splice"))]
711 if self.protocol == Protocol::TCP
712 && self.splice_pipe.is_some()
713 && self.frontend_buffer.available_data() == 0
714 {
715 return self.splice_readable(metrics);
716 }
717
718 self.reset_timeouts();
719
720 trace!("{} pipe readable", log_context!(self));
721 if self.frontend_buffer.available_space() == 0 {
722 self.frontend_readiness.interest.remove(Ready::READABLE);
723 self.backend_readiness.arm_writable();
724 return SessionResult::Continue;
725 }
726
727 let space_before = self.frontend_buffer.available_space();
728 let data_before = self.frontend_buffer.available_data();
729 let bin_before = metrics.bin;
730 let (sz, res) = self.frontend.socket_read(self.frontend_buffer.space());
731 debug_assert!(
734 sz <= space_before,
735 "frontend socket_read reported more bytes ({sz}) than the buffer space offered ({space_before})"
736 );
737 debug!("{} Read {} bytes", log_context!(self), sz);
738
739 if sz > 0 {
740 self.frontend_buffer.fill(sz);
742 debug_assert_eq!(
745 self.frontend_buffer.available_data(),
746 data_before + sz,
747 "fill must grow readable data by exactly the bytes read"
748 );
749
750 count!(names::backend::BYTES_IN, sz as i64);
751 metrics.bin += sz;
752 debug_assert_eq!(
754 metrics.bin,
755 bin_before + sz,
756 "metrics.bin must advance by exactly the bytes read"
757 );
758
759 if self.frontend_buffer.available_space() == 0 {
760 self.frontend_readiness.interest.remove(Ready::READABLE);
761 }
762 self.backend_readiness.arm_writable();
763 } else {
764 self.frontend_readiness.event.remove(Ready::READABLE);
765
766 if res == SocketResult::Continue {
767 self.frontend_status = match self.frontend_status {
768 ConnectionStatus::Normal => ConnectionStatus::WriteOpen,
769 ConnectionStatus::ReadOpen => ConnectionStatus::Closed,
770 s => s,
771 };
772 }
773 }
774
775 if !self.check_connections() {
776 self.reset_readiness_for_close();
777 self.log_request_success(metrics);
778 return SessionResult::Close;
779 }
780
781 match res {
782 SocketResult::Error => {
783 self.reset_readiness_for_close();
784 self.log_request_error(metrics, "front socket read error");
785 return SessionResult::Close;
786 }
787 SocketResult::Closed => {
788 let has_pending = self.frontend_buffer.available_data() > 0;
806 return self.close_frontend_read_side(metrics, has_pending);
807 }
808 SocketResult::WouldBlock => {
809 self.frontend_readiness.event.remove(Ready::READABLE);
810 }
811 SocketResult::Continue => {}
812 };
813
814 self.backend_readiness.arm_writable();
815 SessionResult::Continue
816 }
817
818 pub fn writable(&mut self, metrics: &mut SessionMetrics) -> SessionResult {
820 #[cfg(all(target_os = "linux", feature = "splice"))]
824 if self.protocol == Protocol::TCP
825 && self.splice_pipe.is_some()
826 && self.backend_buffer.available_data() == 0
827 {
828 return self.splice_writable(metrics);
829 }
830
831 trace!("{} Pipe writable", log_context!(self));
832 if self.backend_buffer.available_data() == 0 {
833 self.backend_readiness.interest.insert(Ready::READABLE);
834 self.frontend_readiness.interest.remove(Ready::WRITABLE);
835 return SessionResult::Continue;
836 }
837
838 let queued_total = self.backend_buffer.available_data();
839 let mut sz = 0usize;
840 let mut res = SocketResult::Continue;
841 while res == SocketResult::Continue {
842 if self.backend_buffer.available_data() == 0 {
844 count!(names::backend::BYTES_OUT, sz as i64);
845 metrics.bout += sz;
846 self.backend_readiness.interest.insert(Ready::READABLE);
847 self.frontend_readiness.interest.remove(Ready::WRITABLE);
848 return SessionResult::Continue;
849 }
850 let queued = self.backend_buffer.available_data();
851 let (current_sz, current_res) = self.frontend.socket_write(self.backend_buffer.data());
852 debug_assert!(
855 current_sz <= queued,
856 "frontend socket_write reported {current_sz} bytes but only {queued} were queued"
857 );
858 res = current_res;
859 let consumed = self.backend_buffer.consume(current_sz);
860 debug_assert_eq!(
863 consumed, current_sz,
864 "consume must drop exactly the bytes written to the frontend"
865 );
866 sz += current_sz;
867 debug_assert!(
869 sz <= queued_total,
870 "cumulative frontend write ({sz}) exceeded the queued backend data ({queued_total})"
871 );
872
873 if current_sz == 0 && res == SocketResult::Continue {
874 self.frontend_status = match self.frontend_status {
875 ConnectionStatus::Normal => ConnectionStatus::ReadOpen,
876 ConnectionStatus::WriteOpen => ConnectionStatus::Closed,
877 s => s,
878 };
879 }
880
881 if !self.check_connections() {
882 metrics.bout += sz;
883 count!(names::backend::BYTES_OUT, sz as i64);
884 self.reset_readiness_for_close();
885 self.log_request_success(metrics);
886 return SessionResult::Close;
887 }
888 }
889
890 if sz > 0 {
891 count!(names::backend::BYTES_OUT, sz as i64);
892 self.backend_readiness.interest.insert(Ready::READABLE);
893 metrics.bout += sz;
894 }
895
896 debug!(
897 "{} Wrote {} bytes of {}",
898 log_context!(self),
899 sz,
900 self.backend_buffer.available_data()
901 );
902
903 match res {
904 SocketResult::Error => {
905 self.reset_readiness_for_close();
906 self.log_request_error(metrics, "front socket write error");
907 return SessionResult::Close;
908 }
909 SocketResult::Closed => {
910 self.reset_readiness_for_close();
911 self.log_request_success(metrics);
912 return SessionResult::Close;
913 }
914 SocketResult::WouldBlock => {
915 self.frontend_readiness.event.remove(Ready::WRITABLE);
916 }
917 SocketResult::Continue => {}
918 }
919
920 SessionResult::Continue
921 }
922
923 pub fn backend_writable(&mut self, metrics: &mut SessionMetrics) -> SessionResult {
925 #[cfg(all(target_os = "linux", feature = "splice"))]
929 if self.protocol == Protocol::TCP
930 && self.splice_pipe.is_some()
931 && self.frontend_buffer.available_data() == 0
932 {
933 return self.splice_backend_writable(metrics);
934 }
935
936 trace!("{} pipe back_writable", log_context!(self));
937
938 if self.frontend_buffer.available_data() == 0 {
939 self.frontend_readiness.interest.insert(Ready::READABLE);
940 self.backend_readiness.interest.remove(Ready::WRITABLE);
941 return SessionResult::Continue;
942 }
943
944 let output_size = self.frontend_buffer.available_data();
945
946 let mut sz = 0usize;
947 let mut socket_res = SocketResult::Continue;
948 let mut drained = false;
955
956 if let Some(ref mut backend) = self.backend_socket {
957 while socket_res == SocketResult::Continue {
958 if self.frontend_buffer.available_data() == 0 {
960 self.frontend_readiness.interest.insert(Ready::READABLE);
961 self.backend_readiness.interest.remove(Ready::WRITABLE);
962 drained = true;
963 break;
964 }
965
966 let queued = self.frontend_buffer.available_data();
967 let (current_sz, current_res) = backend.socket_write(self.frontend_buffer.data());
968 debug_assert!(
970 current_sz <= queued,
971 "backend socket_write reported {current_sz} bytes but only {queued} were queued"
972 );
973 socket_res = current_res;
974 let consumed = self.frontend_buffer.consume(current_sz);
975 debug_assert_eq!(
976 consumed, current_sz,
977 "consume must drop exactly the bytes written to the backend"
978 );
979 sz += current_sz;
980 debug_assert!(
982 sz <= output_size,
983 "cumulative backend write ({sz}) exceeded the queued frontend data ({output_size})"
984 );
985
986 if current_sz == 0 && current_res == SocketResult::Continue {
987 self.backend_status = match self.backend_status {
988 ConnectionStatus::Normal => ConnectionStatus::ReadOpen,
989 ConnectionStatus::WriteOpen => ConnectionStatus::Closed,
990 s => s,
991 };
992 }
993 }
994 }
995
996 if drained {
997 count!(names::backend::BACK_BYTES_OUT, sz as i64);
998 metrics.backend_bout += sz;
999 if matches!(
1015 self.frontend_status,
1016 ConnectionStatus::WriteOpen | ConnectionStatus::Closed
1017 ) && !self.check_connections()
1018 {
1019 self.reset_readiness_for_close();
1020 self.log_request_success(metrics);
1021 return SessionResult::Close;
1022 }
1023 return SessionResult::Continue;
1024 }
1025
1026 let backend_bout_before = metrics.backend_bout;
1027 count!(names::backend::BACK_BYTES_OUT, sz as i64);
1028 metrics.backend_bout += sz;
1029 debug_assert_eq!(
1031 metrics.backend_bout,
1032 backend_bout_before + sz,
1033 "metrics.backend_bout must advance by exactly the bytes written"
1034 );
1035
1036 if !self.check_connections() {
1037 self.reset_readiness_for_close();
1038 self.log_request_success(metrics);
1039 return SessionResult::Close;
1040 }
1041
1042 debug!(
1043 "{} Wrote {} bytes of {}",
1044 log_context!(self),
1045 sz,
1046 output_size
1047 );
1048
1049 match socket_res {
1050 SocketResult::Error => {
1051 self.reset_readiness_for_close();
1052 self.log_request_error(metrics, "back socket write error");
1053 return SessionResult::Close;
1054 }
1055 SocketResult::Closed => {
1056 self.reset_readiness_for_close();
1057 self.log_request_success(metrics);
1058 return SessionResult::Close;
1059 }
1060 SocketResult::WouldBlock => {
1061 self.backend_readiness.event.remove(Ready::WRITABLE);
1062 }
1063 SocketResult::Continue => {}
1064 }
1065 SessionResult::Continue
1066 }
1067
1068 pub fn backend_readable(&mut self, metrics: &mut SessionMetrics) -> SessionResult {
1070 #[cfg(all(target_os = "linux", feature = "splice"))]
1074 if self.protocol == Protocol::TCP
1075 && self.splice_pipe.is_some()
1076 && self.backend_buffer.available_data() == 0
1077 {
1078 return self.splice_backend_readable(metrics);
1079 }
1080
1081 self.reset_timeouts();
1082
1083 trace!("{} Pipe backend_readable", log_context!(self));
1084 if self.backend_buffer.available_space() == 0 {
1085 self.backend_readiness.interest.remove(Ready::READABLE);
1086 return SessionResult::Continue;
1087 }
1088
1089 let space_before = self.backend_buffer.available_space();
1090 let data_before = self.backend_buffer.available_data();
1091 let backend_bin_before = metrics.backend_bin;
1092 if let Some(ref mut backend) = self.backend_socket {
1093 let (size, remaining) = backend.socket_read(self.backend_buffer.space());
1094 debug_assert!(
1096 size <= space_before,
1097 "backend socket_read reported more bytes ({size}) than the buffer space offered ({space_before})"
1098 );
1099 self.backend_buffer.fill(size);
1100 debug_assert_eq!(
1103 self.backend_buffer.available_data(),
1104 data_before + size,
1105 "fill must grow readable data by exactly the bytes read"
1106 );
1107
1108 debug!("{} Read {} bytes", log_context!(self), size);
1109
1110 if remaining != SocketResult::Continue || size == 0 {
1111 self.backend_readiness.event.remove(Ready::READABLE);
1112 }
1113 if size > 0 {
1114 self.frontend_readiness.arm_writable();
1115 count!(names::backend::BACK_BYTES_IN, size as i64);
1116 metrics.backend_bin += size;
1117 debug_assert_eq!(
1119 metrics.backend_bin,
1120 backend_bin_before + size,
1121 "metrics.backend_bin must advance by exactly the bytes read"
1122 );
1123 }
1124
1125 if size == 0 && remaining == SocketResult::Closed {
1126 self.backend_status = match self.backend_status {
1127 ConnectionStatus::Normal => ConnectionStatus::WriteOpen,
1128 ConnectionStatus::ReadOpen => ConnectionStatus::Closed,
1129 s => s,
1130 };
1131
1132 if !self.check_connections() {
1133 self.reset_readiness_for_close();
1134 self.log_request_success(metrics);
1135 return SessionResult::Close;
1136 }
1137 }
1138
1139 match remaining {
1140 SocketResult::Error => {
1141 self.reset_readiness_for_close();
1142 self.log_request_error(metrics, "back socket read error");
1143 return SessionResult::Close;
1144 }
1145 SocketResult::Closed => {
1146 if !self.check_connections() {
1147 self.reset_readiness_for_close();
1148 self.log_request_success(metrics);
1149 return SessionResult::Close;
1150 }
1151 }
1152 SocketResult::WouldBlock => {
1153 self.backend_readiness.event.remove(Ready::READABLE);
1154 }
1155 SocketResult::Continue => {}
1156 }
1157 }
1158
1159 SessionResult::Continue
1160 }
1161
1162 #[cfg(all(target_os = "linux", feature = "splice"))]
1170 fn splice_readable(&mut self, metrics: &mut SessionMetrics) -> SessionResult {
1171 self.reset_timeouts();
1172
1173 trace!("{} pipe splice_readable", log_context!(self));
1174 let capacity = self.splice_capacity();
1175 if self.splice_in_pending() >= capacity {
1176 self.frontend_readiness.interest.remove(Ready::READABLE);
1178 self.backend_readiness.arm_writable();
1179 return SessionResult::Continue;
1180 }
1181
1182 let pending_before = self.splice_in_pending();
1183 let bin_before = metrics.bin;
1184 let pipe_write_end = self.splice_pipe.as_ref().unwrap().in_pipe[1];
1185 let (sz, res) = splice::splice_in(self.frontend.socket_ref(), pipe_write_end, capacity);
1186 debug_assert!(
1195 sz <= capacity,
1196 "splice_in reported {sz} bytes but was capped at len {capacity}"
1197 );
1198 debug!("{} Spliced {} bytes from frontend", log_context!(self), sz);
1199
1200 if res == SocketResult::WouldBlock && self.splice_in_pending() > 0 {
1209 debug_assert_eq!(sz, 0, "splice_in must report 0 bytes on EAGAIN");
1210 self.frontend_readiness.interest.remove(Ready::READABLE);
1211 self.backend_readiness.arm_writable();
1212 debug_assert!(
1213 self.frontend_readiness.event.is_readable()
1214 && !self.frontend_readiness.interest.is_readable(),
1215 "a pipe-full EAGAIN must keep the READABLE event pending and park the interest"
1216 );
1217 debug_assert_eq!(
1218 self.splice_in_pending(),
1219 pending_before,
1220 "a pipe-full EAGAIN must not change the in_pipe accounting"
1221 );
1222 return SessionResult::Continue;
1223 }
1224
1225 if sz > 0 {
1226 self.splice_pipe.as_mut().unwrap().in_pipe_pending += sz;
1227 debug_assert_eq!(
1230 self.splice_in_pending(),
1231 pending_before + sz,
1232 "in_pipe_pending must grow by exactly the spliced bytes"
1233 );
1234 count!(names::backend::BYTES_IN, sz as i64);
1235 metrics.bin += sz;
1236 debug_assert_eq!(
1237 metrics.bin,
1238 bin_before + sz,
1239 "metrics.bin must advance by exactly the spliced bytes"
1240 );
1241 self.backend_readiness.arm_writable();
1242 } else {
1243 self.frontend_readiness.event.remove(Ready::READABLE);
1244
1245 if res == SocketResult::Continue {
1246 self.frontend_status = match self.frontend_status {
1247 ConnectionStatus::Normal => ConnectionStatus::WriteOpen,
1248 ConnectionStatus::ReadOpen => ConnectionStatus::Closed,
1249 s => s,
1250 };
1251 }
1252 }
1253
1254 if !self.check_connections() {
1255 self.reset_readiness_for_close();
1256 self.log_request_success(metrics);
1257 return SessionResult::Close;
1258 }
1259
1260 match res {
1261 SocketResult::Error => {
1262 self.reset_readiness_for_close();
1263 self.log_request_error(metrics, "splice front socket read error");
1264 return SessionResult::Close;
1265 }
1266 SocketResult::Closed => {
1267 let has_pending = self.splice_in_pending() > 0;
1282 return self.close_frontend_read_side(metrics, has_pending);
1283 }
1284 SocketResult::WouldBlock => {
1285 self.frontend_readiness.event.remove(Ready::READABLE);
1286 }
1287 SocketResult::Continue => {}
1288 }
1289
1290 self.backend_readiness.arm_writable();
1291 SessionResult::Continue
1292 }
1293
1294 #[cfg(all(target_os = "linux", feature = "splice"))]
1299 fn splice_writable(&mut self, metrics: &mut SessionMetrics) -> SessionResult {
1300 trace!("{} Pipe splice_writable", log_context!(self));
1301 if self.splice_out_pending() == 0 {
1302 self.backend_readiness.interest.insert(Ready::READABLE);
1303 self.frontend_readiness.interest.remove(Ready::WRITABLE);
1304 return SessionResult::Continue;
1305 }
1306
1307 let mut sz = 0usize;
1308 let mut res = SocketResult::Continue;
1309 while res == SocketResult::Continue {
1310 let pending = self.splice_out_pending();
1311 if pending == 0 {
1313 count!(names::backend::BYTES_OUT, sz as i64);
1314 metrics.bout += sz;
1315 self.backend_readiness.interest.insert(Ready::READABLE);
1316 self.frontend_readiness.interest.remove(Ready::WRITABLE);
1317 return SessionResult::Continue;
1318 }
1319
1320 let pipe_read_end = self.splice_pipe.as_ref().unwrap().out_pipe[0];
1321 let (current_sz, current_res) =
1322 splice::splice_out(pipe_read_end, self.frontend.socket_ref(), pending);
1323 debug_assert!(
1327 current_sz <= pending,
1328 "splice_out drained {current_sz} bytes but only {pending} were pending (would underflow)"
1329 );
1330 res = current_res;
1331 if current_sz > 0 {
1332 self.splice_pipe.as_mut().unwrap().out_pipe_pending -= current_sz;
1333 debug_assert_eq!(
1334 self.splice_out_pending(),
1335 pending - current_sz,
1336 "out_pipe_pending must shrink by exactly the drained bytes"
1337 );
1338 }
1339 sz += current_sz;
1340
1341 if current_sz == 0 && res == SocketResult::Continue {
1342 self.frontend_status = match self.frontend_status {
1343 ConnectionStatus::Normal => ConnectionStatus::ReadOpen,
1344 ConnectionStatus::WriteOpen => ConnectionStatus::Closed,
1345 s => s,
1346 };
1347 }
1348
1349 if !self.check_connections() {
1350 metrics.bout += sz;
1351 count!(names::backend::BYTES_OUT, sz as i64);
1352 self.reset_readiness_for_close();
1353 self.log_request_success(metrics);
1354 return SessionResult::Close;
1355 }
1356 }
1357
1358 if sz > 0 {
1359 count!(names::backend::BYTES_OUT, sz as i64);
1360 self.backend_readiness.interest.insert(Ready::READABLE);
1361 metrics.bout += sz;
1362 }
1363
1364 debug!(
1365 "{} Spliced {} bytes (out_pipe_pending={})",
1366 log_context!(self),
1367 sz,
1368 self.splice_out_pending()
1369 );
1370
1371 match res {
1372 SocketResult::Error => {
1373 self.reset_readiness_for_close();
1374 self.log_request_error(metrics, "splice front socket write error");
1375 return SessionResult::Close;
1376 }
1377 SocketResult::Closed => {
1378 self.reset_readiness_for_close();
1379 self.log_request_success(metrics);
1380 return SessionResult::Close;
1381 }
1382 SocketResult::WouldBlock => {
1383 self.frontend_readiness.event.remove(Ready::WRITABLE);
1384 }
1385 SocketResult::Continue => {}
1386 }
1387
1388 SessionResult::Continue
1389 }
1390
1391 #[cfg(all(target_os = "linux", feature = "splice"))]
1396 fn splice_backend_writable(&mut self, metrics: &mut SessionMetrics) -> SessionResult {
1397 trace!("{} pipe splice_backend_writable", log_context!(self));
1398
1399 if self.splice_in_pending() == 0 {
1400 self.frontend_readiness.interest.insert(Ready::READABLE);
1401 self.backend_readiness.interest.remove(Ready::WRITABLE);
1402 return SessionResult::Continue;
1403 }
1404
1405 let output_size = self.splice_in_pending();
1406 let mut sz = 0usize;
1407 let mut socket_res = SocketResult::Continue;
1408
1409 while socket_res == SocketResult::Continue {
1410 let pending = self.splice_in_pending();
1411 if pending == 0 {
1413 self.frontend_readiness.interest.insert(Ready::READABLE);
1414 self.backend_readiness.interest.remove(Ready::WRITABLE);
1415 count!(names::backend::BACK_BYTES_OUT, sz as i64);
1416 metrics.backend_bout += sz;
1417 if matches!(
1434 self.frontend_status,
1435 ConnectionStatus::WriteOpen | ConnectionStatus::Closed
1436 ) && !self.check_connections()
1437 {
1438 self.reset_readiness_for_close();
1439 self.log_request_success(metrics);
1440 return SessionResult::Close;
1441 }
1442 return SessionResult::Continue;
1443 }
1444
1445 let pipe_read_end = self.splice_pipe.as_ref().unwrap().in_pipe[0];
1446 let (current_sz, current_res) = match self.backend_socket.as_ref() {
1447 Some(b) => splice::splice_out(pipe_read_end, b, pending),
1448 None => break,
1449 };
1450 debug_assert!(
1452 current_sz <= pending,
1453 "splice_out drained {current_sz} bytes but only {pending} were pending (would underflow)"
1454 );
1455 socket_res = current_res;
1456 if current_sz > 0 {
1457 self.splice_pipe.as_mut().unwrap().in_pipe_pending -= current_sz;
1458 debug_assert_eq!(
1459 self.splice_in_pending(),
1460 pending - current_sz,
1461 "in_pipe_pending must shrink by exactly the drained bytes"
1462 );
1463 }
1464 sz += current_sz;
1465 debug_assert!(
1467 sz <= output_size,
1468 "cumulative splice drain ({sz}) exceeded the bytes pending at entry ({output_size})"
1469 );
1470
1471 if current_sz == 0 && current_res == SocketResult::Continue {
1472 self.backend_status = match self.backend_status {
1473 ConnectionStatus::Normal => ConnectionStatus::ReadOpen,
1474 ConnectionStatus::WriteOpen => ConnectionStatus::Closed,
1475 s => s,
1476 };
1477 }
1478 }
1479
1480 count!(names::backend::BACK_BYTES_OUT, sz as i64);
1481 metrics.backend_bout += sz;
1482 if sz > 0 {
1483 self.frontend_readiness.interest.insert(Ready::READABLE);
1487 }
1488
1489 if !self.check_connections() {
1490 self.reset_readiness_for_close();
1491 self.log_request_success(metrics);
1492 return SessionResult::Close;
1493 }
1494
1495 debug!(
1496 "{} Spliced {} bytes of {}",
1497 log_context!(self),
1498 sz,
1499 output_size
1500 );
1501
1502 match socket_res {
1503 SocketResult::Error => {
1504 self.reset_readiness_for_close();
1505 self.log_request_error(metrics, "splice back socket write error");
1506 return SessionResult::Close;
1507 }
1508 SocketResult::Closed => {
1509 self.reset_readiness_for_close();
1510 self.log_request_success(metrics);
1511 return SessionResult::Close;
1512 }
1513 SocketResult::WouldBlock => {
1514 self.backend_readiness.event.remove(Ready::WRITABLE);
1515 }
1516 SocketResult::Continue => {}
1517 }
1518 SessionResult::Continue
1519 }
1520
1521 #[cfg(all(target_os = "linux", feature = "splice"))]
1527 fn splice_backend_readable(&mut self, metrics: &mut SessionMetrics) -> SessionResult {
1528 self.reset_timeouts();
1529
1530 trace!("{} Pipe splice_backend_readable", log_context!(self));
1531 let capacity = self.splice_capacity();
1532 if self.splice_out_pending() >= capacity {
1533 self.backend_readiness.interest.remove(Ready::READABLE);
1535 self.frontend_readiness.arm_writable();
1536 return SessionResult::Continue;
1537 }
1538
1539 let pending_before = self.splice_out_pending();
1540 let backend_bin_before = metrics.backend_bin;
1541 let pipe_write_end = self.splice_pipe.as_ref().unwrap().out_pipe[1];
1542 let (size, remaining) = match self.backend_socket.as_ref() {
1543 Some(b) => splice::splice_in(b, pipe_write_end, capacity),
1544 None => return SessionResult::Continue,
1545 };
1546 debug_assert!(
1553 size <= capacity,
1554 "splice_in reported {size} bytes but was capped at len {capacity}"
1555 );
1556
1557 debug!("{} Spliced {} bytes from backend", log_context!(self), size);
1558
1559 let pipe_may_be_full =
1573 remaining == SocketResult::WouldBlock && self.splice_out_pending() > 0;
1574 if pipe_may_be_full {
1575 debug_assert_eq!(size, 0, "splice_in must report 0 bytes on EAGAIN");
1576 self.backend_readiness.interest.remove(Ready::READABLE);
1577 self.frontend_readiness.arm_writable();
1578 debug_assert!(
1579 self.backend_readiness.event.is_readable()
1580 && !self.backend_readiness.interest.is_readable(),
1581 "a pipe-full EAGAIN must keep the READABLE event pending and park the interest"
1582 );
1583 debug_assert_eq!(
1584 self.splice_out_pending(),
1585 pending_before,
1586 "a pipe-full EAGAIN must not change the out_pipe accounting"
1587 );
1588 return SessionResult::Continue;
1589 }
1590 if remaining != SocketResult::Continue || size == 0 {
1591 self.backend_readiness.event.remove(Ready::READABLE);
1592 }
1593 if size > 0 {
1594 self.splice_pipe.as_mut().unwrap().out_pipe_pending += size;
1595 debug_assert_eq!(
1596 self.splice_out_pending(),
1597 pending_before + size,
1598 "out_pipe_pending must grow by exactly the spliced bytes"
1599 );
1600 self.frontend_readiness.arm_writable();
1601 count!(names::backend::BACK_BYTES_IN, size as i64);
1602 metrics.backend_bin += size;
1603 debug_assert_eq!(
1604 metrics.backend_bin,
1605 backend_bin_before + size,
1606 "metrics.backend_bin must advance by exactly the spliced bytes"
1607 );
1608 }
1609
1610 if size == 0 && remaining == SocketResult::Closed {
1611 self.backend_status = match self.backend_status {
1612 ConnectionStatus::Normal => ConnectionStatus::WriteOpen,
1613 ConnectionStatus::ReadOpen => ConnectionStatus::Closed,
1614 s => s,
1615 };
1616
1617 if !self.check_connections() {
1618 self.reset_readiness_for_close();
1619 self.log_request_success(metrics);
1620 return SessionResult::Close;
1621 }
1622 }
1623
1624 match remaining {
1625 SocketResult::Error => {
1626 self.reset_readiness_for_close();
1627 self.log_request_error(metrics, "splice back socket read error");
1628 return SessionResult::Close;
1629 }
1630 SocketResult::Closed => {
1631 if !self.check_connections() {
1632 self.reset_readiness_for_close();
1633 self.log_request_success(metrics);
1634 return SessionResult::Close;
1635 }
1636 }
1637 SocketResult::WouldBlock => {
1638 self.backend_readiness.event.remove(Ready::READABLE);
1639 }
1640 SocketResult::Continue => {}
1641 }
1642
1643 SessionResult::Continue
1644 }
1645
1646 pub fn log_context(&self) -> LogContext<'_> {
1647 LogContext {
1648 session_id: self.session_id,
1649 request_id: Some(self.request_id),
1650 cluster_id: self.cluster_id.as_deref(),
1651 backend_id: self.backend_id.as_deref(),
1652 }
1653 }
1654
1655 fn log_endpoint(&self) -> EndpointRecord<'_> {
1656 match &self.websocket_context {
1657 WebSocketContext::Http {
1658 method,
1659 authority,
1660 path,
1661 status,
1662 reason,
1663 } => EndpointRecord::Http {
1664 method: method.as_deref(),
1665 authority: authority.as_deref(),
1666 path: path.as_deref(),
1667 status: status.to_owned(),
1668 reason: reason.as_deref(),
1669 },
1670 WebSocketContext::Tcp => EndpointRecord::Tcp,
1671 }
1672 }
1673}
1674
1675impl<Front: SocketHandler, L: ListenerHandler> SessionState for Pipe<Front, L> {
1676 fn ready(
1677 &mut self,
1678 _session: Rc<RefCell<dyn crate::ProxySession>>,
1679 _proxy: Rc<RefCell<dyn crate::L7Proxy>>,
1680 metrics: &mut SessionMetrics,
1681 ) -> SessionResult {
1682 let mut counter = 0;
1683
1684 if self.frontend_readiness.event.is_hup() {
1685 return SessionResult::Close;
1686 }
1687
1688 while counter < MAX_LOOP_ITERATIONS {
1689 let frontend_interest = self.frontend_readiness.filter_interest();
1690 let backend_interest = self.backend_readiness.filter_interest();
1691
1692 trace!(
1693 "{} Frontend interest({:?}), backend interest({:?})",
1694 log_context!(self),
1695 frontend_interest,
1696 backend_interest
1697 );
1698 if frontend_interest.is_empty() && backend_interest.is_empty() {
1699 break;
1700 }
1701
1702 if self.backend_readiness.event.is_hup()
1703 && self.frontend_readiness.interest.is_writable()
1704 && !self.frontend_readiness.event.is_writable()
1705 {
1706 break;
1707 }
1708
1709 if frontend_interest.is_readable() && self.readable(metrics) == SessionResult::Close {
1710 return SessionResult::Close;
1711 }
1712
1713 if backend_interest.is_writable()
1714 && self.backend_writable(metrics) == SessionResult::Close
1715 {
1716 return SessionResult::Close;
1717 }
1718
1719 if backend_interest.is_readable()
1720 && self.backend_readable(metrics) == SessionResult::Close
1721 {
1722 return SessionResult::Close;
1723 }
1724
1725 if frontend_interest.is_writable() && self.writable(metrics) == SessionResult::Close {
1726 return SessionResult::Close;
1727 }
1728
1729 if backend_interest.is_hup() && self.backend_hup(metrics) == SessionResult::Close {
1730 return SessionResult::Close;
1731 }
1732
1733 if frontend_interest.is_error() {
1734 error!(
1735 "{} Frontend socket error, disconnecting",
1736 log_context!(self)
1737 );
1738
1739 self.frontend_readiness.interest = Ready::EMPTY;
1740 self.backend_readiness.interest = Ready::EMPTY;
1741
1742 return SessionResult::Close;
1743 }
1744
1745 if backend_interest.is_error() && self.backend_hup(metrics) == SessionResult::Close {
1746 self.frontend_readiness.interest = Ready::EMPTY;
1747 self.backend_readiness.interest = Ready::EMPTY;
1748
1749 error!("{} Backend socket error, disconnecting", log_context!(self));
1750 return SessionResult::Close;
1751 }
1752
1753 counter += 1;
1754 }
1755
1756 if counter >= MAX_LOOP_ITERATIONS {
1757 error!(
1758 "{}\tHandling session went through {} iterations, there's a probable infinite loop bug, closing the connection",
1759 log_context!(self),
1760 MAX_LOOP_ITERATIONS
1761 );
1762
1763 incr!(names::http::INFINITE_LOOP_ERROR);
1764 self.print_state(self.protocol_string());
1765
1766 return SessionResult::Close;
1767 }
1768
1769 SessionResult::Continue
1770 }
1771
1772 fn update_readiness(&mut self, token: Token, events: Ready) {
1773 if self.frontend_token == token {
1774 self.frontend_readiness.event |= events;
1775 } else if self.backend_token == Some(token) {
1776 self.backend_readiness.event |= events;
1777 }
1778 }
1779
1780 fn timeout(&mut self, token: Token, metrics: &mut SessionMetrics) -> StateResult {
1781 if self.frontend_token == token {
1783 self.log_request_timeout(metrics, "frontend socket timeout");
1784 if let Some(timeout) = self.container_frontend_timeout.as_mut() {
1785 timeout.triggered()
1786 }
1787 return StateResult::CloseSession;
1788 }
1789
1790 if self.backend_token == Some(token) {
1791 if let Some(timeout) = self.container_backend_timeout.as_mut() {
1793 timeout.triggered()
1794 }
1795
1796 self.log_request_timeout(metrics, "backend socket timeout");
1797 return StateResult::CloseSession;
1798 }
1799
1800 error!("{} Got timeout for an invalid token", log_context!(self));
1801 self.log_request_error(metrics, "invalid token timeout");
1802 StateResult::CloseSession
1803 }
1804
1805 fn cancel_timeouts(&mut self) {
1806 self.container_frontend_timeout.as_mut().map(|t| t.cancel());
1807 self.container_backend_timeout.as_mut().map(|t| t.cancel());
1808 }
1809
1810 fn close(&mut self, _proxy: Rc<RefCell<dyn L7Proxy>>, _metrics: &mut SessionMetrics) {
1811 if let Some(backend) = self.backend.as_mut() {
1812 let mut backend = backend.borrow_mut();
1813 backend.active_requests = backend.active_requests.saturating_sub(1);
1814 }
1815 }
1816
1817 fn print_state(&self, context: &str) {
1818 error!(
1819 "\
1820{} {} Session(Pipe)
1821\tFrontend:
1822\t\ttoken: {:?}\treadiness: {:?}
1823\tBackend:
1824\t\ttoken: {:?}\treadiness: {:?}",
1825 log_context!(self),
1826 context,
1827 self.frontend_token,
1828 self.frontend_readiness,
1829 self.backend_token,
1830 self.backend_readiness
1831 );
1832 }
1833
1834 fn targets(&self, target: &crate::SessionTarget) -> bool {
1836 self.cluster_id.as_deref().is_some_and(|cluster_id| {
1837 target.matches(cluster_id, self.backend_id.as_deref(), self.backend_address)
1838 })
1839 }
1840}
1841
1842#[cfg(test)]
1843mod tests {
1844 use std::{
1845 collections::BTreeMap,
1846 io::Write,
1847 net::{TcpListener as StdTcpListener, TcpStream as StdTcpStream},
1848 time::Duration,
1849 };
1850
1851 use super::*;
1852 use crate::pool::Pool;
1853
1854 struct TestListener {
1855 address: SocketAddr,
1856 }
1857
1858 impl ListenerHandler for TestListener {
1859 fn get_addr(&self) -> &SocketAddr {
1860 &self.address
1861 }
1862
1863 fn get_tags(&self, _key: &str) -> Option<&sozu_command::logging::CachedTags> {
1864 None
1865 }
1866
1867 fn set_tags(&mut self, _key: String, _tags: Option<BTreeMap<String, String>>) {}
1868
1869 fn protocol(&self) -> Protocol {
1870 Protocol::HTTP
1871 }
1872
1873 fn public_address(&self) -> SocketAddr {
1874 self.address
1875 }
1876 }
1877
1878 fn connected_pair() -> (StdTcpStream, StdTcpStream) {
1879 let listener = StdTcpListener::bind("127.0.0.1:0").expect("bind test listener");
1880 let address = listener.local_addr().expect("listener local addr");
1881 let client = StdTcpStream::connect(address).expect("connect test client");
1882 let (server, _) = listener.accept().expect("accept test server");
1883 client.set_nonblocking(true).expect("client nonblocking");
1884 server.set_nonblocking(true).expect("server nonblocking");
1885 (client, server)
1886 }
1887
1888 #[test]
1889 fn backend_readable_arms_frontend_writable_event_when_buffering_response() {
1890 let (frontend_peer, frontend_socket) = connected_pair();
1891 let (mut backend_peer, backend_socket) = connected_pair();
1892
1893 let mut pool = Pool::with_capacity(2, 2, 4096);
1894 let backend_buffer = pool.checkout().expect("backend buffer");
1895 let frontend_buffer = pool.checkout().expect("frontend buffer");
1896 let address = "127.0.0.1:0".parse().expect("test address");
1897 let listener = Rc::new(RefCell::new(TestListener { address }));
1898
1899 let mut pipe = Pipe::new(
1900 backend_buffer,
1901 None,
1902 Some(TcpStream::from_std(backend_socket)),
1903 None,
1904 None,
1905 None,
1906 None,
1907 frontend_buffer,
1908 Token(0),
1909 TcpStream::from_std(frontend_socket),
1910 listener,
1911 Protocol::HTTP,
1912 Ulid::generate(),
1913 Ulid::generate(),
1914 None,
1915 WebSocketContext::Tcp,
1916 );
1917 pipe.set_back_token(Token(1));
1918 pipe.frontend_readiness.event = Ready::EMPTY;
1919 pipe.frontend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
1920 pipe.backend_readiness.event = Ready::READABLE;
1921 pipe.backend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
1922
1923 backend_peer
1924 .write_all(b"server-speaks-first")
1925 .expect("write backend payload");
1926
1927 let mut metrics = SessionMetrics::new(Some(Duration::ZERO));
1928 assert_eq!(pipe.backend_readable(&mut metrics), SessionResult::Continue);
1929
1930 assert!(
1931 pipe.backend_buffer.available_data() > 0,
1932 "backend_readable must buffer backend bytes"
1933 );
1934 assert!(
1935 pipe.frontend_readiness.interest.is_writable(),
1936 "buffered backend bytes must arm frontend WRITABLE interest"
1937 );
1938 assert!(
1939 pipe.frontend_readiness.event.is_writable(),
1940 "buffered backend bytes must queue a frontend WRITABLE event"
1941 );
1942
1943 drop(frontend_peer);
1944 }
1945
1946 #[test]
1947 fn frontend_readable_arms_backend_writable_event_when_buffering_request() {
1948 let (mut frontend_peer, frontend_socket) = connected_pair();
1949 let (backend_peer, backend_socket) = connected_pair();
1950
1951 let mut pool = Pool::with_capacity(2, 2, 4096);
1952 let backend_buffer = pool.checkout().expect("backend buffer");
1953 let frontend_buffer = pool.checkout().expect("frontend buffer");
1954 let address = "127.0.0.1:0".parse().expect("test address");
1955 let listener = Rc::new(RefCell::new(TestListener { address }));
1956
1957 let mut pipe = Pipe::new(
1958 backend_buffer,
1959 None,
1960 Some(TcpStream::from_std(backend_socket)),
1961 None,
1962 None,
1963 None,
1964 None,
1965 frontend_buffer,
1966 Token(0),
1967 TcpStream::from_std(frontend_socket),
1968 listener,
1969 Protocol::HTTP,
1970 Ulid::generate(),
1971 Ulid::generate(),
1972 None,
1973 WebSocketContext::Tcp,
1974 );
1975 pipe.set_back_token(Token(1));
1976 pipe.frontend_readiness.event = Ready::READABLE;
1977 pipe.frontend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
1978 pipe.backend_readiness.event = Ready::EMPTY;
1979 pipe.backend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
1980
1981 frontend_peer
1982 .write_all(b"client-speaks-after-upgrade")
1983 .expect("write frontend payload");
1984
1985 let mut metrics = SessionMetrics::new(Some(Duration::ZERO));
1986 assert_eq!(pipe.readable(&mut metrics), SessionResult::Continue);
1987
1988 assert!(
1989 pipe.frontend_buffer.available_data() > 0,
1990 "readable must buffer frontend bytes"
1991 );
1992 assert!(
1993 pipe.backend_readiness.interest.is_writable(),
1994 "buffered frontend bytes must arm backend WRITABLE interest"
1995 );
1996 assert!(
1997 pipe.backend_readiness.event.is_writable(),
1998 "buffered frontend bytes must queue a backend WRITABLE event"
1999 );
2000
2001 drop(backend_peer);
2002 }
2003
2004 #[test]
2005 fn restore_readiness_events_rearms_inherited_buffered_writes() {
2006 let (frontend_peer, frontend_socket) = connected_pair();
2007 let (backend_peer, backend_socket) = connected_pair();
2008
2009 let mut pool = Pool::with_capacity(2, 2, 4096);
2010 let mut backend_buffer = pool.checkout().expect("backend buffer");
2011 let mut frontend_buffer = pool.checkout().expect("frontend buffer");
2012 backend_buffer
2013 .write_all(b"backend bytes inherited from 101 read")
2014 .expect("write backend buffer");
2015 frontend_buffer
2016 .write_all(b"frontend bytes inherited from upgrade read")
2017 .expect("write frontend buffer");
2018 let address = "127.0.0.1:0".parse().expect("test address");
2019 let listener = Rc::new(RefCell::new(TestListener { address }));
2020
2021 let mut pipe = Pipe::new(
2022 backend_buffer,
2023 None,
2024 Some(TcpStream::from_std(backend_socket)),
2025 None,
2026 None,
2027 None,
2028 None,
2029 frontend_buffer,
2030 Token(0),
2031 TcpStream::from_std(frontend_socket),
2032 listener,
2033 Protocol::HTTP,
2034 Ulid::generate(),
2035 Ulid::generate(),
2036 None,
2037 WebSocketContext::Tcp,
2038 );
2039
2040 pipe.restore_readiness_events(Ready::EMPTY, Ready::EMPTY);
2041
2042 assert!(
2043 pipe.frontend_readiness.event.is_writable(),
2044 "restoring inherited frontend events must not park backend-buffered bytes"
2045 );
2046 assert!(
2047 pipe.backend_readiness.event.is_writable(),
2048 "restoring inherited backend events must not park frontend-buffered bytes"
2049 );
2050
2051 drop(frontend_peer);
2052 drop(backend_peer);
2053 }
2054
2055 #[cfg(all(target_os = "linux", feature = "splice"))]
2066 #[test]
2067 fn backend_writable_drains_inherited_frontend_buffer_before_splice_engages() {
2068 use std::io::Read;
2069
2070 let (frontend_peer, frontend_socket) = connected_pair();
2071 let (mut backend_peer, backend_socket) = connected_pair();
2072
2073 let mut pool = Pool::with_capacity(2, 2, 4096);
2074 let backend_buffer = pool.checkout().expect("backend buffer");
2075 let mut frontend_buffer = pool.checkout().expect("frontend buffer");
2076 frontend_buffer
2077 .write_all(b"inherited-preread-client-hello")
2078 .expect("write inherited frontend buffer");
2079 let address = "127.0.0.1:0".parse().expect("test address");
2080 let listener = Rc::new(RefCell::new(TestListener { address }));
2081
2082 let mut pipe = Pipe::new(
2083 backend_buffer,
2084 None,
2085 Some(TcpStream::from_std(backend_socket)),
2086 None,
2087 None,
2088 None,
2089 None,
2090 frontend_buffer,
2091 Token(0),
2092 TcpStream::from_std(frontend_socket),
2093 listener,
2094 Protocol::TCP,
2095 Ulid::generate(),
2096 Ulid::generate(),
2097 None,
2098 WebSocketContext::Tcp,
2099 );
2100 pipe.set_back_token(Token(1));
2101
2102 assert!(
2103 pipe.splice_pipe.is_some(),
2104 "Protocol::TCP must allocate the splice kernel pipe for this test to be meaningful"
2105 );
2106 assert!(
2107 pipe.frontend_buffer.available_data() > 0,
2108 "test setup must inherit a non-empty frontend buffer"
2109 );
2110
2111 let mut metrics = SessionMetrics::new(Some(Duration::ZERO));
2112 assert_eq!(pipe.backend_writable(&mut metrics), SessionResult::Continue);
2113
2114 assert_eq!(
2115 pipe.frontend_buffer.available_data(),
2116 0,
2117 "inherited preread bytes must drain through the buffered path before splice engages"
2118 );
2119
2120 let mut received = [0u8; 64];
2121 let n = backend_peer
2122 .read(&mut received)
2123 .expect("backend socket must have received the drained preread bytes");
2124 assert_eq!(&received[..n], b"inherited-preread-client-hello");
2125
2126 drop(frontend_peer);
2127 }
2128
2129 #[test]
2138 fn frontend_eof_flushes_queued_backend_bytes_before_closing() {
2139 use std::io::{Read, Write};
2140
2141 let (frontend_peer, frontend_socket) = connected_pair();
2142 let (mut backend_peer, backend_socket) = connected_pair();
2143
2144 let mut pool = Pool::with_capacity(2, 2, 4096);
2145 let backend_buffer = pool.checkout().expect("backend buffer");
2146 let mut frontend_buffer = pool.checkout().expect("frontend buffer");
2147 frontend_buffer
2149 .write_all(b"front-to-back-tail-still-queued")
2150 .expect("seed queued frontend bytes");
2151 let address = "127.0.0.1:0".parse().expect("test address");
2152 let listener = Rc::new(RefCell::new(TestListener { address }));
2153
2154 let mut pipe = Pipe::new(
2155 backend_buffer,
2156 None,
2157 Some(TcpStream::from_std(backend_socket)),
2158 None,
2159 None,
2160 None,
2161 None,
2162 frontend_buffer,
2163 Token(0),
2164 TcpStream::from_std(frontend_socket),
2165 listener,
2166 Protocol::TCP,
2167 Ulid::generate(),
2168 Ulid::generate(),
2169 None,
2170 WebSocketContext::Tcp,
2171 );
2172 pipe.set_back_token(Token(1));
2173 pipe.frontend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
2174 pipe.frontend_readiness.event = Ready::READABLE;
2175
2176 drop(frontend_peer);
2179
2180 let mut metrics = SessionMetrics::new(Some(Duration::ZERO));
2185 let mut saw_eof = false;
2186 for _ in 0..100_000 {
2187 let result = pipe.readable(&mut metrics);
2188 assert_ne!(
2189 result,
2190 SessionResult::Close,
2191 "readable must not close while {} queued backend bytes remain",
2192 pipe.frontend_buffer.available_data()
2193 );
2194 if matches!(pipe.frontend_status, ConnectionStatus::WriteOpen) {
2195 saw_eof = true;
2196 break;
2197 }
2198 }
2199 assert!(
2200 saw_eof,
2201 "frontend EOF was never observed within the retry budget"
2202 );
2203 assert!(
2204 pipe.frontend_buffer.available_data() > 0,
2205 "the queued backend bytes must survive the frontend EOF, not be dropped"
2206 );
2207
2208 assert_eq!(pipe.backend_writable(&mut metrics), SessionResult::Close);
2215 assert_eq!(
2216 pipe.frontend_buffer.available_data(),
2217 0,
2218 "the queued bytes must all drain to the backend after EOF"
2219 );
2220
2221 let mut received = Vec::new();
2222 for _ in 0..100_000 {
2224 let mut buf = [0u8; 64];
2225 match backend_peer.read(&mut buf) {
2226 Ok(0) => break,
2227 Ok(n) => {
2228 received.extend_from_slice(&buf[..n]);
2229 if received.len() >= b"front-to-back-tail-still-queued".len() {
2230 break;
2231 }
2232 }
2233 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => continue,
2234 Err(_) => break,
2235 }
2236 }
2237 assert_eq!(
2238 received, b"front-to-back-tail-still-queued",
2239 "the backend must receive the queued tail byte-for-byte, not a truncation"
2240 );
2241 }
2242
2243 #[test]
2255 fn frontend_eof_with_queued_bytes_closes_on_the_draining_backend_writable_call() {
2256 use std::io::Read;
2257
2258 let (mut frontend_peer, frontend_socket) = connected_pair();
2259 let (mut backend_peer, backend_socket) = connected_pair();
2260
2261 let mut pool = Pool::with_capacity(2, 2, 4096);
2262 let backend_buffer = pool.checkout().expect("backend buffer");
2263 let frontend_buffer = pool.checkout().expect("frontend buffer");
2264 let address = "127.0.0.1:0".parse().expect("test address");
2265 let listener = Rc::new(RefCell::new(TestListener { address }));
2266
2267 let mut pipe = Pipe::new(
2268 backend_buffer,
2269 None,
2270 Some(TcpStream::from_std(backend_socket)),
2271 None,
2272 None,
2273 None,
2274 None,
2275 frontend_buffer,
2276 Token(0),
2277 TcpStream::from_std(frontend_socket),
2278 listener,
2279 Protocol::HTTP,
2280 Ulid::generate(),
2281 Ulid::generate(),
2282 None,
2283 WebSocketContext::Tcp,
2284 );
2285 pipe.set_back_token(Token(1));
2286 pipe.frontend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
2287 pipe.frontend_readiness.event = Ready::READABLE;
2288
2289 frontend_peer
2293 .write_all(b"queued-request-tail")
2294 .expect("write frontend payload");
2295 drop(frontend_peer);
2296
2297 let mut metrics = SessionMetrics::new(Some(Duration::ZERO));
2301 let mut saw_eof = false;
2302 for _ in 0..100_000 {
2303 let result = pipe.readable(&mut metrics);
2304 assert_ne!(
2305 result,
2306 SessionResult::Close,
2307 "readable must not close while queued backend bytes remain"
2308 );
2309 if matches!(pipe.frontend_status, ConnectionStatus::WriteOpen) {
2310 saw_eof = true;
2311 break;
2312 }
2313 }
2314 assert!(
2315 saw_eof,
2316 "frontend EOF was never observed within the retry budget"
2317 );
2318 assert!(
2319 pipe.frontend_buffer.available_data() > 0,
2320 "the queued bytes must survive the frontend EOF"
2321 );
2322 assert!(
2323 pipe.backend_readiness.interest.is_writable()
2324 && pipe.backend_readiness.event.is_writable(),
2325 "observing EOF with queued bytes must arm backend WRITABLE to drain them"
2326 );
2327
2328 let result = pipe.backend_writable(&mut metrics);
2333
2334 assert_eq!(
2335 result,
2336 SessionResult::Close,
2337 "the single draining backend_writable call must close the session by itself -- \
2338 edge-triggered epoll already consumed the EOF edge and offers no other event to \
2339 hang the teardown on"
2340 );
2341 assert_eq!(
2342 pipe.frontend_buffer.available_data(),
2343 0,
2344 "the queued bytes must have fully drained before the session closed"
2345 );
2346
2347 let mut received = Vec::new();
2348 for _ in 0..100_000 {
2349 let mut buf = [0u8; 64];
2350 match backend_peer.read(&mut buf) {
2351 Ok(0) => break,
2352 Ok(n) => {
2353 received.extend_from_slice(&buf[..n]);
2354 if received.len() >= b"queued-request-tail".len() {
2355 break;
2356 }
2357 }
2358 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => continue,
2359 Err(_) => break,
2360 }
2361 }
2362 assert_eq!(
2363 received, b"queued-request-tail",
2364 "the backend must still receive the queued bytes byte-for-byte before the close"
2365 );
2366 }
2367
2368 #[test]
2379 fn frontend_hup_drains_inflight_request_bytes_before_closing() {
2380 let (frontend_peer, frontend_socket) = connected_pair();
2381 let (_backend_peer, backend_socket) = connected_pair();
2382
2383 let mut pool = Pool::with_capacity(2, 2, 4096);
2384 let backend_buffer = pool.checkout().expect("backend buffer");
2385 let mut frontend_buffer = pool.checkout().expect("frontend buffer");
2386 frontend_buffer
2387 .write_all(b"front-to-back-tail-still-queued")
2388 .expect("seed queued frontend bytes");
2389 let address = "127.0.0.1:0".parse().expect("test address");
2390 let listener = Rc::new(RefCell::new(TestListener { address }));
2391
2392 let mut pipe = Pipe::new(
2393 backend_buffer,
2394 None,
2395 Some(TcpStream::from_std(backend_socket)),
2396 None,
2397 None,
2398 None,
2399 None,
2400 frontend_buffer,
2401 Token(0),
2402 TcpStream::from_std(frontend_socket),
2403 listener,
2404 Protocol::TCP,
2405 Ulid::generate(),
2406 Ulid::generate(),
2407 None,
2408 WebSocketContext::Tcp,
2409 );
2410 pipe.set_back_token(Token(1));
2411 pipe.frontend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
2414 pipe.frontend_readiness.event = Ready::READABLE | Ready::HUP;
2415 pipe.backend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
2416 pipe.backend_readiness.event = Ready::EMPTY;
2417
2418 let mut metrics = SessionMetrics::new(Some(Duration::ZERO));
2419 let result = pipe.frontend_hup(&mut metrics);
2420
2421 assert_eq!(
2422 result,
2423 SessionResult::Continue,
2424 "frontend_hup must keep the session alive while request bytes are still in flight"
2425 );
2426 assert!(
2427 matches!(pipe.frontend_status, ConnectionStatus::Closed),
2428 "frontend_hup must still mark the frontend Closed even on the drain branch"
2429 );
2430 assert!(
2431 pipe.backend_readiness.interest.is_writable()
2432 && pipe.backend_readiness.event.is_writable(),
2433 "the drain branch must arm backend WRITABLE to flush the queued frontend bytes"
2434 );
2435 assert!(
2436 pipe.frontend_readiness.interest.is_readable(),
2437 "the drain branch must retain frontend READABLE interest to read the kernel tail to EOF"
2438 );
2439
2440 drop(frontend_peer);
2441 }
2442
2443 #[test]
2447 fn frontend_hup_closes_immediately_when_nothing_is_inflight() {
2448 let (frontend_peer, frontend_socket) = connected_pair();
2449 let (_backend_peer, backend_socket) = connected_pair();
2450
2451 let mut pool = Pool::with_capacity(2, 2, 4096);
2452 let backend_buffer = pool.checkout().expect("backend buffer");
2453 let frontend_buffer = pool.checkout().expect("frontend buffer");
2454 let address = "127.0.0.1:0".parse().expect("test address");
2455 let listener = Rc::new(RefCell::new(TestListener { address }));
2456
2457 let mut pipe = Pipe::new(
2458 backend_buffer,
2459 None,
2460 Some(TcpStream::from_std(backend_socket)),
2461 None,
2462 None,
2463 None,
2464 None,
2465 frontend_buffer,
2466 Token(0),
2467 TcpStream::from_std(frontend_socket),
2468 listener,
2469 Protocol::TCP,
2470 Ulid::generate(),
2471 Ulid::generate(),
2472 None,
2473 WebSocketContext::Tcp,
2474 );
2475 pipe.set_back_token(Token(1));
2476 pipe.frontend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
2478 pipe.frontend_readiness.event = Ready::HUP;
2479 pipe.backend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
2480 pipe.backend_readiness.event = Ready::EMPTY;
2481
2482 let mut metrics = SessionMetrics::new(Some(Duration::ZERO));
2483 let result = pipe.frontend_hup(&mut metrics);
2484
2485 assert_eq!(
2486 result,
2487 SessionResult::Close,
2488 "frontend_hup must close immediately when nothing is in flight (unchanged legacy behavior)"
2489 );
2490 assert!(
2491 matches!(pipe.frontend_status, ConnectionStatus::Closed),
2492 "frontend_hup must mark the frontend Closed"
2493 );
2494 assert!(
2495 !pipe.backend_readiness.event.is_writable(),
2496 "the close branch must not arm backend WRITABLE when there is nothing queued"
2497 );
2498
2499 drop(frontend_peer);
2500 }
2501
2502 #[cfg(all(target_os = "linux", feature = "splice"))]
2511 #[test]
2512 fn splice_readable_eof_drains_kernel_pipe_bytes_before_closing() {
2513 use std::io::Read;
2514
2515 let (mut frontend_peer, frontend_socket) = connected_pair();
2516 let (mut backend_peer, backend_socket) = connected_pair();
2517
2518 let mut pool = Pool::with_capacity(2, 2, 4096);
2519 let backend_buffer = pool.checkout().expect("backend buffer");
2520 let frontend_buffer = pool.checkout().expect("frontend buffer");
2523 let address = "127.0.0.1:0".parse().expect("test address");
2524 let listener = Rc::new(RefCell::new(TestListener { address }));
2525
2526 let mut pipe = Pipe::new(
2527 backend_buffer,
2528 None,
2529 Some(TcpStream::from_std(backend_socket)),
2530 None,
2531 None,
2532 None,
2533 None,
2534 frontend_buffer,
2535 Token(0),
2536 TcpStream::from_std(frontend_socket),
2537 listener,
2538 Protocol::TCP,
2539 Ulid::generate(),
2540 Ulid::generate(),
2541 None,
2542 WebSocketContext::Tcp,
2543 );
2544 pipe.set_back_token(Token(1));
2545 pipe.frontend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
2546 pipe.frontend_readiness.event = Ready::READABLE;
2547
2548 assert!(
2549 pipe.splice_pipe.is_some(),
2550 "Protocol::TCP must allocate the splice kernel pipe for this test to be meaningful"
2551 );
2552
2553 let payload = vec![0x5a_u8; 32 * 1024];
2556 let capacity = pipe.splice_capacity();
2563 assert!(
2564 capacity >= payload.len(),
2565 "environment precondition not met: splice pipe capacity is {capacity} bytes, \
2566 below the {}-byte payload; this user's pipe buffers likely exceed \
2567 fs.pipe-user-pages-soft ({}). Close pipe-heavy processes or raise the limit, \
2568 then rerun: this is a host limit, not a Sozu regression",
2569 payload.len(),
2570 std::fs::read_to_string("/proc/sys/fs/pipe-user-pages-soft")
2571 .map(|limit| limit.trim().to_owned())
2572 .unwrap_or_else(|error| format!("unreadable: {error}")),
2573 );
2574 frontend_peer
2575 .write_all(&payload)
2576 .expect("write frontend payload");
2577
2578 let mut metrics = SessionMetrics::new(Some(Duration::ZERO));
2582 for _ in 0..100_000 {
2583 if pipe.splice_in_pending() >= payload.len() {
2584 break;
2585 }
2586 let result = pipe.splice_readable(&mut metrics);
2587 assert_ne!(
2588 result,
2589 SessionResult::Close,
2590 "splice_readable must not close while splicing the payload in"
2591 );
2592 }
2593 assert_eq!(
2594 pipe.splice_in_pending(),
2595 payload.len(),
2596 "the whole payload must sit in the kernel in_pipe before EOF"
2597 );
2598
2599 drop(frontend_peer);
2602
2603 let mut saw_eof = false;
2606 for _ in 0..100_000 {
2607 let result = pipe.splice_readable(&mut metrics);
2608 assert_ne!(
2609 result,
2610 SessionResult::Close,
2611 "splice_readable must not close while {} kernel-pipe bytes remain",
2612 pipe.splice_in_pending()
2613 );
2614 if matches!(pipe.frontend_status, ConnectionStatus::WriteOpen) {
2615 saw_eof = true;
2616 break;
2617 }
2618 }
2619 assert!(
2620 saw_eof,
2621 "frontend EOF was never observed within the retry budget"
2622 );
2623 assert_eq!(
2624 pipe.splice_in_pending(),
2625 payload.len(),
2626 "the kernel-pipe bytes must survive the frontend EOF, not be dropped"
2627 );
2628 assert!(
2629 pipe.backend_readiness.interest.is_writable()
2630 && pipe.backend_readiness.event.is_writable(),
2631 "EOF with kernel-pipe bytes pending must arm backend WRITABLE to drain them"
2632 );
2633
2634 let mut received = Vec::with_capacity(payload.len());
2646 let mut closed_during_drain = false;
2647 for _ in 0..100_000 {
2648 let drain = pipe.splice_backend_writable(&mut metrics);
2649 if drain == SessionResult::Close {
2650 assert_eq!(
2651 pipe.splice_in_pending(),
2652 0,
2653 "the session must only close once the kernel pipe is fully drained, \
2654 never mid-flight"
2655 );
2656 closed_during_drain = true;
2657 }
2658 let mut buf = [0u8; 16384];
2659 match backend_peer.read(&mut buf) {
2660 Ok(0) => break,
2661 Ok(n) => received.extend_from_slice(&buf[..n]),
2662 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {}
2663 Err(_) => break,
2664 }
2665 if received.len() >= payload.len() {
2666 break;
2667 }
2668 }
2669 assert_eq!(
2670 received, payload,
2671 "the backend must receive the spliced tail byte-for-byte, not a truncation"
2672 );
2673 assert_eq!(
2674 pipe.splice_in_pending(),
2675 0,
2676 "the kernel in_pipe must be fully drained"
2677 );
2678 assert!(
2679 closed_during_drain,
2680 "the fix under test (sozu-proxy/sozu#1290) must close the session as soon as \
2681 the kernel pipe drains, without waiting for a separate re-observation event"
2682 );
2683
2684 assert!(
2690 !pipe.check_connections(),
2691 "with nothing inflight and the frontend closed, the session must be closeable"
2692 );
2693 assert_eq!(
2694 pipe.splice_readable(&mut metrics),
2695 SessionResult::Close,
2696 "re-observing EOF with nothing inflight must (still) close the session"
2697 );
2698 }
2699
2700 #[cfg(all(target_os = "linux", feature = "splice"))]
2716 #[test]
2717 fn splice_backend_readable_keeps_readable_event_when_pipe_slots_are_exhausted() {
2718 use std::os::unix::io::AsRawFd;
2719
2720 fn unread_bytes(socket: &TcpStream) -> usize {
2721 let mut n: libc::c_int = 0;
2722 let ret = unsafe { libc::ioctl(socket.as_raw_fd(), libc::FIONREAD, &mut n) };
2723 assert_eq!(ret, 0, "FIONREAD must succeed");
2724 n as usize
2725 }
2726
2727 let (_frontend_peer, frontend_socket) = connected_pair();
2728 let (mut backend_peer, backend_socket) = connected_pair();
2729
2730 let mut pool = Pool::with_capacity(2, 2, 4096);
2731 let backend_buffer = pool.checkout().expect("backend buffer");
2732 let frontend_buffer = pool.checkout().expect("frontend buffer");
2733 let address = "127.0.0.1:0".parse().expect("test address");
2734 let listener = Rc::new(RefCell::new(TestListener { address }));
2735
2736 let mut pipe = Pipe::new(
2737 backend_buffer,
2738 None,
2739 Some(TcpStream::from_std(backend_socket)),
2740 None,
2741 None,
2742 None,
2743 None,
2744 frontend_buffer,
2745 Token(0),
2746 TcpStream::from_std(frontend_socket),
2747 listener,
2748 Protocol::TCP,
2749 Ulid::generate(),
2750 Ulid::generate(),
2751 None,
2752 WebSocketContext::Tcp,
2753 );
2754 pipe.set_back_token(Token(1));
2755 pipe.frontend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
2756 pipe.backend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
2757 pipe.backend_readiness.event = Ready::READABLE;
2758
2759 let slot = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
2761 assert!(slot > 0, "sysconf(_SC_PAGESIZE) must succeed");
2762 {
2763 let splice_pipe = pipe
2764 .splice_pipe
2765 .as_mut()
2766 .expect("Protocol::TCP must allocate the splice kernel pipe");
2767 for fd in [splice_pipe.in_pipe[0], splice_pipe.out_pipe[0]] {
2768 let ret = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, slot as libc::c_int) };
2769 assert_eq!(ret, slot as libc::c_int, "F_SETPIPE_SZ(page) must succeed");
2770 }
2771 splice_pipe.capacity = slot;
2772 }
2773 let capacity = pipe.splice_capacity();
2774 assert_eq!(capacity, slot);
2775
2776 let payload = vec![0x5a_u8; 2 * slot];
2778 backend_peer
2779 .write_all(&payload)
2780 .expect("write backend payload");
2781 for _ in 0..1000 {
2782 if unread_bytes(pipe.backend_socket.as_ref().unwrap()) >= payload.len() {
2783 break;
2784 }
2785 std::thread::sleep(Duration::from_millis(1));
2786 }
2787 assert_eq!(
2788 unread_bytes(pipe.backend_socket.as_ref().unwrap()),
2789 payload.len(),
2790 "the whole payload must be queued in the backend socket"
2791 );
2792
2793 let mut metrics = SessionMetrics::new(Some(Duration::ZERO));
2796 assert_ne!(
2797 pipe.splice_backend_readable(&mut metrics),
2798 SessionResult::Close
2799 );
2800 let first = pipe.splice_out_pending();
2801 assert!(
2802 first > 0 && first <= slot,
2803 "the single slot must absorb part of the payload (got {first})"
2804 );
2805 assert!(
2806 pipe.backend_readiness.event.is_readable(),
2807 "the READABLE event must survive a successful splice"
2808 );
2809
2810 {
2813 let pipe_read_end = pipe.splice_pipe.as_ref().unwrap().out_pipe[0];
2814 let (drained, res) = splice::splice_out(pipe_read_end, pipe.frontend.socket_ref(), 1);
2815 assert_eq!((drained, res), (1, SocketResult::Continue));
2816 pipe.splice_pipe.as_mut().unwrap().out_pipe_pending -= drained;
2817 }
2818 assert!(pipe.splice_out_pending() < capacity);
2819
2820 pipe.backend_readiness.interest.insert(Ready::READABLE);
2823 assert_ne!(
2824 pipe.splice_backend_readable(&mut metrics),
2825 SessionResult::Close
2826 );
2827
2828 let still_queued = unread_bytes(pipe.backend_socket.as_ref().unwrap());
2829 assert_eq!(
2830 still_queued,
2831 payload.len() - first,
2832 "precondition: the rest of the payload must still sit in the socket"
2833 );
2834 assert!(
2835 pipe.backend_readiness.event.is_readable(),
2836 "READABLE event must survive a pipe-full EAGAIN: {still_queued} bytes are still \
2837 queued in the backend socket and edge-triggered epoll will never re-signal them"
2838 );
2839 assert!(
2840 !pipe.backend_readiness.interest.is_readable(),
2841 "READABLE interest must be parked while the pipe is full, or the ready loop spins"
2842 );
2843 assert!(
2844 pipe.frontend_readiness.event.is_writable()
2845 && pipe.frontend_readiness.interest.is_writable(),
2846 "the frontend must be armed writable to drain the pipe"
2847 );
2848
2849 assert_ne!(pipe.splice_writable(&mut metrics), SessionResult::Close);
2854 assert_eq!(
2855 pipe.splice_out_pending(),
2856 0,
2857 "the pipe must be fully drained"
2858 );
2859 assert!(
2860 pipe.backend_readiness.interest.is_readable()
2861 && pipe.backend_readiness.event.is_readable(),
2862 "draining the pipe must re-arm the backend read"
2863 );
2864 assert_ne!(
2865 pipe.splice_backend_readable(&mut metrics),
2866 SessionResult::Close
2867 );
2868 assert!(
2869 unread_bytes(pipe.backend_socket.as_ref().unwrap()) < still_queued,
2870 "the remaining response bytes must start leaving the backend socket"
2871 );
2872 assert!(pipe.splice_out_pending() > 0);
2873
2874 for _ in 0..1000 {
2876 if unread_bytes(pipe.backend_socket.as_ref().unwrap()) == 0 {
2877 break;
2878 }
2879 assert_ne!(pipe.splice_writable(&mut metrics), SessionResult::Close);
2880 pipe.backend_readiness.interest.insert(Ready::READABLE);
2881 assert_ne!(
2882 pipe.splice_backend_readable(&mut metrics),
2883 SessionResult::Close
2884 );
2885 }
2886 assert_eq!(unread_bytes(pipe.backend_socket.as_ref().unwrap()), 0);
2887 if pipe.splice_out_pending() == 0 {
2888 backend_peer.write_all(&payload[..slot]).expect("write");
2890 for _ in 0..1000 {
2891 if unread_bytes(pipe.backend_socket.as_ref().unwrap()) >= slot {
2892 break;
2893 }
2894 std::thread::sleep(Duration::from_millis(1));
2895 }
2896 pipe.backend_readiness.interest.insert(Ready::READABLE);
2897 pipe.backend_readiness.event.insert(Ready::READABLE);
2898 assert_ne!(
2899 pipe.splice_backend_readable(&mut metrics),
2900 SessionResult::Close
2901 );
2902 assert_eq!(unread_bytes(pipe.backend_socket.as_ref().unwrap()), 0);
2903 }
2904 assert!(pipe.splice_out_pending() > 0);
2905
2906 pipe.backend_readiness.interest.insert(Ready::READABLE);
2911 assert_ne!(
2912 pipe.splice_backend_readable(&mut metrics),
2913 SessionResult::Close
2914 );
2915 assert!(pipe.backend_readiness.event.is_readable());
2916 assert!(!pipe.backend_readiness.interest.is_readable());
2917 assert_ne!(pipe.splice_writable(&mut metrics), SessionResult::Close);
2918 assert_eq!(pipe.splice_out_pending(), 0);
2919 assert_ne!(
2920 pipe.splice_backend_readable(&mut metrics),
2921 SessionResult::Close
2922 );
2923 assert!(
2924 !pipe.backend_readiness.event.is_readable(),
2925 "a drained socket with an empty pipe must clear the READABLE event"
2926 );
2927 }
2928
2929 #[cfg(all(target_os = "linux", feature = "splice"))]
2930 fn exercise_splice_readable_pipe_full_recovery(force_growth_refusal: bool) -> bool {
2931 use std::{io::Read, os::unix::io::AsRawFd};
2932
2933 fn unread_bytes(socket: &TcpStream) -> usize {
2934 let mut n: libc::c_int = 0;
2935 let ret = unsafe { libc::ioctl(socket.as_raw_fd(), libc::FIONREAD, &mut n) };
2936 assert_eq!(ret, 0, "FIONREAD must succeed");
2937 n as usize
2938 }
2939
2940 fn discard_in_pipe(pipe: &mut Pipe<TcpStream, TestListener>) {
2941 let pending = pipe.splice_in_pending();
2942 let pipe_read_end = pipe.splice_pipe.as_ref().unwrap().in_pipe[0];
2943 let mut discarded = vec![0_u8; pending];
2944 let mut offset = 0;
2945 while offset < pending {
2946 let read = unsafe {
2949 libc::read(
2950 pipe_read_end,
2951 discarded[offset..].as_mut_ptr().cast(),
2952 pending - offset,
2953 )
2954 };
2955 assert!(
2956 read > 0,
2957 "read must drain the populated pipe: {}",
2958 std::io::Error::last_os_error()
2959 );
2960 offset += read as usize;
2961 }
2962 pipe.splice_pipe.as_mut().unwrap().in_pipe_pending = 0;
2963 }
2964
2965 fn request_pipe_capacity(
2966 fd: libc::c_int,
2967 requested: usize,
2968 force_growth_refusal: bool,
2969 ) -> usize {
2970 let requested = if force_growth_refusal {
2973 -1
2974 } else {
2975 libc::c_int::try_from(requested).expect("requested pipe size must fit c_int")
2976 };
2977 let set_ret = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, requested) };
2978 if force_growth_refusal {
2979 assert_eq!(set_ret, -1, "negative pipe growth must be refused");
2980 } else if set_ret < 0 {
2981 eprintln!(
2982 "F_SETPIPE_SZ({requested}) unavailable for test pipe: {}",
2983 std::io::Error::last_os_error()
2984 );
2985 }
2986 let actual = unsafe { libc::fcntl(fd, libc::F_GETPIPE_SZ) };
2988 assert!(actual > 0, "F_GETPIPE_SZ must succeed");
2989 actual as usize
2990 }
2991
2992 fn set_buffer(socket: &impl AsRawFd, opt: libc::c_int, bytes: libc::c_int) {
2993 let ret = unsafe {
2994 libc::setsockopt(
2995 socket.as_raw_fd(),
2996 libc::SOL_SOCKET,
2997 opt,
2998 &bytes as *const libc::c_int as *const libc::c_void,
2999 std::mem::size_of::<libc::c_int>() as libc::socklen_t,
3000 )
3001 };
3002 assert_eq!(ret, 0, "setsockopt must succeed");
3003 }
3004
3005 fn tiny_backend_pair() -> (StdTcpStream, StdTcpStream) {
3012 let listener = StdTcpListener::bind("127.0.0.1:0").expect("bind test listener");
3013 set_buffer(&listener, libc::SO_RCVBUF, 4096);
3014 let address = listener.local_addr().expect("listener local addr");
3015 let socket = socket2_connect_with_sndbuf(address, 4096);
3016 let (peer, _) = listener.accept().expect("accept test server");
3017 socket.set_nonblocking(true).expect("client nonblocking");
3018 peer.set_nonblocking(true).expect("server nonblocking");
3019 (peer, socket)
3020 }
3021
3022 fn socket2_connect_with_sndbuf(address: SocketAddr, bytes: libc::c_int) -> StdTcpStream {
3023 use std::os::unix::io::FromRawFd;
3024 let fd =
3025 unsafe { libc::socket(libc::AF_INET, libc::SOCK_STREAM | libc::SOCK_CLOEXEC, 0) };
3026 assert!(fd >= 0, "socket() must succeed");
3027 let socket = unsafe { StdTcpStream::from_raw_fd(fd) };
3028 set_buffer(&socket, libc::SO_SNDBUF, bytes);
3029 let SocketAddr::V4(v4) = address else {
3030 panic!("test listener must be IPv4")
3031 };
3032 let sin = libc::sockaddr_in {
3033 sin_family: libc::AF_INET as libc::sa_family_t,
3034 sin_port: v4.port().to_be(),
3035 sin_addr: libc::in_addr {
3036 s_addr: u32::from_ne_bytes(v4.ip().octets()),
3037 },
3038 sin_zero: [0; 8],
3039 };
3040 let ret = unsafe {
3041 libc::connect(
3042 fd,
3043 &sin as *const libc::sockaddr_in as *const libc::sockaddr,
3044 std::mem::size_of::<libc::sockaddr_in>() as libc::socklen_t,
3045 )
3046 };
3047 assert_eq!(
3048 ret,
3049 0,
3050 "connect() must succeed: {}",
3051 std::io::Error::last_os_error()
3052 );
3053 socket
3054 }
3055
3056 let (mut frontend_peer, frontend_socket) = connected_pair();
3057 let (mut backend_peer, backend_socket) = tiny_backend_pair();
3058
3059 let mut pool = Pool::with_capacity(2, 2, 4096);
3060 let backend_buffer = pool.checkout().expect("backend buffer");
3061 let frontend_buffer = pool.checkout().expect("frontend buffer");
3062 let address = "127.0.0.1:0".parse().expect("test address");
3063 let listener = Rc::new(RefCell::new(TestListener { address }));
3064
3065 let mut pipe = Pipe::new(
3066 backend_buffer,
3067 None,
3068 Some(TcpStream::from_std(backend_socket)),
3069 None,
3070 None,
3071 None,
3072 None,
3073 frontend_buffer,
3074 Token(0),
3075 TcpStream::from_std(frontend_socket),
3076 listener,
3077 Protocol::TCP,
3078 Ulid::generate(),
3079 Ulid::generate(),
3080 None,
3081 WebSocketContext::Tcp,
3082 );
3083 pipe.set_back_token(Token(1));
3084 pipe.frontend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
3085 pipe.frontend_readiness.event = Ready::READABLE;
3086 pipe.backend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
3087
3088 let slot = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
3089 assert!(slot > 0, "sysconf(_SC_PAGESIZE) must succeed");
3090 {
3091 let splice_pipe = pipe
3092 .splice_pipe
3093 .as_mut()
3094 .expect("Protocol::TCP must allocate the splice kernel pipe");
3095 for fd in [splice_pipe.in_pipe[0], splice_pipe.out_pipe[0]] {
3096 let ret = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, slot as libc::c_int) };
3097 assert_eq!(ret, slot as libc::c_int, "F_SETPIPE_SZ(page) must succeed");
3098 }
3099 splice_pipe.capacity = slot;
3100 }
3101
3102 let payload = vec![0x5a_u8; 3 * slot];
3105 frontend_peer
3106 .write_all(&payload)
3107 .expect("write frontend payload");
3108 for _ in 0..1000 {
3109 if unread_bytes(pipe.frontend.socket_ref()) >= payload.len() {
3110 break;
3111 }
3112 std::thread::sleep(Duration::from_millis(1));
3113 }
3114 assert_eq!(unread_bytes(pipe.frontend.socket_ref()), payload.len());
3115
3116 let mut metrics = SessionMetrics::new(Some(Duration::ZERO));
3117 assert_ne!(pipe.splice_readable(&mut metrics), SessionResult::Close);
3118 let first = pipe.splice_in_pending();
3119 assert!(
3120 first > 0 && first <= slot,
3121 "one slot absorbed (got {first})"
3122 );
3123
3124 {
3126 let pipe_read_end = pipe.splice_pipe.as_ref().unwrap().in_pipe[0];
3127 let backend = pipe.backend_socket.as_ref().unwrap();
3128 let (drained, res) = splice::splice_out(pipe_read_end, backend, 1);
3129 assert_eq!((drained, res), (1, SocketResult::Continue));
3130 pipe.splice_pipe.as_mut().unwrap().in_pipe_pending -= drained;
3131 }
3132
3133 pipe.frontend_readiness.interest.insert(Ready::READABLE);
3134 assert_ne!(pipe.splice_readable(&mut metrics), SessionResult::Close);
3135 let still_queued = unread_bytes(pipe.frontend.socket_ref());
3136 assert_eq!(still_queued, payload.len() - first);
3137 assert!(
3138 pipe.frontend_readiness.event.is_readable(),
3139 "READABLE event must survive a pipe-full EAGAIN with {still_queued} bytes queued"
3140 );
3141 assert!(
3142 !pipe.frontend_readiness.interest.is_readable(),
3143 "READABLE interest must be parked while the pipe is full"
3144 );
3145 assert!(
3146 pipe.backend_readiness.event.is_writable()
3147 && pipe.backend_readiness.interest.is_writable(),
3148 "the backend must be armed writable to drain the pipe"
3149 );
3150
3151 let mut backend_buf = [0_u8; 16 * 1024];
3156 for _ in 0..1000 {
3157 assert_ne!(
3158 pipe.splice_backend_writable(&mut metrics),
3159 SessionResult::Close
3160 );
3161 loop {
3162 match backend_peer.read(&mut backend_buf) {
3163 Ok(0) => panic!("backend peer closed while draining the pipe"),
3164 Ok(_) => {}
3165 Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => break,
3166 Err(error) => panic!("failed to read drained pipe bytes: {error}"),
3167 }
3168 }
3169 if pipe.splice_in_pending() == 0 {
3170 break;
3171 }
3172 std::thread::sleep(Duration::from_millis(1));
3173 }
3174 assert_eq!(
3175 pipe.splice_in_pending(),
3176 0,
3177 "the single-slot pipe must fully drain"
3178 );
3179 assert!(
3180 pipe.frontend_readiness.interest.is_readable()
3181 && pipe.frontend_readiness.event.is_readable(),
3182 "the real backend drain must re-arm the preserved frontend read"
3183 );
3184 let unread_before_retry = unread_bytes(pipe.frontend.socket_ref());
3185 assert_eq!(unread_before_retry, still_queued);
3186 assert_ne!(pipe.splice_readable(&mut metrics), SessionResult::Close);
3187 let unread_after_retry = unread_bytes(pipe.frontend.socket_ref());
3188 assert!(
3189 unread_after_retry < unread_before_retry,
3190 "the preserved READABLE event must resume frontend progress after the drain \
3191 ({unread_before_retry} before, {unread_after_retry} after)"
3192 );
3193 assert!(pipe.splice_in_pending() > 0);
3194
3195 discard_in_pipe(&mut pipe);
3200 let requested_capacity = slot.checked_mul(16).expect("pipe size must fit usize");
3201 let capacity = {
3202 let splice_pipe = pipe.splice_pipe.as_mut().unwrap();
3203 let in_capacity = request_pipe_capacity(
3204 splice_pipe.in_pipe[0],
3205 requested_capacity,
3206 force_growth_refusal,
3207 );
3208 let out_capacity = request_pipe_capacity(
3209 splice_pipe.out_pipe[0],
3210 requested_capacity,
3211 force_growth_refusal,
3212 );
3213 let capacity = in_capacity.min(out_capacity);
3214 splice_pipe.capacity = capacity;
3215 capacity
3216 };
3217 let minimum_capacity = slot
3218 .checked_mul(2)
3219 .expect("minimum pipe size must fit usize")
3220 .max(32 * 1024);
3221 if capacity < minimum_capacity {
3222 eprintln!(
3223 "skipping multi-slot partial-drain phase: realised capacity {capacity} is below \
3224 required {minimum_capacity}"
3225 );
3226 return false;
3227 }
3228
3229 frontend_peer
3233 .write_all(&vec![0x5a_u8; capacity])
3234 .expect("write more frontend payload");
3235 for _ in 0..1000 {
3236 if pipe.splice_in_pending() >= capacity / 2 {
3237 break;
3238 }
3239 pipe.frontend_readiness.interest.insert(Ready::READABLE);
3240 pipe.frontend_readiness.event.insert(Ready::READABLE);
3241 assert_ne!(pipe.splice_readable(&mut metrics), SessionResult::Close);
3242 std::thread::sleep(Duration::from_millis(1));
3243 }
3244 assert!(
3245 pipe.splice_in_pending() >= capacity / 2,
3246 "the pipe must hold at least half its realised capacity (got {})",
3247 pipe.splice_in_pending()
3248 );
3249 pipe.frontend_readiness.interest.remove(Ready::READABLE);
3250
3251 let pending_before_drain = pipe.splice_in_pending();
3255 assert_ne!(
3256 pipe.splice_backend_writable(&mut metrics),
3257 SessionResult::Close
3258 );
3259 let pending = pipe.splice_in_pending();
3260 assert!(
3261 pending < pending_before_drain,
3262 "the backend must accept some bytes ({pending_before_drain} pending)"
3263 );
3264 assert!(
3265 pending > 0,
3266 "the drain must be partial, not complete (accepted {})",
3267 pending_before_drain - pending
3268 );
3269 assert!(
3270 pipe.frontend_readiness.interest.is_readable(),
3271 "a partial drain of the pipe must re-arm the frontend read"
3272 );
3273
3274 discard_in_pipe(&mut pipe);
3278 for _ in 0..1000 {
3279 if unread_bytes(pipe.frontend.socket_ref()) > 0 {
3280 break;
3281 }
3282 std::thread::sleep(Duration::from_millis(1));
3283 }
3284 let unread_before_retry = unread_bytes(pipe.frontend.socket_ref());
3285 assert!(
3286 unread_before_retry > 0,
3287 "request bytes must remain queued for the free-slot retry"
3288 );
3289 assert!(
3290 pipe.frontend_readiness.interest.is_readable()
3291 && pipe.frontend_readiness.event.is_readable(),
3292 "the partial backend drain must leave the frontend retry runnable"
3293 );
3294 assert_ne!(pipe.splice_readable(&mut metrics), SessionResult::Close);
3295 let unread_after_retry = unread_bytes(pipe.frontend.socket_ref());
3296 assert!(
3297 unread_after_retry < unread_before_retry,
3298 "a retry with free pipe slots must consume request bytes \
3299 ({unread_before_retry} before, {unread_after_retry} after)"
3300 );
3301 assert!(pipe.splice_in_pending() > 0);
3302 true
3303 }
3304
3305 #[cfg(all(target_os = "linux", feature = "splice"))]
3310 #[test]
3311 fn splice_readable_keeps_readable_event_when_pipe_slots_are_exhausted() {
3312 exercise_splice_readable_pipe_full_recovery(false);
3313 }
3314
3315 #[cfg(all(target_os = "linux", feature = "splice"))]
3316 #[test]
3317 fn splice_readable_recovers_when_pipe_growth_is_refused() {
3318 assert!(!exercise_splice_readable_pipe_full_recovery(true));
3319 }
3320 #[test]
3335 fn backend_address_is_reported_without_asking_the_socket() {
3336 let refused = {
3337 let listener = StdTcpListener::bind("127.0.0.1:0").expect("bind a throwaway listener");
3338 listener
3339 .local_addr()
3340 .expect("throwaway listener local addr")
3341 };
3342 let backend_socket = TcpStream::connect(refused).expect("a nonblocking connect starts");
3343 let (_frontend_peer, frontend_socket) = connected_pair();
3344
3345 let mut pool = Pool::with_capacity(2, 2, 4096);
3346 let backend_buffer = pool.checkout().expect("backend buffer");
3347 let frontend_buffer = pool.checkout().expect("frontend buffer");
3348 let address = "127.0.0.1:0".parse().expect("test address");
3349 let listener = Rc::new(RefCell::new(TestListener { address }));
3350 let backend = Rc::new(RefCell::new(Backend::new(
3351 "backend-refused",
3352 refused,
3353 None,
3354 None,
3355 None,
3356 )));
3357
3358 let mut pipe = Pipe::new(
3359 backend_buffer,
3360 Some("backend-refused".to_owned()),
3361 Some(backend_socket),
3362 Some(backend),
3363 None,
3364 None,
3365 None,
3366 frontend_buffer,
3367 Token(0),
3368 TcpStream::from_std(frontend_socket),
3369 listener,
3370 Protocol::HTTP,
3371 Ulid::generate(),
3372 Ulid::generate(),
3373 None,
3374 WebSocketContext::Tcp,
3375 );
3376 assert_eq!(
3377 pipe.get_backend_address(),
3378 Some(refused),
3379 "the address of the backend handed to Pipe::new must be reported \
3380 even though its socket was refused"
3381 );
3382
3383 let retried: SocketAddr = "192.0.2.7:4242".parse().expect("retry address");
3384 pipe.set_backend_address(Some(retried));
3385 assert_eq!(
3386 pipe.get_backend_address(),
3387 Some(retried),
3388 "a later dial must replace the reported backend address"
3389 );
3390 }
3391}