Skip to content

Commit

Permalink
Fancier stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
mkeeter committed Oct 19, 2023
1 parent 8091af0 commit 06badc8
Showing 1 changed file with 50 additions and 23 deletions.
73 changes: 50 additions & 23 deletions downstairs/src/extent_inner_raw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ pub struct RawInner {
/// Is the `A` or `B` context slot active, on a per-block basis?
active_context: Vec<ContextSlot>,

/// Do the `A` and `B` blocks contain identical data, on a per-block basis?
context_matching: Vec<bool>,

/// Local cache for the `dirty` value
///
/// This allows us to only write the flag when the value changes
Expand Down Expand Up @@ -535,12 +538,10 @@ impl RawInner {
extent_number,
active_context: vec![
ContextSlot::A; // both slots are empty, so this is fine
def.extent_size().value as usize
],
context_slot_synched_at: vec![
[0, 0];
def.extent_size().value as usize
bcount as usize
],
context_matching: vec![true; bcount as usize],
context_slot_synched_at: vec![[0, 0]; bcount as usize],
sync_index: 0,
extra_syscall_count: 0,
extra_syscall_denominator: 0,
Expand Down Expand Up @@ -652,18 +653,19 @@ impl RawInner {
file.seek(SeekFrom::Start(0))?;
let mut file_buffered = BufReader::with_capacity(64 * 1024, &file);
let mut active_context = vec![];
let mut context_matching = vec![];
let mut buf = vec![0; extent_size.block_size_in_bytes() as usize];
let mut last_seek_block = 0;
for (block, (context_a, context_b)) in context_arrays[0]
.iter()
.zip(context_arrays[1].iter())
.enumerate()
{
let slot = if context_a == context_b {
let (slot, synched) = if context_a == context_b {
// If both slots are identical, then either they're both None or
// we have defragmented recently (which copies the active slot
// to the inactive one). That makes life easy!
ContextSlot::A
(ContextSlot::A, true)
} else {
// Otherwise, we have to compute hashes from the file.
if block != last_seek_block {
Expand All @@ -690,16 +692,21 @@ impl RawInner {
}
}

matching_slot.or(empty_slot).ok_or(CrucibleError::IoError(
format!("open: no slot found for {block}"),
))?
let slot = matching_slot.or(empty_slot).ok_or(
CrucibleError::IoError(format!(
"open: no slot found for {block}"
)),
)?;
(slot, false)
};
active_context.push(slot);
context_matching.push(synched);
}

let mut out = Self {
file,
active_context,
context_matching,
dirty,
extent_number,
extent_size: def.extent_size(),
Expand Down Expand Up @@ -781,9 +788,15 @@ impl RawInner {
CrucibleError::IoError(format!("no slot found for {block}")),
)?;
self.active_context[block as usize] = value;
self.context_matching[block as usize] = false; // TODO
Ok(())
}

/// Writes a set of block contexts to the file
///
/// This does not change `self.active_context`, because that represents
/// which context matches file data; if we fail before writing file data,
/// then `self.active_context` should stay pointing at the previous context.
fn set_block_contexts(
&mut self,
block_contexts: &[DownstairsBlockContext],
Expand Down Expand Up @@ -874,6 +887,11 @@ impl RawInner {
on_disk_hash: block_context.on_disk_hash,
};
bincode::serialize_into(&mut buf[n..], &Some(d))?;

// Pre-emptively mark that the contexts no longer match; even if
// the write fails, this is harmless (it would just lead to a
// little extra work during defragment)
self.context_matching[block_context.block as usize] = false;
}
let offset = self.context_slot_offset(block_start, slot);
nix::sys::uio::pwrite(self.file.as_raw_fd(), &buf, offset as i64)
Expand Down Expand Up @@ -1040,30 +1058,28 @@ impl RawInner {
// This can be inefficient, because it means that a write would have to
// be split into updating multiple regions (instead of a single
// contiguous write). As such, if the context slots disagree, we
// "defragment" them:
//
// - Figure out whether A or B is more popular
// - Copy context data from the less-popular slot to the more-popular
//
// In the example above, `A` is more popular so we would perform the
// following copy:
// "defragment" them, copying from A to B and vice versa.
//
// block | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ... |
// context | A | A | ^ | ^ | ^ | ^ | A | A | A | ... | [A array]
// | | | B | B | B | B | | | | ... | [B array]
// | v | v | B | B | B | B | v | v | v | ... | [B array]
//
// This is safe because it occurs immediately after a flush, so we are
// dead certain that the active context matches file contents. This
// means that we can safely overwrite the old (inactive) context.
//
// We track the number of A vs B slots, as well as the range covered by
// the slots. It's that range that we'll need to read + write, so we
// want to pick whichever slot does less work.
// After this is done, all items in `active_context` point to
// either ContextSlot::A or B (depending on which side did less work),
// and `context_matching` is all `true`
let mut buf = vec![];
for (slot, mut group) in (0..self.active_context.len())
.group_by(|i| self.active_context[*i])
let mut slots_copied = [0; 2];
for ((slot, matching), mut group) in (0..self.active_context.len())
.group_by(|&i| (self.active_context[i], self.context_matching[i]))
.into_iter()
{
if matching {
continue;
}
let start_block = group.next().unwrap() as u64;
let count = group.count() + 1;
buf.resize(count * BLOCK_CONTEXT_SLOT_SIZE_BYTES as usize, 0u8);
Expand All @@ -1079,7 +1095,18 @@ impl RawInner {
self.context_slot_offset(start_block, !slot) as i64,
)
.map_err(|e| CrucibleError::IoError(e.to_string()))?;
for block in start_block..(start_block + count as u64) {
self.context_slot_synched_at[block as usize][!slot as usize] =
self.sync_index + 1;
}
slots_copied[slot as usize] += count;
}
if slots_copied[0] > slots_copied[1] {
self.active_context.fill(ContextSlot::A);
} else {
self.active_context.fill(ContextSlot::B);
}
self.context_matching.fill(true);
Ok(())
}
}
Expand Down

0 comments on commit 06badc8

Please sign in to comment.