metrale_storage/backend/
posix.rs1use anyhow::{Context, Result, bail};
14use std::ffi::c_void;
15
16use super::{ReadRequest, StorageBackend};
17use crate::cuda_min::{PinnedBuffer, copy_h_to_d_async, stream_sync};
18use crate::group::{GroupKey, GroupLayout};
19use crate::layout::Layout;
20
21pub struct PosixBackend {
22 layout: Layout,
23 bounce: PinnedBuffer,
24}
25
26impl PosixBackend {
27 pub fn new(layout: Layout) -> Result<Self> {
28 let bounce = PinnedBuffer::new(layout.group_bytes() as usize)
29 .context("alloc pinned bounce buffer")?;
30 Ok(Self { layout, bounce })
31 }
32 pub fn layout(&self) -> &Layout {
33 &self.layout
34 }
35}
36
37impl StorageBackend for PosixBackend {
38 fn read(&mut self, requests: &[ReadRequest], stream: u64) -> Result<()> {
39 let bytes = self.layout.group_bytes() as usize;
40 let bounce_ptr = self.bounce.ptr;
41 for req in requests {
42 let off = self.layout.offset(req.group);
43 let buf = unsafe { std::slice::from_raw_parts_mut(bounce_ptr as *mut u8, bytes) };
47 crate::tier::pio::read_exact_at(self.layout.file(req.group.layer), buf, off)
48 .with_context(|| format!("read {bytes}@{off}"))?;
49 copy_h_to_d_async(req.dst_dev_ptr, bounce_ptr as *const c_void, bytes, stream)?;
52 stream_sync(stream)?;
53 }
54 Ok(())
55 }
56
57 fn write_from_host(&mut self, key: GroupKey, src: &[u8]) -> Result<()> {
58 let bytes = self.layout.group_bytes() as usize;
59 if src.len() != bytes {
60 bail!(
61 "write_from_host: src len {} != group bytes {bytes}",
62 src.len()
63 );
64 }
65 unsafe {
68 std::ptr::copy_nonoverlapping(src.as_ptr(), self.bounce.ptr as *mut u8, bytes);
69 }
70 let off = self.layout.offset(key);
71 let buf = unsafe { std::slice::from_raw_parts(self.bounce.ptr as *const u8, bytes) };
73 crate::tier::pio::write_all_at(self.layout.file(key.layer), buf, off)
74 .with_context(|| format!("write {bytes}@{off}"))?;
75 Ok(())
77 }
78
79 fn group_layout(&self) -> GroupLayout {
80 self.layout.spec
81 }
82}
83
84impl PosixBackend {
85 #[cfg(unix)]
89 pub fn drop_pagecache(&self) {
90 for layer in 0..self.layout.spec.num_layers {
91 let fd = self.layout.fd(layer);
92 unsafe { libc::posix_fadvise(fd, 0, 0, libc::POSIX_FADV_DONTNEED) };
93 }
94 }
95
96 #[cfg(not(unix))]
97 pub fn drop_pagecache(&self) {}
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103 use crate::group::{GroupLayout, KvKind};
104
105 fn tempdir(name: &str) -> std::path::PathBuf {
106 let p =
107 std::env::temp_dir().join(format!("metrale-storage-{}-{}", name, std::process::id()));
108 let _ = std::fs::remove_dir_all(&p);
109 std::fs::create_dir_all(&p).unwrap();
110 p
111 }
112
113 #[test]
114 #[ignore = "requires GPU"]
115 fn write_then_read_round_trip() {
116 let _ctx = crate::cuda_min::CudaCtx::new(0).expect("cuda init");
118 let dir = tempdir("rt");
119 let spec = GroupLayout::new(1, 2, 1, 16, 128, 2, 4096);
120 let layout = Layout::create(&dir, spec).unwrap();
121 let mut backend = PosixBackend::new(layout).unwrap();
122 let bytes = backend.layout().group_bytes() as usize;
123 let pat: Vec<u8> = (0..bytes).map(|i| (i & 0xFF) as u8).collect();
124 let key = GroupKey::new(0, 1, 0, KvKind::V);
125 backend.write_from_host(key, &pat).unwrap();
126 backend.drop_pagecache();
127
128 let dev = crate::cuda_min::DeviceBuffer::new(bytes).unwrap();
129 let req = ReadRequest {
130 group: key,
131 dst_dev_ptr: dev.ptr,
132 };
133 backend.read(&[req], _ctx.stream).unwrap();
134 let mut host_back = vec![0_u8; bytes];
135 crate::cuda_min::copy_d_to_h_async(
136 host_back.as_mut_ptr() as *mut c_void,
137 dev.ptr,
138 bytes,
139 _ctx.stream,
140 )
141 .unwrap();
142 crate::cuda_min::stream_sync(_ctx.stream).unwrap();
143 assert_eq!(host_back, pat);
144 std::fs::remove_dir_all(&dir).ok();
145 }
146}