use bytes::{BytesMut, BufMut};
use parking_lot::Mutex;
use std::collections::VecDeque;
use std::fs::{File, OpenOptions};
use std::io::{Read, Write, Seek, SeekFrom};
use std::path::Path;
use std::sync::Arc;
use std::time::{Duration, Instant};
use crate::{Error, Result, Record, Lsn, layout::Region, types::checksum};
const RECORD_HEADER_SIZE: usize = 12;
struct WalEntry {
lsn: Lsn,
record: Record,
}
pub struct WalRing {
inner: Arc<Mutex<WalRingInner>>,
}
struct WalRingInner {
file: File,
region: Region,
write_offset: u64, checkpoint_lsn: Lsn, next_lsn: Lsn,
pending: VecDeque<WalEntry>,
last_flush: Instant,
batch_timeout: Duration,
}
impl WalRing {
pub fn create(path: impl AsRef<Path>, region: Region) -> Result<Self> {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(path)?;
file.seek(SeekFrom::Start(region.offset))?;
let zeros = vec![0u8; region.size as usize];
file.write_all(&zeros)?;
file.sync_all()?;
Ok(Self {
inner: Arc::new(Mutex::new(WalRingInner {
file,
region,
write_offset: 0,
checkpoint_lsn: 0,
next_lsn: 1,
pending: VecDeque::new(),
last_flush: Instant::now(),
batch_timeout: Duration::from_millis(10), })),
})
}
pub fn open(path: impl AsRef<Path>, region: Region) -> Result<Self> {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open(path)?;
let records = Self::recover(&mut file, ®ion)?;
let max_lsn = if records.is_empty() {
0
} else {
records.iter().map(|(lsn, _)| *lsn).max().unwrap_or(0)
};
Ok(Self {
inner: Arc::new(Mutex::new(WalRingInner {
file,
region,
write_offset: 0, checkpoint_lsn: 0,
next_lsn: max_lsn + 1,
pending: VecDeque::new(),
last_flush: Instant::now(),
batch_timeout: Duration::from_millis(10),
})),
})
}
pub fn append(&self, record: Record) -> Result<Lsn> {
let mut inner = self.inner.lock();
let lsn = inner.next_lsn;
inner.next_lsn += 1;
inner.pending.push_back(WalEntry { lsn, record });
if inner.last_flush.elapsed() >= inner.batch_timeout {
Self::flush_inner(&mut inner)?;
}
Ok(lsn)
}
pub fn flush(&self) -> Result<()> {
let mut inner = self.inner.lock();
Self::flush_inner(&mut inner)
}
fn flush_inner(inner: &mut WalRingInner) -> Result<()> {
if inner.pending.is_empty() {
return Ok(());
}
let mut buf = BytesMut::new();
for entry in &inner.pending {
let data = bincode::serialize(&entry.record)
.map_err(|e| Error::Internal(format!("Serialize error: {}", e)))?;
buf.put_u64_le(entry.lsn);
buf.put_u32_le(data.len() as u32);
buf.put_slice(&data);
let crc = checksum::compute(&data);
buf.put_u32_le(crc);
}
let total_size = buf.len() as u64;
if inner.write_offset + total_size > inner.region.size {
inner.write_offset = 0;
}
let file_offset = inner.region.offset + inner.write_offset;
inner.file.seek(SeekFrom::Start(file_offset))?;
inner.file.write_all(&buf)?;
inner.file.sync_all()?;
inner.write_offset += total_size;
inner.pending.clear();
inner.last_flush = Instant::now();
Ok(())
}
fn recover(file: &mut File, region: &Region) -> Result<Vec<(Lsn, Record)>> {
let mut records = Vec::new();
file.seek(SeekFrom::Start(region.offset))?;
let mut ring_data = vec![0u8; region.size as usize];
let bytes_read = file.read(&mut ring_data)?;
if bytes_read == 0 {
return Ok(records); }
let mut offset = 0usize;
while offset + RECORD_HEADER_SIZE + 4 < ring_data.len() {
let lsn = u64::from_le_bytes([
ring_data[offset],
ring_data[offset + 1],
ring_data[offset + 2],
ring_data[offset + 3],
ring_data[offset + 4],
ring_data[offset + 5],
ring_data[offset + 6],
ring_data[offset + 7],
]);
if lsn == 0 {
break;
}
let len = u32::from_le_bytes([
ring_data[offset + 8],
ring_data[offset + 9],
ring_data[offset + 10],
ring_data[offset + 11],
]) as usize;
if offset + RECORD_HEADER_SIZE + len + 4 > ring_data.len() {
break; }
let data_start = offset + RECORD_HEADER_SIZE;
let data_end = data_start + len;
let data = &ring_data[data_start..data_end];
let crc_offset = data_end;
let expected_crc = u32::from_le_bytes([
ring_data[crc_offset],
ring_data[crc_offset + 1],
ring_data[crc_offset + 2],
ring_data[crc_offset + 3],
]);
if checksum::verify(data, expected_crc) {
match bincode::deserialize::<Record>(data) {
Ok(record) => {
records.push((lsn, record));
offset = crc_offset + 4;
}
Err(_) => {
break;
}
}
} else {
break;
}
}
records.sort_by_key(|(lsn, _)| *lsn);
Ok(records)
}
pub fn read_all(&self) -> Result<Vec<(Lsn, Record)>> {
let inner = self.inner.lock();
let mut file = inner.file.try_clone()?;
drop(inner);
let inner = self.inner.lock();
let region = inner.region;
drop(inner);
Self::recover(&mut file, ®ion)
}
pub fn set_checkpoint(&self, lsn: Lsn) -> Result<()> {
let mut inner = self.inner.lock();
inner.checkpoint_lsn = lsn;
Ok(())
}
pub fn compact(&self) -> Result<()> {
Ok(())
}
pub fn next_lsn(&self) -> Lsn {
let inner = self.inner.lock();
inner.next_lsn
}
pub fn set_batch_timeout(&self, timeout: Duration) {
let mut inner = self.inner.lock();
inner.batch_timeout = timeout;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Key, Value, layout::Region};
use tempfile::NamedTempFile;
use std::collections::HashMap;
#[test]
fn test_wal_ring_create_and_append() {
let tmp = NamedTempFile::new().unwrap();
let region = Region::new(0, 64 * 1024);
let wal = WalRing::create(tmp.path(), region).unwrap();
let key = Key::new(b"test".to_vec());
let mut item = HashMap::new();
item.insert("value".to_string(), Value::string("hello"));
let record = Record::put(key, item, 1);
let lsn = wal.append(record).unwrap();
assert_eq!(lsn, 1);
wal.flush().unwrap();
let records = wal.read_all().unwrap();
assert_eq!(records.len(), 1);
assert_eq!(records[0].0, 1);
}
#[test]
fn test_wal_ring_recovery() {
let tmp = NamedTempFile::new().unwrap();
let region = Region::new(0, 64 * 1024);
{
let wal = WalRing::create(tmp.path(), region).unwrap();
for i in 0..5 {
let key = Key::new(format!("key{}", i).into_bytes());
let item = HashMap::new();
let record = Record::put(key, item, i);
wal.append(record).unwrap();
}
wal.flush().unwrap();
}
let wal = WalRing::open(tmp.path(), region).unwrap();
assert_eq!(wal.next_lsn(), 6);
let records = wal.read_all().unwrap();
assert_eq!(records.len(), 5);
}
#[test]
fn test_wal_ring_group_commit() {
let tmp = NamedTempFile::new().unwrap();
let region = Region::new(0, 64 * 1024);
let wal = WalRing::create(tmp.path(), region).unwrap();
for i in 0..10 {
let key = Key::new(format!("key{}", i).into_bytes());
let item = HashMap::new();
let record = Record::put(key, item, i);
wal.append(record).unwrap();
}
wal.flush().unwrap();
let records = wal.read_all().unwrap();
assert_eq!(records.len(), 10);
}
#[test]
fn test_wal_ring_wrap_around() {
let tmp = NamedTempFile::new().unwrap();
let region = Region::new(0, 1024);
let wal = WalRing::create(tmp.path(), region).unwrap();
for i in 0..50 {
let key = Key::new(format!("key{}", i).into_bytes());
let mut item = HashMap::new();
item.insert("data".to_string(), Value::string("x".repeat(50)));
let record = Record::put(key, item, i);
wal.append(record).unwrap();
wal.flush().unwrap();
}
let records = wal.read_all().unwrap();
assert!(!records.is_empty());
}
#[test]
fn test_wal_ring_checkpoint() {
let tmp = NamedTempFile::new().unwrap();
let region = Region::new(0, 64 * 1024);
let wal = WalRing::create(tmp.path(), region).unwrap();
for i in 0..10 {
let key = Key::new(format!("key{}", i).into_bytes());
let item = HashMap::new();
let record = Record::put(key, item, i);
wal.append(record).unwrap();
}
wal.flush().unwrap();
wal.set_checkpoint(5).unwrap();
wal.compact().unwrap();
let records = wal.read_all().unwrap();
assert_eq!(records.len(), 10); }
}