use std::io::{self, Write};
use std::sync::atomic::{AtomicBool, Ordering};
use super::directory::StreamingWriter;
fn log_mode_once() {
static LOGGED: AtomicBool = AtomicBool::new(false);
if !LOGGED.swap(true, Ordering::Relaxed) {
#[cfg(target_os = "linux")]
log::info!(
"[cold_io] merge writes drop page cache via sync_file_range + fadvise(DONTNEED)"
);
#[cfg(target_os = "macos")]
log::info!("[cold_io] merge writes bypass buffer cache via F_NOCACHE");
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
log::warn!(
"[cold_io] no page-cache-drop mechanism on this platform — merge writes stay buffered"
);
}
}
const BUF_SIZE: usize = 8 * 1024 * 1024;
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
const DROP_WINDOW: u64 = 64 * 1024 * 1024;
pub(crate) struct ColdStreamingWriter {
file: std::fs::File,
buf: Vec<u8>,
written: u64,
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
writeback_cursor: u64,
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
drop_cursor: u64,
label: std::sync::Arc<str>,
}
impl ColdStreamingWriter {
pub(crate) fn new(file: std::fs::File, label: std::sync::Arc<str>) -> Self {
log_mode_once();
#[cfg(target_os = "macos")]
{
use std::os::fd::AsRawFd;
if unsafe { libc::fcntl(file.as_raw_fd(), libc::F_NOCACHE, 1) } != 0 {
log::warn!(
"[cold_io] F_NOCACHE failed ({}); falling back to cached writes",
io::Error::last_os_error()
);
}
}
Self {
file,
buf: Vec::with_capacity(BUF_SIZE),
written: 0,
writeback_cursor: 0,
drop_cursor: 0,
label,
}
}
fn flush_buf(&mut self) -> io::Result<()> {
if self.buf.is_empty() {
return Ok(());
}
self.file.write_all(&self.buf)?;
self.written += self.buf.len() as u64;
self.buf.clear();
self.maybe_drop(false);
Ok(())
}
#[allow(unused_variables)]
fn maybe_drop(&mut self, final_drop: bool) {
#[cfg(target_os = "linux")]
{
use std::os::fd::AsRawFd;
let fd = self.file.as_raw_fd();
if final_drop {
if self.written > self.drop_cursor {
unsafe {
libc::posix_fadvise(
fd,
self.drop_cursor as libc::off64_t,
(self.written - self.drop_cursor) as libc::off64_t,
libc::POSIX_FADV_DONTNEED,
);
}
self.drop_cursor = self.written;
}
return;
}
let completed = (self.written / DROP_WINDOW) * DROP_WINDOW;
if completed <= self.writeback_cursor {
return;
}
unsafe {
libc::sync_file_range(
fd,
self.writeback_cursor as libc::off64_t,
(completed - self.writeback_cursor) as libc::off64_t,
libc::SYNC_FILE_RANGE_WRITE,
);
if self.writeback_cursor > self.drop_cursor {
libc::posix_fadvise(
fd,
self.drop_cursor as libc::off64_t,
(self.writeback_cursor - self.drop_cursor) as libc::off64_t,
libc::POSIX_FADV_DONTNEED,
);
self.drop_cursor = self.writeback_cursor;
}
}
self.writeback_cursor = completed;
}
}
}
impl io::Write for ColdStreamingWriter {
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
if self.buf.len() + data.len() > BUF_SIZE {
self.flush_buf()?;
}
if data.len() >= BUF_SIZE {
self.file.write_all(data)?;
self.written += data.len() as u64;
self.maybe_drop(false);
} else {
self.buf.extend_from_slice(data);
}
Ok(data.len())
}
fn flush(&mut self) -> io::Result<()> {
self.flush_buf()?;
self.file.flush()
}
}
impl StreamingWriter for ColdStreamingWriter {
fn finish(mut self: Box<Self>) -> io::Result<()> {
self.flush_buf()?;
self.file.sync_all()?;
self.maybe_drop(true);
crate::observe::cold_write(&self.label, self.written as usize);
log::debug!(
"[cold_io] wrote {:.1} MB with page cache dropped behind the cursor",
self.written as f64 / (1024.0 * 1024.0)
);
Ok(())
}
fn bytes_written(&self) -> u64 {
self.written + self.buf.len() as u64
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cold_streaming_writer_writes_byte_identical_output() {
let tmp = tempfile::tempdir().unwrap();
let path = tmp.path().join("cold.bin");
let file = std::fs::File::create(&path).unwrap();
let mut writer: Box<dyn StreamingWriter> =
Box::new(ColdStreamingWriter::new(file, std::sync::Arc::from("test")));
let mut expected: Vec<u8> = Vec::new();
for i in 0..1000u32 {
let chunk = i.to_le_bytes();
writer.write_all(&chunk).unwrap();
expected.extend_from_slice(&chunk);
}
let big = vec![0xABu8; BUF_SIZE];
writer.write_all(&big).unwrap();
expected.extend_from_slice(&big);
let bigger = vec![0xCDu8; BUF_SIZE + 12345];
writer.write_all(&bigger).unwrap();
expected.extend_from_slice(&bigger);
writer.write_all(b"tail").unwrap();
expected.extend_from_slice(b"tail");
assert_eq!(writer.bytes_written(), expected.len() as u64);
writer.finish().unwrap();
let got = std::fs::read(&path).unwrap();
assert_eq!(got.len(), expected.len());
assert_eq!(got, expected, "cold writer corrupted the byte stream");
}
}