ring_file/
lib.rs

1//! # RingFile
2//!
3//! The purpose of this tool is to help debug deadlock problems, that only occur under
4//! high-performance scenarios. Because writing log to disk will slow down execution,
5//! which makes deadlock hidden without racing conditions met.
6//!
7//! This crate provides a ringbuffer like file, in order to store byte content.
8//! Already integrated into [captain-log](https://docs.rs/captains-log) as `LogRingFile` sink.
9//!
10//! The content is kept in memory when written, when offset rewinds, new content will overwrite old content,
11//! So that memory consumption is limited to buf_size.
12//! Once deadlock encountered and process hangs, no more message will be written,
13//! you can safely dump the content to disk.
14//!
15//! # Example:
16//!
17//! ```rust
18//! use ring_file::RingFile;
19//! let mut file = RingFile::new(512*1024*1024, "/tmp/ringfile.store");
20//! file.write_all("log message").expect("write ok");
21//! file.dump().expect("dump ok");
22//! ```
23
24use std::path::PathBuf;
25use io_buffer::{Buffer, safe_copy};
26use std::io::{Result, Write};
27use std::fs::*;
28
29pub struct RingFile {
30    end: usize,
31    inner: Buffer,
32    full: bool,
33    file_path: PathBuf,
34}
35
36impl RingFile {
37
38    /// Allocate a whole buffer specified by `buf_size`, size can not exceed 2GB.
39    pub fn new<P: Into<PathBuf>>(buf_size: i32, file_path: P) -> Self {
40        assert!(buf_size > 0);
41        let inner = Buffer::alloc(buf_size).expect("alloc");
42        Self{
43            end: 0,
44            inner,
45            full: false,
46            file_path: file_path.into(),
47        }
48    }
49
50    /// Will create a truncated file and write all data from mem to disk.
51    pub fn dump(&self) -> Result<()> {
52        let mut file = OpenOptions::new().write(true).create(true).truncate(true).open(&self.file_path)?;
53        if self.full {
54            file.write_all(&self.inner[self.end..])?;
55            return file.write_all(&self.inner[0..self.end]);
56        } else {
57            return file.write_all(&self.inner[0..self.end]);
58        }
59    }
60
61}
62
63impl std::io::Write for RingFile {
64
65    /// Write will abort when reaching the boundary of buffer, rewind the offset to 0 and return the bytes written.
66    /// You can use Write::write_all() provided by the trait to cover the rewinding logic.
67    #[inline]
68    fn write(&mut self, buf: &[u8]) -> Result<usize> {
69        let bound = self.inner.capacity();
70        let l = buf.len();
71        if self.end + l >= bound {
72            let l1 = safe_copy(&mut self.inner[self.end..], &buf);
73            self.full = true;
74            self.end = 0;
75            return Ok(l1);
76        } else {
77            safe_copy(&mut self.inner[self.end..], buf);
78            self.end += l;
79            return Ok(l);
80        }
81    }
82
83    #[inline]
84    fn flush(&mut self) -> Result<()> {
85        Ok(())
86    }
87}