use std::fs::{File, OpenOptions}; use std::hash::Hasher; use std::io::{Read, Write}; use std::path::Path; use std::sync::Arc; use anyhow::{bail, Context, Result}; use bytes::{Buf, BufMut, Bytes}; use crossbeam_skiplist::SkipMap; use parking_lot::Mutex; use crate::key::{KeyBytes, KeySlice}; pub struct Wal { file: Arc>, } impl Wal { pub fn create(path: impl AsRef) -> Result { Ok(Self { file: Arc::new(Mutex::new( OpenOptions::new() .read(true) .create_new(true) .write(true) .open(path) .context("failed to create WAL")?, )), }) } pub fn recover(path: impl AsRef, skiplist: &SkipMap) -> Result { let path = path.as_ref(); let mut file = OpenOptions::new() .read(true) .append(true) .open(path) .context("failed to recover from WAL")?; let mut buf = Vec::new(); file.read_to_end(&mut buf)?; let mut rbuf: &[u8] = buf.as_slice(); while rbuf.has_remaining() { let mut hasher = crc32fast::Hasher::new(); let key_len = rbuf.get_u16() as usize; hasher.write_u16(key_len as u16); let key = Bytes::copy_from_slice(&rbuf[..key_len]); hasher.write(&key); rbuf.advance(key_len); let ts = rbuf.get_u64(); hasher.write_u64(ts); let value_len = rbuf.get_u16() as usize; hasher.write_u16(value_len as u16); let value = Bytes::copy_from_slice(&rbuf[..value_len]); hasher.write(&value); rbuf.advance(value_len); let checksum = rbuf.get_u32(); if hasher.finalize() != checksum { bail!("checksum mismatch"); } skiplist.insert(KeyBytes::from_bytes_with_ts(key, ts), value); } Ok(Self { file: Arc::new(Mutex::new(file)), }) } pub fn put(&self, key: KeySlice, value: &[u8]) -> Result<()> { let mut file = self.file.lock(); let mut buf: Vec = Vec::with_capacity(key.raw_len() + value.len() + std::mem::size_of::()); let mut hasher = crc32fast::Hasher::new(); hasher.write_u16(key.key_len() as u16); buf.put_u16(key.key_len() as u16); hasher.write(key.key_ref()); buf.put_slice(key.key_ref()); hasher.write_u64(key.ts()); buf.put_u64(key.ts()); hasher.write_u16(value.len() as u16); buf.put_u16(value.len() as u16); buf.put_slice(value); hasher.write(value); // add checksum: week 2 day 7 buf.put_u32(hasher.finalize()); file.write_all(&buf)?; Ok(()) } pub fn sync(&self) -> Result<()> { let file = self.file.lock(); file.sync_all()?; Ok(()) } }