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}