Skip to main content

edgestore/
fdp_backend.rs

1use std::path::{Path, PathBuf};
2use std::sync::Mutex;
3
4use crate::error::EdgestoreError;
5use crate::storage_backend::{PlacementHint, StorageBackend};
6
7/// FDP-aware storage backend.
8///
9/// Wraps another StorageBackend and emits FDP placement hints before writes
10/// on platforms that support NVMe 2.0 Flexible Data Placement.
11/// On unsupported platforms, the hint is silently ignored (logged at `info!`).
12pub struct FdpStorageBackend {
13    inner: Box<dyn StorageBackend>,
14}
15
16impl FdpStorageBackend {
17    /// Wrap an existing backend with FDP hints.
18    pub fn new(inner: Box<dyn StorageBackend>) -> Self {
19        FdpStorageBackend { inner }
20    }
21}
22
23impl StorageBackend for FdpStorageBackend {
24    fn read(&self, path: &Path, offset: u64, data: &mut [u8]) -> Result<usize, EdgestoreError> {
25        self.inner.read(path, offset, data)
26    }
27
28    fn write(&self, path: &Path, offset: u64, data: &[u8]) -> Result<(), EdgestoreError> {
29        self.inner.write(path, offset, data)
30    }
31
32    fn write_with_hint(
33        &self,
34        path: &Path,
35        offset: u64,
36        data: &[u8],
37        hint: PlacementHint,
38    ) -> Result<(), EdgestoreError> {
39        #[cfg(target_os = "linux")]
40        {
41            // Attempt to emit FDP hint via fcntl on the target file descriptor.
42            // Actual FDP ioctl constants vary by filesystem; this is a stub.
43            // Best-effort: if the file is not yet on disk (e.g. in-memory test backend),
44            // skip the hint silently.
45            if let Ok(file) = std::fs::OpenOptions::new().write(true).open(path) {
46                let _fd = std::os::fd::AsRawFd::as_raw_fd(&file);
47                // FDP hint would be emitted here via fcntl(fd, F_SET_FILE_DATA_PLACEMENT_HINT, ...)
48                // For now, just log the hint.
49                log::info!("FDP hint: cohort_bucket={} for {:?}", hint.cohort_bucket, path);
50            }
51        }
52        #[cfg(not(target_os = "linux"))]
53        {
54            let _ = hint; // suppress unused warning on non-Linux
55        }
56        self.inner.write_with_hint(path, offset, data, hint)
57    }
58
59    fn flush(&self, path: &Path) -> Result<(), EdgestoreError> {
60        self.inner.flush(path)
61    }
62}
63
64/// Mock FDP backend that records placement hints for test verification.
65pub struct MockFdpBackend {
66    inner: Box<dyn StorageBackend>,
67    /// Hints recorded by this mock backend for test verification.
68    pub recorded_hints: Mutex<Vec<(PathBuf, u64, PlacementHint)>>,
69}
70
71impl MockFdpBackend {
72    /// Create a new mock backend wrapping the given inner backend.
73    pub fn new(inner: Box<dyn StorageBackend>) -> Self {
74        MockFdpBackend {
75            inner,
76            recorded_hints: Mutex::new(Vec::new()),
77        }
78    }
79}
80
81impl StorageBackend for MockFdpBackend {
82    fn read(&self, path: &Path, offset: u64, data: &mut [u8]) -> Result<usize, EdgestoreError> {
83        self.inner.read(path, offset, data)
84    }
85
86    fn write(&self, path: &Path, offset: u64, data: &[u8]) -> Result<(), EdgestoreError> {
87        self.inner.write(path, offset, data)
88    }
89
90    fn write_with_hint(
91        &self,
92        path: &Path,
93        offset: u64,
94        data: &[u8],
95        hint: PlacementHint,
96    ) -> Result<(), EdgestoreError> {
97        self.recorded_hints.lock().unwrap().push((path.to_path_buf(), offset, hint));
98        self.inner.write_with_hint(path, offset, data, hint)
99    }
100
101    fn flush(&self, path: &Path) -> Result<(), EdgestoreError> {
102        self.inner.flush(path)
103    }
104}
105
106// ── Tests ──────────────────────────────────────────────────────────────────
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111    use crate::storage_backend::MemoryStorageBackend;
112
113    #[test]
114    fn test_mock_fdp_records_hint() {
115        let inner = Box::new(MemoryStorageBackend::new());
116        let backend = MockFdpBackend::new(inner);
117        let path = Path::new("/tmp/test.bin");
118
119        let hint = PlacementHint { cohort_bucket: 42 };
120        backend.write_with_hint(path, 0, b"hello", hint).unwrap();
121
122        let hints = backend.recorded_hints.lock().unwrap();
123        assert_eq!(hints.len(), 1);
124        assert_eq!(hints[0].0, path);
125        assert_eq!(hints[0].1, 0);
126        assert_eq!(hints[0].2.cohort_bucket, 42);
127    }
128
129    #[test]
130    fn test_fdp_backend_no_panic() {
131        let inner = Box::new(MemoryStorageBackend::new());
132        let backend = FdpStorageBackend::new(inner);
133        let path = Path::new("/tmp/test.bin");
134
135        let hint = PlacementHint { cohort_bucket: 7 };
136        backend.write_with_hint(path, 0, b"data", hint).unwrap();
137        backend.flush(path).unwrap();
138
139        let mut buf = [0u8; 4];
140        let n = backend.read(path, 0, &mut buf).unwrap();
141        assert_eq!(n, 4);
142        assert_eq!(&buf, b"data");
143    }
144}