sozu_command_lib/
writer.rs1use std::io::{self, Error, ErrorKind, Write};
2
3pub 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 {
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 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 if written > 0 {
71 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 let _r = self.flush_buf(true);
116 }
117 }
118}
119
120#[cfg(test)]
154mod tests {
155 use super::MultiLineWriter;
168 use std::io::Write;
169
170 const CAPACITY: usize = 16;
174
175 #[test]
176 fn an_oversized_record_on_a_fresh_writer_does_not_panic() {
177 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 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 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 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}