Skip to content

Commit

Permalink
enhancement: add consistency check before updates
Browse files Browse the repository at this point in the history
  • Loading branch information
drystone committed Nov 7, 2024
1 parent 06e9e56 commit 12abac5
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 5 deletions.
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use std::{fmt::Display, fmt::Formatter, io};
pub enum Error {
/// IO error.
Io(io::Error),
/// Database may be inconsistent since an earlier error. Writes are disabled.
Inconsistent,
/// Bucket has too many elements or bucket bits > directory bits.
BadBucket {
/// Bucket file offset.
Expand Down
57 changes: 52 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,20 @@ pub enum ExportBinMode {
Exp64,
}

#[derive(Copy, Clone, Debug, Default, PartialEq)]
enum WriteState {
#[default]
Clean,
Dirty,
Inconsistent,
}

#[derive(Copy, Clone, Debug, Default)]
pub struct ReadOnly;
#[derive(Copy, Clone, Debug, Default)]
pub struct ReadWrite {
sync: bool,
state: WriteState,
}

pub trait CacheBucket {
Expand Down Expand Up @@ -458,6 +467,7 @@ impl Gdbm<ReadWrite> {
bucket_cache,
read_write: ReadWrite {
sync: open_options.write.sync,
state: WriteState::Dirty,
},
};

Expand Down Expand Up @@ -646,19 +656,29 @@ impl Gdbm<ReadWrite> {

// write out any cached, not-yet-written metadata and data to storage
fn write_dirty(&mut self) -> io::Result<()> {
self.read_write.state = WriteState::Inconsistent;

self.write_buckets()?;
self.write_dir()?;
self.write_header()?;

self.read_write.state = WriteState::Clean;

Ok(())
}

// API: ensure database is flushed to stable storage
pub fn sync(&mut self) -> Result<()> {
self.header.increment_numsync();
self.write_dirty()
.and_then(|_| self.f.sync_data())
.map_err(Error::Io)
match self.read_write.state {
WriteState::Clean => Ok(()),
WriteState::Inconsistent => Err(Error::Inconsistent),
WriteState::Dirty => {
self.header.increment_numsync();
self.write_dirty()
.and_then(|_| self.f.sync_data())
.map_err(Error::Io)
}
}
}

fn int_remove(&mut self, key: &[u8]) -> Result<Option<Vec<u8>>> {
Expand All @@ -667,6 +687,13 @@ impl Gdbm<ReadWrite> {
if get_opt.is_none() {
return Ok(None);
}

if self.read_write.state == WriteState::Inconsistent {
return Err(Error::Inconsistent);
}

self.read_write.state = WriteState::Inconsistent;

let (elem_ofs, data) = get_opt.unwrap();

let elem = self
Expand All @@ -678,6 +705,8 @@ impl Gdbm<ReadWrite> {
// release record bytes to available-space pool
self.free_record(elem.data_ofs, elem.key_size + elem.data_size)?;

self.read_write.state = WriteState::Dirty;

Ok(Some(data))
}

Expand Down Expand Up @@ -718,6 +747,12 @@ impl Gdbm<ReadWrite> {
}

fn int_insert(&mut self, key: Vec<u8>, data: Vec<u8>) -> Result<()> {
if self.read_write.state == WriteState::Inconsistent {
return Err(Error::Inconsistent);
}

self.read_write.state = WriteState::Inconsistent;

let offset = self.allocate_record((key.len() + data.len()) as u32)?;

self.f
Expand All @@ -738,6 +773,8 @@ impl Gdbm<ReadWrite> {
.unwrap()
.insert(bucket_elem);

self.read_write.state = WriteState::Dirty;

Ok(())
}

Expand Down Expand Up @@ -842,11 +879,21 @@ impl Gdbm<ReadWrite> {

// API: convert
pub fn convert(&mut self, options: &ConvertOptions) -> Result<()> {
if self.read_write.state == WriteState::Inconsistent {
return Err(Error::Inconsistent);
}

self.read_write.state = WriteState::Inconsistent;

self.header
.convert_numsync(options.numsync)
.into_iter()
.try_for_each(|(offset, length)| self.free_record(offset, length))
.map_err(Error::Io)
.map_err(Error::Io)?;

self.read_write.state = WriteState::Dirty;

Ok(())
}
}

Expand Down

0 comments on commit 12abac5

Please sign in to comment.