Skip to main content

noxu_log/
file_handle.rs

1//! File handle with latch protection.
2//!
3//!
4//! A FileHandle wraps a file descriptor with a latch to ensure exclusive
5//! access during I/O operations.
6
7use crate::error::{LogError, Result};
8use noxu_latch::{ExclusiveLatch, ExclusiveLatchGuard};
9use noxu_sync::Mutex;
10use std::fs::File;
11use std::sync::Arc;
12
13use crate::posio;
14
15/// A file handle with latch protection for thread-safe I/O.
16///
17/// The handle holds a file descriptor and an exclusive latch.
18/// All I/O operations must be performed while holding the latch.
19pub struct FileHandle {
20    /// The underlying file (wrapped in Mutex for interior mutability).
21    file: Mutex<Option<File>>,
22    /// Latch protecting access to the file.
23    latch: Arc<ExclusiveLatch>,
24    /// Log version of this file.
25    log_version: u32,
26    /// File number this handle represents.
27    file_num: u32,
28}
29
30impl FileHandle {
31    /// Creates a new uninitialized file handle.
32    ///
33    /// The file must be initialized via `init()` before use.
34    pub fn new(file_num: u32) -> Self {
35        let latch =
36            Arc::new(ExclusiveLatch::named(format!("file_{:08x}", file_num)));
37
38        FileHandle { file: Mutex::new(None), latch, log_version: 0, file_num }
39    }
40
41    /// Initializes the handle with an open file and log version.
42    pub fn init(&mut self, file: File, log_version: u32) {
43        let mut f = self.file.lock();
44        assert!(f.is_none(), "FileHandle already initialized");
45        *f = Some(file);
46        self.log_version = log_version;
47    }
48
49    /// Returns the file number.
50    pub fn file_num(&self) -> u32 {
51        self.file_num
52    }
53
54    /// Returns the log version.
55    pub fn log_version(&self) -> u32 {
56        self.log_version
57    }
58
59    /// Returns true if the file is initialized.
60    pub fn is_initialized(&self) -> bool {
61        self.file.lock().is_some()
62    }
63
64    /// Acquires the latch and returns a guard that provides access to the file.
65    ///
66    /// Returns `Ok(guard)` on success, or `Err(LogError::LatchTimeout)` if the
67    /// latch acquisition times out. The latch is released when the guard drops.
68    pub fn acquire(&self) -> Result<FileHandleGuard<'_>> {
69        let _latch_guard = self
70            .latch
71            .acquire()
72            .map_err(|e| LogError::LatchTimeout(e.to_string()))?;
73        Ok(FileHandleGuard { handle: self, _latch_guard })
74    }
75
76    /// Attempts to acquire the latch without blocking.
77    ///
78    /// Returns `None` if the latch is currently held.
79    pub fn try_acquire(&self) -> Option<FileHandleGuard<'_>> {
80        self.latch
81            .try_acquire()
82            .map(|_latch_guard| FileHandleGuard { handle: self, _latch_guard })
83    }
84
85    /// Closes the file handle.
86    ///
87    /// This should only be called when the handle is no longer in use.
88    pub fn close(&mut self) -> Result<()> {
89        if let Some(file) = self.file.lock().take() {
90            drop(file); // File is closed when dropped
91        }
92        Ok(())
93    }
94}
95
96impl Drop for FileHandle {
97    fn drop(&mut self) {
98        let _ = self.close();
99    }
100}
101
102/// RAII guard providing access to the file while the latch is held.
103pub struct FileHandleGuard<'a> {
104    handle: &'a FileHandle,
105    _latch_guard: ExclusiveLatchGuard<'a>,
106}
107
108impl<'a> FileHandleGuard<'a> {
109    /// Reads data from the file at the given offset.
110    ///
111    /// # Arguments
112    ///
113    /// * `offset` - File offset to read from
114    /// * `buf` - Buffer to read into
115    ///
116    /// # Returns
117    ///
118    /// Number of bytes read.
119    /// Reads data from the file at the given offset.
120    ///
121    /// Uses `pread64` (one syscall) instead of `lseek + read` (two syscalls).
122    /// The JVM
123    /// lowers to `pread64` on Linux.
124    pub fn read_at(&mut self, offset: u64, buf: &mut [u8]) -> Result<usize> {
125        let file_guard = self.handle.file.lock();
126        let file = file_guard.as_ref().ok_or_else(|| {
127            LogError::Internal("FileHandle not initialized".to_string())
128        })?;
129        Ok(posio::read_at(file, buf, offset)?)
130    }
131
132    /// Reads exactly `buf.len()` bytes from the file at the given offset.
133    ///
134    /// Uses `pread64` in a retry loop.
135    /// Returns an error if fewer bytes are available.
136    pub fn read_exact_at(&mut self, offset: u64, buf: &mut [u8]) -> Result<()> {
137        let file_guard = self.handle.file.lock();
138        let file = file_guard.as_ref().ok_or_else(|| {
139            LogError::Internal("FileHandle not initialized".to_string())
140        })?;
141        posio::read_exact_at(file, buf, offset)?;
142        Ok(())
143    }
144
145    /// Writes data to the file at the given offset.
146    ///
147    /// Uses `pwrite64` (one syscall) instead of `lseek + write` (two syscalls).
148    /// `FileChannel.write(ByteBuffer, position)` which the JVM
149    /// lowers to `pwrite64` on Linux.  This eliminates half the syscalls on
150    /// the hot write path and removes the need to serialise seek+write under
151    /// the guard (pwrite64 is inherently positional and thread-safe).
152    ///
153    /// # Arguments
154    ///
155    /// * `offset` - File offset to write to (passed directly to pwrite64)
156    /// * `buf` - Data to write
157    ///
158    /// # Returns
159    ///
160    /// Number of bytes written (always `buf.len()` on success).
161    pub fn write_at(&mut self, offset: u64, buf: &[u8]) -> Result<usize> {
162        let file_guard = self.handle.file.lock();
163        let file = file_guard.as_ref().ok_or_else(|| {
164            LogError::Internal("FileHandle not initialized".to_string())
165        })?;
166        posio::write_all_at(file, buf, offset)?;
167        Ok(buf.len())
168    }
169
170    /// Syncs all file data and metadata to disk (fsync).
171    ///
172    /// Use this when the file's metadata (size, mtime) must also be durable —
173    /// typically for file-header writes.  For log-data writes prefer
174    /// `sync_data()` which is faster.
175    pub fn sync(&mut self) -> Result<()> {
176        let file_guard = self.handle.file.lock();
177        let file = file_guard.as_ref().ok_or_else(|| {
178            LogError::Internal("FileHandle not initialized".to_string())
179        })?;
180        file.sync_all()?;
181        Ok(())
182    }
183
184    /// Syncs only the file data to disk (fdatasync).
185    ///
186    /// Faster than `sync()` because it does not flush file metadata (mtime
187    /// etc.).  uses `FileChannel.force(false)` (= fdatasync) for all
188    /// log-data writes and `force(true)` (= fsync) only for file-header writes.
189    ///
190    /// / `FileChannel.force(false)`.
191    pub fn sync_data(&mut self) -> Result<()> {
192        let file_guard = self.handle.file.lock();
193        let file = file_guard.as_ref().ok_or_else(|| {
194            LogError::Internal("FileHandle not initialized".to_string())
195        })?;
196        file.sync_data()?;
197        Ok(())
198    }
199
200    /// Returns true if the file is empty.
201    pub fn is_empty(&mut self) -> Result<bool> {
202        Ok(self.len()? == 0)
203    }
204
205    /// Returns the file length.
206    pub fn len(&mut self) -> Result<u64> {
207        let file_guard = self.handle.file.lock();
208        let file = file_guard.as_ref().ok_or_else(|| {
209            LogError::Internal("FileHandle not initialized".to_string())
210        })?;
211        Ok(file.metadata()?.len())
212    }
213
214    /// Truncates the file to the given length.
215    pub fn truncate(&mut self, len: u64) -> Result<()> {
216        let file_guard = self.handle.file.lock();
217        let file = file_guard.as_ref().ok_or_else(|| {
218            LogError::Internal("FileHandle not initialized".to_string())
219        })?;
220        file.set_len(len)?;
221        Ok(())
222    }
223
224    /// Returns the file number.
225    pub fn file_num(&self) -> u32 {
226        self.handle.file_num()
227    }
228
229    /// Returns the log version.
230    pub fn log_version(&self) -> u32 {
231        self.handle.log_version()
232    }
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238    use std::io::Write;
239    use tempfile::NamedTempFile;
240
241    #[test]
242    fn test_file_handle_basic() {
243        let mut temp_file = NamedTempFile::new().unwrap();
244        temp_file.write_all(b"Hello, world!").unwrap();
245        temp_file.flush().unwrap();
246
247        let file = File::open(temp_file.path()).unwrap();
248
249        let mut handle = FileHandle::new(0);
250        handle.init(file, 1);
251
252        assert_eq!(handle.file_num(), 0);
253        assert_eq!(handle.log_version(), 1);
254        assert!(handle.is_initialized());
255    }
256
257    #[test]
258    fn test_file_handle_read_write() {
259        let temp_file = NamedTempFile::new().unwrap();
260        let file = File::options()
261            .read(true)
262            .write(true)
263            .open(temp_file.path())
264            .unwrap();
265
266        let mut handle = FileHandle::new(0);
267        handle.init(file, 1);
268
269        {
270            let mut guard = handle.acquire().expect("acquire");
271            guard.write_at(0, b"test data").unwrap();
272            guard.sync().unwrap();
273        }
274
275        {
276            let mut guard = handle.acquire().expect("acquire");
277            let mut buf = vec![0u8; 9];
278            let n = guard.read_at(0, &mut buf).unwrap();
279            assert_eq!(n, 9);
280            assert_eq!(&buf, b"test data");
281        }
282    }
283
284    #[test]
285    fn test_file_handle_new_uninitialized() {
286        let handle = FileHandle::new(42);
287        assert_eq!(handle.file_num(), 42);
288        assert_eq!(handle.log_version(), 0);
289        assert!(!handle.is_initialized());
290    }
291
292    #[test]
293    fn test_file_handle_log_version_set_on_init() {
294        let temp_file = NamedTempFile::new().unwrap();
295        let file = File::open(temp_file.path()).unwrap();
296        let mut handle = FileHandle::new(7);
297        handle.init(file, 5);
298        assert_eq!(handle.log_version(), 5);
299        assert!(handle.is_initialized());
300    }
301
302    #[test]
303    fn test_file_handle_file_num_preserved() {
304        let mut handle = FileHandle::new(0xFF);
305        let temp_file = NamedTempFile::new().unwrap();
306        let file = File::open(temp_file.path()).unwrap();
307        handle.init(file, 1);
308        assert_eq!(handle.file_num(), 0xFF);
309    }
310
311    #[test]
312    fn test_file_handle_close_uninitialised() {
313        let mut handle = FileHandle::new(0);
314        // Closing a non-initialized handle should not error
315        assert!(handle.close().is_ok());
316        assert!(!handle.is_initialized());
317    }
318
319    #[test]
320    fn test_file_handle_close_initialized() {
321        let temp_file = NamedTempFile::new().unwrap();
322        let file = File::open(temp_file.path()).unwrap();
323        let mut handle = FileHandle::new(1);
324        handle.init(file, 1);
325        assert!(handle.is_initialized());
326        assert!(handle.close().is_ok());
327        assert!(!handle.is_initialized());
328    }
329
330    #[test]
331    fn test_file_handle_guard_file_num() {
332        let temp_file = NamedTempFile::new().unwrap();
333        let file = File::open(temp_file.path()).unwrap();
334        let mut handle = FileHandle::new(99);
335        handle.init(file, 3);
336        let guard = handle.acquire().expect("acquire");
337        assert_eq!(guard.file_num(), 99);
338        assert_eq!(guard.log_version(), 3);
339    }
340
341    #[test]
342    fn test_file_handle_guard_read_exact() {
343        let temp_file = NamedTempFile::new().unwrap();
344        let file = File::options()
345            .read(true)
346            .write(true)
347            .open(temp_file.path())
348            .unwrap();
349        let mut handle = FileHandle::new(0);
350        handle.init(file, 1);
351
352        {
353            let mut guard = handle.acquire().expect("acquire");
354            guard.write_at(0, b"hello").unwrap();
355        }
356        {
357            let mut guard = handle.acquire().expect("acquire");
358            let mut buf = vec![0u8; 5];
359            guard.read_exact_at(0, &mut buf).unwrap();
360            assert_eq!(&buf, b"hello");
361        }
362    }
363
364    #[test]
365    fn test_file_handle_guard_len_and_is_empty() {
366        let temp_file = NamedTempFile::new().unwrap();
367        let file = File::options()
368            .read(true)
369            .write(true)
370            .open(temp_file.path())
371            .unwrap();
372        let mut handle = FileHandle::new(0);
373        handle.init(file, 1);
374
375        {
376            let mut guard = handle.acquire().expect("acquire");
377            assert!(guard.is_empty().unwrap());
378            assert_eq!(guard.len().unwrap(), 0);
379            guard.write_at(0, b"abc").unwrap();
380        }
381        {
382            let mut guard = handle.acquire().expect("acquire");
383            assert!(!guard.is_empty().unwrap());
384            assert_eq!(guard.len().unwrap(), 3);
385        }
386    }
387
388    #[test]
389    fn test_file_handle_guard_truncate() {
390        let temp_file = NamedTempFile::new().unwrap();
391        let file = File::options()
392            .read(true)
393            .write(true)
394            .open(temp_file.path())
395            .unwrap();
396        let mut handle = FileHandle::new(0);
397        handle.init(file, 1);
398
399        {
400            let mut guard = handle.acquire().expect("acquire");
401            guard.write_at(0, b"hello world").unwrap();
402        }
403        {
404            let mut guard = handle.acquire().expect("acquire");
405            guard.truncate(5).unwrap();
406            assert_eq!(guard.len().unwrap(), 5);
407        }
408    }
409
410    #[test]
411    fn test_file_handle_try_acquire() {
412        let temp_file = NamedTempFile::new().unwrap();
413        let file = File::open(temp_file.path()).unwrap();
414        let mut handle = FileHandle::new(0);
415        handle.init(file, 1);
416
417        let guard = handle.try_acquire();
418        assert!(guard.is_some());
419        // Guard released when dropped, then try_acquire succeeds again
420        drop(guard);
421        let guard2 = handle.try_acquire();
422        assert!(guard2.is_some());
423    }
424
425    #[test]
426    fn test_file_handle_read_at_offset() {
427        let temp_file = NamedTempFile::new().unwrap();
428        let file = File::options()
429            .read(true)
430            .write(true)
431            .open(temp_file.path())
432            .unwrap();
433        let mut handle = FileHandle::new(0);
434        handle.init(file, 1);
435
436        {
437            let mut guard = handle.acquire().expect("acquire");
438            guard.write_at(0, b"ABCDEF").unwrap();
439        }
440        {
441            let mut guard = handle.acquire().expect("acquire");
442            let mut buf = vec![0u8; 3];
443            let n = guard.read_at(2, &mut buf).unwrap();
444            assert_eq!(n, 3);
445            assert_eq!(&buf, b"CDE");
446        }
447    }
448
449    #[test]
450    fn test_file_handle_write_at_offset() {
451        let temp_file = NamedTempFile::new().unwrap();
452        let file = File::options()
453            .read(true)
454            .write(true)
455            .open(temp_file.path())
456            .unwrap();
457        let mut handle = FileHandle::new(0);
458        handle.init(file, 1);
459
460        {
461            let mut guard = handle.acquire().expect("acquire");
462            guard.write_at(0, b"XXXXXXXX").unwrap();
463            guard.write_at(2, b"AB").unwrap();
464        }
465        {
466            let mut guard = handle.acquire().expect("acquire");
467            let mut buf = vec![0u8; 8];
468            guard.read_exact_at(0, &mut buf).unwrap();
469            assert_eq!(&buf[2..4], b"AB");
470        }
471    }
472}