use std::sync::atomic::{AtomicU64, Ordering};
use wdev::Device;
use wrecord::{HEADER_SIZE, MAX_FILLER_BYTES, RecordHeader, RecordMut};
use super::HybridLog;
use crate::error::{Error, Result};
impl<D: Device> HybridLog<D> {
pub fn try_update_in_place(
&self,
addr: u64,
expected_key: &[u8],
new_val: &[u8],
) -> Result<bool> {
self.with_mutable_record(addr, expected_key, false, |rec_mut| {
if rec_mut.is_tombstone() || !rec_mut.can_update_with_slack(new_val.len()) {
return Ok(false);
}
rec_mut.update_value_with_slack(new_val)?;
Ok(true)
})
}
pub fn try_set_tombstone_in_place(&self, addr: u64, expected_key: &[u8]) -> Result<bool> {
self.with_mutable_record(addr, expected_key, false, |rec_mut| {
if rec_mut.is_tombstone() {
return Ok(false);
}
rec_mut.set_tombstone(true);
rec_mut.set_modified(true);
Ok(true)
})
}
pub fn try_modify_record_in_place<R>(
&self,
addr: u64,
expected_key: &[u8],
f: impl FnOnce(&mut [u8]) -> Option<R>,
) -> Result<Option<R>> {
self.with_mutable_record(addr, expected_key, None, |rec_mut| {
if rec_mut.is_tombstone() {
return Ok(None);
}
let r = f(rec_mut.value_mut());
if r.is_some() {
rec_mut.set_modified(true);
}
Ok(r)
})
}
pub fn try_revivify_in_chain(
&self,
addr: u64,
expected_key: &[u8],
new_val: &[u8],
) -> Result<bool> {
self.with_mutable_record(addr, expected_key, false, |rec_mut| {
if !rec_mut.is_tombstone()
|| new_val.len() > rec_mut.val_capacity()
|| (rec_mut.val_capacity() - new_val.len()) > MAX_FILLER_BYTES
{
return Ok(false);
}
rec_mut.revivify_with_slack(new_val)?;
Ok(true)
})
}
pub fn revivify_record_at(
&self,
addr: u64,
slot_size: usize,
key: &[u8],
val: &[u8],
prev_addr: u64,
is_tombstone: bool,
) -> Result<()> {
if !self.addresses.is_mutable(addr) {
return Err(Error::AddressOutOfRange {
addr,
begin: self.addresses.begin(),
tail: self.addresses.tail(),
});
}
let p = super::RecParams {
prev_addr,
key,
val,
is_tombstone,
};
let rec_size = self.validate_append_args(&p)?;
if slot_size < rec_size {
return Err(Error::RecordTooLarge {
size: rec_size,
page_size: slot_size,
});
}
let page_id = self.config.page_id(addr);
let offset = self.config.page_offset(addr);
if !self.buffer.is_page_loaded(page_id) {
return Err(Error::PageNotReady(page_id));
}
let mut guard = self.buffer.write_page(page_id);
if !self.addresses.is_mutable(addr) || !self.buffer.is_page_loaded(page_id) {
return Err(Error::AddressOutOfRange {
addr,
begin: self.addresses.begin(),
tail: self.addresses.tail(),
});
}
let Some(slot_buf) = guard.get_mut(offset..offset + slot_size) else {
return Err(Error::RecordTooLarge {
size: slot_size,
page_size: self.config.page_size,
});
};
let mut header =
RecordHeader::new(prev_addr, key.len() as u32, val.len() as u32, is_tombstone)?;
let kv_end = HEADER_SIZE + key.len() + val.len();
let remaining = slot_size - rec_size;
unsafe { &*(slot_buf.as_ptr() as *const AtomicU64) }
.store(header.prev_address, Ordering::Release);
slot_buf[HEADER_SIZE..HEADER_SIZE + key.len()].copy_from_slice(key);
slot_buf[HEADER_SIZE + key.len()..kv_end].copy_from_slice(val);
slot_buf[kv_end..rec_size].fill(0);
if remaining >= HEADER_SIZE {
let pad_header = RecordHeader::pad(remaining);
slot_buf[rec_size..rec_size + HEADER_SIZE].copy_from_slice(&pad_header.to_bytes());
} else if remaining > 0 {
header.set_filler_bytes(remaining);
slot_buf[rec_size..].fill(0);
}
unsafe { &*(slot_buf.as_ptr().wrapping_add(8) as *const AtomicU64) }
.store(header.rdh_word, Ordering::Release);
Ok(())
}
fn with_mutable_record<T>(
&self,
addr: u64,
expected_key: &[u8],
degraded: T,
f: impl FnOnce(&mut RecordMut<'_>) -> Result<T>,
) -> Result<T> {
let ro = self.addresses.read_only();
if addr < ro || addr >= self.addresses.tail() {
return Ok(degraded);
}
let page_id = self.config.page_id(addr);
let offset = self.config.page_offset(addr);
if offset + HEADER_SIZE > self.config.page_size || !self.buffer.is_page_loaded(page_id) {
return Ok(degraded);
}
let mut guard = self.buffer.write_page(page_id);
if addr < self.addresses.read_only() || !self.buffer.is_page_loaded(page_id) {
return Ok(degraded);
}
let mut rec_mut = match RecordMut::from_slice_mut(&mut guard[offset..]) {
Ok(r) => r,
Err(_) => return Ok(degraded),
};
if !rec_mut.matches_key(expected_key) {
return Ok(degraded);
}
f(&mut rec_mut)
}
}