@@ -19,6 +19,7 @@ pub struct CircularFileWriter {
1919struct WriterState {
2020 file : Option < File > ,
2121 lines_since_prune : u32 ,
22+ is_pruning : bool ,
2223}
2324
2425impl CircularFileWriter {
@@ -29,6 +30,7 @@ impl CircularFileWriter {
2930 state : Arc :: new ( Mutex :: new ( WriterState {
3031 file : None ,
3132 lines_since_prune : 0 ,
33+ is_pruning : false ,
3234 } ) ) ,
3335 }
3436 }
@@ -45,26 +47,42 @@ impl CircularFileWriter {
4547 Ok ( state. file . as_mut ( ) . expect ( "file was just opened" ) )
4648 }
4749
48- fn prune ( & self , state : & mut WriterState ) -> io:: Result < ( ) > {
49- // Close file before pruning
50- state. file = None ;
50+ fn spawn_prune ( & self ) {
51+ let path = self . path . clone ( ) ;
52+ let max_lines = self . max_lines ;
53+ let state_arc = self . state . clone ( ) ;
5154
52- if !Path :: new ( & self . path ) . exists ( ) {
55+ std:: thread:: spawn ( move || {
56+ if let Err ( e) = Self :: do_prune ( & path, max_lines) {
57+ eprintln ! ( "Failed to prune log file '{}': {}" , path, e) ;
58+ }
59+ let mut state = state_arc. lock ( ) ;
60+ state. is_pruning = false ;
61+ } ) ;
62+ }
63+
64+ fn do_prune ( path : & str , max_lines : u32 ) -> io:: Result < ( ) > {
65+ if !Path :: new ( path) . exists ( ) {
5366 return Ok ( ( ) ) ;
5467 }
5568
5669 let lines: Vec < String > = {
57- let file = File :: open ( & self . path ) ?;
70+ let file = File :: open ( path) ?;
5871 let reader = BufReader :: new ( file) ;
5972 reader. lines ( ) . collect :: < Result < _ , _ > > ( ) ?
6073 } ;
6174
62- if lines. len ( ) > self . max_lines as usize {
63- let start = lines. len ( ) - self . max_lines as usize ;
64- let mut file = File :: create ( & self . path ) ?;
65- for line in & lines[ start..] {
66- writeln ! ( file, "{}" , line) ?;
75+ if lines. len ( ) > max_lines as usize {
76+ let start = lines. len ( ) - max_lines as usize ;
77+ // Atomic-ish replacement: write to .tmp then rename
78+ let tmp_path = format ! ( "{}.tmp" , path) ;
79+ {
80+ let mut file = File :: create ( & tmp_path) ?;
81+ for line in & lines[ start..] {
82+ writeln ! ( file, "{}" , line) ?;
83+ }
6784 }
85+ std:: fs:: rename ( tmp_path, path) ?;
6886 }
6987 Ok ( ( ) )
7088 }
@@ -73,19 +91,19 @@ impl CircularFileWriter {
7391impl io:: Write for CircularFileWriter {
7492 fn write ( & mut self , buf : & [ u8 ] ) -> io:: Result < usize > {
7593 let mut state = self . state . lock ( ) ;
94+
7695 let file = self . ensure_file_open ( & mut state) ?;
77-
7896 file. write_all ( buf) ?;
7997
8098 let new_lines = buf. iter ( ) . filter ( |& & b| b == b'\n' ) . count ( ) as u32 ;
8199 state. lines_since_prune += new_lines;
82100
83101 let prune_threshold = ( self . max_lines / 10 ) . max ( 50 ) ;
84- if state. lines_since_prune >= prune_threshold {
85- if let Err ( e) = self . prune ( & mut state) {
86- eprintln ! ( "Failed to prune log file '{}': {}" , self . path, e) ;
87- }
102+ if state. lines_since_prune >= prune_threshold && !state. is_pruning {
103+ state. is_pruning = true ;
88104 state. lines_since_prune = 0 ;
105+ state. file = None ; // Close file so rename can happen on Windows if needed
106+ self . spawn_prune ( ) ;
89107 }
90108
91109 Ok ( buf. len ( ) )
0 commit comments