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
1835#[cfg(test)]
1836mod tests {
1837 use std::{
1838 collections::BTreeMap,
1839 io::Write,
1840 net::{TcpListener as StdTcpListener, TcpStream as StdTcpStream},
1841 time::Duration,
1842 };
1843
1844 use super::*;
1845 use crate::pool::Pool;
1846
1847 struct TestListener {
1848 address: SocketAddr,
1849 }
1850
1851 impl ListenerHandler for TestListener {
1852 fn get_addr(&self) -> &SocketAddr {
1853 &self.address
1854 }
1855
1856 fn get_tags(&self, _key: &str) -> Option<&sozu_command::logging::CachedTags> {
1857 None
1858 }
1859
1860 fn set_tags(&mut self, _key: String, _tags: Option<BTreeMap<String, String>>) {}
1861
1862 fn protocol(&self) -> Protocol {
1863 Protocol::HTTP
1864 }
1865
1866 fn public_address(&self) -> SocketAddr {
1867 self.address
1868 }
1869 }
1870
1871 fn connected_pair() -> (StdTcpStream, StdTcpStream) {
1872 let listener = StdTcpListener::bind("127.0.0.1:0").expect("bind test listener");
1873 let address = listener.local_addr().expect("listener local addr");
1874 let client = StdTcpStream::connect(address).expect("connect test client");
1875 let (server, _) = listener.accept().expect("accept test server");
1876 client.set_nonblocking(true).expect("client nonblocking");
1877 server.set_nonblocking(true).expect("server nonblocking");
1878 (client, server)
1879 }
1880
1881 #[test]
1882 fn backend_readable_arms_frontend_writable_event_when_buffering_response() {
1883 let (frontend_peer, frontend_socket) = connected_pair();
1884 let (mut backend_peer, backend_socket) = connected_pair();
1885
1886 let mut pool = Pool::with_capacity(2, 2, 4096);
1887 let backend_buffer = pool.checkout().expect("backend buffer");
1888 let frontend_buffer = pool.checkout().expect("frontend buffer");
1889 let address = "127.0.0.1:0".parse().expect("test address");
1890 let listener = Rc::new(RefCell::new(TestListener { address }));
1891
1892 let mut pipe = Pipe::new(
1893 backend_buffer,
1894 None,
1895 Some(TcpStream::from_std(backend_socket)),
1896 None,
1897 None,
1898 None,
1899 None,
1900 frontend_buffer,
1901 Token(0),
1902 TcpStream::from_std(frontend_socket),
1903 listener,
1904 Protocol::HTTP,
1905 Ulid::generate(),
1906 Ulid::generate(),
1907 None,
1908 WebSocketContext::Tcp,
1909 );
1910 pipe.set_back_token(Token(1));
1911 pipe.frontend_readiness.event = Ready::EMPTY;
1912 pipe.frontend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
1913 pipe.backend_readiness.event = Ready::READABLE;
1914 pipe.backend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
1915
1916 backend_peer
1917 .write_all(b"server-speaks-first")
1918 .expect("write backend payload");
1919
1920 let mut metrics = SessionMetrics::new(Some(Duration::ZERO));
1921 assert_eq!(pipe.backend_readable(&mut metrics), SessionResult::Continue);
1922
1923 assert!(
1924 pipe.backend_buffer.available_data() > 0,
1925 "backend_readable must buffer backend bytes"
1926 );
1927 assert!(
1928 pipe.frontend_readiness.interest.is_writable(),
1929 "buffered backend bytes must arm frontend WRITABLE interest"
1930 );
1931 assert!(
1932 pipe.frontend_readiness.event.is_writable(),
1933 "buffered backend bytes must queue a frontend WRITABLE event"
1934 );
1935
1936 drop(frontend_peer);
1937 }
1938
1939 #[test]
1940 fn frontend_readable_arms_backend_writable_event_when_buffering_request() {
1941 let (mut frontend_peer, frontend_socket) = connected_pair();
1942 let (backend_peer, backend_socket) = connected_pair();
1943
1944 let mut pool = Pool::with_capacity(2, 2, 4096);
1945 let backend_buffer = pool.checkout().expect("backend buffer");
1946 let frontend_buffer = pool.checkout().expect("frontend buffer");
1947 let address = "127.0.0.1:0".parse().expect("test address");
1948 let listener = Rc::new(RefCell::new(TestListener { address }));
1949
1950 let mut pipe = Pipe::new(
1951 backend_buffer,
1952 None,
1953 Some(TcpStream::from_std(backend_socket)),
1954 None,
1955 None,
1956 None,
1957 None,
1958 frontend_buffer,
1959 Token(0),
1960 TcpStream::from_std(frontend_socket),
1961 listener,
1962 Protocol::HTTP,
1963 Ulid::generate(),
1964 Ulid::generate(),
1965 None,
1966 WebSocketContext::Tcp,
1967 );
1968 pipe.set_back_token(Token(1));
1969 pipe.frontend_readiness.event = Ready::READABLE;
1970 pipe.frontend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
1971 pipe.backend_readiness.event = Ready::EMPTY;
1972 pipe.backend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
1973
1974 frontend_peer
1975 .write_all(b"client-speaks-after-upgrade")
1976 .expect("write frontend payload");
1977
1978 let mut metrics = SessionMetrics::new(Some(Duration::ZERO));
1979 assert_eq!(pipe.readable(&mut metrics), SessionResult::Continue);
1980
1981 assert!(
1982 pipe.frontend_buffer.available_data() > 0,
1983 "readable must buffer frontend bytes"
1984 );
1985 assert!(
1986 pipe.backend_readiness.interest.is_writable(),
1987 "buffered frontend bytes must arm backend WRITABLE interest"
1988 );
1989 assert!(
1990 pipe.backend_readiness.event.is_writable(),
1991 "buffered frontend bytes must queue a backend WRITABLE event"
1992 );
1993
1994 drop(backend_peer);
1995 }
1996
1997 #[test]
1998 fn restore_readiness_events_rearms_inherited_buffered_writes() {
1999 let (frontend_peer, frontend_socket) = connected_pair();
2000 let (backend_peer, backend_socket) = connected_pair();
2001
2002 let mut pool = Pool::with_capacity(2, 2, 4096);
2003 let mut backend_buffer = pool.checkout().expect("backend buffer");
2004 let mut frontend_buffer = pool.checkout().expect("frontend buffer");
2005 backend_buffer
2006 .write_all(b"backend bytes inherited from 101 read")
2007 .expect("write backend buffer");
2008 frontend_buffer
2009 .write_all(b"frontend bytes inherited from upgrade read")
2010 .expect("write frontend buffer");
2011 let address = "127.0.0.1:0".parse().expect("test address");
2012 let listener = Rc::new(RefCell::new(TestListener { address }));
2013
2014 let mut pipe = Pipe::new(
2015 backend_buffer,
2016 None,
2017 Some(TcpStream::from_std(backend_socket)),
2018 None,
2019 None,
2020 None,
2021 None,
2022 frontend_buffer,
2023 Token(0),
2024 TcpStream::from_std(frontend_socket),
2025 listener,
2026 Protocol::HTTP,
2027 Ulid::generate(),
2028 Ulid::generate(),
2029 None,
2030 WebSocketContext::Tcp,
2031 );
2032
2033 pipe.restore_readiness_events(Ready::EMPTY, Ready::EMPTY);
2034
2035 assert!(
2036 pipe.frontend_readiness.event.is_writable(),
2037 "restoring inherited frontend events must not park backend-buffered bytes"
2038 );
2039 assert!(
2040 pipe.backend_readiness.event.is_writable(),
2041 "restoring inherited backend events must not park frontend-buffered bytes"
2042 );
2043
2044 drop(frontend_peer);
2045 drop(backend_peer);
2046 }
2047
2048 #[cfg(all(target_os = "linux", feature = "splice"))]
2059 #[test]
2060 fn backend_writable_drains_inherited_frontend_buffer_before_splice_engages() {
2061 use std::io::Read;
2062
2063 let (frontend_peer, frontend_socket) = connected_pair();
2064 let (mut backend_peer, backend_socket) = connected_pair();
2065
2066 let mut pool = Pool::with_capacity(2, 2, 4096);
2067 let backend_buffer = pool.checkout().expect("backend buffer");
2068 let mut frontend_buffer = pool.checkout().expect("frontend buffer");
2069 frontend_buffer
2070 .write_all(b"inherited-preread-client-hello")
2071 .expect("write inherited frontend buffer");
2072 let address = "127.0.0.1:0".parse().expect("test address");
2073 let listener = Rc::new(RefCell::new(TestListener { address }));
2074
2075 let mut pipe = Pipe::new(
2076 backend_buffer,
2077 None,
2078 Some(TcpStream::from_std(backend_socket)),
2079 None,
2080 None,
2081 None,
2082 None,
2083 frontend_buffer,
2084 Token(0),
2085 TcpStream::from_std(frontend_socket),
2086 listener,
2087 Protocol::TCP,
2088 Ulid::generate(),
2089 Ulid::generate(),
2090 None,
2091 WebSocketContext::Tcp,
2092 );
2093 pipe.set_back_token(Token(1));
2094
2095 assert!(
2096 pipe.splice_pipe.is_some(),
2097 "Protocol::TCP must allocate the splice kernel pipe for this test to be meaningful"
2098 );
2099 assert!(
2100 pipe.frontend_buffer.available_data() > 0,
2101 "test setup must inherit a non-empty frontend buffer"
2102 );
2103
2104 let mut metrics = SessionMetrics::new(Some(Duration::ZERO));
2105 assert_eq!(pipe.backend_writable(&mut metrics), SessionResult::Continue);
2106
2107 assert_eq!(
2108 pipe.frontend_buffer.available_data(),
2109 0,
2110 "inherited preread bytes must drain through the buffered path before splice engages"
2111 );
2112
2113 let mut received = [0u8; 64];
2114 let n = backend_peer
2115 .read(&mut received)
2116 .expect("backend socket must have received the drained preread bytes");
2117 assert_eq!(&received[..n], b"inherited-preread-client-hello");
2118
2119 drop(frontend_peer);
2120 }
2121
2122 #[test]
2131 fn frontend_eof_flushes_queued_backend_bytes_before_closing() {
2132 use std::io::{Read, Write};
2133
2134 let (frontend_peer, frontend_socket) = connected_pair();
2135 let (mut backend_peer, backend_socket) = connected_pair();
2136
2137 let mut pool = Pool::with_capacity(2, 2, 4096);
2138 let backend_buffer = pool.checkout().expect("backend buffer");
2139 let mut frontend_buffer = pool.checkout().expect("frontend buffer");
2140 frontend_buffer
2142 .write_all(b"front-to-back-tail-still-queued")
2143 .expect("seed queued frontend bytes");
2144 let address = "127.0.0.1:0".parse().expect("test address");
2145 let listener = Rc::new(RefCell::new(TestListener { address }));
2146
2147 let mut pipe = Pipe::new(
2148 backend_buffer,
2149 None,
2150 Some(TcpStream::from_std(backend_socket)),
2151 None,
2152 None,
2153 None,
2154 None,
2155 frontend_buffer,
2156 Token(0),
2157 TcpStream::from_std(frontend_socket),
2158 listener,
2159 Protocol::TCP,
2160 Ulid::generate(),
2161 Ulid::generate(),
2162 None,
2163 WebSocketContext::Tcp,
2164 );
2165 pipe.set_back_token(Token(1));
2166 pipe.frontend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
2167 pipe.frontend_readiness.event = Ready::READABLE;
2168
2169 drop(frontend_peer);
2172
2173 let mut metrics = SessionMetrics::new(Some(Duration::ZERO));
2178 let mut saw_eof = false;
2179 for _ in 0..100_000 {
2180 let result = pipe.readable(&mut metrics);
2181 assert_ne!(
2182 result,
2183 SessionResult::Close,
2184 "readable must not close while {} queued backend bytes remain",
2185 pipe.frontend_buffer.available_data()
2186 );
2187 if matches!(pipe.frontend_status, ConnectionStatus::WriteOpen) {
2188 saw_eof = true;
2189 break;
2190 }
2191 }
2192 assert!(
2193 saw_eof,
2194 "frontend EOF was never observed within the retry budget"
2195 );
2196 assert!(
2197 pipe.frontend_buffer.available_data() > 0,
2198 "the queued backend bytes must survive the frontend EOF, not be dropped"
2199 );
2200
2201 assert_eq!(pipe.backend_writable(&mut metrics), SessionResult::Close);
2208 assert_eq!(
2209 pipe.frontend_buffer.available_data(),
2210 0,
2211 "the queued bytes must all drain to the backend after EOF"
2212 );
2213
2214 let mut received = Vec::new();
2215 for _ in 0..100_000 {
2217 let mut buf = [0u8; 64];
2218 match backend_peer.read(&mut buf) {
2219 Ok(0) => break,
2220 Ok(n) => {
2221 received.extend_from_slice(&buf[..n]);
2222 if received.len() >= b"front-to-back-tail-still-queued".len() {
2223 break;
2224 }
2225 }
2226 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => continue,
2227 Err(_) => break,
2228 }
2229 }
2230 assert_eq!(
2231 received, b"front-to-back-tail-still-queued",
2232 "the backend must receive the queued tail byte-for-byte, not a truncation"
2233 );
2234 }
2235
2236 #[test]
2248 fn frontend_eof_with_queued_bytes_closes_on_the_draining_backend_writable_call() {
2249 use std::io::Read;
2250
2251 let (mut frontend_peer, frontend_socket) = connected_pair();
2252 let (mut backend_peer, backend_socket) = connected_pair();
2253
2254 let mut pool = Pool::with_capacity(2, 2, 4096);
2255 let backend_buffer = pool.checkout().expect("backend buffer");
2256 let frontend_buffer = pool.checkout().expect("frontend buffer");
2257 let address = "127.0.0.1:0".parse().expect("test address");
2258 let listener = Rc::new(RefCell::new(TestListener { address }));
2259
2260 let mut pipe = Pipe::new(
2261 backend_buffer,
2262 None,
2263 Some(TcpStream::from_std(backend_socket)),
2264 None,
2265 None,
2266 None,
2267 None,
2268 frontend_buffer,
2269 Token(0),
2270 TcpStream::from_std(frontend_socket),
2271 listener,
2272 Protocol::HTTP,
2273 Ulid::generate(),
2274 Ulid::generate(),
2275 None,
2276 WebSocketContext::Tcp,
2277 );
2278 pipe.set_back_token(Token(1));
2279 pipe.frontend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
2280 pipe.frontend_readiness.event = Ready::READABLE;
2281
2282 frontend_peer
2286 .write_all(b"queued-request-tail")
2287 .expect("write frontend payload");
2288 drop(frontend_peer);
2289
2290 let mut metrics = SessionMetrics::new(Some(Duration::ZERO));
2294 let mut saw_eof = false;
2295 for _ in 0..100_000 {
2296 let result = pipe.readable(&mut metrics);
2297 assert_ne!(
2298 result,
2299 SessionResult::Close,
2300 "readable must not close while queued backend bytes remain"
2301 );
2302 if matches!(pipe.frontend_status, ConnectionStatus::WriteOpen) {
2303 saw_eof = true;
2304 break;
2305 }
2306 }
2307 assert!(
2308 saw_eof,
2309 "frontend EOF was never observed within the retry budget"
2310 );
2311 assert!(
2312 pipe.frontend_buffer.available_data() > 0,
2313 "the queued bytes must survive the frontend EOF"
2314 );
2315 assert!(
2316 pipe.backend_readiness.interest.is_writable()
2317 && pipe.backend_readiness.event.is_writable(),
2318 "observing EOF with queued bytes must arm backend WRITABLE to drain them"
2319 );
2320
2321 let result = pipe.backend_writable(&mut metrics);
2326
2327 assert_eq!(
2328 result,
2329 SessionResult::Close,
2330 "the single draining backend_writable call must close the session by itself -- \
2331 edge-triggered epoll already consumed the EOF edge and offers no other event to \
2332 hang the teardown on"
2333 );
2334 assert_eq!(
2335 pipe.frontend_buffer.available_data(),
2336 0,
2337 "the queued bytes must have fully drained before the session closed"
2338 );
2339
2340 let mut received = Vec::new();
2341 for _ in 0..100_000 {
2342 let mut buf = [0u8; 64];
2343 match backend_peer.read(&mut buf) {
2344 Ok(0) => break,
2345 Ok(n) => {
2346 received.extend_from_slice(&buf[..n]);
2347 if received.len() >= b"queued-request-tail".len() {
2348 break;
2349 }
2350 }
2351 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => continue,
2352 Err(_) => break,
2353 }
2354 }
2355 assert_eq!(
2356 received, b"queued-request-tail",
2357 "the backend must still receive the queued bytes byte-for-byte before the close"
2358 );
2359 }
2360
2361 #[test]
2372 fn frontend_hup_drains_inflight_request_bytes_before_closing() {
2373 let (frontend_peer, frontend_socket) = connected_pair();
2374 let (_backend_peer, backend_socket) = connected_pair();
2375
2376 let mut pool = Pool::with_capacity(2, 2, 4096);
2377 let backend_buffer = pool.checkout().expect("backend buffer");
2378 let mut frontend_buffer = pool.checkout().expect("frontend buffer");
2379 frontend_buffer
2380 .write_all(b"front-to-back-tail-still-queued")
2381 .expect("seed queued frontend bytes");
2382 let address = "127.0.0.1:0".parse().expect("test address");
2383 let listener = Rc::new(RefCell::new(TestListener { address }));
2384
2385 let mut pipe = Pipe::new(
2386 backend_buffer,
2387 None,
2388 Some(TcpStream::from_std(backend_socket)),
2389 None,
2390 None,
2391 None,
2392 None,
2393 frontend_buffer,
2394 Token(0),
2395 TcpStream::from_std(frontend_socket),
2396 listener,
2397 Protocol::TCP,
2398 Ulid::generate(),
2399 Ulid::generate(),
2400 None,
2401 WebSocketContext::Tcp,
2402 );
2403 pipe.set_back_token(Token(1));
2404 pipe.frontend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
2407 pipe.frontend_readiness.event = Ready::READABLE | Ready::HUP;
2408 pipe.backend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
2409 pipe.backend_readiness.event = Ready::EMPTY;
2410
2411 let mut metrics = SessionMetrics::new(Some(Duration::ZERO));
2412 let result = pipe.frontend_hup(&mut metrics);
2413
2414 assert_eq!(
2415 result,
2416 SessionResult::Continue,
2417 "frontend_hup must keep the session alive while request bytes are still in flight"
2418 );
2419 assert!(
2420 matches!(pipe.frontend_status, ConnectionStatus::Closed),
2421 "frontend_hup must still mark the frontend Closed even on the drain branch"
2422 );
2423 assert!(
2424 pipe.backend_readiness.interest.is_writable()
2425 && pipe.backend_readiness.event.is_writable(),
2426 "the drain branch must arm backend WRITABLE to flush the queued frontend bytes"
2427 );
2428 assert!(
2429 pipe.frontend_readiness.interest.is_readable(),
2430 "the drain branch must retain frontend READABLE interest to read the kernel tail to EOF"
2431 );
2432
2433 drop(frontend_peer);
2434 }
2435
2436 #[test]
2440 fn frontend_hup_closes_immediately_when_nothing_is_inflight() {
2441 let (frontend_peer, frontend_socket) = connected_pair();
2442 let (_backend_peer, backend_socket) = connected_pair();
2443
2444 let mut pool = Pool::with_capacity(2, 2, 4096);
2445 let backend_buffer = pool.checkout().expect("backend buffer");
2446 let frontend_buffer = pool.checkout().expect("frontend buffer");
2447 let address = "127.0.0.1:0".parse().expect("test address");
2448 let listener = Rc::new(RefCell::new(TestListener { address }));
2449
2450 let mut pipe = Pipe::new(
2451 backend_buffer,
2452 None,
2453 Some(TcpStream::from_std(backend_socket)),
2454 None,
2455 None,
2456 None,
2457 None,
2458 frontend_buffer,
2459 Token(0),
2460 TcpStream::from_std(frontend_socket),
2461 listener,
2462 Protocol::TCP,
2463 Ulid::generate(),
2464 Ulid::generate(),
2465 None,
2466 WebSocketContext::Tcp,
2467 );
2468 pipe.set_back_token(Token(1));
2469 pipe.frontend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
2471 pipe.frontend_readiness.event = Ready::HUP;
2472 pipe.backend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
2473 pipe.backend_readiness.event = Ready::EMPTY;
2474
2475 let mut metrics = SessionMetrics::new(Some(Duration::ZERO));
2476 let result = pipe.frontend_hup(&mut metrics);
2477
2478 assert_eq!(
2479 result,
2480 SessionResult::Close,
2481 "frontend_hup must close immediately when nothing is in flight (unchanged legacy behavior)"
2482 );
2483 assert!(
2484 matches!(pipe.frontend_status, ConnectionStatus::Closed),
2485 "frontend_hup must mark the frontend Closed"
2486 );
2487 assert!(
2488 !pipe.backend_readiness.event.is_writable(),
2489 "the close branch must not arm backend WRITABLE when there is nothing queued"
2490 );
2491
2492 drop(frontend_peer);
2493 }
2494
2495 #[cfg(all(target_os = "linux", feature = "splice"))]
2504 #[test]
2505 fn splice_readable_eof_drains_kernel_pipe_bytes_before_closing() {
2506 use std::io::Read;
2507
2508 let (mut frontend_peer, frontend_socket) = connected_pair();
2509 let (mut backend_peer, backend_socket) = connected_pair();
2510
2511 let mut pool = Pool::with_capacity(2, 2, 4096);
2512 let backend_buffer = pool.checkout().expect("backend buffer");
2513 let frontend_buffer = pool.checkout().expect("frontend buffer");
2516 let address = "127.0.0.1:0".parse().expect("test address");
2517 let listener = Rc::new(RefCell::new(TestListener { address }));
2518
2519 let mut pipe = Pipe::new(
2520 backend_buffer,
2521 None,
2522 Some(TcpStream::from_std(backend_socket)),
2523 None,
2524 None,
2525 None,
2526 None,
2527 frontend_buffer,
2528 Token(0),
2529 TcpStream::from_std(frontend_socket),
2530 listener,
2531 Protocol::TCP,
2532 Ulid::generate(),
2533 Ulid::generate(),
2534 None,
2535 WebSocketContext::Tcp,
2536 );
2537 pipe.set_back_token(Token(1));
2538 pipe.frontend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
2539 pipe.frontend_readiness.event = Ready::READABLE;
2540
2541 assert!(
2542 pipe.splice_pipe.is_some(),
2543 "Protocol::TCP must allocate the splice kernel pipe for this test to be meaningful"
2544 );
2545
2546 let payload = vec![0x5a_u8; 32 * 1024];
2549 let capacity = pipe.splice_capacity();
2556 assert!(
2557 capacity >= payload.len(),
2558 "environment precondition not met: splice pipe capacity is {capacity} bytes, \
2559 below the {}-byte payload; this user's pipe buffers likely exceed \
2560 fs.pipe-user-pages-soft ({}). Close pipe-heavy processes or raise the limit, \
2561 then rerun: this is a host limit, not a Sozu regression",
2562 payload.len(),
2563 std::fs::read_to_string("/proc/sys/fs/pipe-user-pages-soft")
2564 .map(|limit| limit.trim().to_owned())
2565 .unwrap_or_else(|error| format!("unreadable: {error}")),
2566 );
2567 frontend_peer
2568 .write_all(&payload)
2569 .expect("write frontend payload");
2570
2571 let mut metrics = SessionMetrics::new(Some(Duration::ZERO));
2575 for _ in 0..100_000 {
2576 if pipe.splice_in_pending() >= payload.len() {
2577 break;
2578 }
2579 let result = pipe.splice_readable(&mut metrics);
2580 assert_ne!(
2581 result,
2582 SessionResult::Close,
2583 "splice_readable must not close while splicing the payload in"
2584 );
2585 }
2586 assert_eq!(
2587 pipe.splice_in_pending(),
2588 payload.len(),
2589 "the whole payload must sit in the kernel in_pipe before EOF"
2590 );
2591
2592 drop(frontend_peer);
2595
2596 let mut saw_eof = false;
2599 for _ in 0..100_000 {
2600 let result = pipe.splice_readable(&mut metrics);
2601 assert_ne!(
2602 result,
2603 SessionResult::Close,
2604 "splice_readable must not close while {} kernel-pipe bytes remain",
2605 pipe.splice_in_pending()
2606 );
2607 if matches!(pipe.frontend_status, ConnectionStatus::WriteOpen) {
2608 saw_eof = true;
2609 break;
2610 }
2611 }
2612 assert!(
2613 saw_eof,
2614 "frontend EOF was never observed within the retry budget"
2615 );
2616 assert_eq!(
2617 pipe.splice_in_pending(),
2618 payload.len(),
2619 "the kernel-pipe bytes must survive the frontend EOF, not be dropped"
2620 );
2621 assert!(
2622 pipe.backend_readiness.interest.is_writable()
2623 && pipe.backend_readiness.event.is_writable(),
2624 "EOF with kernel-pipe bytes pending must arm backend WRITABLE to drain them"
2625 );
2626
2627 let mut received = Vec::with_capacity(payload.len());
2639 let mut closed_during_drain = false;
2640 for _ in 0..100_000 {
2641 let drain = pipe.splice_backend_writable(&mut metrics);
2642 if drain == SessionResult::Close {
2643 assert_eq!(
2644 pipe.splice_in_pending(),
2645 0,
2646 "the session must only close once the kernel pipe is fully drained, \
2647 never mid-flight"
2648 );
2649 closed_during_drain = true;
2650 }
2651 let mut buf = [0u8; 16384];
2652 match backend_peer.read(&mut buf) {
2653 Ok(0) => break,
2654 Ok(n) => received.extend_from_slice(&buf[..n]),
2655 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {}
2656 Err(_) => break,
2657 }
2658 if received.len() >= payload.len() {
2659 break;
2660 }
2661 }
2662 assert_eq!(
2663 received, payload,
2664 "the backend must receive the spliced tail byte-for-byte, not a truncation"
2665 );
2666 assert_eq!(
2667 pipe.splice_in_pending(),
2668 0,
2669 "the kernel in_pipe must be fully drained"
2670 );
2671 assert!(
2672 closed_during_drain,
2673 "the fix under test (sozu-proxy/sozu#1290) must close the session as soon as \
2674 the kernel pipe drains, without waiting for a separate re-observation event"
2675 );
2676
2677 assert!(
2683 !pipe.check_connections(),
2684 "with nothing inflight and the frontend closed, the session must be closeable"
2685 );
2686 assert_eq!(
2687 pipe.splice_readable(&mut metrics),
2688 SessionResult::Close,
2689 "re-observing EOF with nothing inflight must (still) close the session"
2690 );
2691 }
2692
2693 #[cfg(all(target_os = "linux", feature = "splice"))]
2709 #[test]
2710 fn splice_backend_readable_keeps_readable_event_when_pipe_slots_are_exhausted() {
2711 use std::os::unix::io::AsRawFd;
2712
2713 fn unread_bytes(socket: &TcpStream) -> usize {
2714 let mut n: libc::c_int = 0;
2715 let ret = unsafe { libc::ioctl(socket.as_raw_fd(), libc::FIONREAD, &mut n) };
2716 assert_eq!(ret, 0, "FIONREAD must succeed");
2717 n as usize
2718 }
2719
2720 let (_frontend_peer, frontend_socket) = connected_pair();
2721 let (mut backend_peer, backend_socket) = connected_pair();
2722
2723 let mut pool = Pool::with_capacity(2, 2, 4096);
2724 let backend_buffer = pool.checkout().expect("backend buffer");
2725 let frontend_buffer = pool.checkout().expect("frontend buffer");
2726 let address = "127.0.0.1:0".parse().expect("test address");
2727 let listener = Rc::new(RefCell::new(TestListener { address }));
2728
2729 let mut pipe = Pipe::new(
2730 backend_buffer,
2731 None,
2732 Some(TcpStream::from_std(backend_socket)),
2733 None,
2734 None,
2735 None,
2736 None,
2737 frontend_buffer,
2738 Token(0),
2739 TcpStream::from_std(frontend_socket),
2740 listener,
2741 Protocol::TCP,
2742 Ulid::generate(),
2743 Ulid::generate(),
2744 None,
2745 WebSocketContext::Tcp,
2746 );
2747 pipe.set_back_token(Token(1));
2748 pipe.frontend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
2749 pipe.backend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
2750 pipe.backend_readiness.event = Ready::READABLE;
2751
2752 let slot = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
2754 assert!(slot > 0, "sysconf(_SC_PAGESIZE) must succeed");
2755 {
2756 let splice_pipe = pipe
2757 .splice_pipe
2758 .as_mut()
2759 .expect("Protocol::TCP must allocate the splice kernel pipe");
2760 for fd in [splice_pipe.in_pipe[0], splice_pipe.out_pipe[0]] {
2761 let ret = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, slot as libc::c_int) };
2762 assert_eq!(ret, slot as libc::c_int, "F_SETPIPE_SZ(page) must succeed");
2763 }
2764 splice_pipe.capacity = slot;
2765 }
2766 let capacity = pipe.splice_capacity();
2767 assert_eq!(capacity, slot);
2768
2769 let payload = vec![0x5a_u8; 2 * slot];
2771 backend_peer
2772 .write_all(&payload)
2773 .expect("write backend payload");
2774 for _ in 0..1000 {
2775 if unread_bytes(pipe.backend_socket.as_ref().unwrap()) >= payload.len() {
2776 break;
2777 }
2778 std::thread::sleep(Duration::from_millis(1));
2779 }
2780 assert_eq!(
2781 unread_bytes(pipe.backend_socket.as_ref().unwrap()),
2782 payload.len(),
2783 "the whole payload must be queued in the backend socket"
2784 );
2785
2786 let mut metrics = SessionMetrics::new(Some(Duration::ZERO));
2789 assert_ne!(
2790 pipe.splice_backend_readable(&mut metrics),
2791 SessionResult::Close
2792 );
2793 let first = pipe.splice_out_pending();
2794 assert!(
2795 first > 0 && first <= slot,
2796 "the single slot must absorb part of the payload (got {first})"
2797 );
2798 assert!(
2799 pipe.backend_readiness.event.is_readable(),
2800 "the READABLE event must survive a successful splice"
2801 );
2802
2803 {
2806 let pipe_read_end = pipe.splice_pipe.as_ref().unwrap().out_pipe[0];
2807 let (drained, res) = splice::splice_out(pipe_read_end, pipe.frontend.socket_ref(), 1);
2808 assert_eq!((drained, res), (1, SocketResult::Continue));
2809 pipe.splice_pipe.as_mut().unwrap().out_pipe_pending -= drained;
2810 }
2811 assert!(pipe.splice_out_pending() < capacity);
2812
2813 pipe.backend_readiness.interest.insert(Ready::READABLE);
2816 assert_ne!(
2817 pipe.splice_backend_readable(&mut metrics),
2818 SessionResult::Close
2819 );
2820
2821 let still_queued = unread_bytes(pipe.backend_socket.as_ref().unwrap());
2822 assert_eq!(
2823 still_queued,
2824 payload.len() - first,
2825 "precondition: the rest of the payload must still sit in the socket"
2826 );
2827 assert!(
2828 pipe.backend_readiness.event.is_readable(),
2829 "READABLE event must survive a pipe-full EAGAIN: {still_queued} bytes are still \
2830 queued in the backend socket and edge-triggered epoll will never re-signal them"
2831 );
2832 assert!(
2833 !pipe.backend_readiness.interest.is_readable(),
2834 "READABLE interest must be parked while the pipe is full, or the ready loop spins"
2835 );
2836 assert!(
2837 pipe.frontend_readiness.event.is_writable()
2838 && pipe.frontend_readiness.interest.is_writable(),
2839 "the frontend must be armed writable to drain the pipe"
2840 );
2841
2842 assert_ne!(pipe.splice_writable(&mut metrics), SessionResult::Close);
2847 assert_eq!(
2848 pipe.splice_out_pending(),
2849 0,
2850 "the pipe must be fully drained"
2851 );
2852 assert!(
2853 pipe.backend_readiness.interest.is_readable()
2854 && pipe.backend_readiness.event.is_readable(),
2855 "draining the pipe must re-arm the backend read"
2856 );
2857 assert_ne!(
2858 pipe.splice_backend_readable(&mut metrics),
2859 SessionResult::Close
2860 );
2861 assert!(
2862 unread_bytes(pipe.backend_socket.as_ref().unwrap()) < still_queued,
2863 "the remaining response bytes must start leaving the backend socket"
2864 );
2865 assert!(pipe.splice_out_pending() > 0);
2866
2867 for _ in 0..1000 {
2869 if unread_bytes(pipe.backend_socket.as_ref().unwrap()) == 0 {
2870 break;
2871 }
2872 assert_ne!(pipe.splice_writable(&mut metrics), SessionResult::Close);
2873 pipe.backend_readiness.interest.insert(Ready::READABLE);
2874 assert_ne!(
2875 pipe.splice_backend_readable(&mut metrics),
2876 SessionResult::Close
2877 );
2878 }
2879 assert_eq!(unread_bytes(pipe.backend_socket.as_ref().unwrap()), 0);
2880 if pipe.splice_out_pending() == 0 {
2881 backend_peer.write_all(&payload[..slot]).expect("write");
2883 for _ in 0..1000 {
2884 if unread_bytes(pipe.backend_socket.as_ref().unwrap()) >= slot {
2885 break;
2886 }
2887 std::thread::sleep(Duration::from_millis(1));
2888 }
2889 pipe.backend_readiness.interest.insert(Ready::READABLE);
2890 pipe.backend_readiness.event.insert(Ready::READABLE);
2891 assert_ne!(
2892 pipe.splice_backend_readable(&mut metrics),
2893 SessionResult::Close
2894 );
2895 assert_eq!(unread_bytes(pipe.backend_socket.as_ref().unwrap()), 0);
2896 }
2897 assert!(pipe.splice_out_pending() > 0);
2898
2899 pipe.backend_readiness.interest.insert(Ready::READABLE);
2904 assert_ne!(
2905 pipe.splice_backend_readable(&mut metrics),
2906 SessionResult::Close
2907 );
2908 assert!(pipe.backend_readiness.event.is_readable());
2909 assert!(!pipe.backend_readiness.interest.is_readable());
2910 assert_ne!(pipe.splice_writable(&mut metrics), SessionResult::Close);
2911 assert_eq!(pipe.splice_out_pending(), 0);
2912 assert_ne!(
2913 pipe.splice_backend_readable(&mut metrics),
2914 SessionResult::Close
2915 );
2916 assert!(
2917 !pipe.backend_readiness.event.is_readable(),
2918 "a drained socket with an empty pipe must clear the READABLE event"
2919 );
2920 }
2921
2922 #[cfg(all(target_os = "linux", feature = "splice"))]
2923 fn exercise_splice_readable_pipe_full_recovery(force_growth_refusal: bool) -> bool {
2924 use std::{io::Read, os::unix::io::AsRawFd};
2925
2926 fn unread_bytes(socket: &TcpStream) -> usize {
2927 let mut n: libc::c_int = 0;
2928 let ret = unsafe { libc::ioctl(socket.as_raw_fd(), libc::FIONREAD, &mut n) };
2929 assert_eq!(ret, 0, "FIONREAD must succeed");
2930 n as usize
2931 }
2932
2933 fn discard_in_pipe(pipe: &mut Pipe<TcpStream, TestListener>) {
2934 let pending = pipe.splice_in_pending();
2935 let pipe_read_end = pipe.splice_pipe.as_ref().unwrap().in_pipe[0];
2936 let mut discarded = vec![0_u8; pending];
2937 let mut offset = 0;
2938 while offset < pending {
2939 let read = unsafe {
2942 libc::read(
2943 pipe_read_end,
2944 discarded[offset..].as_mut_ptr().cast(),
2945 pending - offset,
2946 )
2947 };
2948 assert!(
2949 read > 0,
2950 "read must drain the populated pipe: {}",
2951 std::io::Error::last_os_error()
2952 );
2953 offset += read as usize;
2954 }
2955 pipe.splice_pipe.as_mut().unwrap().in_pipe_pending = 0;
2956 }
2957
2958 fn request_pipe_capacity(
2959 fd: libc::c_int,
2960 requested: usize,
2961 force_growth_refusal: bool,
2962 ) -> usize {
2963 let requested = if force_growth_refusal {
2966 -1
2967 } else {
2968 libc::c_int::try_from(requested).expect("requested pipe size must fit c_int")
2969 };
2970 let set_ret = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, requested) };
2971 if force_growth_refusal {
2972 assert_eq!(set_ret, -1, "negative pipe growth must be refused");
2973 } else if set_ret < 0 {
2974 eprintln!(
2975 "F_SETPIPE_SZ({requested}) unavailable for test pipe: {}",
2976 std::io::Error::last_os_error()
2977 );
2978 }
2979 let actual = unsafe { libc::fcntl(fd, libc::F_GETPIPE_SZ) };
2981 assert!(actual > 0, "F_GETPIPE_SZ must succeed");
2982 actual as usize
2983 }
2984
2985 fn set_buffer(socket: &impl AsRawFd, opt: libc::c_int, bytes: libc::c_int) {
2986 let ret = unsafe {
2987 libc::setsockopt(
2988 socket.as_raw_fd(),
2989 libc::SOL_SOCKET,
2990 opt,
2991 &bytes as *const libc::c_int as *const libc::c_void,
2992 std::mem::size_of::<libc::c_int>() as libc::socklen_t,
2993 )
2994 };
2995 assert_eq!(ret, 0, "setsockopt must succeed");
2996 }
2997
2998 fn tiny_backend_pair() -> (StdTcpStream, StdTcpStream) {
3005 let listener = StdTcpListener::bind("127.0.0.1:0").expect("bind test listener");
3006 set_buffer(&listener, libc::SO_RCVBUF, 4096);
3007 let address = listener.local_addr().expect("listener local addr");
3008 let socket = socket2_connect_with_sndbuf(address, 4096);
3009 let (peer, _) = listener.accept().expect("accept test server");
3010 socket.set_nonblocking(true).expect("client nonblocking");
3011 peer.set_nonblocking(true).expect("server nonblocking");
3012 (peer, socket)
3013 }
3014
3015 fn socket2_connect_with_sndbuf(address: SocketAddr, bytes: libc::c_int) -> StdTcpStream {
3016 use std::os::unix::io::FromRawFd;
3017 let fd =
3018 unsafe { libc::socket(libc::AF_INET, libc::SOCK_STREAM | libc::SOCK_CLOEXEC, 0) };
3019 assert!(fd >= 0, "socket() must succeed");
3020 let socket = unsafe { StdTcpStream::from_raw_fd(fd) };
3021 set_buffer(&socket, libc::SO_SNDBUF, bytes);
3022 let SocketAddr::V4(v4) = address else {
3023 panic!("test listener must be IPv4")
3024 };
3025 let sin = libc::sockaddr_in {
3026 sin_family: libc::AF_INET as libc::sa_family_t,
3027 sin_port: v4.port().to_be(),
3028 sin_addr: libc::in_addr {
3029 s_addr: u32::from_ne_bytes(v4.ip().octets()),
3030 },
3031 sin_zero: [0; 8],
3032 };
3033 let ret = unsafe {
3034 libc::connect(
3035 fd,
3036 &sin as *const libc::sockaddr_in as *const libc::sockaddr,
3037 std::mem::size_of::<libc::sockaddr_in>() as libc::socklen_t,
3038 )
3039 };
3040 assert_eq!(
3041 ret,
3042 0,
3043 "connect() must succeed: {}",
3044 std::io::Error::last_os_error()
3045 );
3046 socket
3047 }
3048
3049 let (mut frontend_peer, frontend_socket) = connected_pair();
3050 let (mut backend_peer, backend_socket) = tiny_backend_pair();
3051
3052 let mut pool = Pool::with_capacity(2, 2, 4096);
3053 let backend_buffer = pool.checkout().expect("backend buffer");
3054 let frontend_buffer = pool.checkout().expect("frontend buffer");
3055 let address = "127.0.0.1:0".parse().expect("test address");
3056 let listener = Rc::new(RefCell::new(TestListener { address }));
3057
3058 let mut pipe = Pipe::new(
3059 backend_buffer,
3060 None,
3061 Some(TcpStream::from_std(backend_socket)),
3062 None,
3063 None,
3064 None,
3065 None,
3066 frontend_buffer,
3067 Token(0),
3068 TcpStream::from_std(frontend_socket),
3069 listener,
3070 Protocol::TCP,
3071 Ulid::generate(),
3072 Ulid::generate(),
3073 None,
3074 WebSocketContext::Tcp,
3075 );
3076 pipe.set_back_token(Token(1));
3077 pipe.frontend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
3078 pipe.frontend_readiness.event = Ready::READABLE;
3079 pipe.backend_readiness.interest = Ready::READABLE | Ready::HUP | Ready::ERROR;
3080
3081 let slot = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
3082 assert!(slot > 0, "sysconf(_SC_PAGESIZE) must succeed");
3083 {
3084 let splice_pipe = pipe
3085 .splice_pipe
3086 .as_mut()
3087 .expect("Protocol::TCP must allocate the splice kernel pipe");
3088 for fd in [splice_pipe.in_pipe[0], splice_pipe.out_pipe[0]] {
3089 let ret = unsafe { libc::fcntl(fd, libc::F_SETPIPE_SZ, slot as libc::c_int) };
3090 assert_eq!(ret, slot as libc::c_int, "F_SETPIPE_SZ(page) must succeed");
3091 }
3092 splice_pipe.capacity = slot;
3093 }
3094
3095 let payload = vec![0x5a_u8; 3 * slot];
3098 frontend_peer
3099 .write_all(&payload)
3100 .expect("write frontend payload");
3101 for _ in 0..1000 {
3102 if unread_bytes(pipe.frontend.socket_ref()) >= payload.len() {
3103 break;
3104 }
3105 std::thread::sleep(Duration::from_millis(1));
3106 }
3107 assert_eq!(unread_bytes(pipe.frontend.socket_ref()), payload.len());
3108
3109 let mut metrics = SessionMetrics::new(Some(Duration::ZERO));
3110 assert_ne!(pipe.splice_readable(&mut metrics), SessionResult::Close);
3111 let first = pipe.splice_in_pending();
3112 assert!(
3113 first > 0 && first <= slot,
3114 "one slot absorbed (got {first})"
3115 );
3116
3117 {
3119 let pipe_read_end = pipe.splice_pipe.as_ref().unwrap().in_pipe[0];
3120 let backend = pipe.backend_socket.as_ref().unwrap();
3121 let (drained, res) = splice::splice_out(pipe_read_end, backend, 1);
3122 assert_eq!((drained, res), (1, SocketResult::Continue));
3123 pipe.splice_pipe.as_mut().unwrap().in_pipe_pending -= drained;
3124 }
3125
3126 pipe.frontend_readiness.interest.insert(Ready::READABLE);
3127 assert_ne!(pipe.splice_readable(&mut metrics), SessionResult::Close);
3128 let still_queued = unread_bytes(pipe.frontend.socket_ref());
3129 assert_eq!(still_queued, payload.len() - first);
3130 assert!(
3131 pipe.frontend_readiness.event.is_readable(),
3132 "READABLE event must survive a pipe-full EAGAIN with {still_queued} bytes queued"
3133 );
3134 assert!(
3135 !pipe.frontend_readiness.interest.is_readable(),
3136 "READABLE interest must be parked while the pipe is full"
3137 );
3138 assert!(
3139 pipe.backend_readiness.event.is_writable()
3140 && pipe.backend_readiness.interest.is_writable(),
3141 "the backend must be armed writable to drain the pipe"
3142 );
3143
3144 let mut backend_buf = [0_u8; 16 * 1024];
3149 for _ in 0..1000 {
3150 assert_ne!(
3151 pipe.splice_backend_writable(&mut metrics),
3152 SessionResult::Close
3153 );
3154 loop {
3155 match backend_peer.read(&mut backend_buf) {
3156 Ok(0) => panic!("backend peer closed while draining the pipe"),
3157 Ok(_) => {}
3158 Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => break,
3159 Err(error) => panic!("failed to read drained pipe bytes: {error}"),
3160 }
3161 }
3162 if pipe.splice_in_pending() == 0 {
3163 break;
3164 }
3165 std::thread::sleep(Duration::from_millis(1));
3166 }
3167 assert_eq!(
3168 pipe.splice_in_pending(),
3169 0,
3170 "the single-slot pipe must fully drain"
3171 );
3172 assert!(
3173 pipe.frontend_readiness.interest.is_readable()
3174 && pipe.frontend_readiness.event.is_readable(),
3175 "the real backend drain must re-arm the preserved frontend read"
3176 );
3177 let unread_before_retry = unread_bytes(pipe.frontend.socket_ref());
3178 assert_eq!(unread_before_retry, still_queued);
3179 assert_ne!(pipe.splice_readable(&mut metrics), SessionResult::Close);
3180 let unread_after_retry = unread_bytes(pipe.frontend.socket_ref());
3181 assert!(
3182 unread_after_retry < unread_before_retry,
3183 "the preserved READABLE event must resume frontend progress after the drain \
3184 ({unread_before_retry} before, {unread_after_retry} after)"
3185 );
3186 assert!(pipe.splice_in_pending() > 0);
3187
3188 discard_in_pipe(&mut pipe);
3193 let requested_capacity = slot.checked_mul(16).expect("pipe size must fit usize");
3194 let capacity = {
3195 let splice_pipe = pipe.splice_pipe.as_mut().unwrap();
3196 let in_capacity = request_pipe_capacity(
3197 splice_pipe.in_pipe[0],
3198 requested_capacity,
3199 force_growth_refusal,
3200 );
3201 let out_capacity = request_pipe_capacity(
3202 splice_pipe.out_pipe[0],
3203 requested_capacity,
3204 force_growth_refusal,
3205 );
3206 let capacity = in_capacity.min(out_capacity);
3207 splice_pipe.capacity = capacity;
3208 capacity
3209 };
3210 let minimum_capacity = slot
3211 .checked_mul(2)
3212 .expect("minimum pipe size must fit usize")
3213 .max(32 * 1024);
3214 if capacity < minimum_capacity {
3215 eprintln!(
3216 "skipping multi-slot partial-drain phase: realised capacity {capacity} is below \
3217 required {minimum_capacity}"
3218 );
3219 return false;
3220 }
3221
3222 frontend_peer
3226 .write_all(&vec![0x5a_u8; capacity])
3227 .expect("write more frontend payload");
3228 for _ in 0..1000 {
3229 if pipe.splice_in_pending() >= capacity / 2 {
3230 break;
3231 }
3232 pipe.frontend_readiness.interest.insert(Ready::READABLE);
3233 pipe.frontend_readiness.event.insert(Ready::READABLE);
3234 assert_ne!(pipe.splice_readable(&mut metrics), SessionResult::Close);
3235 std::thread::sleep(Duration::from_millis(1));
3236 }
3237 assert!(
3238 pipe.splice_in_pending() >= capacity / 2,
3239 "the pipe must hold at least half its realised capacity (got {})",
3240 pipe.splice_in_pending()
3241 );
3242 pipe.frontend_readiness.interest.remove(Ready::READABLE);
3243
3244 let pending_before_drain = pipe.splice_in_pending();
3248 assert_ne!(
3249 pipe.splice_backend_writable(&mut metrics),
3250 SessionResult::Close
3251 );
3252 let pending = pipe.splice_in_pending();
3253 assert!(
3254 pending < pending_before_drain,
3255 "the backend must accept some bytes ({pending_before_drain} pending)"
3256 );
3257 assert!(
3258 pending > 0,
3259 "the drain must be partial, not complete (accepted {})",
3260 pending_before_drain - pending
3261 );
3262 assert!(
3263 pipe.frontend_readiness.interest.is_readable(),
3264 "a partial drain of the pipe must re-arm the frontend read"
3265 );
3266
3267 discard_in_pipe(&mut pipe);
3271 for _ in 0..1000 {
3272 if unread_bytes(pipe.frontend.socket_ref()) > 0 {
3273 break;
3274 }
3275 std::thread::sleep(Duration::from_millis(1));
3276 }
3277 let unread_before_retry = unread_bytes(pipe.frontend.socket_ref());
3278 assert!(
3279 unread_before_retry > 0,
3280 "request bytes must remain queued for the free-slot retry"
3281 );
3282 assert!(
3283 pipe.frontend_readiness.interest.is_readable()
3284 && pipe.frontend_readiness.event.is_readable(),
3285 "the partial backend drain must leave the frontend retry runnable"
3286 );
3287 assert_ne!(pipe.splice_readable(&mut metrics), SessionResult::Close);
3288 let unread_after_retry = unread_bytes(pipe.frontend.socket_ref());
3289 assert!(
3290 unread_after_retry < unread_before_retry,
3291 "a retry with free pipe slots must consume request bytes \
3292 ({unread_before_retry} before, {unread_after_retry} after)"
3293 );
3294 assert!(pipe.splice_in_pending() > 0);
3295 true
3296 }
3297
3298 #[cfg(all(target_os = "linux", feature = "splice"))]
3303 #[test]
3304 fn splice_readable_keeps_readable_event_when_pipe_slots_are_exhausted() {
3305 exercise_splice_readable_pipe_full_recovery(false);
3306 }
3307
3308 #[cfg(all(target_os = "linux", feature = "splice"))]
3309 #[test]
3310 fn splice_readable_recovers_when_pipe_growth_is_refused() {
3311 assert!(!exercise_splice_readable_pipe_full_recovery(true));
3312 }
3313 #[test]
3328 fn backend_address_is_reported_without_asking_the_socket() {
3329 let refused = {
3330 let listener = StdTcpListener::bind("127.0.0.1:0").expect("bind a throwaway listener");
3331 listener
3332 .local_addr()
3333 .expect("throwaway listener local addr")
3334 };
3335 let backend_socket = TcpStream::connect(refused).expect("a nonblocking connect starts");
3336 let (_frontend_peer, frontend_socket) = connected_pair();
3337
3338 let mut pool = Pool::with_capacity(2, 2, 4096);
3339 let backend_buffer = pool.checkout().expect("backend buffer");
3340 let frontend_buffer = pool.checkout().expect("frontend buffer");
3341 let address = "127.0.0.1:0".parse().expect("test address");
3342 let listener = Rc::new(RefCell::new(TestListener { address }));
3343 let backend = Rc::new(RefCell::new(Backend::new(
3344 "backend-refused",
3345 refused,
3346 None,
3347 None,
3348 None,
3349 )));
3350
3351 let mut pipe = Pipe::new(
3352 backend_buffer,
3353 Some("backend-refused".to_owned()),
3354 Some(backend_socket),
3355 Some(backend),
3356 None,
3357 None,
3358 None,
3359 frontend_buffer,
3360 Token(0),
3361 TcpStream::from_std(frontend_socket),
3362 listener,
3363 Protocol::HTTP,
3364 Ulid::generate(),
3365 Ulid::generate(),
3366 None,
3367 WebSocketContext::Tcp,
3368 );
3369 assert_eq!(
3370 pipe.get_backend_address(),
3371 Some(refused),
3372 "the address of the backend handed to Pipe::new must be reported \
3373 even though its socket was refused"
3374 );
3375
3376 let retried: SocketAddr = "192.0.2.7:4242".parse().expect("retry address");
3377 pipe.set_backend_address(Some(retried));
3378 assert_eq!(
3379 pipe.get_backend_address(),
3380 Some(retried),
3381 "a later dial must replace the reported backend address"
3382 );
3383 }
3384}