spark_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 atlas_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)?;
55 stream_sync(stream)?;
56 }
57 Ok(())
58 }
59
60 fn write_from_host(&mut self, key: GroupKey, src: &[u8]) -> Result<()> {
61 let bytes = self.layout.group_bytes() as usize;
62 if src.len() != bytes {
63 bail!(
64 "write_from_host: src len {} != group bytes {bytes}",
65 src.len()
66 );
67 }
68 unsafe {
71 std::ptr::copy_nonoverlapping(src.as_ptr(), self.bounce.ptr as *mut u8, bytes);
72 }
73 let off = self.layout.offset(key);
74 let buf = unsafe { std::slice::from_raw_parts(self.bounce.ptr as *const u8, bytes) };
76 atlas_tier::pio::write_all_at(self.layout.file(key.layer), buf, off)
77 .with_context(|| format!("write {bytes}@{off}"))?;
78 Ok(())
81 }
82
83 fn group_layout(&self) -> GroupLayout {
84 self.layout.spec
85 }
86}
87
88impl PosixBackend {
89 #[cfg(unix)]
98 pub fn drop_pagecache(&self) {
99 for layer in 0..self.layout.spec.num_layers {
100 let fd = self.layout.fd(layer);
101 unsafe { libc::posix_fadvise(fd, 0, 0, libc::POSIX_FADV_DONTNEED) };
102 }
103 }
104
105 #[cfg(not(unix))]
106 pub fn drop_pagecache(&self) {}
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use crate::group::{GroupLayout, KvKind};
113
114 fn tempdir(name: &str) -> std::path::PathBuf {
115 let p = std::env::temp_dir().join(format!("atlas-storage-{}-{}", name, std::process::id()));
116 let _ = std::fs::remove_dir_all(&p);
117 std::fs::create_dir_all(&p).unwrap();
118 p
119 }
120
121 #[test]
122 #[ignore = "requires GPU"]
123 fn write_then_read_round_trip() {
124 let _ctx = crate::cuda_min::CudaCtx::new(0).expect("cuda init");
126 let dir = tempdir("rt");
127 let spec = GroupLayout::new(1, 2, 1, 16, 128, 2, 4096);
128 let layout = Layout::create(&dir, spec).unwrap();
129 let mut backend = PosixBackend::new(layout).unwrap();
130 let bytes = backend.layout().group_bytes() as usize;
131 let pat: Vec<u8> = (0..bytes).map(|i| (i & 0xFF) as u8).collect();
132 let key = GroupKey::new(0, 1, 0, KvKind::V);
133 backend.write_from_host(key, &pat).unwrap();
134 backend.drop_pagecache();
135
136 let dev = crate::cuda_min::DeviceBuffer::new(bytes).unwrap();
137 let req = ReadRequest {
138 group: key,
139 dst_dev_ptr: dev.ptr,
140 };
141 backend.read(&[req], _ctx.stream).unwrap();
144 let mut host_back = vec![0_u8; bytes];
145 crate::cuda_min::copy_d_to_h_async(
146 host_back.as_mut_ptr() as *mut c_void,
147 dev.ptr,
148 bytes,
149 _ctx.stream,
150 )
151 .unwrap();
152 crate::cuda_min::stream_sync(_ctx.stream).unwrap();
153 assert_eq!(host_back, pat);
154 std::fs::remove_dir_all(&dir).ok();
155 }
156}