11//! Low-level I/O utilities for reading and writing segment files.
22
3- use std:: io:: Write ;
43use std:: path:: { Path , PathBuf } ;
54
65use crate :: error:: { StorageError , StorageResult } ;
@@ -21,7 +20,7 @@ fn get_temp_path(path: &Path) -> PathBuf {
2120
2221/// Atomically write data to a file.
2322/// Uses temporary file + sync + rename pattern for crash resilience.
24- pub ( crate ) async fn atomic_write ( path : & Path , data : Vec < u8 > ) -> StorageResult < ( ) > {
23+ pub ( crate ) async fn atomic_write ( path : & Path , data : & [ u8 ] ) -> StorageResult < ( ) > {
2524 // Ensure parent directory exists
2625 if let Some ( parent) = path. parent ( ) {
2726 tokio:: fs:: create_dir_all ( parent)
@@ -30,35 +29,32 @@ pub(crate) async fn atomic_write(path: &Path, data: Vec<u8>) -> StorageResult<()
3029 }
3130
3231 let temp_path = get_temp_path ( path) ;
33- let target_path = path. to_path_buf ( ) ;
34-
35- // Use blocking I/O to ensure file handle is fully closed before rename.
36- // On Windows, async file drops don't guarantee synchronous closure.
37- tokio:: task:: spawn_blocking ( move || {
38- // Write to temp file
39- let write_result = ( || {
40- let mut file = std:: fs:: File :: create ( & temp_path) ?;
41- file. write_all ( & data) ?;
42- file. sync_all ( ) ?;
43- // File handle closed here when `file` goes out of scope
44- Ok :: < ( ) , std:: io:: Error > ( ( ) )
45- } ) ( ) ;
46-
47- if let Err ( e) = write_result {
48- let _ = std:: fs:: remove_file ( & temp_path) ;
49- return Err ( StorageError :: WriteFailed ( format ! ( "Failed to write temp file: {}" , e) ) ) ;
50- }
5132
52- // Atomic rename
53- if let Err ( e) = std:: fs:: rename ( & temp_path, & target_path) {
54- let _ = std:: fs:: remove_file ( & temp_path) ;
55- return Err ( StorageError :: WriteFailed ( format ! ( "Failed to rename temp file: {}" , e) ) ) ;
56- }
33+ // Write to temporary file
34+ let write_result = async {
35+ tokio:: fs:: write ( & temp_path, data) . await ?;
36+
37+ // Sync to disk - open the file and call sync_all
38+ let file = tokio:: fs:: File :: open ( & temp_path) . await ?;
39+ file. sync_all ( ) . await ?;
40+
41+ Ok :: < ( ) , std:: io:: Error > ( ( ) )
42+ }
43+ . await ;
44+
45+ // Clean up temp file on error
46+ if let Err ( e) = write_result {
47+ let _ = tokio:: fs:: remove_file ( & temp_path) . await ;
48+ return Err ( StorageError :: WriteFailed ( format ! ( "Failed to write temp file: {}" , e) ) ) ;
49+ }
50+
51+ // Atomic rename
52+ if let Err ( e) = tokio:: fs:: rename ( & temp_path, path) . await {
53+ let _ = tokio:: fs:: remove_file ( & temp_path) . await ;
54+ return Err ( StorageError :: WriteFailed ( format ! ( "Failed to rename temp file: {}" , e) ) ) ;
55+ }
5756
58- Ok ( ( ) )
59- } )
60- . await
61- . map_err ( |e| StorageError :: WriteFailed ( format ! ( "atomic_write join error: {}" , e) ) ) ?
57+ Ok ( ( ) )
6258}
6359
6460#[ cfg( test) ]
@@ -91,7 +87,7 @@ mod tests {
9187 let path = temp_dir. path ( ) . join ( "test.dat" ) ;
9288
9389 let content = b"hello world" ;
94- atomic_write ( & path, content. to_vec ( ) ) . await . unwrap ( ) ;
90+ atomic_write ( & path, content) . await . unwrap ( ) ;
9591
9692 assert ! ( path. exists( ) ) ;
9793 let read_content = tokio:: fs:: read ( & path) . await . unwrap ( ) ;
@@ -104,7 +100,7 @@ mod tests {
104100 let path = temp_dir. path ( ) . join ( "nested" ) . join ( "dirs" ) . join ( "test.dat" ) ;
105101
106102 let content = b"nested content" ;
107- atomic_write ( & path, content. to_vec ( ) ) . await . unwrap ( ) ;
103+ atomic_write ( & path, content) . await . unwrap ( ) ;
108104
109105 assert ! ( path. exists( ) ) ;
110106 let read_content = tokio:: fs:: read ( & path) . await . unwrap ( ) ;
@@ -121,7 +117,7 @@ mod tests {
121117
122118 // Overwrite with atomic write
123119 let new_content = b"new content" ;
124- atomic_write ( & path, new_content. to_vec ( ) ) . await . unwrap ( ) ;
120+ atomic_write ( & path, new_content) . await . unwrap ( ) ;
125121
126122 let read_content = tokio:: fs:: read ( & path) . await . unwrap ( ) ;
127123 assert_eq ! ( read_content, new_content) ;
@@ -132,7 +128,7 @@ mod tests {
132128 let temp_dir = TempDir :: new ( ) . unwrap ( ) ;
133129 let path = temp_dir. path ( ) . join ( "test.dat" ) ;
134130
135- atomic_write ( & path, b"data" . to_vec ( ) ) . await . unwrap ( ) ;
131+ atomic_write ( & path, b"data" ) . await . unwrap ( ) ;
136132
137133 // Check no .tmp files remain
138134 let mut entries = tokio:: fs:: read_dir ( temp_dir. path ( ) ) . await . unwrap ( ) ;
@@ -158,7 +154,7 @@ mod tests {
158154 // Try to write to an invalid path (directory instead of file)
159155 let invalid_path = temp_dir. path ( ) . join ( "test.dat" ) . join ( "invalid" ) ;
160156
161- let result = atomic_write ( & invalid_path, b"new content" . to_vec ( ) ) . await ;
157+ let result = atomic_write ( & invalid_path, b"new content" ) . await ;
162158 assert ! ( result. is_err( ) ) ;
163159
164160 // Original file should still have original content
@@ -177,7 +173,7 @@ mod tests {
177173 // Try to write to a path where parent "directory" is actually a file
178174 let invalid_path = blocker_path. join ( "subdir" ) . join ( "file.dat" ) ;
179175
180- let result = atomic_write ( & invalid_path, b"data" . to_vec ( ) ) . await ;
176+ let result = atomic_write ( & invalid_path, b"data" ) . await ;
181177 assert ! ( result. is_err( ) ) ;
182178
183179 // No temp files should remain in the base temp dir
@@ -197,7 +193,7 @@ mod tests {
197193
198194 // Write binary data with null bytes and various byte values
199195 let binary_data: Vec < u8 > = ( 0u8 ..=255 ) . collect ( ) ;
200- atomic_write ( & path, binary_data. clone ( ) ) . await . unwrap ( ) ;
196+ atomic_write ( & path, & binary_data) . await . unwrap ( ) ;
201197
202198 let read_content = tokio:: fs:: read ( & path) . await . unwrap ( ) ;
203199 assert_eq ! ( read_content, binary_data) ;
@@ -210,7 +206,7 @@ mod tests {
210206
211207 // Write 1MB of data
212208 let large_data: Vec < u8 > = ( 0 ..1_000_000 ) . map ( |i| ( i % 256 ) as u8 ) . collect ( ) ;
213- atomic_write ( & path, large_data. clone ( ) ) . await . unwrap ( ) ;
209+ atomic_write ( & path, & large_data) . await . unwrap ( ) ;
214210
215211 let read_content = tokio:: fs:: read ( & path) . await . unwrap ( ) ;
216212 assert_eq ! ( read_content. len( ) , large_data. len( ) ) ;
@@ -226,7 +222,7 @@ mod tests {
226222 // you cannot rename a file over an existing directory
227223 tokio:: fs:: create_dir ( & path) . await . unwrap ( ) ;
228224
229- let result = atomic_write ( & path, b"data" . to_vec ( ) ) . await ;
225+ let result = atomic_write ( & path, b"data" ) . await ;
230226 assert ! ( result. is_err( ) ) ;
231227
232228 // The target directory should still exist
0 commit comments