sozu_command_lib/channel.rs
1//! Bidirectional length-delimited unix-socket channel.
2//!
3//! Implements the master ↔ worker / master ↔ CLI message channel: each
4//! payload is preceded by a native `usize` length prefix (NOT a NUL
5//! separator — that scheme belongs to the state-file save format written
6//! by `ConfigState::write_requests_to_file` in `command/src/state.rs`).
7//! Bounded by the per-channel `Channel::max_buffer_size`, checked against
8//! the declared length in `try_read_delimited_message` before any payload
9//! byte is allocated.
10
11use std::{
12 cmp::min,
13 fmt::Debug,
14 io::{self, ErrorKind, Read, Write},
15 marker::PhantomData,
16 os::unix::{
17 io::{AsRawFd, FromRawFd, IntoRawFd, RawFd},
18 net::UnixStream as StdUnixStream,
19 },
20 time::Duration,
21};
22
23use mio::{event::Source, net::UnixStream as MioUnixStream};
24use prost::{DecodeError, Message as ProstMessage};
25
26use crate::{buffer::growable::Buffer, ready::Ready};
27
28/// High watermark threshold: log a warning when buffer usage exceeds 80% of max
29const HIGH_WATERMARK_RATIO: f64 = 0.8;
30const CHANNEL_SNAPSHOT_VERSION: u16 = 1;
31const CHANNEL_READY_MASK: u16 =
32 Ready::READABLE.0 | Ready::WRITABLE.0 | Ready::ERROR.0 | Ready::HUP.0 | Ready::WRITE_CLOSED.0;
33
34#[derive(thiserror::Error, Debug)]
35pub enum ChannelError {
36 #[error("io read error")]
37 Read(std::io::Error),
38 #[error("no byte written on the channel")]
39 NoByteWritten,
40 #[error("no byte left to read on the channel")]
41 NoByteToRead,
42 #[error(
43 "message ({message_len} bytes) too large for back buffer capacity ({capacity} bytes, max {max} bytes)"
44 )]
45 MessageTooLarge {
46 message_len: usize,
47 capacity: usize,
48 max: usize,
49 },
50 #[error(
51 "declared message length ({message_len} bytes) is shorter than the {delimiter_size}-byte length prefix"
52 )]
53 MessageLengthUnderDelimiter {
54 message_len: usize,
55 delimiter_size: usize,
56 },
57 #[error("io write error")]
58 Write(std::io::Error),
59 #[error("channel buffer is full ({capacity} bytes, max {max} bytes), cannot grow more")]
60 BufferFull { capacity: usize, max: usize },
61 #[error("Timeout is reached: {0:?}")]
62 TimeoutReached(Duration),
63 #[error("Could not read anything on the channel")]
64 NothingRead,
65 #[error("invalid char set in command message, ignoring: {0}")]
66 InvalidCharSet(String),
67 #[error("could not set the timeout of the unix stream with file descriptor {fd}: {error}")]
68 SetTimeout { fd: i32, error: String },
69 #[error(
70 "Could not change the blocking status ef the unix stream with file descriptor {fd}: {error}"
71 )]
72 BlockingStatus { fd: i32, error: String },
73 #[error("Connection error: {0:?}")]
74 Connection(Option<std::io::Error>),
75 #[error("Invalid protobuf message: {0}")]
76 InvalidProtobufMessage(DecodeError),
77 #[error("This should never happen (index out of bound on a tested buffer)")]
78 MismatchBufferSize,
79}
80
81/// Serializable userspace state of a [`Channel`].
82///
83/// The Hub supplies the effective descriptor after fork/exec inheritance or
84/// explicit duplication. Only bytes that have not been consumed are captured;
85/// buffer offsets are normalized on restore while their capacities and
86/// backpressure state are preserved.
87#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
88pub struct ChannelSnapshot {
89 version: u16,
90 front: Vec<u8>,
91 back: Vec<u8>,
92 initial_buffer_size: usize,
93 max_buffer_size: usize,
94 front_buffer_capacity: usize,
95 back_buffer_capacity: usize,
96 readiness: u16,
97 interest: u16,
98 blocking: bool,
99 front_high_watermark_logged: bool,
100 back_high_watermark_logged: bool,
101}
102
103#[derive(thiserror::Error, Debug, Eq, PartialEq)]
104pub enum ChannelSnapshotError {
105 #[error("unsupported channel snapshot version {version}; supported version is {supported}")]
106 UnsupportedVersion { version: u16, supported: u16 },
107 #[error(
108 "invalid channel snapshot buffer bounds: initial capacity {initial} exceeds maximum {max}"
109 )]
110 InvalidBufferBounds { initial: usize, max: usize },
111 #[error(
112 "invalid {buffer} buffer capacity {capacity}; expected a value between initial {initial} and maximum {max}"
113 )]
114 InvalidBufferCapacity {
115 buffer: &'static str,
116 capacity: usize,
117 initial: usize,
118 max: usize,
119 },
120 #[error(
121 "{buffer} buffer carries {data_len} bytes but its restored capacity is only {capacity}"
122 )]
123 BufferedDataExceedsCapacity {
124 buffer: &'static str,
125 data_len: usize,
126 capacity: usize,
127 },
128 #[error("invalid channel snapshot {field} bits: {bits:#06x}")]
129 InvalidReadyBits { field: &'static str, bits: u16 },
130 #[error(
131 "channel snapshot buffer limits ({snapshot_initial}, {snapshot_max}) do not match the receiver's configured limits ({expected_initial}, {expected_max})"
132 )]
133 BufferLimitsMismatch {
134 snapshot_initial: usize,
135 snapshot_max: usize,
136 expected_initial: usize,
137 expected_max: usize,
138 },
139 #[error("could not read blocking mode of channel descriptor {fd}: {error}")]
140 BlockingModeQuery { fd: RawFd, error: String },
141 #[error(
142 "channel snapshot blocking mode ({snapshot_blocking}) disagrees with descriptor blocking mode ({fd_blocking})"
143 )]
144 BlockingModeMismatch {
145 snapshot_blocking: bool,
146 fd_blocking: bool,
147 },
148}
149
150impl ChannelSnapshot {
151 /// Whether restoring this channel leaves input already available in
152 /// userspace, independently of kernel readiness notifications.
153 pub fn has_buffered_input(&self) -> bool {
154 !self.front.is_empty()
155 }
156
157 /// Whether restoring this channel leaves output queued in userspace,
158 /// independently of kernel readiness notifications.
159 pub fn has_buffered_output(&self) -> bool {
160 !self.back.is_empty()
161 }
162
163 /// Total userspace bytes that the kernel cannot report through readiness.
164 pub fn buffered_bytes(&self) -> usize {
165 self.front.len().saturating_add(self.back.len())
166 }
167
168 fn validate(&self) -> Result<(), ChannelSnapshotError> {
169 if self.version != CHANNEL_SNAPSHOT_VERSION {
170 return Err(ChannelSnapshotError::UnsupportedVersion {
171 version: self.version,
172 supported: CHANNEL_SNAPSHOT_VERSION,
173 });
174 }
175 if self.initial_buffer_size > self.max_buffer_size {
176 return Err(ChannelSnapshotError::InvalidBufferBounds {
177 initial: self.initial_buffer_size,
178 max: self.max_buffer_size,
179 });
180 }
181
182 for (buffer, capacity) in [
183 ("front", self.front_buffer_capacity),
184 ("back", self.back_buffer_capacity),
185 ] {
186 if capacity < self.initial_buffer_size || capacity > self.max_buffer_size {
187 return Err(ChannelSnapshotError::InvalidBufferCapacity {
188 buffer,
189 capacity,
190 initial: self.initial_buffer_size,
191 max: self.max_buffer_size,
192 });
193 }
194 }
195
196 for (buffer, data_len, capacity) in [
197 ("front", self.front.len(), self.front_buffer_capacity),
198 ("back", self.back.len(), self.back_buffer_capacity),
199 ] {
200 if data_len > capacity {
201 return Err(ChannelSnapshotError::BufferedDataExceedsCapacity {
202 buffer,
203 data_len,
204 capacity,
205 });
206 }
207 }
208
209 for (field, bits) in [("readiness", self.readiness), ("interest", self.interest)] {
210 if bits & !CHANNEL_READY_MASK != 0 {
211 return Err(ChannelSnapshotError::InvalidReadyBits { field, bits });
212 }
213 }
214
215 Ok(())
216 }
217}
218
219/// Does this read failure leave a channel that could parse the peer's next
220/// frame, if only it were allowed to refill from the socket?
221///
222/// `readable()` drops `Ready::READABLE` from `interest` whenever it fills
223/// `front_buf` to a capacity it may not grow past, and then refuses to run at
224/// all while the bit is missing (`if !(self.interest & self.readiness)
225/// .is_readable()`). That is correct backpressure, and the parse that frees
226/// room is what undoes it -- but until sozu-proxy/sozu#1445 only ONE parse
227/// outcome did: `read_message_nonblocking` re-armed on its `NothingRead` path
228/// and propagated every error through `?` before reaching it. A frame the
229/// parser rejected and then re-synchronised past therefore re-aligned a buffer
230/// it could no longer refill, and the bytes still in the socket were stranded
231/// on a session `wants_to_tick` (`bin/src/command/sessions.rs`) does not
232/// re-schedule for being merely readable.
233///
234/// The answer is per-variant, not a blanket re-arm before the `?`: a socket
235/// that just failed a `read(2)`, or a peer this end has decided to drop, must
236/// not have its readability re-asserted. The match below is exhaustive with no
237/// wildcard arm ON PURPOSE -- a variant added to `ChannelError` must fail to
238/// compile here rather than inherit someone else's disposition.
239///
240/// Re-arm (the channel retired bytes and is framed on a boundary again; the
241/// only thing missing is more bytes):
242///
243/// - `NothingRead` -- the frame is simply incomplete. The pre-existing case,
244/// and the reason the bit is ever re-armed at all.
245/// - `MessageLengthUnderDelimiter` -- the parser consumed exactly
246/// `delimiter_size()` bytes carrying a declared length no writer can emit for
247/// any payload, so those bytes are provably not a header and skipping them
248/// re-aligns the stream. Re-syncing a buffer that may never refill only
249/// re-syncs the bytes already in hand, which is the case #1445 was filed on.
250/// - `InvalidProtobufMessage` -- since sozu-proxy/sozu#1428 the whole frame is
251/// consumed before the decode failure is returned, so the buffer is left
252/// exactly as the decode-success path leaves it. The frame is lost; the
253/// channel is not.
254///
255/// Do not re-arm:
256///
257/// - `MessageTooLarge` -- twice not, and the first reason does not depend on
258/// the second. It consumes NOTHING: unlike its two siblings above it cannot,
259/// because the bytes behind the header may be payload rather than a fresh
260/// delimiter and re-framing on them would decode peer-chosen bytes as a
261/// control-plane request. So the buffer is not on a boundary and refilling
262/// only re-parses the same header -- a busy loop, not a recovery, and the
263/// frame could not complete anyway, a length above `max_buffer_size` not
264/// fitting a buffer bounded by `max_buffer_size` however much more is read.
265/// On top of that the branch has marked `Ready::ERROR` since #1428, so
266/// re-arming would assert readability on a channel this end has already
267/// decided to close.
268/// - `BufferFull` -- past #1436's compaction this arm means a compacted buffer
269/// at the ceiling whose whole capacity cannot hold a length prefix, so
270/// `available_space()` is zero and cannot grow. `readable()` would read zero
271/// bytes and immediately remove the bit again; refilling cannot change the
272/// outcome.
273/// - `MismatchBufferSize` -- "this should never happen": a `try_into` on a
274/// slice whose length was just checked against `delimiter_size()`. It is an
275/// internal invariant violation, and the same slice fails identically on
276/// every retry, so re-arming would spin instead of recovering.
277/// - `NoByteToRead` -- `read(2)` returned zero, the peer is gone.
278/// `readable()` has already set `interest = Ready::EMPTY` and raised HUP;
279/// re-arming would contradict the hangup it just recorded.
280/// - `Read` -- a hard socket failure, after which `readable()` has already
281/// cleared `interest` and raised HUP and ERROR (sozu-proxy/sozu#1560).
282/// - `Connection` -- either a failed `connect(2)` or `readable()`/`writable()`
283/// rejecting the call because the interest gate is shut. Restoring from here
284/// the very bit that gate just refused is the loop this function exists to
285/// avoid.
286/// - `NoByteWritten`, `Write` -- back-buffer/socket write failures, raised on
287/// the write path and terminal there (`NoByteWritten` raises HUP, a
288/// `writable()` socket error raises HUP and ERROR); nothing about them says
289/// the read side may refill.
290/// - `TimeoutReached` -- the blocking read path's deadline
291/// (`read_message_blocking_timeout`), an operator-visible bound rather than a
292/// framing state.
293/// - `SetTimeout`, `BlockingStatus` -- `setsockopt`/`fcntl` failures on the
294/// underlying stream, i.e. blocking-mode plumbing.
295/// - `InvalidCharSet` -- declared here but constructed nowhere; the live
296/// variant of that name is `ScmSocketError::InvalidCharSet`
297/// (`command/src/scm_socket.rs`). It is listed so the match stays exhaustive,
298/// and it is not a framing state either way.
299///
300/// Exactly six of the fifteen reach this function today.
301/// `try_read_delimited_message` raises five of them -- `MessageTooLarge`,
302/// `MessageLengthUnderDelimiter`, `InvalidProtobufMessage`, `BufferFull` and
303/// `MismatchBufferSize` -- and `read_message_nonblocking` mints the sixth,
304/// `NothingRead`, from its `Ok(None)`. The other nine (`NoByteToRead`, `Read`,
305/// `Connection`, `NoByteWritten`, `Write`, `TimeoutReached`, `SetTimeout`,
306/// `BlockingStatus`, `InvalidCharSet`) are dispositioned anyway rather than
307/// left to a wildcard, because a future call site is exactly how a wildcard
308/// becomes a wrong answer nobody wrote down.
309fn rearms_readable(error: &ChannelError) -> bool {
310 match error {
311 ChannelError::NothingRead
312 | ChannelError::MessageLengthUnderDelimiter { .. }
313 | ChannelError::InvalidProtobufMessage(_) => true,
314
315 ChannelError::MessageTooLarge { .. }
316 | ChannelError::BufferFull { .. }
317 | ChannelError::MismatchBufferSize
318 | ChannelError::NoByteToRead
319 | ChannelError::Read(_)
320 | ChannelError::Connection(_)
321 | ChannelError::NoByteWritten
322 | ChannelError::Write(_)
323 | ChannelError::TimeoutReached(_)
324 | ChannelError::SetTimeout { .. }
325 | ChannelError::BlockingStatus { .. }
326 | ChannelError::InvalidCharSet(_) => false,
327 }
328}
329
330/// Channel meant for communication between Sōzu processes over a UNIX socket.
331/// It wraps a unix socket using the mio crate, and transmit prost messages
332/// by serializing them in a binary format, with a fix-sized delimiter.
333/// To function, channels must come in pairs, one for each agent.
334/// They can function in a blocking or non-blocking way.
335pub struct Channel<Tx, Rx> {
336 pub sock: MioUnixStream,
337 pub front_buf: Buffer,
338 pub back_buf: Buffer,
339 initial_buffer_size: usize,
340 max_buffer_size: usize,
341 pub readiness: Ready,
342 pub interest: Ready,
343 blocking: bool,
344 /// true if a high watermark warning has been logged for the front buffer
345 front_high_watermark_logged: bool,
346 /// true if a high watermark warning has been logged for the back buffer
347 back_high_watermark_logged: bool,
348 phantom_tx: PhantomData<Tx>,
349 phantom_rx: PhantomData<Rx>,
350}
351
352/// A restored channel that may be registered with mio but cannot perform I/O.
353///
354/// Keeping the active [`Channel`] private prevents the receiving process from
355/// reading or writing a shared socket before the upgrade handoff commits.
356pub struct PausedChannel<Tx, Rx> {
357 channel: Channel<Tx, Rx>,
358}
359
360impl<Tx, Rx> PausedChannel<Tx, Rx> {
361 /// Returns the received descriptor for handoff bookkeeping.
362 pub fn fd(&self) -> RawFd {
363 self.channel.sock.as_raw_fd()
364 }
365
366 /// Validate mio registration during the prepared handoff phase.
367 ///
368 /// Registration changes neither the socket's blocking mode nor its byte
369 /// streams. No readiness is handled until [`PausedChannel::resume`]
370 /// returns the active channel after commit.
371 pub fn register(
372 &mut self,
373 registry: &Registry,
374 token: Token,
375 interests: Interest,
376 ) -> io::Result<()> {
377 self.channel.sock.register(registry, token, interests)
378 }
379
380 /// Activate the restored channel after the upgrade handoff commits.
381 pub fn resume(self) -> Channel<Tx, Rx> {
382 self.channel
383 }
384}
385
386impl<Tx, Rx> std::fmt::Debug for Channel<Tx, Rx> {
387 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
388 f.debug_struct(&format!(
389 "Channel<{}, {}>",
390 std::any::type_name::<Tx>(),
391 std::any::type_name::<Rx>()
392 ))
393 .field("sock", &self.sock.as_raw_fd())
394 // .field("front_buf", &self.front_buf)
395 // .field("back_buf", &self.back_buf)
396 // .field("max_buffer_size", &self.max_buffer_size)
397 .field("readiness", &self.readiness)
398 .field("interest", &self.interest)
399 .field("blocking", &self.blocking)
400 .finish()
401 }
402}
403
404impl<Tx: Debug + ProstMessage + Default, Rx: Debug + ProstMessage + Default> Channel<Tx, Rx> {
405 /// Creates a nonblocking channel on a given socket path
406 pub fn from_path(
407 path: &str,
408 buffer_size: u64,
409 max_buffer_size: u64,
410 ) -> Result<Channel<Tx, Rx>, ChannelError> {
411 let unix_stream = MioUnixStream::connect(path)
412 .map_err(|io_error| ChannelError::Connection(Some(io_error)))?;
413 Ok(Channel::new(unix_stream, buffer_size, max_buffer_size))
414 }
415
416 /// Creates a nonblocking channel, using a unix stream
417 ///
418 /// `buffer_size` is clamped to `max_buffer_size` (sozu-proxy/sozu#1416):
419 /// every growth/shrink path in this file — `grow_size`, `try_read_delimited_message`,
420 /// `try_shrink_front_buf`/`try_shrink_back_buf` — reasons about `front_buf`/`back_buf`
421 /// capacity never exceeding `max_buffer_size`; a caller-supplied `buffer_size` above
422 /// that ceiling would start the channel already violating the invariant those paths
423 /// assert, before a single byte is read.
424 ///
425 /// A clamp, not a fallible constructor, because `max_buffer_size` and
426 /// `initial_buffer_size` are private fields with no external struct-literal path
427 /// around this function: `Channel::new` is the *only* place a `Channel` is built, so
428 /// unlike `H2FloodConfig::new` (sozu-proxy/sozu#1418, whose clamp is bypassed in
429 /// production by a raw struct literal in `get_h2_flood_config`) this clamp cannot be
430 /// routed around — it runs on every construction, including the dozen-plus call sites
431 /// across `bin/`, `e2e/`, `lib/examples/` and this module's own tests. Config load
432 /// (`ConfigBuilder::into_config`) is the operator-facing gate and rejects the
433 /// misconfiguration with a named error before a `Channel` is ever built; this clamp is
434 /// the structural backstop for any other direct caller that bypasses config
435 /// validation (tests, examples, a future call site), so it logs instead of silently
436 /// doing nothing observable.
437 pub fn new(sock: MioUnixStream, buffer_size: u64, max_buffer_size: u64) -> Channel<Tx, Rx> {
438 let buffer_size = buffer_size as usize;
439 let max_buffer_size = max_buffer_size as usize;
440 let initial_buffer_size = min(buffer_size, max_buffer_size);
441 if initial_buffer_size < buffer_size {
442 warn!(
443 "channel buffer_size ({}) exceeds max_buffer_size ({}); clamping initial \
444 buffer capacity to {}",
445 buffer_size, max_buffer_size, initial_buffer_size
446 );
447 }
448 // Postcondition: the invariant every growth/shrink path in this file assumes.
449 debug_assert!(
450 initial_buffer_size <= max_buffer_size,
451 "initial buffer capacity must never exceed max_buffer_size"
452 );
453 Channel {
454 sock,
455 front_buf: Buffer::with_capacity(initial_buffer_size),
456 back_buf: Buffer::with_capacity(initial_buffer_size),
457 initial_buffer_size,
458 max_buffer_size,
459 readiness: Ready::EMPTY,
460 interest: Ready::READABLE,
461 blocking: false,
462 front_high_watermark_logged: false,
463 back_high_watermark_logged: false,
464 phantom_tx: PhantomData,
465 phantom_rx: PhantomData,
466 }
467 }
468
469 /// Capture every userspace byte and scheduling bit needed to continue this
470 /// channel after a main-process upgrade.
471 pub fn snapshot(&self) -> ChannelSnapshot {
472 debug_assert!(self.initial_buffer_size <= self.max_buffer_size);
473 debug_assert!(self.front_buf.capacity() <= self.max_buffer_size);
474 debug_assert!(self.back_buf.capacity() <= self.max_buffer_size);
475
476 ChannelSnapshot {
477 version: CHANNEL_SNAPSHOT_VERSION,
478 front: self.front_buf.data().to_owned(),
479 back: self.back_buf.data().to_owned(),
480 initial_buffer_size: self.initial_buffer_size,
481 max_buffer_size: self.max_buffer_size,
482 front_buffer_capacity: self.front_buf.capacity(),
483 back_buffer_capacity: self.back_buf.capacity(),
484 readiness: self.readiness.0,
485 interest: self.interest.0,
486 blocking: self.blocking,
487 front_high_watermark_logged: self.front_high_watermark_logged,
488 back_high_watermark_logged: self.back_high_watermark_logged,
489 }
490 }
491
492 /// Rebuild a channel around a received socket without reading, writing, or
493 /// changing flags on the shared open file description.
494 ///
495 /// The receiver supplies the configured buffer limits independently. A
496 /// snapshot cannot use its own size fields to authorize an allocation.
497 /// The descriptor's actual `O_NONBLOCK` flag must likewise agree with the
498 /// captured mode; neither validation changes that shared flag.
499 ///
500 /// The returned wrapper permits mio registration during PREPARED, but only
501 /// [`PausedChannel::resume`] exposes the active channel after COMMIT.
502 pub fn restore_paused(
503 sock: MioUnixStream,
504 snapshot: ChannelSnapshot,
505 expected_initial_buffer_size: usize,
506 expected_max_buffer_size: usize,
507 ) -> Result<PausedChannel<Tx, Rx>, ChannelSnapshotError> {
508 snapshot.validate()?;
509
510 if snapshot.initial_buffer_size != expected_initial_buffer_size
511 || snapshot.max_buffer_size != expected_max_buffer_size
512 {
513 return Err(ChannelSnapshotError::BufferLimitsMismatch {
514 snapshot_initial: snapshot.initial_buffer_size,
515 snapshot_max: snapshot.max_buffer_size,
516 expected_initial: expected_initial_buffer_size,
517 expected_max: expected_max_buffer_size,
518 });
519 }
520
521 let fd = sock.as_raw_fd();
522 // SAFETY: `sock` owns a live descriptor for this call. F_GETFL only
523 // reads flags from its open file description and cannot change the
524 // descriptor shared with the old main process during PREPARED.
525 let flags = unsafe { libc::fcntl(fd, libc::F_GETFL) };
526 if flags < 0 {
527 return Err(ChannelSnapshotError::BlockingModeQuery {
528 fd,
529 error: io::Error::last_os_error().to_string(),
530 });
531 }
532 let fd_blocking = flags & libc::O_NONBLOCK == 0;
533 if snapshot.blocking != fd_blocking {
534 return Err(ChannelSnapshotError::BlockingModeMismatch {
535 snapshot_blocking: snapshot.blocking,
536 fd_blocking,
537 });
538 }
539
540 let mut front_buf = Buffer::from_slice(&snapshot.front);
541 front_buf.grow(snapshot.front_buffer_capacity);
542 let mut back_buf = Buffer::from_slice(&snapshot.back);
543 back_buf.grow(snapshot.back_buffer_capacity);
544
545 debug_assert_eq!(front_buf.capacity(), snapshot.front_buffer_capacity);
546 debug_assert_eq!(back_buf.capacity(), snapshot.back_buffer_capacity);
547 debug_assert_eq!(front_buf.data(), snapshot.front);
548 debug_assert_eq!(back_buf.data(), snapshot.back);
549
550 Ok(PausedChannel {
551 channel: Channel {
552 sock,
553 front_buf,
554 back_buf,
555 initial_buffer_size: snapshot.initial_buffer_size,
556 max_buffer_size: snapshot.max_buffer_size,
557 readiness: Ready(snapshot.readiness),
558 interest: Ready(snapshot.interest),
559 blocking: snapshot.blocking,
560 front_high_watermark_logged: snapshot.front_high_watermark_logged,
561 back_high_watermark_logged: snapshot.back_high_watermark_logged,
562 phantom_tx: PhantomData,
563 phantom_rx: PhantomData,
564 },
565 })
566 }
567
568 pub fn into<Tx2: Debug + ProstMessage + Default, Rx2: Debug + ProstMessage + Default>(
569 self,
570 ) -> Channel<Tx2, Rx2> {
571 Channel {
572 sock: self.sock,
573 front_buf: self.front_buf,
574 back_buf: self.back_buf,
575 initial_buffer_size: self.initial_buffer_size,
576 max_buffer_size: self.max_buffer_size,
577 readiness: self.readiness,
578 interest: self.interest,
579 blocking: self.blocking,
580 front_high_watermark_logged: self.front_high_watermark_logged,
581 back_high_watermark_logged: self.back_high_watermark_logged,
582 phantom_tx: PhantomData,
583 phantom_rx: PhantomData,
584 }
585 }
586
587 // Since MioUnixStream does not have a set_nonblocking method, we have to use the standard library.
588 // We get the file descriptor of the MioUnixStream socket, create a standard library UnixStream,
589 // set it to nonblocking, let go of the file descriptor
590 fn set_nonblocking(&mut self, nonblocking: bool) -> Result<(), ChannelError> {
591 // SAFETY: `fd` is borrowed from `self.sock` for the duration of this
592 // block. We wrap it in a `StdUnixStream` to call `set_nonblocking`,
593 // then immediately release ownership again with `into_raw_fd` so the
594 // descriptor is not closed by `Drop`. `self.sock` retains the
595 // original ownership.
596 unsafe {
597 let fd = self.sock.as_raw_fd();
598 let stream = StdUnixStream::from_raw_fd(fd);
599 stream
600 .set_nonblocking(nonblocking)
601 .map_err(|error| ChannelError::BlockingStatus {
602 fd,
603 error: error.to_string(),
604 })?;
605 let _fd = stream.into_raw_fd();
606 }
607 self.blocking = !nonblocking;
608 Ok(())
609 }
610
611 /// set the read_timeout of the unix stream. This works only temporary, be sure to set the timeout to None afterwards.
612 fn set_timeout(&mut self, timeout: Option<Duration>) -> Result<(), ChannelError> {
613 // SAFETY: `fd` is borrowed from `self.sock` for the duration of this
614 // block. We wrap it in a `StdUnixStream` to call `set_read_timeout`,
615 // then immediately release ownership again with `into_raw_fd` so the
616 // descriptor is not closed by `Drop`. `self.sock` retains the
617 // original ownership.
618 unsafe {
619 let fd = self.sock.as_raw_fd();
620 let stream = StdUnixStream::from_raw_fd(fd);
621 stream
622 .set_read_timeout(timeout)
623 .map_err(|error| ChannelError::SetTimeout {
624 fd,
625 error: error.to_string(),
626 })?;
627 let _fd = stream.into_raw_fd();
628 }
629 Ok(())
630 }
631
632 /// set the channel to be blocking
633 pub fn blocking(&mut self) -> Result<(), ChannelError> {
634 self.set_nonblocking(false)
635 }
636
637 /// set the channel to be nonblocking
638 pub fn nonblocking(&mut self) -> Result<(), ChannelError> {
639 self.set_nonblocking(true)
640 }
641
642 pub fn is_blocking(&self) -> bool {
643 self.blocking
644 }
645
646 /// Get the raw file descriptor of the UNIX socket
647 pub fn fd(&self) -> RawFd {
648 self.sock.as_raw_fd()
649 }
650
651 pub fn handle_events(&mut self, events: Ready) {
652 self.readiness |= events;
653 }
654
655 pub fn readiness(&self) -> Ready {
656 self.readiness & self.interest
657 }
658
659 /// Compute the next buffer size using a doubling strategy, capped at max_buffer_size.
660 /// Returns None if the buffer is already at max capacity.
661 fn grow_size(&self, current_capacity: usize) -> Option<usize> {
662 if current_capacity >= self.max_buffer_size {
663 return None;
664 }
665 // Precondition: we only reach here below the ceiling, so a strictly
666 // larger size always exists within [current+1, max_buffer_size].
667 debug_assert!(
668 current_capacity < self.max_buffer_size,
669 "grow_size must only grow buffers that are strictly below the max ceiling"
670 );
671 // double the capacity, but don't exceed max
672 let new_size = min(current_capacity.saturating_mul(2), self.max_buffer_size);
673 // ensure we grow by at least something (in case current_capacity is 0)
674 let new_size = new_size.max(current_capacity + 1);
675 let new_size = min(new_size, self.max_buffer_size);
676 // Postconditions: the new capacity strictly grows (forward progress,
677 // no spin) and never overshoots the configured ceiling.
678 debug_assert!(
679 new_size > current_capacity,
680 "grow_size must make forward progress (new capacity strictly larger)"
681 );
682 debug_assert!(
683 new_size <= self.max_buffer_size,
684 "grow_size must never exceed the configured max_buffer_size ceiling"
685 );
686 Some(new_size)
687 }
688
689 /// Check if a buffer has exceeded the high watermark and log a warning once
690 fn check_high_watermark(
691 buffer_name: &str,
692 capacity: usize,
693 max: usize,
694 already_logged: &mut bool,
695 ) {
696 if *already_logged {
697 return;
698 }
699 let threshold = (max as f64 * HIGH_WATERMARK_RATIO) as usize;
700 if capacity >= threshold {
701 warn!(
702 "channel {} buffer reached high watermark: {} bytes ({:.0}% of {} max)",
703 buffer_name,
704 capacity,
705 (capacity as f64 / max as f64) * 100.0,
706 max,
707 );
708 *already_logged = true;
709 }
710 }
711
712 /// Check wether we want and can read or write, and calls the appropriate handler.
713 pub fn run(&mut self) -> Result<(), ChannelError> {
714 let interest = self.interest & self.readiness;
715
716 if interest.is_readable() {
717 let _ = self.readable()?;
718 }
719
720 if interest.is_writable() {
721 let _ = self.writable()?;
722 }
723 Ok(())
724 }
725
726 /// Handle readability by filling the front buffer with the socket data.
727 /// Grows the front buffer when full using a doubling strategy, up to max_buffer_size.
728 pub fn readable(&mut self) -> Result<usize, ChannelError> {
729 if !(self.interest & self.readiness).is_readable() {
730 return Err(ChannelError::Connection(None));
731 }
732
733 let mut count = 0usize;
734 loop {
735 let size = self.front_buf.available_space();
736 trace!("channel available space: {}", size);
737 if size == 0 {
738 // try to grow the buffer before giving up
739 if let Some(new_size) = self.grow_size(self.front_buf.capacity()) {
740 Self::check_high_watermark(
741 "front",
742 new_size,
743 self.max_buffer_size,
744 &mut self.front_high_watermark_logged,
745 );
746 self.front_buf.grow(new_size);
747 // The read buffer must never grow past the configured ceiling.
748 debug_assert!(
749 self.front_buf.capacity() <= self.max_buffer_size,
750 "front buffer capacity must stay within the max_buffer_size ceiling"
751 );
752 } else {
753 self.interest.remove(Ready::READABLE);
754 break;
755 }
756 }
757
758 // The slice handed to `read` is exactly the free tail; the kernel
759 // can never write more than that, so a successful read of N bytes
760 // is bounded by the space we just measured (post-grow).
761 let space_before = self.front_buf.available_space();
762 debug_assert!(
763 space_before > 0,
764 "readable must only call read() with non-empty space (zero-space path grows or breaks)"
765 );
766 let data_before = self.front_buf.available_data();
767 match self.sock.read(self.front_buf.space()) {
768 Ok(0) => {
769 self.interest = Ready::EMPTY;
770 self.readiness.remove(Ready::READABLE);
771 self.readiness.insert(Ready::HUP);
772 return Err(ChannelError::NoByteToRead);
773 }
774 Err(read_error) => match read_error.kind() {
775 ErrorKind::WouldBlock => {
776 self.readiness.remove(Ready::READABLE);
777 break;
778 }
779 _ => {
780 // Mark the channel for closing, as the `Ok(0)` arm
781 // does: the owner closes on HUP or ERROR, and an
782 // edge-triggered poller will not report them again
783 // (sozu-proxy/sozu#1560). Assignment, not `insert`:
784 // the socket has failed, so no other bit may stay
785 // armed.
786 self.interest = Ready::EMPTY;
787 self.readiness = Ready::HUP | Ready::ERROR;
788 return Err(ChannelError::Read(read_error));
789 }
790 },
791 Ok(bytes_read) => {
792 // A read can never deliver more bytes than the free space
793 // it was handed; otherwise `fill` would corrupt offsets.
794 debug_assert!(
795 bytes_read <= space_before,
796 "read delivered more bytes than the buffer space it was given"
797 );
798 count += bytes_read;
799 self.front_buf.fill(bytes_read);
800 // `fill` advances `end` by exactly the bytes read, so the
801 // available data grows by that delta — pair-assert the
802 // offset mutation.
803 debug_assert_eq!(
804 self.front_buf.available_data(),
805 data_before + bytes_read,
806 "front buffer available_data must increase by exactly bytes_read"
807 );
808 }
809 };
810 }
811
812 Ok(count)
813 }
814
815 /// Handle writability by writing the content of the back buffer onto the socket.
816 /// Shrinks the back buffer back toward initial size once fully drained.
817 pub fn writable(&mut self) -> Result<usize, ChannelError> {
818 if !(self.interest & self.readiness).is_writable() {
819 return Err(ChannelError::Connection(None));
820 }
821
822 let mut count = 0usize;
823 loop {
824 let size = self.back_buf.available_data();
825 if size == 0 {
826 self.interest.remove(Ready::WRITABLE);
827 self.try_shrink_back_buf();
828 break;
829 }
830
831 let data_before = self.back_buf.available_data();
832 match self.sock.write(self.back_buf.data()) {
833 Ok(0) => {
834 self.interest = Ready::EMPTY;
835 self.readiness.insert(Ready::HUP);
836 return Err(ChannelError::NoByteWritten);
837 }
838 Ok(bytes_written) => {
839 // The kernel cannot accept more than the slice (`data()`)
840 // it was handed; otherwise `consume` would underflow.
841 debug_assert!(
842 bytes_written <= data_before,
843 "write reported more bytes than the buffer data it was given"
844 );
845 count += bytes_written;
846 let consumed = self.back_buf.consume(bytes_written);
847 // `consume` is saturating but the bound above guarantees an
848 // exact consume here; pair-assert the offset mutation.
849 debug_assert_eq!(
850 consumed, bytes_written,
851 "back buffer must consume exactly the bytes written to the socket"
852 );
853 debug_assert_eq!(
854 self.back_buf.available_data(),
855 data_before - bytes_written,
856 "back buffer available_data must shrink by exactly bytes_written"
857 );
858 }
859 Err(write_error) => match write_error.kind() {
860 ErrorKind::WouldBlock => {
861 self.readiness.remove(Ready::WRITABLE);
862 break;
863 }
864 _ => {
865 // Same as the read side: keep the channel marked for
866 // closing instead of wiping HUP and ERROR. WRITABLE
867 // must go: the worker's `Server::send_queue` keeps
868 // looping while it is set and data is pending.
869 self.interest = Ready::EMPTY;
870 self.readiness = Ready::HUP | Ready::ERROR;
871 return Err(ChannelError::Write(write_error));
872 }
873 },
874 }
875 }
876
877 Ok(count)
878 }
879
880 /// Depending on the blocking status:
881 ///
882 /// Blocking: wait for the front buffer to be filled, and parse a message from it
883 ///
884 /// Nonblocking: parse a message from the front buffer, without waiting.
885 /// Prefer using `channel.readable()` before
886 pub fn read_message(&mut self) -> Result<Rx, ChannelError> {
887 if self.blocking {
888 self.read_message_blocking()
889 } else {
890 self.read_message_nonblocking()
891 }
892 }
893
894 fn read_message_blocking(&mut self) -> Result<Rx, ChannelError> {
895 self.read_message_blocking_timeout(None)
896 }
897
898 /// Parse a message from the front buffer, without waiting
899 fn read_message_nonblocking(&mut self) -> Result<Rx, ChannelError> {
900 // `NothingRead` is not a special case, it is one row of
901 // [`rearms_readable`]'s table: an incomplete frame and a frame the
902 // parser rejected and re-synchronised past are the same situation from
903 // `readable()`'s point of view, and propagating the second through `?`
904 // before the re-arm is what stranded the socket bytes behind it
905 // (sozu-proxy/sozu#1445).
906 let error = match self.try_read_delimited_message() {
907 Ok(Some(message)) => {
908 self.try_shrink_front_buf();
909 return Ok(message);
910 }
911 Ok(None) => ChannelError::NothingRead,
912 Err(error) => error,
913 };
914
915 if rearms_readable(&error) {
916 self.interest.insert(Ready::READABLE);
917 }
918 Err(error)
919 }
920
921 /// Wait for the front buffer to be filled, and parses a message from it.
922 ///
923 /// The wait ends with a message, a read error, or once `timeout` has
924 /// elapsed since the call. A read interrupted by a signal (`EINTR`) is
925 /// retried against that same deadline, never restarting it.
926 pub fn read_message_blocking_timeout(
927 &mut self,
928 timeout: Option<Duration>,
929 ) -> Result<Rx, ChannelError> {
930 let now = std::time::Instant::now();
931
932 // 10 ms = 100 syscalls/sec on idle WouldBlock, pinning a CPU on
933 // long blocking waits with no payload. 100 ms is
934 // a usability-acceptable resolution for the outer `timeout`
935 // deadline check (the wait is bounded by `timeout`, not by this
936 // value) and drops the steady-state read syscall rate to 10/sec.
937 self.set_timeout(Some(Duration::from_millis(100)))?;
938
939 let status = loop {
940 if let Some(timeout) = timeout
941 && now.elapsed() >= timeout
942 {
943 break Err(ChannelError::TimeoutReached(timeout));
944 }
945
946 if let Some(message) = self.try_read_delimited_message()? {
947 self.try_shrink_front_buf();
948 return Ok(message);
949 }
950
951 match self.sock.read(self.front_buf.space()) {
952 Ok(0) => return Err(ChannelError::NoByteToRead),
953 Ok(bytes_read) => self.front_buf.fill(bytes_read),
954 Err(io_error) => match io_error.kind() {
955 // The 100 ms `SO_RCVTIMEO` slice elapsed with no byte.
956 ErrorKind::WouldBlock => continue,
957 // signal(7): a read with `SO_RCVTIMEO` is never restarted,
958 // even under `SA_RESTART`, so any handled signal (the main
959 // process's `SIGTERM`) lands here. Retry: the deadline
960 // check at the top of the loop still bounds the wait.
961 ErrorKind::Interrupted => continue,
962 _ => break Err(ChannelError::Read(io_error)),
963 },
964 };
965 };
966
967 self.set_timeout(None)?;
968
969 status
970 }
971
972 /// parse a prost message from the front buffer, grow it if necessary
973 fn try_read_delimited_message(&mut self) -> Result<Option<Rx>, ChannelError> {
974 // Invariant guarding all the slice indexing below: the front buffer can
975 // never have grown past the configured ceiling. Every grow site routes
976 // through `grow_size`/`max_buffer_size`; if this ever fired the length
977 // checks would be reasoning against a stale bound.
978 debug_assert!(
979 self.front_buf.capacity() <= self.max_buffer_size,
980 "front buffer capacity must never exceed max_buffer_size"
981 );
982 let buffer = self.front_buf.data();
983 // `data()` returns `memory[position..end]`, so its length is exactly the
984 // available data and can never exceed the buffer capacity.
985 debug_assert!(
986 buffer.len() <= self.front_buf.capacity(),
987 "available data slice cannot exceed buffer capacity"
988 );
989 if buffer.len() >= delimiter_size() {
990 let delimiter = buffer[..delimiter_size()]
991 .try_into()
992 .map_err(|_| ChannelError::MismatchBufferSize)?;
993 let message_len = usize::from_le_bytes(delimiter);
994
995 // Defense in depth: bound the parser-side length up-front.
996 // Without this an attacker who controls the
997 // first 8 bytes of a frame can declare an arbitrarily large
998 // message and drive `Buffer::grow` toward the
999 // `max_buffer_size` ceiling before any byte of payload has
1000 // been read. Reject as `MessageTooLarge` before the doubling
1001 // growth strategy ever runs on attacker-supplied numbers.
1002 //
1003 // Unlike the `MessageLengthUnderDelimiter` case below, this one
1004 // must NOT consume and re-sync. That one can: a declared length
1005 // under `delimiter_size()` is a value no writer can produce for
1006 // any payload, so those bytes are provably not a real header and
1007 // skipping exactly them re-aligns the stream. Here the declared
1008 // length may be honest, in which case the bytes behind the header
1009 // are payload -- dropping the header and re-framing on them would
1010 // decode peer-chosen bytes as a fresh control-plane request. And
1011 // the frame can never complete either way: a length above
1012 // `max_buffer_size` does not fit a buffer bounded by
1013 // `max_buffer_size`, however much more is read.
1014 //
1015 // So it is fatal for that peer, which is already the stance taken
1016 // for the mirror condition on the write side
1017 // (`is_transient_overflow`, `bin/src/command/sessions.rs`: "a
1018 // frame bigger than the ceiling itself, which no amount of
1019 // draining will ever admit"). That is a statement about this end,
1020 // NOT about the peer's good faith: the two ends size their
1021 // channels from their own configuration, so a peer built with a
1022 // larger `max_command_buffer_size` emits frames this end refuses
1023 // while conforming perfectly -- `write_delimited_message` below
1024 // bounds an outgoing frame against the *writer's* ceiling, and the
1025 // two configurations shipped here already disagree tenfold
1026 // (`bin/config.toml` 163_840, `os-build/config.toml` 1_638_400).
1027 // A `sozu ctl --config` pointed at one while the supervisor was
1028 // started from the other, or two binaries built with different
1029 // `SOZU_CONFIG` defaults (`bin/build.rs`, `bin/src/util.rs`), is
1030 // all it takes. Closing is still the only correct exit for this
1031 // end, which can neither complete nor
1032 // re-sync the frame; the log line says what to reconcile instead
1033 // of implying an attack. Mark the channel errored -- the single
1034 // signal `ClientSession::ready`, `WorkerSession::ready` and
1035 // `wants_to_tick` all key on -- so the supervisor drops the
1036 // session. Without it the header is re-parsed forever,
1037 // `extract_messages` swallows the error with a bare `Err(_)`, and
1038 // the session wedges while pinning its file descriptor and buffer
1039 // (sozu-proxy/sozu#1428).
1040 //
1041 // `insert`, never assignment: on the worker's own channel
1042 // (`lib/src/server.rs`) `interest` carries only READABLE and
1043 // WRITABLE, so `readiness()` masks ERROR away and nothing there
1044 // reads `is_error()`. An assignment's only effect on that side
1045 // would be clearing WRITABLE, and `Server::send_queue` gates on
1046 // `channel.readiness.is_writable()`, so already-queued worker
1047 // responses would stall until the next edge-triggered writability
1048 // event.
1049 if message_len > self.max_buffer_size {
1050 error!(
1051 "peer declared a {}-byte frame, above this end's {}-byte ceiling; closing \
1052 the connection. The two ends size their channels independently, so this is \
1053 more often a configuration mismatch than a hostile peer: reconcile \
1054 `max_command_buffer_size` (and `command_buffer_size`, which must stay below \
1055 it) between this end and the peer's",
1056 message_len, self.max_buffer_size
1057 );
1058 self.readiness.insert(Ready::ERROR);
1059 return Err(ChannelError::MessageTooLarge {
1060 message_len,
1061 capacity: self.front_buf.capacity(),
1062 max: self.max_buffer_size,
1063 });
1064 }
1065
1066 // A length-delimited frame is `[delimiter][payload]`. The declared
1067 // `message_len` is the total frame size and MUST therefore be at
1068 // least `delimiter_size()`. A peer-controlled value below that
1069 // ceiling makes `&buffer[delimiter_size()..message_len]` slice
1070 // backwards and panic; reject it the same way as oversized frames.
1071 //
1072 // Drop the bogus delimiter bytes before returning. Without this,
1073 // every subsequent `read_message()` re-reads the same bad header
1074 // from the front buffer and the worker burns CPU on the same error
1075 // until the peer disconnects.
1076 //
1077 // This re-aligns what is already in `front_buf`, and nothing more;
1078 // `rearms_readable` above carries the refill half.
1079 if message_len < delimiter_size() {
1080 self.front_buf.consume(delimiter_size());
1081 return Err(ChannelError::MessageLengthUnderDelimiter {
1082 message_len,
1083 delimiter_size: delimiter_size(),
1084 });
1085 }
1086
1087 if buffer.len() >= message_len {
1088 // By the time we slice, the two guards above have proven the
1089 // length prefix is well-formed: it is at least the delimiter
1090 // (so `delimiter_size()..message_len` runs forward) and at most
1091 // the configured ceiling (so it cannot drive growth). The
1092 // `buffer.len() >= message_len` branch then guarantees the whole
1093 // frame is in the buffer. These are the exact invariants that
1094 // keep the slice and the `consume` in bounds — never reachable
1095 // from a malformed length, which already returned an error.
1096 debug_assert!(
1097 message_len >= delimiter_size(),
1098 "decode path requires a frame at least as large as its delimiter"
1099 );
1100 debug_assert!(
1101 message_len <= self.max_buffer_size,
1102 "decode path requires the declared length within the max ceiling"
1103 );
1104 debug_assert!(
1105 message_len <= buffer.len(),
1106 "decode path requires the full frame to be buffered before slicing"
1107 );
1108 let available_before = self.front_buf.available_data();
1109 debug_assert_eq!(
1110 available_before,
1111 buffer.len(),
1112 "available_data must equal the data slice length we validated against"
1113 );
1114 // Decode first, consume unconditionally, propagate a decode
1115 // failure only afterwards. Returning before the consume (as
1116 // this did until sozu-proxy/sozu#1428) left the whole frame at
1117 // the head of the buffer and moved no readiness bit, and
1118 // `extract_messages` (`bin/src/command/sessions.rs`) swallows
1119 // the error with its bare `Err(_)` arm, so the session
1120 // re-decoded the same bytes on every later read and wedged.
1121 //
1122 // Note what does NOT justify this. `message_len` is only
1123 // range-checked above -- at least `delimiter_size()`, at most
1124 // `max_buffer_size`, no larger than what is buffered. It is
1125 // not proven to be a real frame boundary, so on a
1126 // desynchronised stream consuming it re-frames on bytes the
1127 // sender never framed, which is the very hazard that argues
1128 // against consuming in the `MessageTooLarge` branch above.
1129 // Nor is this the `MessageLengthUnderDelimiter` stance: that
1130 // one consumes exactly `delimiter_size()` on a value no writer
1131 // can emit for any payload, an impossibility proof this branch
1132 // does not have.
1133 //
1134 // What justifies it is coherence with the success path four
1135 // lines down, which already consumes this same peer-supplied
1136 // `message_len` and re-frames on whatever follows. The
1137 // desynchronisation hazard is therefore identical whether the
1138 // payload decodes or not, and it predates this change.
1139 // Closing the peer only on the failing half would punish the
1140 // case more likely to be honest protobuf skew while silently
1141 // accepting the case that is not.
1142 let decoded = Rx::decode(&buffer[delimiter_size()..message_len]);
1143 let consumed = self.front_buf.consume(message_len);
1144 // The whole frame (delimiter + payload) is consumed exactly:
1145 // pair-assert that consume advanced by message_len and the data
1146 // pointer moved forward by the same amount.
1147 debug_assert_eq!(
1148 consumed, message_len,
1149 "must consume exactly the validated frame length"
1150 );
1151 debug_assert_eq!(
1152 self.front_buf.available_data(),
1153 available_before - message_len,
1154 "available_data must drop by exactly the consumed frame length"
1155 );
1156 let message = decoded.map_err(|decode_error| {
1157 error!(
1158 "could not decode a {}-byte frame from the peer; that frame is dropped \
1159 and the channel re-syncs on the next one: {}",
1160 message_len, decode_error
1161 );
1162 ChannelError::InvalidProtobufMessage(decode_error)
1163 })?;
1164 return Ok(Some(message));
1165 }
1166 }
1167
1168 if self.front_buf.available_space() == 0 {
1169 // Compact before concluding anything about the space (fixes
1170 // sozu-proxy/sozu#1436). `available_space()` is `capacity - end`,
1171 // so it measures the free TAIL, not the free room: a buffer whose
1172 // `position` sits mid-way reports zero space while holding
1173 // `position` reusable bytes at its head. `Buffer::consume`
1174 // (`command/src/buffer/growable.rs`) only compacts past
1175 // `capacity / 2`, so decoding one frame of at most half the
1176 // capacity out of a buffer filled to capacity leaves exactly that
1177 // layout, and `Buffer::shift` hands the room straight back.
1178 //
1179 // Without it this arm returned `BufferFull` without consuming and
1180 // without moving a readiness bit -- the shape
1181 // sozu-proxy/sozu#1428 removed from the two branches above -- and
1182 // no later call could recover: `readable()` sees
1183 // `available_space() == 0`, gets `None` from `grow_size` at the
1184 // ceiling and executes `interest.remove(Ready::READABLE)`, so
1185 // every later `readable()` returns `Err(Connection(None))` and
1186 // `fill` is out of reach; `consume` needs a decoded frame or an
1187 // under-delimiter length, which this return path never reaches;
1188 // and `try_shrink_front_buf` is never reached, because both
1189 // `read_message` paths call it only after a frame decoded, and
1190 // both of its own early returns would stop it anyway --
1191 // `capacity <= initial_buffer_size` whenever the channel was
1192 // configured with `command_buffer_size == max_command_buffer_size`,
1193 // and `available_data() * 4 < initial_buffer_size`, which is false
1194 // with that much pending. Not because it shrinks rather than
1195 // shifts: `Buffer::shrink` calls `shift` unconditionally before its
1196 // own size bail, so reaching it WOULD have compacted. The session
1197 // then wedged permanently, holding its file descriptor and a full
1198 // buffer, on a peer that had done nothing wrong.
1199 //
1200 // Compacting here also changes the sub-ceiling path: a front buffer
1201 // still below `max_buffer_size` now compacts on its first zero-space
1202 // parse and grows on the next one, rather than doubling straight
1203 // away. The frame still completes -- the compacted buffer refills to
1204 // `end == capacity` at `position == 0`, where the shift is a no-op
1205 // and the grow below runs -- one read-parse round later, against one
1206 // fewer doubling.
1207 //
1208 // Compaction is the whole recovery, NOT a close: the frame is
1209 // under this end's ceiling and its buffered bytes are intact, so
1210 // only the buffer's internal offsets stand in the way. It cannot
1211 // mask an unsatisfiable frame either -- a declared length above
1212 // `max_buffer_size` returns `MessageTooLarge` at the guard above,
1213 // before control ever arrives here, so the ordering of the two is
1214 // what keeps "too large for the ceiling" and "too large for the
1215 // current layout" apart. The write side already compacts on the
1216 // same reasoning before it considers growing
1217 // (`write_delimited_message` below).
1218 //
1219 // Compacting makes the channel recoverable; it does not by itself
1220 // make a session recover. Freeing space without changing capacity
1221 // ended `extract_messages`' drain loop (`bin/src/command/sessions.rs`)
1222 // on the very parse that made the room, one read short of completing
1223 // the frame, and nothing re-schedules a merely-readable session. That
1224 // loop's termination anchor was corrected in the same changeset; a
1225 // change to what this arm does to the buffer without changing
1226 // capacity has to be checked against it.
1227 self.front_buf.shift();
1228 }
1229
1230 if self.front_buf.available_space() == 0 {
1231 if self.front_buf.capacity() >= self.max_buffer_size {
1232 // Past the compaction above, zero space means `position == 0`
1233 // and `end == capacity`, so the whole capacity is pending data;
1234 // with `capacity == max_buffer_size` any in-range declared
1235 // length would have decoded already. Only a capacity too small
1236 // to hold a length prefix at all reaches this -- a
1237 // configuration that can never frame a message, not a buffer
1238 // that has run out of room.
1239 debug_assert!(
1240 self.front_buf.available_data() < delimiter_size(),
1241 "a compacted, full buffer at the ceiling can only fail to parse when its capacity cannot hold a length prefix"
1242 );
1243 return Err(ChannelError::BufferFull {
1244 capacity: self.front_buf.capacity(),
1245 max: self.max_buffer_size,
1246 });
1247 }
1248 let new_size = self
1249 .grow_size(self.front_buf.capacity())
1250 .unwrap_or(self.max_buffer_size);
1251 Self::check_high_watermark(
1252 "front",
1253 new_size,
1254 self.max_buffer_size,
1255 &mut self.front_high_watermark_logged,
1256 );
1257 self.front_buf.grow(new_size);
1258 }
1259 Ok(None)
1260 }
1261
1262 /// Checks whether the channel is blocking or nonblocking, writes the message.
1263 ///
1264 /// If the channel is nonblocking, you have to flush using `channel.run()` afterwards
1265 pub fn write_message(&mut self, message: &Tx) -> Result<(), ChannelError> {
1266 if self.blocking {
1267 self.write_message_blocking(message)
1268 } else {
1269 self.write_message_nonblocking(message)
1270 }
1271 }
1272
1273 /// Writes the message in the buffer, but NOT on the socket.
1274 /// you have to call channel.run() afterwards
1275 fn write_message_nonblocking(&mut self, message: &Tx) -> Result<(), ChannelError> {
1276 self.write_delimited_message(message)?;
1277
1278 self.interest.insert(Ready::WRITABLE);
1279
1280 Ok(())
1281 }
1282
1283 /// fills the back buffer with data AND writes on the socket
1284 ///
1285 /// Returns `Ok(())` only once the whole back buffer reached the socket.
1286 /// A `write(2)` interrupted by a signal is retried; every other error is
1287 /// returned as `ChannelError::Write`, and the bytes not yet written stay in
1288 /// `back_buf` (sozu-proxy/sozu#1563).
1289 ///
1290 /// `WouldBlock` is not retried. On a blocking socket it only follows a send
1291 /// timeout (`SO_SNDTIMEO`), and this channel never arms one: `set_timeout`
1292 /// sets the read timeout alone. Retrying would spin on a descriptor someone
1293 /// else made nonblocking, and success would claim a delivery that did not
1294 /// happen, so it is reported like any other write failure.
1295 fn write_message_blocking(&mut self, message: &Tx) -> Result<(), ChannelError> {
1296 self.write_delimited_message(message)?;
1297
1298 loop {
1299 let size = self.back_buf.available_data();
1300 if size == 0 {
1301 break;
1302 }
1303
1304 match self.sock.write(self.back_buf.data()) {
1305 Ok(0) => return Err(ChannelError::NoByteWritten),
1306 Ok(bytes_written) => {
1307 self.back_buf.consume(bytes_written);
1308 }
1309 Err(error) if error.kind() == ErrorKind::Interrupted => continue,
1310 Err(error) => return Err(ChannelError::Write(error)),
1311 }
1312 }
1313 Ok(())
1314 }
1315
1316 /// write a message on the back buffer, using our own delimiter (the delimiter of prost
1317 /// is not trustworthy since its size may change)
1318 pub fn write_delimited_message(&mut self, message: &Tx) -> Result<(), ChannelError> {
1319 let payload = message.encode_to_vec();
1320
1321 let payload_len = payload.len() + delimiter_size();
1322
1323 // The framed length is the payload plus a fixed delimiter, so it is by
1324 // construction at least one delimiter wide — the mirror of the
1325 // `MessageLengthUnderDelimiter` invariant the reader enforces.
1326 debug_assert!(
1327 payload_len >= delimiter_size(),
1328 "framed length must include the fixed-size delimiter prefix"
1329 );
1330
1331 let delimiter = payload_len.to_le_bytes();
1332
1333 if payload_len > self.back_buf.available_space() {
1334 self.back_buf.shift();
1335 }
1336
1337 let data_before = self.back_buf.available_data();
1338 if payload_len > self.back_buf.available_space() {
1339 let needed = payload_len - self.back_buf.available_space() + self.back_buf.capacity();
1340 if needed > self.max_buffer_size {
1341 return Err(ChannelError::MessageTooLarge {
1342 message_len: payload_len,
1343 capacity: self.back_buf.capacity(),
1344 max: self.max_buffer_size,
1345 });
1346 }
1347 // Past the ceiling check, the required size is within the ceiling
1348 // and at least the current capacity (we only enter on shortfall).
1349 debug_assert!(
1350 needed <= self.max_buffer_size,
1351 "grow target must be within the max ceiling once the cap check passed"
1352 );
1353
1354 let capacity_before = self.back_buf.capacity();
1355 // use doubling strategy to reach at least `needed`, amortizing future writes
1356 let mut new_length = self.back_buf.capacity();
1357 while new_length < needed {
1358 new_length = new_length.saturating_mul(2).max(new_length + 1);
1359 }
1360 new_length = min(new_length, self.max_buffer_size);
1361 // Post-grow target: large enough to fit the frame yet capped at the
1362 // configured ceiling and never below where we started.
1363 debug_assert!(
1364 new_length >= needed,
1365 "doubling growth must reach at least the needed capacity"
1366 );
1367 debug_assert!(
1368 new_length <= self.max_buffer_size,
1369 "grown back buffer must stay within the max_buffer_size ceiling"
1370 );
1371 debug_assert!(
1372 new_length >= capacity_before,
1373 "growth must never shrink the back buffer"
1374 );
1375 Self::check_high_watermark(
1376 "back",
1377 new_length,
1378 self.max_buffer_size,
1379 &mut self.back_high_watermark_logged,
1380 );
1381 self.back_buf.grow(new_length);
1382 // After the grow the frame must fit in the now-available space.
1383 debug_assert!(
1384 payload_len <= self.back_buf.available_space(),
1385 "back buffer must have room for the full frame after growth"
1386 );
1387 }
1388
1389 self.back_buf
1390 .write_all(&delimiter)
1391 .map_err(ChannelError::Write)?;
1392 self.back_buf
1393 .write_all(&payload)
1394 .map_err(ChannelError::Write)?;
1395
1396 // The two writes appended exactly `payload_len` bytes (delimiter +
1397 // payload) to the back buffer's pending data.
1398 debug_assert_eq!(
1399 self.back_buf.available_data(),
1400 data_before + payload_len,
1401 "back buffer pending data must grow by exactly the framed length"
1402 );
1403 debug_assert!(
1404 self.back_buf.capacity() <= self.max_buffer_size,
1405 "back buffer capacity must never exceed the max_buffer_size ceiling"
1406 );
1407
1408 Ok(())
1409 }
1410
1411 /// Shrink the front buffer back toward initial_buffer_size when it is
1412 /// mostly empty (data consumed) and was previously grown.
1413 fn try_shrink_front_buf(&mut self) {
1414 let capacity = self.front_buf.capacity();
1415 if capacity <= self.initial_buffer_size {
1416 return;
1417 }
1418 // Past the early return, we are strictly above the floor, so a shrink
1419 // back to `initial_buffer_size` is a genuine reduction.
1420 debug_assert!(
1421 capacity > self.initial_buffer_size,
1422 "shrink path only runs when capacity is above the initial floor"
1423 );
1424 // only shrink when the buffer has little pending data
1425 if self.front_buf.available_data() * 4 < self.initial_buffer_size {
1426 let data_before = self.front_buf.available_data();
1427 self.front_buf.shrink(self.initial_buffer_size);
1428 self.front_high_watermark_logged = false;
1429 // Shrink preserves pending data and never drops below the floor.
1430 debug_assert!(
1431 self.front_buf.capacity() >= self.initial_buffer_size,
1432 "front buffer must never shrink below the initial buffer size floor"
1433 );
1434 debug_assert_eq!(
1435 self.front_buf.available_data(),
1436 data_before,
1437 "shrink must preserve all pending front-buffer data"
1438 );
1439 trace!(
1440 "front buffer shrunk from {} to {} bytes",
1441 capacity, self.initial_buffer_size
1442 );
1443 }
1444 }
1445
1446 /// Shrink the back buffer back toward initial_buffer_size when fully drained.
1447 fn try_shrink_back_buf(&mut self) {
1448 let capacity = self.back_buf.capacity();
1449 if capacity <= self.initial_buffer_size {
1450 return;
1451 }
1452 debug_assert!(
1453 capacity > self.initial_buffer_size,
1454 "shrink path only runs when capacity is above the initial floor"
1455 );
1456 if self.back_buf.available_data() == 0 {
1457 self.back_buf.shrink(self.initial_buffer_size);
1458 self.back_high_watermark_logged = false;
1459 // The back buffer is only shrunk once fully drained; it must end at
1460 // the floor with no pending data resurrected.
1461 debug_assert!(
1462 self.back_buf.capacity() >= self.initial_buffer_size,
1463 "back buffer must never shrink below the initial buffer size floor"
1464 );
1465 debug_assert_eq!(
1466 self.back_buf.available_data(),
1467 0,
1468 "back buffer must stay empty across a drained shrink"
1469 );
1470 trace!(
1471 "back buffer shrunk from {} to {} bytes",
1472 capacity, self.initial_buffer_size
1473 );
1474 }
1475 }
1476}
1477
1478/// the payload is prefixed with a delimiter of sizeof(usize) bytes
1479pub const fn delimiter_size() -> usize {
1480 std::mem::size_of::<usize>()
1481}
1482
1483type ChannelResult<Tx, Rx> = Result<(Channel<Tx, Rx>, Channel<Rx, Tx>), ChannelError>;
1484
1485impl<Tx: Debug + ProstMessage + Default, Rx: Debug + ProstMessage + Default> Channel<Tx, Rx> {
1486 /// creates a channel pair: `(blocking_channel, nonblocking_channel)`
1487 pub fn generate(buffer_size: u64, max_buffer_size: u64) -> ChannelResult<Tx, Rx> {
1488 let (command, proxy) = MioUnixStream::pair().map_err(ChannelError::Read)?;
1489 let proxy_channel = Channel::new(proxy, buffer_size, max_buffer_size);
1490 let mut command_channel = Channel::new(command, buffer_size, max_buffer_size);
1491 command_channel.blocking()?;
1492 Ok((command_channel, proxy_channel))
1493 }
1494
1495 /// creates a pair of nonblocking channels
1496 pub fn generate_nonblocking(buffer_size: u64, max_buffer_size: u64) -> ChannelResult<Tx, Rx> {
1497 let (command, proxy) = MioUnixStream::pair().map_err(ChannelError::Read)?;
1498 let proxy_channel = Channel::new(proxy, buffer_size, max_buffer_size);
1499 let command_channel = Channel::new(command, buffer_size, max_buffer_size);
1500 Ok((command_channel, proxy_channel))
1501 }
1502}
1503
1504impl<Tx: Debug + ProstMessage + Default, Rx: Debug + ProstMessage + Default> Iterator
1505 for Channel<Tx, Rx>
1506{
1507 type Item = Rx;
1508 fn next(&mut self) -> Option<Self::Item> {
1509 self.read_message().ok()
1510 }
1511}
1512
1513use mio::{Interest, Registry, Token};
1514impl<Tx, Rx> Source for Channel<Tx, Rx> {
1515 fn register(
1516 &mut self,
1517 registry: &Registry,
1518 token: Token,
1519 interests: Interest,
1520 ) -> io::Result<()> {
1521 self.sock.register(registry, token, interests)
1522 }
1523
1524 fn reregister(
1525 &mut self,
1526 registry: &Registry,
1527 token: Token,
1528 interests: Interest,
1529 ) -> io::Result<()> {
1530 self.sock.reregister(registry, token, interests)
1531 }
1532
1533 fn deregister(&mut self, registry: &Registry) -> io::Result<()> {
1534 self.sock.deregister(registry)
1535 }
1536}
1537
1538#[cfg(test)]
1539mod tests {
1540 use std::{
1541 io::Write,
1542 os::fd::{AsRawFd, FromRawFd, OwnedFd},
1543 thread,
1544 time::Duration,
1545 };
1546
1547 use super::*;
1548 use mio::{Interest, Poll, Token};
1549
1550 #[derive(Clone, PartialEq, prost::Message)]
1551 pub struct ProtobufMessage {
1552 #[prost(uint32, required, tag = "1")]
1553 inner: u32,
1554 }
1555
1556 fn test_channels() -> (
1557 Channel<ProtobufMessage, ProtobufMessage>,
1558 Channel<ProtobufMessage, ProtobufMessage>,
1559 ) {
1560 Channel::generate(1000, 10000).expect("could not generate blocking channels for testing")
1561 }
1562
1563 #[test]
1564 fn channel_snapshot_round_trips_unconsumed_offsets_capacities_bits_and_mode() {
1565 let (mut channel, _peer): (
1566 Channel<ProtobufMessage, ProtobufMessage>,
1567 Channel<ProtobufMessage, ProtobufMessage>,
1568 ) = Channel::generate_nonblocking(8, 64).expect("could not generate channels");
1569
1570 channel.front_buf.grow(32);
1571 channel
1572 .front_buf
1573 .write_all(b"retired-front-pending")
1574 .expect("could not seed front buffer");
1575 channel.front_buf.consume(b"retired-".len());
1576 channel.back_buf.grow(48);
1577 channel
1578 .back_buf
1579 .write_all(b"sent-back-pending")
1580 .expect("could not seed back buffer");
1581 channel.back_buf.consume(b"sent-".len());
1582 channel.readiness =
1583 Ready::READABLE | Ready::WRITABLE | Ready::ERROR | Ready::HUP | Ready::WRITE_CLOSED;
1584 channel.interest = Ready::READABLE | Ready::WRITABLE;
1585 channel.front_high_watermark_logged = true;
1586
1587 let snapshot_json =
1588 serde_json::to_vec(&channel.snapshot()).expect("channel snapshot should serialize");
1589 let snapshot: ChannelSnapshot =
1590 serde_json::from_slice(&snapshot_json).expect("channel snapshot should deserialize");
1591 assert!(snapshot.has_buffered_input());
1592 assert!(snapshot.has_buffered_output());
1593 let original_fd = channel.fd();
1594 let paused: PausedChannel<ProtobufMessage, ProtobufMessage> =
1595 Channel::restore_paused(channel.sock, snapshot, 8, 64)
1596 .expect("valid channel snapshot should restore");
1597
1598 assert_eq!(paused.fd(), original_fd);
1599 let restored = paused.resume();
1600 assert_eq!(restored.front_buf.data(), b"front-pending");
1601 assert_eq!(restored.back_buf.data(), b"back-pending");
1602 assert_eq!(restored.front_buf.capacity(), 32);
1603 assert_eq!(restored.back_buf.capacity(), 48);
1604 assert_eq!(restored.initial_buffer_size, 8);
1605 assert_eq!(restored.max_buffer_size, 64);
1606 assert_eq!(
1607 restored.readiness,
1608 Ready::READABLE | Ready::WRITABLE | Ready::ERROR | Ready::HUP | Ready::WRITE_CLOSED
1609 );
1610 assert_eq!(restored.interest, Ready::READABLE | Ready::WRITABLE);
1611 assert!(!restored.is_blocking());
1612 assert!(restored.front_high_watermark_logged);
1613 assert!(!restored.back_high_watermark_logged);
1614 }
1615
1616 #[test]
1617 fn channel_snapshot_paused_registration_does_not_flush_before_resume() {
1618 let (mut channel, mut peer): (
1619 Channel<ProtobufMessage, ProtobufMessage>,
1620 Channel<ProtobufMessage, ProtobufMessage>,
1621 ) = Channel::generate_nonblocking(32, 128).expect("could not generate channels");
1622 channel
1623 .write_message(&ProtobufMessage { inner: 42 })
1624 .expect("could not queue message");
1625 channel.handle_events(Ready::WRITABLE);
1626
1627 let snapshot = channel.snapshot();
1628 let mut paused: PausedChannel<ProtobufMessage, ProtobufMessage> =
1629 Channel::restore_paused(channel.sock, snapshot, 32, 128)
1630 .expect("valid channel snapshot should restore");
1631 let poll = Poll::new().expect("could not create poll registry");
1632 paused
1633 .register(
1634 poll.registry(),
1635 Token(7),
1636 Interest::READABLE | Interest::WRITABLE,
1637 )
1638 .expect("paused channel should register");
1639
1640 peer.handle_events(Ready::READABLE);
1641 assert_eq!(peer.readable().expect("peer read probe should not fail"), 0);
1642 assert!(matches!(
1643 peer.read_message(),
1644 Err(ChannelError::NothingRead)
1645 ));
1646
1647 let mut restored = paused.resume();
1648 restored.run().expect("resumed channel should flush");
1649 peer.handle_events(Ready::READABLE);
1650 peer.readable().expect("peer should receive resumed bytes");
1651 assert_eq!(
1652 peer.read_message()
1653 .expect("peer should decode queued message"),
1654 ProtobufMessage { inner: 42 }
1655 );
1656 }
1657
1658 #[test]
1659 fn channel_snapshot_rejects_invalid_version_capacities_bytes_and_ready_bits() {
1660 fn snapshot_and_socket() -> (
1661 ChannelSnapshot,
1662 MioUnixStream,
1663 Channel<ProtobufMessage, ProtobufMessage>,
1664 ) {
1665 let (channel, peer): (
1666 Channel<ProtobufMessage, ProtobufMessage>,
1667 Channel<ProtobufMessage, ProtobufMessage>,
1668 ) = Channel::generate_nonblocking(8, 64).expect("could not generate channels");
1669 (channel.snapshot(), channel.sock, peer)
1670 }
1671
1672 let (mut invalid_version, sock, _peer) = snapshot_and_socket();
1673 invalid_version.version += 1;
1674 assert!(matches!(
1675 Channel::<ProtobufMessage, ProtobufMessage>::restore_paused(
1676 sock,
1677 invalid_version,
1678 8,
1679 64,
1680 ),
1681 Err(ChannelSnapshotError::UnsupportedVersion { .. })
1682 ));
1683
1684 let (mut invalid_bounds, sock, _peer) = snapshot_and_socket();
1685 invalid_bounds.initial_buffer_size = invalid_bounds.max_buffer_size + 1;
1686 assert!(matches!(
1687 Channel::<ProtobufMessage, ProtobufMessage>::restore_paused(
1688 sock,
1689 invalid_bounds,
1690 8,
1691 64,
1692 ),
1693 Err(ChannelSnapshotError::InvalidBufferBounds { .. })
1694 ));
1695
1696 let (mut invalid_capacity, sock, _peer) = snapshot_and_socket();
1697 invalid_capacity.front_buffer_capacity = invalid_capacity.initial_buffer_size - 1;
1698 assert!(matches!(
1699 Channel::<ProtobufMessage, ProtobufMessage>::restore_paused(
1700 sock,
1701 invalid_capacity,
1702 8,
1703 64,
1704 ),
1705 Err(ChannelSnapshotError::InvalidBufferCapacity { .. })
1706 ));
1707
1708 let (mut invalid_bytes, sock, _peer) = snapshot_and_socket();
1709 invalid_bytes.front = vec![0; invalid_bytes.front_buffer_capacity + 1];
1710 assert!(matches!(
1711 Channel::<ProtobufMessage, ProtobufMessage>::restore_paused(sock, invalid_bytes, 8, 64,),
1712 Err(ChannelSnapshotError::BufferedDataExceedsCapacity { .. })
1713 ));
1714
1715 let (mut invalid_readiness, sock, _peer) = snapshot_and_socket();
1716 invalid_readiness.readiness = 1 << 5;
1717 assert!(matches!(
1718 Channel::<ProtobufMessage, ProtobufMessage>::restore_paused(
1719 sock,
1720 invalid_readiness,
1721 8,
1722 64,
1723 ),
1724 Err(ChannelSnapshotError::InvalidReadyBits {
1725 field: "readiness",
1726 ..
1727 })
1728 ));
1729
1730 let (mut invalid_interest, sock, _peer) = snapshot_and_socket();
1731 invalid_interest.interest = 1 << 5;
1732 assert!(matches!(
1733 Channel::<ProtobufMessage, ProtobufMessage>::restore_paused(
1734 sock,
1735 invalid_interest,
1736 8,
1737 64,
1738 ),
1739 Err(ChannelSnapshotError::InvalidReadyBits {
1740 field: "interest",
1741 ..
1742 })
1743 ));
1744 }
1745
1746 #[test]
1747 fn channel_snapshot_accepts_every_defined_ready_subset_in_both_fields() {
1748 let defined_bits = Ready::READABLE.0
1749 | Ready::WRITABLE.0
1750 | Ready::ERROR.0
1751 | Ready::HUP.0
1752 | Ready::WRITE_CLOSED.0;
1753
1754 for bits in 0..=defined_bits {
1755 for field in ["readiness", "interest"] {
1756 let (channel, _peer): (
1757 Channel<ProtobufMessage, ProtobufMessage>,
1758 Channel<ProtobufMessage, ProtobufMessage>,
1759 ) = Channel::generate_nonblocking(8, 64).expect("could not generate channels");
1760 let mut snapshot = channel.snapshot();
1761 if field == "readiness" {
1762 snapshot.readiness = bits;
1763 } else {
1764 snapshot.interest = bits;
1765 }
1766
1767 let restored = Channel::<ProtobufMessage, ProtobufMessage>::restore_paused(
1768 channel.sock,
1769 snapshot,
1770 8,
1771 64,
1772 )
1773 .unwrap_or_else(|error| {
1774 panic!("{field} rejected defined bits {bits:#04x}: {error}")
1775 })
1776 .resume();
1777 let restored_bits = if field == "readiness" {
1778 restored.readiness.0
1779 } else {
1780 restored.interest.0
1781 };
1782 assert_eq!(restored_bits, bits, "{field} changed bits {bits:#04x}");
1783 }
1784 }
1785 }
1786
1787 #[test]
1788 fn channel_snapshot_rejects_blocking_mode_that_disagrees_with_received_socket() {
1789 let (channel, _peer): (
1790 Channel<ProtobufMessage, ProtobufMessage>,
1791 Channel<ProtobufMessage, ProtobufMessage>,
1792 ) = Channel::generate_nonblocking(8, 64).expect("could not generate channels");
1793 let mut snapshot = channel.snapshot();
1794 snapshot.blocking = true;
1795
1796 // SAFETY: `channel.fd()` is live for this call, and a non-negative
1797 // result is immediately owned so every path closes the duplicate.
1798 let duplicate_fd = unsafe { libc::dup(channel.fd()) };
1799 assert!(duplicate_fd >= 0, "could not duplicate channel descriptor");
1800 // SAFETY: `dup` returned a fresh descriptor owned by this test.
1801 let duplicate = unsafe { OwnedFd::from_raw_fd(duplicate_fd) };
1802 // SAFETY: `duplicate` owns a live descriptor and F_GETFL only reads its
1803 // shared open-file-description flags.
1804 let flags_before = unsafe { libc::fcntl(duplicate.as_raw_fd(), libc::F_GETFL) };
1805 assert!(flags_before >= 0, "could not read channel status flags");
1806 assert_ne!(flags_before & libc::O_NONBLOCK, 0);
1807
1808 let restored = Channel::<ProtobufMessage, ProtobufMessage>::restore_paused(
1809 channel.sock,
1810 snapshot,
1811 8,
1812 64,
1813 );
1814
1815 // SAFETY: the duplicate remains live even when restoration rejects
1816 // and closes the separately owned descriptor supplied above.
1817 let flags_after = unsafe { libc::fcntl(duplicate.as_raw_fd(), libc::F_GETFL) };
1818 assert_eq!(
1819 flags_after, flags_before,
1820 "restore must not change fd flags"
1821 );
1822 assert!(matches!(
1823 restored,
1824 Err(ChannelSnapshotError::BlockingModeMismatch {
1825 snapshot_blocking: true,
1826 fd_blocking: false,
1827 })
1828 ));
1829 }
1830
1831 #[test]
1832 fn channel_snapshot_rejects_untrusted_limits_before_allocation() {
1833 let (channel, _peer): (
1834 Channel<ProtobufMessage, ProtobufMessage>,
1835 Channel<ProtobufMessage, ProtobufMessage>,
1836 ) = Channel::generate_nonblocking(8, 64).expect("could not generate channels");
1837 let mut snapshot = channel.snapshot();
1838 snapshot.max_buffer_size = usize::MAX;
1839 snapshot.front_buffer_capacity = usize::MAX;
1840 snapshot.back_buffer_capacity = usize::MAX;
1841
1842 assert!(matches!(
1843 Channel::<ProtobufMessage, ProtobufMessage>::restore_paused(
1844 channel.sock,
1845 snapshot,
1846 8,
1847 64,
1848 ),
1849 Err(ChannelSnapshotError::BufferLimitsMismatch {
1850 snapshot_initial: 8,
1851 snapshot_max: usize::MAX,
1852 expected_initial: 8,
1853 expected_max: 64,
1854 })
1855 ));
1856 }
1857
1858 #[test]
1859 fn channel_snapshot_restores_full_front_buffer_backpressure_and_rearms_after_drain() {
1860 let (mut frame_source, _frame_peer): (
1861 Channel<ProtobufMessage, ProtobufMessage>,
1862 Channel<ProtobufMessage, ProtobufMessage>,
1863 ) = Channel::generate_nonblocking(32, 32).expect("could not generate channels");
1864 frame_source
1865 .write_delimited_message(&ProtobufMessage { inner: 7 })
1866 .expect("could not frame message");
1867 let frame = frame_source.back_buf.data().to_owned();
1868 let (mut reader, _reader_peer): (
1869 Channel<ProtobufMessage, ProtobufMessage>,
1870 Channel<ProtobufMessage, ProtobufMessage>,
1871 ) = Channel::generate_nonblocking(frame.len() as u64, frame.len() as u64)
1872 .expect("could not generate capacity-bound reader");
1873 reader
1874 .front_buf
1875 .write_all(&frame)
1876 .expect("frame should fit");
1877 reader.handle_events(Ready::READABLE);
1878 assert_eq!(
1879 reader.readable().expect("full buffer should backpressure"),
1880 0
1881 );
1882 assert!(!reader.interest.is_readable());
1883
1884 let snapshot = reader.snapshot();
1885 let paused: PausedChannel<ProtobufMessage, ProtobufMessage> =
1886 Channel::restore_paused(reader.sock, snapshot, frame.len(), frame.len())
1887 .expect("backpressured channel should restore");
1888 let mut restored = paused.resume();
1889
1890 assert_eq!(restored.front_buf.data(), frame);
1891 assert!(!restored.interest.is_readable());
1892 assert!(restored.readiness.is_readable());
1893 assert_eq!(
1894 restored
1895 .read_message()
1896 .expect("buffered frame should decode"),
1897 ProtobufMessage { inner: 7 }
1898 );
1899 assert!(matches!(
1900 restored.read_message(),
1901 Err(ChannelError::NothingRead)
1902 ));
1903 assert!(restored.interest.is_readable());
1904 assert_eq!(restored.front_buf.available_data(), 0);
1905 }
1906
1907 #[test]
1908 fn unblock_a_channel() {
1909 let (mut blocking, _nonblocking) = test_channels();
1910 assert!(blocking.nonblocking().is_ok())
1911 }
1912
1913 #[test]
1914 fn generate_blocking_and_nonblocking_channels() {
1915 let (blocking_channel, nonblocking_channel) = test_channels();
1916
1917 assert!(blocking_channel.is_blocking());
1918 assert!(!nonblocking_channel.is_blocking());
1919
1920 let (nonblocking_channel_1, nonblocking_channel_2): (
1921 Channel<ProtobufMessage, ProtobufMessage>,
1922 Channel<ProtobufMessage, ProtobufMessage>,
1923 ) = Channel::generate_nonblocking(1000, 10000)
1924 .expect("could not generatie nonblocking channels");
1925
1926 assert!(!nonblocking_channel_1.is_blocking());
1927 assert!(!nonblocking_channel_2.is_blocking());
1928 }
1929
1930 #[test]
1931 fn write_and_read_message_blocking() {
1932 let (mut blocking_channel, mut nonblocking_channel) = test_channels();
1933
1934 let message_to_send = ProtobufMessage { inner: 42 };
1935
1936 nonblocking_channel
1937 .blocking()
1938 .expect("Could not block channel");
1939 nonblocking_channel
1940 .write_message(&message_to_send)
1941 .expect("Could not write message on channel");
1942
1943 trace!("we wrote a message!");
1944
1945 trace!("reading message..");
1946 // blocking_channel.readable();
1947 let message = blocking_channel
1948 .read_message()
1949 .expect("Could not read message on channel");
1950 trace!("read message!");
1951
1952 assert_eq!(message, ProtobufMessage { inner: 42 });
1953 }
1954
1955 #[test]
1956 fn read_message_blocking_with_timeout_fails() {
1957 let (mut reading_channel, mut writing_channel) = test_channels();
1958 writing_channel.blocking().expect("Could not block channel");
1959
1960 trace!("reading message in a detached thread, with a timeout of 100 milliseconds...");
1961 // Hand the channel back with the result: dropping it when the thread
1962 // ends would close the peer, and the late write below would then fail
1963 // with `EPIPE` instead of arriving too late (sozu-proxy/sozu#1563).
1964 let awaiting_with_timeout = thread::spawn(move || {
1965 let message =
1966 reading_channel.read_message_blocking_timeout(Some(Duration::from_millis(100)));
1967 trace!("read message!");
1968 (message, reading_channel)
1969 });
1970
1971 trace!("Waiting 200 milliseconds…");
1972 thread::sleep(std::time::Duration::from_millis(200));
1973
1974 writing_channel
1975 .write_message(&ProtobufMessage { inner: 200 })
1976 .expect("Could not write message on channel");
1977 trace!("we wrote a message that should arrive too late!");
1978
1979 let (arrived_too_late, _reading_channel) = awaiting_with_timeout
1980 .join()
1981 .expect("error with receiving message from awaiting thread");
1982
1983 assert!(arrived_too_late.is_err());
1984 }
1985
1986 /// Install a no-op `SIGUSR1` handler with `SA_RESTART`, as the main
1987 /// process does for `SIGTERM`. signal(7): a socket read with a receive
1988 /// timeout (`SO_RCVTIMEO`) is never restarted, it fails with `EINTR`.
1989 #[cfg(target_os = "linux")]
1990 fn install_restarting_sigusr1_handler() {
1991 extern "C" fn ignore(_: libc::c_int) {}
1992 // SAFETY: the handler is async-signal-safe (it does nothing), and the
1993 // `sigaction` struct is fully initialised before the call.
1994 unsafe {
1995 let mut action: libc::sigaction = std::mem::zeroed();
1996 action.sa_sigaction = ignore as extern "C" fn(libc::c_int) as libc::sighandler_t;
1997 action.sa_flags = libc::SA_RESTART;
1998 libc::sigemptyset(&mut action.sa_mask);
1999 assert_eq!(
2000 libc::sigaction(libc::SIGUSR1, &action, std::ptr::null_mut()),
2001 0
2002 );
2003 }
2004 }
2005
2006 /// Spawn a reader blocked in `read_message_blocking_timeout`, then
2007 /// interrupt its thread with `SIGUSR1` every 5 ms for `flood`, so at least
2008 /// one signal lands while the 100 ms `SO_RCVTIMEO` read is blocked.
2009 #[cfg(target_os = "linux")]
2010 fn read_blocking_under_signal_flood(
2011 timeout: Duration,
2012 flood: Duration,
2013 message: Option<ProtobufMessage>,
2014 ) -> (Result<ProtobufMessage, ChannelError>, Duration) {
2015 install_restarting_sigusr1_handler();
2016 let (mut reading_channel, mut writing_channel) = test_channels();
2017 writing_channel.blocking().expect("Could not block channel");
2018
2019 let (thread_sender, thread_receiver) = std::sync::mpsc::channel();
2020 let reader = thread::spawn(move || {
2021 // SAFETY: `pthread_self` has no preconditions.
2022 thread_sender
2023 .send(unsafe { libc::pthread_self() })
2024 .expect("send reader thread id");
2025 let started = std::time::Instant::now();
2026 let message = reading_channel.read_message_blocking_timeout(Some(timeout));
2027 (message, started.elapsed(), reading_channel)
2028 });
2029 let reader_thread = thread_receiver.recv().expect("reader thread id");
2030
2031 let flood_end = std::time::Instant::now() + flood;
2032 while std::time::Instant::now() < flood_end {
2033 thread::sleep(Duration::from_millis(5));
2034 // SAFETY: `reader` is joined below, so `reader_thread` names a
2035 // thread that is running or exited but not yet joined.
2036 unsafe { libc::pthread_kill(reader_thread, libc::SIGUSR1) };
2037 }
2038 if let Some(message) = message {
2039 writing_channel
2040 .write_message(&message)
2041 .expect("Could not write message on channel");
2042 }
2043
2044 let (result, elapsed, _reading_channel) = reader.join().expect("reader thread");
2045 (result, elapsed)
2046 }
2047
2048 /// A signal delivered while a blocking read waits (a whole-cgroup
2049 /// `SIGTERM` reaching a replacement main that waits for COMMIT) must not
2050 /// abort the wait: the message sent afterwards is still received.
2051 #[cfg(target_os = "linux")]
2052 #[test]
2053 fn read_message_blocking_with_timeout_survives_interrupting_signals() {
2054 let (result, _) = read_blocking_under_signal_flood(
2055 Duration::from_secs(5),
2056 Duration::from_millis(300),
2057 Some(ProtobufMessage { inner: 7 }),
2058 );
2059 assert_eq!(
2060 result.expect("an interrupted read must keep waiting"),
2061 ProtobufMessage { inner: 7 }
2062 );
2063 }
2064
2065 /// Retrying an interrupted read keeps the caller's deadline: signals
2066 /// arriving for longer than the timeout neither abort nor extend it.
2067 #[cfg(target_os = "linux")]
2068 #[test]
2069 fn read_message_blocking_with_timeout_keeps_its_deadline_under_signals() {
2070 let timeout = Duration::from_millis(300);
2071 let (result, elapsed) =
2072 read_blocking_under_signal_flood(timeout, Duration::from_millis(600), None);
2073 assert!(
2074 matches!(result, Err(ChannelError::TimeoutReached(reached)) if reached == timeout),
2075 "expected the deadline to be reached, got {result:?}"
2076 );
2077 assert!(
2078 elapsed >= timeout,
2079 "returned before the deadline: {elapsed:?}"
2080 );
2081 // One 100 ms `SO_RCVTIMEO` slice of overshoot, plus scheduling slack.
2082 assert!(
2083 elapsed < timeout + Duration::from_millis(400),
2084 "the deadline was extended: {elapsed:?}"
2085 );
2086 }
2087
2088 #[test]
2089 fn read_message_blocking_with_timeout_succeeds() {
2090 let (mut reading_channel, mut writing_channel) = test_channels();
2091 writing_channel.blocking().expect("Could not block channel");
2092
2093 trace!("reading message in a detached thread, with a timeout of 200 milliseconds...");
2094 let awaiting_with_timeout = thread::spawn(move || {
2095 let message = reading_channel
2096 .read_message_blocking_timeout(Some(Duration::from_millis(200)))
2097 .expect("Could not read message with timeout on blocking channel");
2098 trace!("read message!");
2099 message
2100 });
2101
2102 trace!("Waiting 100 milliseconds…");
2103 thread::sleep(std::time::Duration::from_millis(100));
2104
2105 writing_channel
2106 .write_message(&ProtobufMessage { inner: 100 })
2107 .expect("Could not write message on channel");
2108 trace!("we wrote a message that should arrive on time!");
2109
2110 let arrived_on_time = awaiting_with_timeout
2111 .join()
2112 .expect("error with receiving message from awaiting thread");
2113
2114 assert_eq!(arrived_on_time, ProtobufMessage { inner: 100 });
2115 }
2116
2117 #[test]
2118 fn exhaustive_use_of_nonblocking_channels() {
2119 // - two nonblocking channels A and B, identical
2120 let (mut channel_a, mut channel_b) = test_channels();
2121 channel_a.nonblocking().expect("Could not block channel");
2122
2123 // write on A
2124 channel_a
2125 .write_message(&ProtobufMessage { inner: 1 })
2126 .expect("Could not write message on channel");
2127
2128 // set B as readable, normally mio tells when to, by giving events
2129 channel_b.handle_events(Ready::READABLE);
2130
2131 // read on B
2132 let should_err = channel_b.read_message();
2133 assert!(should_err.is_err());
2134
2135 // write another message on A
2136 channel_a
2137 .write_message(&ProtobufMessage { inner: 2 })
2138 .expect("Could not write message on channel");
2139
2140 // insert a handle_events Ready::writable on A
2141 channel_a.handle_events(Ready::WRITABLE);
2142
2143 // flush A with run()
2144 channel_a.run().expect("Failed to run the channel");
2145
2146 // maybe a thread sleep
2147 thread::sleep(std::time::Duration::from_millis(100));
2148
2149 // receive with B using run()
2150 channel_b.run().expect("Failed to run the channel");
2151
2152 // use read_message() twice on B, check them
2153 let message_1 = channel_b
2154 .read_message()
2155 .expect("Could not read message on channel");
2156 assert_eq!(message_1, ProtobufMessage { inner: 1 });
2157
2158 let message_2 = channel_b
2159 .read_message()
2160 .expect("Could not read message on channel");
2161 assert_eq!(message_2, ProtobufMessage { inner: 2 });
2162 }
2163
2164 #[test]
2165 fn buffer_grows_with_doubling_strategy() {
2166 let (writing_channel, _reading_channel): (
2167 Channel<ProtobufMessage, ProtobufMessage>,
2168 Channel<ProtobufMessage, ProtobufMessage>,
2169 ) = Channel::generate(100, 10000).expect("could not generate channels");
2170
2171 assert_eq!(writing_channel.back_buf.capacity(), 100);
2172
2173 assert_eq!(writing_channel.grow_size(100), Some(200));
2174 assert_eq!(writing_channel.grow_size(200), Some(400));
2175 assert_eq!(writing_channel.grow_size(5000), Some(10000));
2176 assert_eq!(writing_channel.grow_size(10000), None);
2177 }
2178
2179 #[test]
2180 fn buffer_cap_returns_error() {
2181 let (mut writing_channel, _reading_channel): (
2182 Channel<ProtobufMessage, ProtobufMessage>,
2183 Channel<ProtobufMessage, ProtobufMessage>,
2184 ) = Channel::generate(50, 50).expect("could not generate channels");
2185
2186 writing_channel.blocking().expect("Could not block channel");
2187
2188 let mut i = 0u32;
2189 let result = loop {
2190 let msg = ProtobufMessage { inner: i };
2191 match writing_channel.write_delimited_message(&msg) {
2192 Ok(()) => i += 1,
2193 Err(e) => break Err(e),
2194 }
2195 if i > 10000 {
2196 break Ok(());
2197 }
2198 };
2199
2200 assert!(result.is_err());
2201 let err = result.unwrap_err();
2202 let err_msg = format!("{err}");
2203 assert!(
2204 err_msg.contains("too large") || err_msg.contains("cannot grow"),
2205 "unexpected error: {err_msg}"
2206 );
2207 }
2208
2209 #[test]
2210 fn back_buffer_shrinks_after_drain() {
2211 let (mut channel, _other): (
2212 Channel<ProtobufMessage, ProtobufMessage>,
2213 Channel<ProtobufMessage, ProtobufMessage>,
2214 ) = Channel::generate(100, 10000).expect("could not generate channels");
2215
2216 // Write directly to the back buffer (without draining to socket)
2217 // to force growth. Each message is ~10 bytes (delimiter + varint).
2218 for i in 0..20 {
2219 channel
2220 .write_delimited_message(&ProtobufMessage { inner: i })
2221 .expect("Could not write message");
2222 }
2223
2224 let grown_capacity = channel.back_buf.capacity();
2225 assert!(
2226 grown_capacity > 100,
2227 "expected buffer growth, got capacity {grown_capacity}"
2228 );
2229
2230 // Simulate full drain by consuming all data
2231 let data_len = channel.back_buf.available_data();
2232 channel.back_buf.consume(data_len);
2233 assert_eq!(channel.back_buf.available_data(), 0);
2234
2235 channel.try_shrink_back_buf();
2236 assert_eq!(
2237 channel.back_buf.capacity(),
2238 100,
2239 "back buffer should shrink to initial size after drain"
2240 );
2241 }
2242
2243 #[test]
2244 fn back_buffer_grows_with_doubling_on_write() {
2245 let (mut channel, _other): (
2246 Channel<ProtobufMessage, ProtobufMessage>,
2247 Channel<ProtobufMessage, ProtobufMessage>,
2248 ) = Channel::generate(32, 10000).expect("could not generate channels");
2249
2250 assert_eq!(channel.back_buf.capacity(), 32);
2251
2252 // Write enough messages to force growth beyond initial capacity.
2253 // Each ProtobufMessage encodes to ~4 bytes + 8-byte delimiter = ~12 bytes.
2254 for i in 0..10 {
2255 channel
2256 .write_delimited_message(&ProtobufMessage { inner: i })
2257 .expect("Could not write message");
2258 }
2259
2260 let grown = channel.back_buf.capacity();
2261 assert!(grown > 32, "expected buffer growth beyond 32, got {grown}");
2262 // doubling from 32 should yield a power-of-two-like size (64, 128, 256, ...)
2263 // rather than the exact needed amount
2264 assert!(
2265 grown.is_power_of_two() || grown == 10000,
2266 "expected doubling growth pattern, got {grown}"
2267 );
2268 }
2269
2270 /// Regression: a peer that writes a length-delimited frame whose
2271 /// declared length is *less than* the delimiter itself must be
2272 /// rejected with `MessageLengthUnderDelimiter`, never panic the
2273 /// reader with `slice index starts at N but ends at M`.
2274 ///
2275 /// Without the bounds check, `&buffer[delimiter_size()..message_len]`
2276 /// at `try_read_delimited_message` panics for any peer-controlled
2277 /// `message_len < delimiter_size()` (= 8 on 64-bit) — a one-packet
2278 /// denial-of-service against the master command socket.
2279 #[test]
2280 fn rejects_declared_length_below_delimiter() {
2281 let (mut reader, mut writer): (
2282 Channel<ProtobufMessage, ProtobufMessage>,
2283 Channel<ProtobufMessage, ProtobufMessage>,
2284 ) = Channel::generate(1000, 10000).expect("could not generate channels");
2285 writer.blocking().expect("writer to block");
2286 reader.blocking().expect("reader to block");
2287
2288 // Craft a delimiter that lies: message_len = 5 (< delimiter_size() = 8).
2289 // Send it as raw bytes, bypassing write_delimited_message.
2290 let bogus: usize = 5;
2291 let bytes = bogus.to_le_bytes();
2292 std::io::Write::write_all(&mut writer.sock, &bytes).expect("raw write of bogus delimiter");
2293
2294 match reader.read_message() {
2295 Err(ChannelError::MessageLengthUnderDelimiter {
2296 message_len,
2297 delimiter_size,
2298 }) => {
2299 assert_eq!(message_len, 5);
2300 assert_eq!(delimiter_size, std::mem::size_of::<usize>());
2301 }
2302 other => panic!(
2303 "expected MessageLengthUnderDelimiter, got {other:?}\n\
2304 NOTE: a panic here means the slice-OOB hardening was reverted",
2305 ),
2306 }
2307 }
2308
2309 /// Regression for sozu-proxy/sozu#1416: `Channel::new`/`generate_nonblocking` took
2310 /// `buffer_size` and `max_buffer_size` without ever comparing them, so a caller
2311 /// passing `buffer_size > max_buffer_size` built a `front_buf` already larger than
2312 /// the ceiling it must respect. `45, 32` is the exact pair the fuzz target's
2313 /// construction generator produced before it was clamped to work around this defect.
2314 /// `try_read_delimited_message` then tripped its own
2315 /// "front buffer capacity must never exceed max_buffer_size" `debug_assert!` on the
2316 /// very first parse, before any wire byte was examined.
2317 #[test]
2318 fn channel_new_clamps_buffer_size_above_max() {
2319 let (mut reader, _writer): (
2320 Channel<ProtobufMessage, ProtobufMessage>,
2321 Channel<ProtobufMessage, ProtobufMessage>,
2322 ) = Channel::generate_nonblocking(45, 32).expect("could not generate channels");
2323
2324 // Parsing an empty buffer must not trip the capacity invariant assertion
2325 // (pre-fix: `try_read_delimited_message`'s own "front buffer capacity must
2326 // never exceed max_buffer_size" `debug_assert!` fires right here, before any
2327 // wire byte is examined).
2328 assert_eq!(
2329 reader
2330 .try_read_delimited_message()
2331 .expect("parsing an empty buffer must not error"),
2332 None
2333 );
2334
2335 assert!(
2336 reader.front_buf.capacity() <= 32,
2337 "front buffer capacity {} must never exceed max_buffer_size 32",
2338 reader.front_buf.capacity()
2339 );
2340 }
2341
2342 /// Regression for sozu-proxy/sozu#1428: a peer that declares a frame
2343 /// length above `max_buffer_size` must mark the channel for closing.
2344 ///
2345 /// The oversize guard rejects the frame but cannot re-sync past it. The
2346 /// declared length is *unsatisfiable* — the frame can never fit a buffer
2347 /// capped at `max_buffer_size`, so no amount of further reading completes
2348 /// it — and the bytes behind the delimiter are of unknown provenance:
2349 /// dropping the eight header bytes and re-framing would decode
2350 /// peer-chosen payload bytes as a fresh control-plane command. That is
2351 /// why this branch must NOT consume, while its sibling
2352 /// `MessageLengthUnderDelimiter` must: a declared length below
2353 /// `delimiter_size()` is one no writer can emit for any payload, so those
2354 /// eight bytes are provably not a real header and skipping exactly them
2355 /// re-aligns the stream.
2356 ///
2357 /// Left unconsumed AND unsignalled, the delimiter is re-parsed on every
2358 /// later read forever: `read_message_nonblocking` propagates the error
2359 /// with no log, `extract_messages` (`bin/src/command/sessions.rs`)
2360 /// discards it with a bare `Err(_)`, and neither `ClientSession::ready`
2361 /// nor `WorkerSession::ready` closes the session because both key on
2362 /// `readiness.is_error() || is_hup()`, which no parse error ever sets.
2363 /// The session then wedges permanently, pinning its file descriptor and
2364 /// up to `max_buffer_size` of buffer, and `doc/configure_admin_ops.md`
2365 /// §5.2's "the supervisor logs and drops the offending peer's session"
2366 /// never happens.
2367 #[test]
2368 fn oversized_declared_length_marks_the_channel_for_closing() {
2369 let (mut reader, mut writer): (
2370 Channel<ProtobufMessage, ProtobufMessage>,
2371 Channel<ProtobufMessage, ProtobufMessage>,
2372 ) = Channel::generate_nonblocking(1000, 10000)
2373 .expect("could not generate nonblocking channels");
2374
2375 // One byte past this reader's `max_buffer_size`. Written raw only
2376 // because both ends of this pair share one ceiling, so the writer's own
2377 // `write_delimited_message` would refuse it. That is not true of a real
2378 // deployment: the two ends size their channels from their own
2379 // `max_command_buffer_size` (`bin/config.toml` ships 163_840,
2380 // `os-build/config.toml` 1_638_400, the compiled default is 2_000_000),
2381 // so a peer configured higher puts this frame on the wire through the
2382 // ordinary write path while conforming perfectly. What follows is
2383 // therefore about what this end can do with the frame, not about the
2384 // peer's good faith.
2385 let oversized: usize = 10_001;
2386 std::io::Write::write_all(&mut writer.sock, &oversized.to_le_bytes())
2387 .expect("raw write of the oversized delimiter");
2388
2389 reader.handle_events(Ready::READABLE);
2390 reader
2391 .readable()
2392 .expect("reader must buffer the delimiter bytes");
2393
2394 let available_before = reader.front_buf.available_data();
2395 assert_eq!(
2396 available_before,
2397 delimiter_size(),
2398 "the delimiter must be buffered before the parse under test"
2399 );
2400
2401 // Arm writability before the parse that marks the channel, so the
2402 // assertion at the end of this test can tell an `insert` from an
2403 // assignment.
2404 reader.handle_events(Ready::WRITABLE);
2405
2406 match reader.read_message() {
2407 Err(ChannelError::MessageTooLarge {
2408 message_len, max, ..
2409 }) => {
2410 assert_eq!(message_len, oversized);
2411 assert_eq!(max, 10000);
2412 }
2413 other => panic!("expected MessageTooLarge, got {other:?}"),
2414 }
2415
2416 // The bytes stay put -- deliberately, see the doc comment above.
2417 assert_eq!(
2418 reader.front_buf.available_data(),
2419 available_before,
2420 "an unsatisfiable declared length must not be consumed: re-framing \
2421 on the bytes behind it would decode peer-chosen bytes as a command"
2422 );
2423
2424 // So a second parse returns the identical error against the identical
2425 // bytes. The parser alone can never make progress on this stream.
2426 match reader.read_message() {
2427 Err(ChannelError::MessageTooLarge { message_len, .. }) => {
2428 assert_eq!(message_len, oversized)
2429 }
2430 other => panic!("expected the identical MessageTooLarge, got {other:?}"),
2431 }
2432 assert_eq!(reader.front_buf.available_data(), available_before);
2433
2434 // Which leaves closing the peer as the only correct exit, and the channel
2435 // must say so through the one signal `ClientSession::ready`,
2436 // `WorkerSession::ready` and `wants_to_tick` all key on
2437 // (`bin/src/command/sessions.rs`).
2438 assert!(
2439 reader.readiness.is_error(),
2440 "an oversized declaration must mark the channel errored so the \
2441 supervisor drops the session; without it the same delimiter is \
2442 re-parsed forever, the error is swallowed by `extract_messages`'s \
2443 `Err(_)` arm, and the session wedges holding its fd and buffer"
2444 );
2445 // And it must INSERT that bit rather than assign it. On the worker's
2446 // own channel (`lib/src/server.rs`) `interest` carries only READABLE
2447 // and WRITABLE, so `readiness()` masks ERROR away and nothing there
2448 // reads `is_error()`: an assignment's only effect on that side is
2449 // clearing WRITABLE, and `Server::send_queue` gates on
2450 // `channel.readiness.is_writable()`, so responses already queued for
2451 // the supervisor would stall until the next edge-triggered writability
2452 // event.
2453 assert!(
2454 reader.readiness.is_writable(),
2455 "marking the channel errored must not clear the other readiness \
2456 bits; wiping WRITABLE stalls `Server::send_queue` until the next \
2457 writability event"
2458 );
2459 }
2460
2461 /// Regression for the second shape of sozu-proxy/sozu#1428: a frame whose
2462 /// declared length is valid but whose payload does not decode must be
2463 /// consumed, so the channel re-syncs on the next frame instead of
2464 /// re-decoding the same bytes for the life of the session.
2465 ///
2466 /// `try_read_delimited_message` propagated `InvalidProtobufMessage` from
2467 /// the `?` on `Rx::decode`, one line *before* the `consume`. The malformed
2468 /// frame therefore stayed at the head of `front_buf`, no readiness bit
2469 /// moved, `extract_messages` (`bin/src/command/sessions.rs`) discarded the
2470 /// error with its bare `Err(_)` arm, and the session wedged exactly as the
2471 /// oversize branch above did. Eight valid length bytes followed by garbage
2472 /// reach it, and so does protobuf skew between two builds.
2473 ///
2474 /// Consuming is coherent with the success path rather than provably safe:
2475 /// `message_len` is only range-checked, but the success path already
2476 /// consumes that same peer-supplied length and re-frames on whatever
2477 /// follows, so the decode-failure path now re-frames on exactly the bytes
2478 /// the decode-success path already did. One undecodable payload therefore
2479 /// costs its frame rather than the peer.
2480 ///
2481 /// SCOPE: this asserts the channel contract, not a session one. Unlike the
2482 /// oversize branch above, this shape has no `ClientSession`-level test, so
2483 /// nothing here proves a session-level property. A frame pipelined behind
2484 /// the malformed one is delivered on the same tick; the session-level proof
2485 /// of that is
2486 /// `client_session_delivers_the_frame_behind_a_malformed_length_prefix`,
2487 /// built on the sibling `MessageLengthUnderDelimiter` shape.
2488 #[test]
2489 fn malformed_payload_is_consumed_so_the_channel_resyncs() {
2490 let (mut reader, mut writer): (
2491 Channel<ProtobufMessage, ProtobufMessage>,
2492 Channel<ProtobufMessage, ProtobufMessage>,
2493 ) = Channel::generate_nonblocking(1000, 10000)
2494 .expect("could not generate nonblocking channels");
2495
2496 // A well-formed frame whose payload is not a `ProtobufMessage`: a
2497 // leading zero byte is field number 0, which no protobuf decoder
2498 // accepts. The length prefix itself is entirely valid. Hand-crafted
2499 // rather than random so the failure is deterministic and independent
2500 // of how much arbitrary input prost happens to accept.
2501 let frame_len: usize = delimiter_size() + 8;
2502 let mut malformed = frame_len.to_le_bytes().to_vec();
2503 malformed.extend_from_slice(&[0x00_u8; 8]);
2504 std::io::Write::write_all(&mut writer.sock, &malformed)
2505 .expect("raw write of the malformed frame");
2506
2507 // ... immediately followed by a perfectly good one.
2508 writer
2509 .write_message(&ProtobufMessage { inner: 7 })
2510 .expect("could not queue the following message");
2511 writer.handle_events(Ready::WRITABLE);
2512 writer.run().expect("could not flush the following message");
2513
2514 reader.handle_events(Ready::READABLE);
2515 reader.readable().expect("reader must buffer both frames");
2516
2517 let available_before = reader.front_buf.available_data();
2518 assert!(
2519 available_before > frame_len,
2520 "both frames must be buffered before the parse under test"
2521 );
2522
2523 match reader.read_message() {
2524 Err(ChannelError::InvalidProtobufMessage(_)) => {}
2525 other => panic!("expected InvalidProtobufMessage, got {other:?}"),
2526 }
2527
2528 assert_eq!(
2529 reader.front_buf.available_data(),
2530 available_before - frame_len,
2531 "a malformed payload behind a valid length must be consumed: the \
2532 frame boundary is exact, so dropping exactly those bytes re-syncs \
2533 the stream"
2534 );
2535
2536 assert_eq!(
2537 reader.read_message().expect("the next frame must decode"),
2538 ProtobufMessage { inner: 7 },
2539 "the channel must make progress past a malformed frame instead of \
2540 re-decoding it on every later read"
2541 );
2542 }
2543
2544 /// Builds the exact front-buffer layout of sozu-proxy/sozu#1436 and returns
2545 /// the reader sitting in it, the writer still holding the pending frame's
2546 /// tail, and that tail.
2547 ///
2548 /// `Buffer::fill` (`command/src/buffer/growable.rs`) compacts whenever a
2549 /// read reaches the end of the buffer, so a front buffer filled to capacity
2550 /// always lands at `position == 0`. `Buffer::consume` compacts only past
2551 /// `capacity / 2`, so decoding one frame of at most half the capacity out of
2552 /// that full buffer advances `position` without reaching the threshold and
2553 /// leaves `available_space() == 0` with `capacity - first.len()` bytes
2554 /// pending. At `capacity == max_buffer_size` nothing can grow out of it
2555 /// either.
2556 ///
2557 /// `declared` is the length the second frame announces; the caller picks it
2558 /// on whichever side of the ceiling it wants to exercise.
2559 fn wedged_reader(
2560 capacity: usize,
2561 declared: usize,
2562 ) -> (
2563 Channel<ProtobufMessage, ProtobufMessage>,
2564 Channel<ProtobufMessage, ProtobufMessage>,
2565 Vec<u8>,
2566 ) {
2567 let (mut reader, mut writer): (
2568 Channel<ProtobufMessage, ProtobufMessage>,
2569 Channel<ProtobufMessage, ProtobufMessage>,
2570 ) = Channel::generate_nonblocking(capacity as u64, capacity as u64)
2571 .expect("could not generate nonblocking channels");
2572
2573 // A real frame, produced by the production write path.
2574 writer
2575 .write_delimited_message(&ProtobufMessage { inner: 1 })
2576 .expect("could not frame the first message");
2577 let first = writer.back_buf.data().to_vec();
2578 writer.back_buf.consume(first.len());
2579 assert!(
2580 first.len() <= capacity / 2,
2581 "the first frame ({} bytes) must stay at or under `Buffer::consume`'s \
2582 {}-byte shift threshold, or the layout under test never forms",
2583 first.len(),
2584 capacity / 2
2585 );
2586
2587 // The second frame. `[0x08, 0x2a]` is field 1 set to 42, repeated: a
2588 // non-repeated scalar takes its last occurrence, so the payload decodes
2589 // at any EVEN length -- callers pass an even `declared`, since an odd
2590 // one would truncate the last pair to a tag with no varint behind it.
2591 // Written raw because both ends of this pair share one ceiling, so
2592 // `write_delimited_message` cannot emit a frame this large -- a peer
2593 // configured with a larger `max_command_buffer_size` puts exactly these
2594 // bytes on the wire through the ordinary write path.
2595 let mut second = declared.to_le_bytes().to_vec();
2596 while second.len() < declared {
2597 second.extend_from_slice(&[0x08, 0x2a]);
2598 }
2599 debug_assert_eq!(
2600 declared % 2,
2601 0,
2602 "an odd declared length truncates the padding to a dangling tag"
2603 );
2604 second.truncate(declared.max(delimiter_size()));
2605
2606 // Fill the front buffer to the brim: the first frame, then as much of
2607 // the second as fits. The rest stays in the writer.
2608 let head = capacity - first.len();
2609 let head = head.min(second.len());
2610 let mut wire = first.clone();
2611 wire.extend_from_slice(&second[..head]);
2612 std::io::Write::write_all(&mut writer.sock, &wire)
2613 .expect("raw write of the first frame and the second frame's head");
2614
2615 reader.handle_events(Ready::READABLE);
2616 assert_eq!(
2617 reader
2618 .readable()
2619 .expect("the reader must fill its front buffer"),
2620 wire.len()
2621 );
2622 assert_eq!(
2623 reader.front_buf.available_space(),
2624 0,
2625 "the front buffer must be filled to the brim before the decode"
2626 );
2627
2628 assert_eq!(
2629 reader.read_message().expect("the first frame must decode"),
2630 ProtobufMessage { inner: 1 }
2631 );
2632 assert_eq!(reader.front_buf.available_data(), capacity - first.len());
2633 assert_eq!(
2634 reader.front_buf.available_space(),
2635 0,
2636 "the layout under test: data pending, zero space, capacity pinned at \
2637 the ceiling -- `Buffer::consume` did not reach its shift threshold"
2638 );
2639
2640 let tail = second[head..].to_vec();
2641 (reader, writer, tail)
2642 }
2643
2644 /// Regression for sozu-proxy/sozu#1436: a frame that fits the ceiling but
2645 /// not the front buffer's current *layout* must be rescued by compaction,
2646 /// not rejected.
2647 ///
2648 /// The `BufferFull` arm of `try_read_delimited_message` returned without
2649 /// consuming and without moving a readiness bit -- the shape
2650 /// sozu-proxy/sozu#1428 removed from its two siblings -- and nothing
2651 /// downstream compensated: `extract_messages` (`bin/src/command/sessions.rs`)
2652 /// discards the error with a bare `Err(_)`, and `ClientSession::ready` /
2653 /// `WorkerSession::ready` close only on `readiness.is_error() || is_hup()`.
2654 /// Worse, it was unrecoverable: the three `front_buf` mutators that can
2655 /// trigger a `Buffer::shift` are all out of reach afterwards. `fill` needs a
2656 /// successful read, but `readable()` finds `available_space() == 0`, gets
2657 /// `None` from `grow_size` at the ceiling and executes
2658 /// `interest.remove(Ready::READABLE)`, so every later `readable()` returns
2659 /// `Err(Connection(None))`; `consume` needs a decoded frame or an
2660 /// under-delimiter length, and the parse returns before either;
2661 /// `try_shrink_front_buf` is only called after a frame decoded, and both
2662 /// of its own early returns would stop it anyway -- `capacity <=
2663 /// initial_buffer_size`, which holds whenever the channel was configured
2664 /// with `command_buffer_size == max_command_buffer_size`, and
2665 /// `available_data() * 4 < initial_buffer_size`, which is false with that
2666 /// much pending. Not because it shrinks rather than shifts: `Buffer::shrink`
2667 /// calls `shift` unconditionally before its own size bail.
2668 ///
2669 /// SCOPE: this is the channel contract. It proves the parser makes room and
2670 /// the frame decodes once its tail is buffered; it proves nothing about the
2671 /// session, whose drain loop had its own reason to stop one read short --
2672 /// see `client_session_completes_a_compacted_frame_without_another_peer_write`
2673 /// in `bin/src/command/sessions.rs`.
2674 ///
2675 /// The recovery is NOT the oversize branch's close. That branch drops the
2676 /// peer because the declared length can never be satisfied by a buffer
2677 /// bounded at `max_buffer_size`. Here the frame is under the ceiling and its
2678 /// buffered bytes are intact: only the buffer's internal offsets are in the
2679 /// way, and a single `Buffer::shift` hands back exactly the space the
2680 /// consumed frame left behind. Closing would drop a conforming peer over an
2681 /// internal buffer-management detail.
2682 #[test]
2683 fn a_frame_within_the_ceiling_is_rescued_by_compaction() {
2684 let capacity = 64usize;
2685 // Exactly at the ceiling: the largest frame this end may legitimately
2686 // be asked to accept, and the boundary case of the guard above.
2687 let (mut reader, mut writer, tail) = wedged_reader(capacity, capacity);
2688 assert!(
2689 !tail.is_empty(),
2690 "the second frame must still be incomplete"
2691 );
2692
2693 std::io::Write::write_all(&mut writer.sock, &tail)
2694 .expect("raw write of the second frame's tail");
2695
2696 // The parse that used to wedge. It must make room and ask for more
2697 // bytes, not declare the buffer unusable.
2698 match reader.read_message() {
2699 Err(ChannelError::NothingRead) => {}
2700 other => panic!(
2701 "expected the parser to compact and wait for the rest of the \
2702 frame, got {other:?}"
2703 ),
2704 }
2705 assert!(
2706 reader.front_buf.available_space() >= tail.len(),
2707 "compaction must hand back the space the consumed frame left behind: \
2708 {} bytes of room for a {}-byte tail",
2709 reader.front_buf.available_space(),
2710 tail.len()
2711 );
2712 assert!(
2713 !reader.readiness.is_error(),
2714 "a frame within the ceiling must not mark the channel for closing"
2715 );
2716
2717 // And the session is alive: it reads again, and the frame completes.
2718 reader.handle_events(Ready::READABLE);
2719 assert_eq!(
2720 reader
2721 .readable()
2722 .expect("the reader must still accept the frame's tail"),
2723 tail.len()
2724 );
2725 assert_eq!(
2726 reader
2727 .read_message()
2728 .expect("the second frame must decode once its tail is buffered"),
2729 ProtobufMessage { inner: 42 },
2730 "a conforming peer's frame must complete rather than wedge the session"
2731 );
2732 }
2733
2734 /// The other side of sozu-proxy/sozu#1436's boundary: compaction must not
2735 /// rescue a frame that is genuinely unsatisfiable.
2736 ///
2737 /// A declared length above `max_buffer_size` does not fit a buffer bounded
2738 /// at `max_buffer_size` however it is compacted, so it must keep taking the
2739 /// sozu-proxy/sozu#1428 path -- `MessageTooLarge`, logged, channel marked
2740 /// errored -- from this layout exactly as from an empty buffer. The ceiling
2741 /// check runs before the zero-space arm precisely so that the two cases
2742 /// cannot be confused.
2743 #[test]
2744 fn a_frame_above_the_ceiling_still_closes_from_the_same_layout() {
2745 let capacity = 64usize;
2746 // Past the ceiling, in the very layout the compaction path rescues at
2747 // `capacity`. Even, because `wedged_reader` pads with two-byte protobuf
2748 // pairs and an odd length would leave a dangling tag; `capacity + 1` is
2749 // rejected by the identical guard, one byte earlier.
2750 let (mut reader, _writer, _tail) = wedged_reader(capacity, capacity + 2);
2751
2752 let pending = reader.front_buf.available_data();
2753 match reader.read_message() {
2754 Err(ChannelError::MessageTooLarge {
2755 message_len, max, ..
2756 }) => {
2757 assert_eq!(message_len, capacity + 2);
2758 assert_eq!(max, capacity);
2759 }
2760 other => panic!(
2761 "expected MessageTooLarge: compaction must not rescue a frame the \
2762 ceiling can never admit, got {other:?}"
2763 ),
2764 }
2765 assert_eq!(
2766 reader.front_buf.available_data(),
2767 pending,
2768 "an unsatisfiable declared length must still not be consumed"
2769 );
2770 assert!(
2771 reader.readiness.is_error(),
2772 "an unsatisfiable declared length must still mark the channel for \
2773 closing, whatever the buffer layout it was parsed from"
2774 );
2775 }
2776
2777 /// A peer that dies with bytes still queued towards it makes the next
2778 /// `read(2)` fail with `ECONNRESET` rather than return EOF. That failure
2779 /// must leave the channel marked for closing exactly as EOF does: the
2780 /// supervisor's `WorkerSession::ready` closes a session on HUP or ERROR
2781 /// only, and an edge-triggered poller reports the hangup once, so a read
2782 /// error that wiped the readiness left a dead worker's session open for
2783 /// good (sozu-proxy/sozu#1560).
2784 #[test]
2785 fn a_read_error_keeps_the_channel_marked_for_closing() {
2786 for initial in [
2787 Ready::READABLE | Ready::WRITABLE | Ready::HUP | Ready::ERROR,
2788 Ready::READABLE | Ready::WRITABLE,
2789 ] {
2790 let (mut local, peer) =
2791 Channel::<ProtobufMessage, ProtobufMessage>::generate_nonblocking(1000, 10000)
2792 .expect("could not generate nonblocking channels");
2793 // Leave a byte unread in the peer's receive queue, then close the
2794 // peer: the kernel resets the connection instead of sending EOF.
2795 assert_eq!(local.sock.write(b"x").expect("write to the live peer"), 1);
2796 drop(peer);
2797
2798 local.readiness = initial;
2799 match local.readable() {
2800 Err(ChannelError::Read(error)) => {
2801 assert_eq!(error.kind(), ErrorKind::ConnectionReset)
2802 }
2803 other => panic!("expected a connection reset, got {other:?}"),
2804 }
2805 assert!(
2806 local.readiness.is_hup() && local.readiness.is_error(),
2807 "a read error must leave HUP and ERROR set (from {initial:?}), got {:?}",
2808 local.readiness
2809 );
2810 assert!(!local.readiness.is_readable() && !local.readiness.is_writable());
2811 assert_eq!(local.interest, Ready::EMPTY);
2812 }
2813 }
2814
2815 /// Same contract on the write side: `EPIPE` towards a closed peer must
2816 /// mark the channel for closing, as `Ok(0)` already does, and is reported
2817 /// as `ChannelError::Write`, not `Read` (sozu-proxy/sozu#1563).
2818 #[test]
2819 fn a_write_error_keeps_the_channel_marked_for_closing() {
2820 for initial in [
2821 Ready::READABLE | Ready::WRITABLE | Ready::HUP | Ready::ERROR,
2822 Ready::READABLE | Ready::WRITABLE,
2823 ] {
2824 let (mut local, peer) =
2825 Channel::<ProtobufMessage, ProtobufMessage>::generate_nonblocking(1000, 10000)
2826 .expect("could not generate nonblocking channels");
2827 drop(peer);
2828
2829 local
2830 .write_delimited_message(&ProtobufMessage { inner: 7 })
2831 .expect("queue a message in the back buffer");
2832 local.interest.insert(Ready::WRITABLE);
2833 local.readiness = initial;
2834 match local.writable() {
2835 Err(ChannelError::Write(error)) => {
2836 assert_eq!(error.kind(), ErrorKind::BrokenPipe)
2837 }
2838 other => panic!("expected a broken pipe reported as a write error, got {other:?}"),
2839 }
2840 assert!(
2841 local.readiness.is_hup() && local.readiness.is_error(),
2842 "a write error must leave HUP and ERROR set (from {initial:?}), got {:?}",
2843 local.readiness
2844 );
2845 assert!(!local.readiness.is_readable() && !local.readiness.is_writable());
2846 assert_eq!(local.interest, Ready::EMPTY);
2847 }
2848 }
2849
2850 /// A blocking write towards a closed peer must fail with the socket error.
2851 /// It used to return `Ok(())` for any `write(2)` error, leaving the frame
2852 /// in `back_buf` while the caller believed it delivered
2853 /// (sozu-proxy/sozu#1563).
2854 #[test]
2855 fn a_blocking_write_to_a_closed_peer_is_an_error() {
2856 let (mut blocking, peer) = test_channels();
2857 assert!(blocking.is_blocking());
2858 drop(peer);
2859
2860 match blocking.write_message(&ProtobufMessage { inner: 9 }) {
2861 Err(ChannelError::Write(error)) => assert_eq!(error.kind(), ErrorKind::BrokenPipe),
2862 other => panic!("expected a broken pipe reported as a write error, got {other:?}"),
2863 }
2864 assert!(
2865 blocking.back_buf.available_data() > 0,
2866 "the unsent frame must stay in the back buffer"
2867 );
2868 }
2869}