Skip to main content

sozu_command_lib/
writer.rs

1use std::io::{self, Error, ErrorKind, Write};
2
3/// A multiline writer used for logging
4pub struct MultiLineWriter<W: Write> {
5    inner: Option<W>,
6    buf: Vec<u8>,
7    last_newline: usize,
8    panicked: bool,
9}
10
11impl<W: Write> MultiLineWriter<W> {
12    pub fn new(inner: W) -> MultiLineWriter<W> {
13        MultiLineWriter::with_capacity(4096, inner)
14    }
15
16    pub fn with_capacity(capacity: usize, inner: W) -> MultiLineWriter<W> {
17        //MultiLineWriter { inner: BufWriter::with_capacity(capacity, inner), capacity, last_newline: usize, need_flush: false }
18        MultiLineWriter {
19            inner: Some(inner),
20            buf: Vec::with_capacity(capacity),
21            panicked: false,
22            last_newline: 0,
23        }
24    }
25
26    pub fn get_ref(&self) -> &W {
27        self.inner.as_ref().unwrap()
28    }
29
30    pub fn get_mut(&mut self) -> &mut W {
31        self.inner.as_mut().unwrap()
32    }
33
34    fn flush_buf(&mut self, flush_entire_buffer: bool) -> io::Result<()> {
35        let mut written = 0;
36        // `last_newline` is stale whenever the buffer is empty: `flush_buf`
37        // resets it to 0, which is also a valid index, so a partial flush of an
38        // EMPTY buffer used to slice `..1` out of a zero-length `Vec` and panic
39        // in release. `write` takes exactly that path for a record larger than
40        // the capacity, and access-log records are network-influenced.
41        let len = if flush_entire_buffer {
42            self.buf.len()
43        } else {
44            (self.last_newline + 1).min(self.buf.len())
45        };
46
47        let mut ret = Ok(());
48        while written < len {
49            self.panicked = true;
50            let r = self.inner.as_mut().unwrap().write(&self.buf[written..len]);
51            self.panicked = false;
52
53            match r {
54                Ok(0) => {
55                    ret = Err(Error::new(
56                        ErrorKind::WriteZero,
57                        "failed to write the buffered data",
58                    ));
59                    break;
60                }
61                Ok(n) => written += n,
62                Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
63                Err(e) => {
64                    ret = Err(e);
65                    break;
66                }
67            }
68        }
69        //println!("buf len {} last newline {}, written {}", self.buf.len(), self.last_newline, written);
70        if written > 0 {
71            //println!("FLUSHED: {}", ::std::str::from_utf8(&self.buf[..written]).unwrap());
72            self.buf.drain(..written);
73        }
74
75        if flush_entire_buffer {
76            self.last_newline = 0;
77        } else if written > self.last_newline {
78            self.last_newline = 0
79        } else {
80            self.last_newline -= written;
81        }
82
83        ret
84    }
85}
86
87impl<W: Write> Write for MultiLineWriter<W> {
88    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
89        if self.buf.len() + buf.len() > self.buf.capacity() {
90            self.flush_buf(false)?;
91        }
92        if buf.len() >= self.buf.capacity() {
93            self.panicked = true;
94            let r = self.get_mut().write(buf);
95            self.panicked = false;
96            r
97        } else {
98            if let Some(i) = memchr::memrchr(b'\n', buf) {
99                self.last_newline = self.buf.len() + i;
100            };
101
102            self.buf.write(buf)
103        }
104    }
105
106    fn flush(&mut self) -> io::Result<()> {
107        self.flush_buf(true).and_then(|()| self.get_mut().flush())
108    }
109}
110
111impl<W: Write> Drop for MultiLineWriter<W> {
112    fn drop(&mut self) {
113        if self.inner.is_some() && !self.panicked {
114            // dtors should not panic, so we ignore a failed flush
115            let _r = self.flush_buf(true);
116        }
117    }
118}
119
120/*
121impl<W: Write> Write for MultiLineWriter<W> {
122  fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
123    let buffer_len = self.inner.buffer().len();
124
125    let i = match memchr::memrchr(b'\n', buf) {
126      Some(i) => i,
127      None => buf.len(),
128    };
129
130    if buffer_len + i > self.capacity {
131      self.inner.flush()?;
132      self.last_newline = 0;
133    }
134
135
136
137
138  }
139}
140*/
141
142/*
143impl<W: Write> Drop for BufWriter<W> {
144  fn drop(&mut self) {
145    if self.inner.is_some() && !self.panicked {
146      // dtors should not panic, so we ignore a failed flush
147      let _r = self.flush_buf();
148    }
149  }
150}
151*/
152
153#[cfg(test)]
154mod tests {
155    //! `MultiLineWriter::write` must never index past its own buffer. The
156    //! `file://` logger backend (`command/src/logging/logs.rs`) and
157    //! `MetricsWriter` (`lib/src/metrics/writer.rs`) both wrap one of these
158    //! around network-influenced records, so a record larger than the 4096-byte
159    //! capacity is reachable from traffic — and it panics in RELEASE, where a
160    //! slice index is not a `debug_assert!`.
161    //!
162    //! To SEE THESE RED (regression proof): restore the pre-fix body of
163    //! [`super::MultiLineWriter::flush_buf`]'s partial branch, `self.last_newline + 1`
164    //! without the `.min(self.buf.len())` clamp — the two oversized-record
165    //! expectations below then panic with
166    //! `range end index 1 out of range for slice of length 0`.
167    use super::MultiLineWriter;
168    use std::io::Write;
169
170    /// Capacity of the writer under test. The production default is 4096
171    /// (`MultiLineWriter::new`); the bug is a function of `record > capacity`,
172    /// not of the capacity's value, so a small one keeps the fixtures readable.
173    const CAPACITY: usize = 16;
174
175    #[test]
176    fn an_oversized_record_on_a_fresh_writer_does_not_panic() {
177        // `last_newline == 0` with an EMPTY buffer is the initial state: the
178        // partial flush computed `len = 1` against a zero-length `Vec`.
179        let mut writer = MultiLineWriter::with_capacity(CAPACITY, Vec::new());
180        let record = vec![b'a'; CAPACITY * 4];
181
182        let written = writer
183            .write(&record)
184            .expect("a record larger than the capacity is written straight through");
185
186        assert_eq!(
187            written,
188            record.len(),
189            "the oversized record must be handed to the inner writer whole"
190        );
191        assert_eq!(
192            writer.get_ref(),
193            &record,
194            "the inner writer must receive exactly the oversized record"
195        );
196    }
197
198    #[test]
199    fn an_oversized_record_after_a_flush_does_not_panic() {
200        // The same empty-buffer/`last_newline == 0` state, reached the way a
201        // live logger reaches it: any previous `flush()` leaves it behind.
202        let mut writer = MultiLineWriter::with_capacity(CAPACITY, Vec::new());
203        writer.write_all(b"x\n").expect("a short line is buffered");
204        writer.flush().expect("the flush empties the buffer");
205        assert_eq!(
206            writer.get_ref(),
207            b"x\n",
208            "the short line reached the inner writer"
209        );
210
211        let record = vec![b'b'; CAPACITY * 4];
212        let written = writer
213            .write(&record)
214            .expect("a record larger than the capacity is written straight through");
215
216        assert_eq!(written, record.len());
217        assert_eq!(
218            &writer.get_ref()[2..],
219            &record[..],
220            "the oversized record must follow the already-flushed line"
221        );
222    }
223
224    #[test]
225    fn a_partial_flush_still_stops_at_the_last_newline() {
226        // The clamp must not change the path it was NOT meant to touch: with a
227        // non-empty buffer, a partial flush still writes up to and including
228        // the last newline and keeps the unterminated tail buffered.
229        let mut writer = MultiLineWriter::with_capacity(CAPACITY, Vec::new());
230        writer
231            .write_all(b"aaaa\nbb")
232            .expect("buffered below capacity");
233        assert!(
234            writer.get_ref().is_empty(),
235            "nothing reaches the inner writer before a flush is triggered"
236        );
237
238        // 7 + 10 > 16 triggers the partial flush of the completed line only.
239        writer
240            .write_all(b"cccccccccc")
241            .expect("triggers a partial flush");
242        assert_eq!(
243            writer.get_ref(),
244            b"aaaa\n",
245            "a partial flush writes up to and including the last newline"
246        );
247
248        writer.flush().expect("the final flush drains the tail");
249        assert_eq!(
250            writer.get_ref(),
251            b"aaaa\nbbcccccccccc",
252            "the buffered tail must survive the partial flush"
253        );
254    }
255}