vtcode_core/utils/
async_line_writer.rs1use 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
16pub 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 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 pub fn write_line(&self, line: String) {
43 let _ = self.sender.try_send(LogMessage::Line(line));
44 }
45
46 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}