use std::fs::{File, OpenOptions};
use std::io::{self, BufWriter, Seek, SeekFrom, Write};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::atomic::AtomicU64;
use std::time::Instant;
use kevy_resp::ArgvView;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) use crate::aof_mapped::{MapHandle, Mapped, sync_handles};
use crate::aof_rewrite::RewriteStats;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) use crate::aof_stage::Stage;
#[cfg(target_arch = "wasm32")]
pub(crate) use crate::aof_stage_off::{MapHandle, Mapped, Stage, sync_handles};
use crate::estimate_multibulk_bytes;
pub use crate::modes::Fsync;
use crate::modes::ReplayMode;
use crate::record::RECORD_HEADER;
use crate::record_pieces::{record_header, write_frame};
pub const AOF_MAGIC: &[u8; 9] = b"KEVYAOF1\n";
pub(crate) const AOF_BUF_CAP: usize = 256 * 1024;
#[derive(Debug)]
pub struct Aof {
pub(crate) file: BufWriter<File>,
pub(crate) in_txn: bool,
pub(crate) path: PathBuf,
pub(crate) fsync: Fsync,
pub(crate) dirty: bool,
pub(crate) last_sync: Instant,
pub(crate) sync_started: u64,
pub(crate) sync_confirmed: Arc<AtomicU64>,
pub(crate) size_bytes: u64,
pub(crate) size_at_last_rewrite: u64,
pub(crate) rewrites_total: u64,
pub(crate) deferred: bool,
pub(crate) rewrite_tee: Option<Vec<u8>>,
pub(crate) tee_spare: Option<Vec<u8>>,
pub(crate) swap_trash: Option<PathBuf>,
pub(crate) swap_hold: bool,
open_quarantine: Option<PathBuf>,
pub(crate) last_rewrite_at: Instant,
pub(crate) format: crate::AofFormat,
pub(crate) stage: Option<Stage>,
pub(crate) mapped: Option<Mapped>,
#[cfg(not(target_arch = "wasm32"))]
pub(crate) maps: bool,
pub(crate) queue: Option<Vec<u8>>,
pub(crate) queued_offset: u64,
pub(crate) queued_seq: u64,
}
impl Aof {
#[must_use]
pub fn format(&self) -> crate::AofFormat {
self.format
}
pub fn open(path: &Path, fsync: Fsync) -> io::Result<Self> {
Self::open_with_repair(path, fsync, ReplayMode::Strict)
}
pub fn open_with_repair(path: &Path, fsync: Fsync, mode: ReplayMode) -> io::Result<Self> {
Self::open_after_replay(path, fsync, mode, None)
}
pub fn open_after_replay(
path: &Path,
fsync: Fsync,
mode: ReplayMode,
settled: Option<u64>,
) -> io::Result<Self> {
let resync = mode == ReplayMode::Resync;
let (file, size, format, quarantined) = Self::prepare_file(path, resync, settled)?;
Ok(Aof {
in_txn: false,
file: BufWriter::with_capacity(AOF_BUF_CAP, file),
path: path.to_path_buf(),
fsync,
dirty: false,
last_sync: Instant::now(),
sync_started: 0,
sync_confirmed: Arc::new(AtomicU64::new(0)),
size_bytes: size,
size_at_last_rewrite: size,
rewrites_total: 0,
deferred: false,
rewrite_tee: None,
tee_spare: None,
swap_trash: None,
swap_hold: false,
open_quarantine: quarantined,
last_rewrite_at: Instant::now(),
format,
queue: None,
queued_offset: size,
queued_seq: 0,
stage: None,
mapped: None,
#[cfg(not(target_arch = "wasm32"))]
maps: false,
})
}
fn prepare_file(
path: &Path,
resync: bool,
settled: Option<u64>,
) -> io::Result<(File, u64, crate::AofFormat, Option<PathBuf>)> {
let mut file = OpenOptions::new().create(true).append(true).open(path)?;
let mut size = file.metadata().map_or(0, |m| m.len());
if size == 0 {
file.write_all(crate::record::AOF2_MAGIC)?;
file.sync_data()?;
size = crate::record::AOF2_MAGIC.len() as u64;
return Ok((file, size, crate::AofFormat::V2, None));
}
let format = crate::replay::sniff_format(path)?;
let settled = settled.filter(|_| format == crate::AofFormat::V2);
let quarantined =
crate::aof_util::settle_tail(path, &mut file, &mut size, settled, resync)?;
Ok((file, size, format, quarantined))
}
#[inline]
pub fn open_quarantine(&self) -> Option<&Path> {
self.open_quarantine.as_deref()
}
#[inline]
pub(crate) fn last_rewrite_at(&self) -> Instant {
self.last_rewrite_at
}
#[inline]
pub fn fsync_policy(&self) -> Fsync {
self.fsync
}
pub fn set_fsync(&mut self, fsync: Fsync) -> io::Result<()> {
let upgrading_to_always =
matches!(fsync, Fsync::Always) && !matches!(self.fsync, Fsync::Always);
self.fsync = fsync;
if upgrading_to_always {
self.flush_queued()?;
self.stop_mapping()?;
}
if upgrading_to_always && (self.dirty || self.sync_unconfirmed()) {
self.file.flush()?;
self.file.get_ref().sync_data()?;
self.dirty = false;
self.last_sync = Instant::now();
self.confirm_started_syncs();
}
Ok(())
}
pub fn append<A: ArgvView + ?Sized>(&mut self, args: &A) -> io::Result<()> {
let v2 = matches!(self.format, crate::AofFormat::V2);
let head = (v2 || self.rewrite_tee.is_some()).then(|| record_header(args));
let own = if v2 { head.as_ref() } else { None };
if let Some(q) = &mut self.queue {
write_frame(q, own, args)?;
self.queued_seq += 1;
} else if let Some(m) = &mut self.mapped {
write_frame(m, own, args)?;
} else if let Some(h) = own.filter(|_| self.stage.is_some()) {
let len = RECORD_HEADER + u32::from_le_bytes([h[0], h[1], h[2], h[3]]) as usize;
let staged = self.stage_record(len, |mut slot| {
write_frame(&mut slot, Some(h), args).expect("the slot is the record's exact size");
})?;
if !staged {
write_frame(&mut self.file, own, args)?;
self.stage_bypassed()?;
}
} else {
write_frame(&mut self.file, own, args)?;
}
if let (Some(tee), Some(h)) = (&mut self.rewrite_tee, &head) {
write_frame(tee, Some(h), args)?;
}
let overhead = match self.format {
crate::AofFormat::V2 => crate::record::RECORD_HEADER as u64,
crate::AofFormat::V1 => 0,
};
self.size_bytes =
self.size_bytes.saturating_add(estimate_multibulk_bytes(args)).saturating_add(overhead);
match self.fsync {
Fsync::Always if self.deferred || self.queue.is_some() => self.dirty = true,
Fsync::Always => {
self.file.flush()?;
self.file.get_ref().sync_data()?;
}
Fsync::EverySec | Fsync::No => self.dirty = true,
}
Ok(())
}
pub fn truncate(&mut self) -> io::Result<()> {
self.flush_queued()?;
self.unmap()?;
self.file.flush()?;
let f = self.file.get_mut();
f.set_len(0)?;
f.seek(SeekFrom::Start(0))?; f.write_all(crate::record::AOF2_MAGIC)?;
f.sync_all()?;
self.dirty = false;
self.format = crate::AofFormat::V2; self.size_bytes = crate::record::AOF2_MAGIC.len() as u64;
self.queued_offset = self.size_bytes;
self.size_at_last_rewrite = crate::record::AOF2_MAGIC.len() as u64;
self.last_rewrite_at = Instant::now();
self.after_file_change()
}
#[inline]
pub fn size_bytes(&self) -> u64 {
self.size_bytes
}
#[inline]
pub fn size_at_last_rewrite(&self) -> u64 {
self.size_at_last_rewrite
}
pub fn anchor_rewrite_baseline(&mut self, bytes: u64) {
if !self.is_rewriting() {
self.size_at_last_rewrite = bytes.max(crate::record::AOF2_MAGIC.len() as u64);
}
}
#[inline]
pub fn rewrites_total(&self) -> u64 {
self.rewrites_total
}
pub fn rewrite_from<S: crate::SnapshotSource>(
&mut self,
store: &S,
) -> io::Result<RewriteStats> {
self.flush_queued()?;
self.unmap()?;
self.file.flush()?;
let tmp = crate::aof_util::rewrite_tmp_path(&self.path);
let RewriteStats { keys, bytes } = crate::dump_aof(&tmp, store)?;
std::fs::rename(&tmp, &self.path)?;
let f = OpenOptions::new().append(true).open(&self.path)?;
self.file = BufWriter::with_capacity(AOF_BUF_CAP, f);
self.format = crate::AofFormat::V2; self.size_bytes = bytes;
self.queued_offset = bytes;
self.size_at_last_rewrite = bytes;
self.last_rewrite_at = Instant::now();
self.dirty = false;
self.rewrites_total = self.rewrites_total.saturating_add(1);
self.after_file_change()?;
Ok(RewriteStats { keys, bytes })
}
#[inline]
pub fn is_rewriting(&self) -> bool {
self.rewrite_tee.is_some()
}
}