BufWriter insinde OnceLock does nothing

Hey there,

In the example bellow, the StdoutMessege struct works as intended by itself, however if I wrap it in a OnceLock I doesn't actually print anything, even though the function print gets called every time.

use std::{
    fmt::Display,
    io::{self, BufWriter, Stdout, Write},
    sync::{Mutex, OnceLock},
    thread,
};

static STDOUT_BUF: OnceLock<StdoutMessage> = OnceLock::new();

#[derive(Debug)]
struct StdoutMessage {
    writer: Mutex<BufWriter<Stdout>>,
}

impl StdoutMessage {
    fn new() -> Self {
        Self {
            writer: Mutex::new(BufWriter::new(io::stdout())),
        }
    }

    fn print(&self, message: impl Display) {
        let mut writer = self.writer.lock().unwrap();
        writer.write_all(format!("{message}\n").as_bytes()).unwrap();
    }
}

fn stdout_init() {
    STDOUT_BUF.set(StdoutMessage::new()).unwrap();
}

fn stdout_print(message: impl Display) {
    STDOUT_BUF.get().unwrap().print(message);
}

fn main() {
    stdout_init();
    thread::scope(|s| {
        for id in 0..5 {
            s.spawn(move || {
                for n in 0..10 {
                    stdout_print(format!("message {n} from thread {id}"))
                }
            });
        }
    });
}

You aren't flushing the buffer there, so the program ends without the messages being written to Stdout. Normally a BufWriter flushes itself when it drops, but putting it into a static (via a OnceLock) means it doesn't get dropped. You should insert calls to flush() if you want StdoutMessage to be in a static.

3 Likes

You shouldn't normally need to use BufWriter with println!() or std::io::Stdout. They already do line buffering. If you want better performance you can call lock on Stdout to get a locked stdout handle rather than locking on every write.

4 Likes

you forgot flush

fn print(&self, message: impl Display) {
        let mut writer = self.writer.lock().unwrap();
        writer.write_all(format!("{message}\n").as_bytes()).unwrap();
        writer.flush().unwrap();
}

Thanks, I can't avoid getting a lock every time because of the multithreading, but I didn't know it was already buffered.

FWIW, you should really be using writeln! instead of write_bytes(format!(...)), as it's significantly more efficient. Of course, that could be negligible in your app; I'm just trying to do my part to combat software bloat.

1 Like