use std::io::{self, Write};
use std::sync::{Arc, Mutex};
enum State<W> {
Deferring(Vec<u8>),
Passthrough(W),
}
pub(crate) struct DeferredLogSink<W> {
state: Arc<Mutex<State<W>>>,
}
impl<W> Clone for DeferredLogSink<W> {
fn clone(&self) -> Self {
Self {
state: Arc::clone(&self.state),
}
}
}
impl<W: Write> DeferredLogSink<W> {
pub(crate) fn new() -> Self {
Self {
state: Arc::new(Mutex::new(State::Deferring(Vec::new()))),
}
}
pub(crate) fn release(&self, mut destination: W) -> io::Result<()> {
let mut guard = self
.state
.lock()
.expect("deferred log sink mutex must not be poisoned");
let State::Deferring(buffered) = &*guard else {
return Ok(());
};
destination.write_all(buffered)?;
destination.flush()?;
*guard = State::Passthrough(destination);
Ok(())
}
}
pub(crate) struct ReleaseGuard<W: Write, F: FnOnce() -> W> {
sink: DeferredLogSink<W>,
destination: Option<F>,
}
impl<W: Write, F: FnOnce() -> W> ReleaseGuard<W, F> {
pub(crate) fn new(sink: DeferredLogSink<W>, destination: F) -> Self {
Self {
sink,
destination: Some(destination),
}
}
}
impl<W: Write, F: FnOnce() -> W> Drop for ReleaseGuard<W, F> {
fn drop(&mut self) {
if let Some(destination) = self.destination.take() {
let _ = self.sink.release(destination());
}
}
}
impl<W: Write> Write for DeferredLogSink<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let mut guard = self
.state
.lock()
.expect("deferred log sink mutex must not be poisoned");
match &mut *guard {
State::Deferring(buffered) => buffered.extend_from_slice(buf),
State::Passthrough(destination) => return destination.write(buf),
}
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
let mut guard = self
.state
.lock()
.expect("deferred log sink mutex must not be poisoned");
if let State::Passthrough(destination) = &mut *guard {
return destination.flush();
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use rstest::rstest;
#[rstest]
fn should_buffer_writes_when_deferring() {
let mut sink = DeferredLogSink::new();
sink.write_all(b"first ").unwrap();
sink.write_all(b"second").unwrap();
let destination = Vec::new();
sink.release(destination.clone()).unwrap();
assert_eq!(Vec::<u8>::new(), destination);
}
#[rstest]
fn should_drain_buffered_bytes_in_order_when_released() {
let mut sink = DeferredLogSink::new();
sink.write_all(b"first ").unwrap();
sink.write_all(b"second").unwrap();
let destination: Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(Vec::new()));
sink.release(SharedVec(Arc::clone(&destination))).unwrap();
let actual = destination.lock().unwrap().clone();
assert_eq!(b"first second".to_vec(), actual);
}
#[rstest]
fn should_pass_writes_through_when_released() {
let mut sink = DeferredLogSink::new();
sink.write_all(b"buffered ").unwrap();
let destination: Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(Vec::new()));
sink.release(SharedVec(Arc::clone(&destination))).unwrap();
sink.write_all(b"live").unwrap();
let actual = destination.lock().unwrap().clone();
assert_eq!(b"buffered live".to_vec(), actual);
}
#[rstest]
fn should_ignore_second_release_when_already_released() {
let mut sink = DeferredLogSink::new();
sink.write_all(b"buffered").unwrap();
let first: Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(Vec::new()));
sink.release(SharedVec(Arc::clone(&first))).unwrap();
let second: Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(Vec::new()));
sink.release(SharedVec(Arc::clone(&second))).unwrap();
let actual_first = first.lock().unwrap().clone();
let actual_second = second.lock().unwrap().clone();
assert_eq!(b"buffered".to_vec(), actual_first);
assert_eq!(Vec::<u8>::new(), actual_second);
}
#[rstest]
fn should_release_buffered_bytes_when_guard_is_dropped() {
let mut sink = DeferredLogSink::new();
sink.write_all(b"buffered").unwrap();
let destination: Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(Vec::new()));
let destination_for_guard = Arc::clone(&destination);
let guard = ReleaseGuard::new(sink, move || SharedVec(destination_for_guard));
drop(guard);
let actual = destination.lock().unwrap().clone();
assert_eq!(b"buffered".to_vec(), actual);
}
#[rstest]
fn should_not_release_when_guard_is_defused_by_explicit_release() {
let mut sink = DeferredLogSink::new();
sink.write_all(b"buffered").unwrap();
let explicit: Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(Vec::new()));
sink.release(SharedVec(Arc::clone(&explicit))).unwrap();
let guard_destination: Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(Vec::new()));
let guard_destination_for_guard = Arc::clone(&guard_destination);
let guard = ReleaseGuard::new(sink, move || SharedVec(guard_destination_for_guard));
drop(guard);
let actual_explicit = explicit.lock().unwrap().clone();
let actual_guard = guard_destination.lock().unwrap().clone();
assert_eq!(b"buffered".to_vec(), actual_explicit);
assert_eq!(Vec::<u8>::new(), actual_guard);
}
#[rstest]
fn should_share_state_across_clones() {
let sink = DeferredLogSink::new();
let mut handle_a = sink.clone();
let mut handle_b = sink.clone();
handle_a.write_all(b"from a, ").unwrap();
handle_b.write_all(b"from b").unwrap();
let destination: Arc<Mutex<Vec<u8>>> = Arc::new(Mutex::new(Vec::new()));
sink.release(SharedVec(Arc::clone(&destination))).unwrap();
let actual = destination.lock().unwrap().clone();
assert_eq!(b"from a, from b".to_vec(), actual);
}
#[derive(Clone)]
struct SharedVec(Arc<Mutex<Vec<u8>>>);
impl Write for SharedVec {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.lock().unwrap().extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
}