Skip to main content

vtcode_core/utils/
async_line_writer.rs

1use anyhow::{Context, Result};
2use std::fs::OpenOptions;
3use std::io::{BufWriter, Write};
4use std::path::{Path, PathBuf};
5use std::sync::mpsc::{Receiver, SyncSender, sync_channel};
6use std::thread;
7
8use crate::config::constants::defaults;
9use crate::utils::file_utils::ensure_dir_exists_sync;
10
11enum LogMessage {
12    Line(String),
13    Flush(SyncSender<()>),
14}
15
16/// Asynchronous line writer that buffers writes on a background thread.
17pub struct AsyncLineWriter {
18    sender: SyncSender<LogMessage>,
19    _handle: thread::JoinHandle<()>,
20}
21
22impl AsyncLineWriter {
23    pub fn new(path: PathBuf) -> Result<Self> {
24        if let Some(parent) = path.parent() {
25            ensure_dir_exists_sync(parent)
26                .with_context(|| format!("Failed to create log directory: {}", parent.display()))?;
27        }
28
29        // Create the current log file eagerly so callers can observe it immediately.
30        let _ = OpenOptions::new().create(true).append(true).open(&path);
31
32        let (sender, receiver) = sync_channel(defaults::DEFAULT_TRAJECTORY_LOG_CHANNEL_CAPACITY);
33        let handle = thread::spawn(move || writer_loop(&path, receiver));
34
35        Ok(Self {
36            sender,
37            _handle: handle,
38        })
39    }
40
41    /// Queue a line for writing. Drops the line if the queue is full.
42    pub fn write_line(&self, line: String) {
43        let _ = self.sender.try_send(LogMessage::Line(line));
44    }
45
46    /// Flush pending writes and wait for completion.
47    pub fn flush(&self) {
48        let (tx, rx) = sync_channel(1);
49        let _ = self.sender.send(LogMessage::Flush(tx));
50        let _ = rx.recv();
51    }
52}
53
54fn writer_loop(path: &Path, receiver: Receiver<LogMessage>) {
55    let file = OpenOptions::new().create(true).append(true).open(path);
56    let mut writer = match file {
57        Ok(file) => BufWriter::new(file),
58        Err(_) => return,
59    };
60
61    while let Ok(message) = receiver.recv() {
62        match message {
63            LogMessage::Line(line) => {
64                let _ = writeln!(writer, "{line}");
65            }
66            LogMessage::Flush(ack) => {
67                let _ = writer.flush();
68                let _ = ack.send(());
69            }
70        }
71    }
72
73    let _ = writer.flush();
74}