From f47c66eed8ce90cefdec1f98f7cbc4c4c466d7be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Fri, 16 Jun 2023 18:47:56 +0200 Subject: [PATCH 01/34] Add function that can be called to free space --- src/lib.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 01049af..6530e8f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -584,9 +584,18 @@ where log::trace!("Storage::find_new_object_location({ty:?}, {len})"); // find block with most free space - let block = self + let block = match self .blocks - .find_alloc_block(ty, M::align(ObjectHeader::byte_count::()) + len)?; + .find_alloc_block(ty, M::align(ObjectHeader::byte_count::()) + len) + { + Ok(block) => block, + Err(StorageError::InsufficientSpace) => { + self.try_to_free_space(ty, len).await?; + self.blocks + .find_alloc_block(ty, M::align(ObjectHeader::byte_count::()) + len)? + } + Err(e) => return Err(e), + }; if self.blocks.blocks[block].header.kind() == BlockHeaderKind::Known(BlockType::Undefined) { BlockOps::new(&mut self.medium) @@ -604,6 +613,11 @@ where Ok(location) } + + async fn try_to_free_space(&mut self, _ty: BlockType, _len: usize) -> Result<(), StorageError> { + // TODO + Err(StorageError::InsufficientSpace) + } } fn hash_path(path: &str) -> u32 { From e47a73688699e3882f7b5ae93ce13910f11ab246 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Sat, 17 Jun 2023 20:36:47 +0200 Subject: [PATCH 02/34] Select erase target --- src/lib.rs | 31 +++++++++++++++++++++++++++++-- src/ll/blocks.rs | 17 +++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6530e8f..41d0fdf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -614,8 +614,35 @@ where Ok(location) } - async fn try_to_free_space(&mut self, _ty: BlockType, _len: usize) -> Result<(), StorageError> { - // TODO + async fn try_to_free_space(&mut self, ty: BlockType, len: usize) -> Result<(), StorageError> { + let mut ops = BlockOps::new(&mut self.medium); + + let mut target_block = None::; + + // Select block with enough freeable space and minimum erase counter + for (block_idx, info) in self + .blocks + .iter() + .enumerate() + .filter(|(_, block)| block.header.kind() == BlockHeaderKind::Known(ty)) + { + if ops.calculate_freeable_space(block_idx).await? > len { + match target_block { + Some(idx) => { + if info.header.erase_count() < self.blocks[idx].header.erase_count() { + target_block = Some(block_idx); + } + } + + None => target_block = Some(block_idx), + } + } + } + + if let Some(target) = target_block { + // TODO + } + Err(StorageError::InsufficientSpace) } } diff --git a/src/ll/blocks.rs b/src/ll/blocks.rs index 5ce5ce8..27c961b 100644 --- a/src/ll/blocks.rs +++ b/src/ll/blocks.rs @@ -297,6 +297,23 @@ impl<'a, M: StorageMedium> BlockOps<'a, M> { Ok(true) } + pub async fn calculate_freeable_space(&mut self, block: usize) -> Result { + let mut iter = ObjectIterator::new::(block); + + let mut deleted = 0; + + while let Some(object) = iter.next(self.medium).await? { + match object.state() { + ObjectState::Allocated | ObjectState::Deleted => deleted += object.total_size(), + ObjectState::Free | ObjectState::Finalized => {} + } + } + + let free_space = M::BLOCK_SIZE - iter.current_offset(); + + Ok(free_space + deleted) + } + pub async fn read_header(&mut self, block: usize) -> Result, StorageError> { BlockHeader::read(self.medium, block).await } From 08b2f672ad6d60e2cd04dcbf2364ae013d39c9e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Sat, 17 Jun 2023 21:48:09 +0200 Subject: [PATCH 03/34] Save space info, only run object moving when necessary --- src/lib.rs | 35 +++++++++++++++++++++-------------- src/ll/blocks.rs | 8 ++++++++ 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 41d0fdf..469d2f5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,7 +66,7 @@ where // Try to find a used block with enough free space if let Some(block) = self.blocks.iter().position(|info| { - info.header.kind() == BlockHeaderKind::Known(ty) + info.kind() == BlockHeaderKind::Known(ty) && !info.is_empty() && info.free_space() >= min_free }) { @@ -79,10 +79,10 @@ where .iter() .enumerate() .filter(|(_, info)| { - info.header.kind() == BlockHeaderKind::Known(BlockType::Undefined) + info.kind() == BlockHeaderKind::Known(BlockType::Undefined) && info.free_space() >= min_free }) - .min_by_key(|(_, info)| info.header.erase_count()) + .min_by_key(|(_, info)| info.erase_count()) { return Ok(block); } @@ -270,7 +270,7 @@ where let mut used_bytes = 0; for (block_idx, info) in self.blocks.blocks.iter().enumerate() { - match info.header.kind() { + match info.kind() { BlockHeaderKind::Empty => {} BlockHeaderKind::Known(BlockType::Undefined) | BlockHeaderKind::Unknown => { used_bytes += info.used_bytes(); @@ -597,7 +597,7 @@ where Err(e) => return Err(e), }; - if self.blocks.blocks[block].header.kind() == BlockHeaderKind::Known(BlockType::Undefined) { + if self.blocks.blocks[block].kind() == BlockHeaderKind::Known(BlockType::Undefined) { BlockOps::new(&mut self.medium) .set_block_type(block, ty) .await?; @@ -617,30 +617,37 @@ where async fn try_to_free_space(&mut self, ty: BlockType, len: usize) -> Result<(), StorageError> { let mut ops = BlockOps::new(&mut self.medium); - let mut target_block = None::; + let mut target_block = None::<(usize, usize)>; // Select block with enough freeable space and minimum erase counter for (block_idx, info) in self + .blocks .blocks .iter() .enumerate() - .filter(|(_, block)| block.header.kind() == BlockHeaderKind::Known(ty)) + .filter(|(_, block)| block.kind() == BlockHeaderKind::Known(ty)) { - if ops.calculate_freeable_space(block_idx).await? > len { + let freeable = ops.calculate_freeable_space(block_idx).await?; + + if freeable > len { match target_block { - Some(idx) => { - if info.header.erase_count() < self.blocks[idx].header.erase_count() { - target_block = Some(block_idx); + Some((idx, _)) => { + if info.erase_count() < self.blocks.blocks[idx].erase_count() { + target_block = Some((block_idx, freeable)); } } - None => target_block = Some(block_idx), + None => target_block = Some((block_idx, freeable)), } } } - if let Some(target) = target_block { - // TODO + if let Some((target, freeable)) = target_block { + if freeable != self.blocks.blocks[target].used_bytes() { + // TODO move objects out of target block + } + + ops.format_block(target).await?; } Err(StorageError::InsufficientSpace) diff --git a/src/ll/blocks.rs b/src/ll/blocks.rs index 27c961b..8ea58c6 100644 --- a/src/ll/blocks.rs +++ b/src/ll/blocks.rs @@ -261,6 +261,14 @@ impl BlockInfo { pub fn used_bytes(&self) -> usize { self.used_bytes } + + pub fn kind(&self) -> BlockHeaderKind { + self.header.kind() + } + + pub fn erase_count(&self) -> u32 { + self.header.erase_count + } } pub(crate) struct BlockOps<'a, M> { From c62c2bb2f83b3befa08136e015d1d23902c9cb71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Sat, 17 Jun 2023 21:49:18 +0200 Subject: [PATCH 04/34] Account for block erase --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 469d2f5..33c0481 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -648,6 +648,7 @@ where } ops.format_block(target).await?; + self.blocks[target].update_stats_after_erase(); } Err(StorageError::InsufficientSpace) From bd86b763d29753d638815e9f4237cafb13b0ae2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Sat, 17 Jun 2023 21:53:19 +0200 Subject: [PATCH 05/34] Keep blocks for GC --- src/lib.rs | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 33c0481..cf7c418 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,18 +73,27 @@ where return Ok(block); } - // Pick a free block. Prioritize lesser used blocks. - if let Some((block, _)) = self + // We reserve 2 blocks for GC. + if self .blocks .iter() - .enumerate() - .filter(|(_, info)| { - info.kind() == BlockHeaderKind::Known(BlockType::Undefined) - && info.free_space() >= min_free - }) - .min_by_key(|(_, info)| info.erase_count()) + .filter(|info| info.kind() == BlockHeaderKind::Known(BlockType::Undefined)) + .count() + > 2 { - return Ok(block); + // Pick a free block. Prioritize lesser used blocks. + if let Some((block, _)) = self + .blocks + .iter() + .enumerate() + .filter(|(_, info)| { + info.kind() == BlockHeaderKind::Known(BlockType::Undefined) + && info.free_space() >= min_free + }) + .min_by_key(|(_, info)| info.erase_count()) + { + return Ok(block); + } } // No block found From f1be6220df799e1849c1abc518b9dac61af8d71b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Sat, 17 Jun 2023 21:56:35 +0200 Subject: [PATCH 06/34] Simplify, free space for whole object not just payload --- src/lib.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index cf7c418..dac2991 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -593,15 +593,12 @@ where log::trace!("Storage::find_new_object_location({ty:?}, {len})"); // find block with most free space - let block = match self - .blocks - .find_alloc_block(ty, M::align(ObjectHeader::byte_count::()) + len) - { + let object_size = M::align(ObjectHeader::byte_count::()) + len; + let block = match self.blocks.find_alloc_block(ty, object_size) { Ok(block) => block, Err(StorageError::InsufficientSpace) => { - self.try_to_free_space(ty, len).await?; - self.blocks - .find_alloc_block(ty, M::align(ObjectHeader::byte_count::()) + len)? + self.try_to_free_space(ty, object_size).await?; + self.blocks.find_alloc_block(ty, object_size)? } Err(e) => return Err(e), }; @@ -657,7 +654,7 @@ where } ops.format_block(target).await?; - self.blocks[target].update_stats_after_erase(); + self.blocks.blocks[target].update_stats_after_erase(); } Err(StorageError::InsufficientSpace) From 7d2a4d1ba2771540a17551a6a0f7f76746c1a011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 19 Jun 2023 09:59:19 +0200 Subject: [PATCH 07/34] Extract block search --- src/lib.rs | 72 +++++++++++++++++++++++++++++------------------- src/ll/blocks.rs | 42 ++++++++++++++++------------ 2 files changed, 69 insertions(+), 45 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index dac2991..6b18156 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ use crate::{ diag::Counters, ll::{ - blocks::{BlockHeaderKind, BlockInfo, BlockOps, BlockType}, + blocks::{BlockHeaderKind, BlockInfo, BlockOps, BlockType, IndexedBlockInfo}, objects::{ MetadataObjectHeader, ObjectHeader, ObjectIterator, ObjectLocation, ObjectReader, ObjectState, ObjectType, ObjectWriter, @@ -99,6 +99,42 @@ where // No block found Err(StorageError::InsufficientSpace) } + + pub(crate) async fn find_block_to_free( + &mut self, + ty: BlockType, + len: usize, + medium: &mut M, + ) -> Result, StorageError> { + let mut target_block = None::<(usize, usize)>; + + // Select block with enough freeable space and minimum erase counter + for (block_idx, info) in self + .blocks + .iter() + .copied() + .enumerate() + .filter(|(_, block)| block.kind() == BlockHeaderKind::Known(ty)) + { + let freeable = IndexedBlockInfo(block_idx, info) + .calculate_freeable_space(medium) + .await?; + + if freeable > len { + match target_block { + Some((idx, _)) => { + if info.erase_count() < self.blocks[idx].erase_count() { + target_block = Some((block_idx, freeable)); + } + } + + None => target_block = Some((block_idx, freeable)), + } + } + } + + Ok(target_block) + } } /// A mounted storage partition. @@ -621,39 +657,19 @@ where } async fn try_to_free_space(&mut self, ty: BlockType, len: usize) -> Result<(), StorageError> { - let mut ops = BlockOps::new(&mut self.medium); - - let mut target_block = None::<(usize, usize)>; - - // Select block with enough freeable space and minimum erase counter - for (block_idx, info) in self + if let Some((target, freeable)) = self .blocks - .blocks - .iter() - .enumerate() - .filter(|(_, block)| block.kind() == BlockHeaderKind::Known(ty)) + .find_block_to_free(ty, len, &mut self.medium) + .await? { - let freeable = ops.calculate_freeable_space(block_idx).await?; - - if freeable > len { - match target_block { - Some((idx, _)) => { - if info.erase_count() < self.blocks.blocks[idx].erase_count() { - target_block = Some((block_idx, freeable)); - } - } - - None => target_block = Some((block_idx, freeable)), - } - } - } - - if let Some((target, freeable)) = target_block { if freeable != self.blocks.blocks[target].used_bytes() { // TODO move objects out of target block + if ty == BlockType::Data { + // TODO when moving a data object, update the file metadata + } } - ops.format_block(target).await?; + BlockOps::new(&mut self.medium).format_block(target).await?; self.blocks.blocks[target].update_stats_after_erase(); } diff --git a/src/ll/blocks.rs b/src/ll/blocks.rs index 8ea58c6..ece3826 100644 --- a/src/ll/blocks.rs +++ b/src/ll/blocks.rs @@ -271,6 +271,31 @@ impl BlockInfo { } } +pub struct IndexedBlockInfo(pub usize, pub BlockInfo); +impl IndexedBlockInfo { + pub async fn calculate_freeable_space( + &mut self, + medium: &mut M, + ) -> Result { + let Self(block, _info) = self; + + let mut iter = ObjectIterator::new::(*block); + + let mut deleted = 0; + + while let Some(object) = iter.next(medium).await? { + match object.state() { + ObjectState::Allocated | ObjectState::Deleted => deleted += object.total_size(), + ObjectState::Free | ObjectState::Finalized => {} + } + } + + let free_space = M::BLOCK_SIZE - iter.current_offset(); + + Ok(free_space + deleted) + } +} + pub(crate) struct BlockOps<'a, M> { medium: &'a mut M, } @@ -305,23 +330,6 @@ impl<'a, M: StorageMedium> BlockOps<'a, M> { Ok(true) } - pub async fn calculate_freeable_space(&mut self, block: usize) -> Result { - let mut iter = ObjectIterator::new::(block); - - let mut deleted = 0; - - while let Some(object) = iter.next(self.medium).await? { - match object.state() { - ObjectState::Allocated | ObjectState::Deleted => deleted += object.total_size(), - ObjectState::Free | ObjectState::Finalized => {} - } - } - - let free_space = M::BLOCK_SIZE - iter.current_offset(); - - Ok(free_space + deleted) - } - pub async fn read_header(&mut self, block: usize) -> Result, StorageError> { BlockHeader::read(self.medium, block).await } From 22dac338958cf1549e8c929eb1953ab036b526e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 19 Jun 2023 10:08:44 +0200 Subject: [PATCH 08/34] Clean up block type comparisons --- src/lib.rs | 21 +++++++++------------ src/ll/blocks.rs | 8 ++++++++ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6b18156..1789d66 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,11 +65,11 @@ where log::trace!("Storage::find_alloc_block({ty:?}, {min_free})"); // Try to find a used block with enough free space - if let Some(block) = self.blocks.iter().position(|info| { - info.kind() == BlockHeaderKind::Known(ty) - && !info.is_empty() - && info.free_space() >= min_free - }) { + if let Some(block) = self + .blocks + .iter() + .position(|info| info.is_type(ty) && !info.is_empty() && info.free_space() >= min_free) + { return Ok(block); } @@ -77,7 +77,7 @@ where if self .blocks .iter() - .filter(|info| info.kind() == BlockHeaderKind::Known(BlockType::Undefined)) + .filter(|info| info.is_type(BlockType::Undefined)) .count() > 2 { @@ -86,10 +86,7 @@ where .blocks .iter() .enumerate() - .filter(|(_, info)| { - info.kind() == BlockHeaderKind::Known(BlockType::Undefined) - && info.free_space() >= min_free - }) + .filter(|(_, info)| info.is_unassigned() && info.free_space() >= min_free) .min_by_key(|(_, info)| info.erase_count()) { return Ok(block); @@ -114,7 +111,7 @@ where .iter() .copied() .enumerate() - .filter(|(_, block)| block.kind() == BlockHeaderKind::Known(ty)) + .filter(|(_, block)| block.is_type(ty)) { let freeable = IndexedBlockInfo(block_idx, info) .calculate_freeable_space(medium) @@ -639,7 +636,7 @@ where Err(e) => return Err(e), }; - if self.blocks.blocks[block].kind() == BlockHeaderKind::Known(BlockType::Undefined) { + if self.blocks.blocks[block].is_unassigned() { BlockOps::new(&mut self.medium) .set_block_type(block, ty) .await?; diff --git a/src/ll/blocks.rs b/src/ll/blocks.rs index ece3826..a72125b 100644 --- a/src/ll/blocks.rs +++ b/src/ll/blocks.rs @@ -266,6 +266,14 @@ impl BlockInfo { self.header.kind() } + pub fn is_type(&self, ty: BlockType) -> bool { + self.header.kind() == BlockHeaderKind::Known(ty) + } + + pub fn is_unassigned(&self) -> bool { + self.is_type(BlockType::Undefined) + } + pub fn erase_count(&self) -> u32 { self.header.erase_count } From 1ff504f6553990a40b2c2a47e809bf28a41c465f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 19 Jun 2023 10:21:25 +0200 Subject: [PATCH 09/34] Allow allocating objects in GC blocks --- src/lib.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 1789d66..ab266fc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,6 +64,17 @@ where fn find_alloc_block(&self, ty: BlockType, min_free: usize) -> Result { log::trace!("Storage::find_alloc_block({ty:?}, {min_free})"); + self.find_alloc_block_impl(ty, min_free, false) + } + + fn find_alloc_block_impl( + &self, + ty: BlockType, + min_free: usize, + allow_gc_block: bool, + ) -> Result { + log::trace!("Storage::find_alloc_block({ty:?}, {min_free})"); + // Try to find a used block with enough free space if let Some(block) = self .blocks @@ -80,6 +91,7 @@ where .filter(|info| info.is_type(BlockType::Undefined)) .count() > 2 + || allow_gc_block { // Pick a free block. Prioritize lesser used blocks. if let Some((block, _)) = self @@ -660,10 +672,20 @@ where .await? { if freeable != self.blocks.blocks[target].used_bytes() { - // TODO move objects out of target block + // We need to move objects out of this block + + // TODO: for each object + let copy_target = self + .blocks + .find_alloc_block_impl(ty, freeable, true) + .map_err(|_| StorageError::InsufficientSpace)?; + if ty == BlockType::Data { // TODO when moving a data object, update the file metadata } + + // Can not move object(s) + return Err(StorageError::InsufficientSpace); } BlockOps::new(&mut self.medium).format_block(target).await?; From 874e8832793f172b33deb774e6f5aa00961d1106 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 19 Jun 2023 10:43:43 +0200 Subject: [PATCH 10/34] Implement moving metadata objects --- src/lib.rs | 49 ++++++++++++++++++++++++++++++++++------------- src/ll/objects.rs | 18 +++++++++++++++++ 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ab266fc..c0a8ed6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -666,33 +666,56 @@ where } async fn try_to_free_space(&mut self, ty: BlockType, len: usize) -> Result<(), StorageError> { - if let Some((target, freeable)) = self + let Some((block_to_free, freeable)) = self .blocks .find_block_to_free(ty, len, &mut self.medium) .await? - { - if freeable != self.blocks.blocks[target].used_bytes() { - // We need to move objects out of this block + else { + return Err(StorageError::InsufficientSpace); + }; + + if freeable != self.blocks.blocks[block_to_free].used_bytes() { + // We need to move objects out of this block + let mut iter = ObjectIterator::new::(block_to_free); + + while let Some(object) = iter.next(&mut self.medium).await? { + match object.state() { + ObjectState::Free | ObjectState::Deleted => continue, + ObjectState::Allocated => return Err(StorageError::InsufficientSpace), // TODO: retry in a different object + ObjectState::Finalized => {} + } - // TODO: for each object let copy_target = self .blocks - .find_alloc_block_impl(ty, freeable, true) + .find_alloc_block_impl(ty, object.total_size(), true) .map_err(|_| StorageError::InsufficientSpace)?; + let copy_location = ObjectLocation { + block: copy_target, + offset: self.blocks.blocks[copy_target].used_bytes(), + }; if ty == BlockType::Data { // TODO when moving a data object, update the file metadata + // TODO: find metadata object + // TODO: copy metadata object while replacing current object location to + // new location + // TODO: copy object + // TODO: finalize metadata object + // TODO: delete old metadata object + // TODO: delete old object + return Err(StorageError::InsufficientSpace); + } else { + object.move_object(&mut self.medium, copy_location).await?; } - - // Can not move object(s) - return Err(StorageError::InsufficientSpace); } - - BlockOps::new(&mut self.medium).format_block(target).await?; - self.blocks.blocks[target].update_stats_after_erase(); } - Err(StorageError::InsufficientSpace) + BlockOps::new(&mut self.medium) + .format_block(block_to_free) + .await?; + self.blocks.blocks[block_to_free].update_stats_after_erase(); + + Ok(()) } } diff --git a/src/ll/objects.rs b/src/ll/objects.rs index 51cdbd2..783ad50 100644 --- a/src/ll/objects.rs +++ b/src/ll/objects.rs @@ -764,6 +764,24 @@ impl ObjectInfo { Ok(Some(Self::with_header(header))) } + + pub async fn move_object( + mut self, + medium: &mut M, + dst: ObjectLocation, + ) -> Result<(), StorageError> { + let mut source = ObjectReader::new(self.location(), medium, false).await?; + let mut target = ObjectWriter::allocate(dst, self.header.object_type()?, medium).await?; + + let mut buffer = [0; 16]; + while source.remaining() > 0 { + let read_size = source.read(medium, &mut buffer).await?; + target.write(medium, &buffer[0..read_size]).await?; + } + + target.finalize(medium).await?; + self.header.update_state(medium, ObjectState::Deleted).await + } } pub struct ObjectIterator { From eb90b0e6830f7d4d1939c23a751a3f1ca3c142b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 19 Jun 2023 11:16:40 +0200 Subject: [PATCH 11/34] Add a function to copy an object --- src/lib.rs | 45 ++++++++++++++++++++++++++++++++++----------- src/ll/objects.rs | 22 ++++++++++++++++++---- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c0a8ed6..4bfb9c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,8 +9,8 @@ use crate::{ ll::{ blocks::{BlockHeaderKind, BlockInfo, BlockOps, BlockType, IndexedBlockInfo}, objects::{ - MetadataObjectHeader, ObjectHeader, ObjectIterator, ObjectLocation, ObjectReader, - ObjectState, ObjectType, ObjectWriter, + MetadataObjectHeader, ObjectHeader, ObjectInfo, ObjectIterator, ObjectLocation, + ObjectReader, ObjectState, ObjectType, ObjectWriter, }, }, medium::{StorageMedium, StoragePrivate}, @@ -665,6 +665,36 @@ where Ok(location) } + async fn move_data_object( + &mut self, + object: ObjectInfo, + destination: ObjectLocation, + ) -> Result, StorageError> { + let meta = self.find_metadata_of_object(&object).await?; + // let new_meta_location = self + // .find_new_metadata_object_location(meta.total_size()) + // .await?; + + // TODO: copy metadata object while replacing current object location to + // new location + + // TODO: copy object + let copied = object.copy_object(&mut self.medium, destination).await?; + // TODO: finalize metadata object + // TODO: delete old metadata object + // TODO: delete old object + object.delete(&mut self.medium).await?; + + Ok(copied) + } + + async fn find_metadata_of_object( + &mut self, + object: &ObjectInfo, + ) -> Result, StorageError> { + todo!() + } + async fn try_to_free_space(&mut self, ty: BlockType, len: usize) -> Result<(), StorageError> { let Some((block_to_free, freeable)) = self .blocks @@ -695,15 +725,8 @@ where }; if ty == BlockType::Data { - // TODO when moving a data object, update the file metadata - // TODO: find metadata object - // TODO: copy metadata object while replacing current object location to - // new location - // TODO: copy object - // TODO: finalize metadata object - // TODO: delete old metadata object - // TODO: delete old object - return Err(StorageError::InsufficientSpace); + // When moving a data object, we need to update the file metadata, too. + self.move_data_object(object, copy_location).await?; } else { object.move_object(&mut self.medium, copy_location).await?; } diff --git a/src/ll/objects.rs b/src/ll/objects.rs index 783ad50..d75e137 100644 --- a/src/ll/objects.rs +++ b/src/ll/objects.rs @@ -765,11 +765,11 @@ impl ObjectInfo { Ok(Some(Self::with_header(header))) } - pub async fn move_object( - mut self, + pub async fn copy_object( + &self, medium: &mut M, dst: ObjectLocation, - ) -> Result<(), StorageError> { + ) -> Result, StorageError> { let mut source = ObjectReader::new(self.location(), medium, false).await?; let mut target = ObjectWriter::allocate(dst, self.header.object_type()?, medium).await?; @@ -779,7 +779,21 @@ impl ObjectInfo { target.write(medium, &buffer[0..read_size]).await?; } - target.finalize(medium).await?; + target.finalize(medium).await + } + + pub async fn move_object( + self, + medium: &mut M, + dst: ObjectLocation, + ) -> Result, StorageError> { + let new = self.copy_object(medium, dst).await?; + self.delete(medium).await?; + + Ok(new) + } + + pub async fn delete(mut self, medium: &mut M) -> Result<(), StorageError> { self.header.update_state(medium, ObjectState::Deleted).await } } From e23e5d8dea15e4ed9fbfa730703b1192f6db76d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 19 Jun 2023 13:41:09 +0200 Subject: [PATCH 12/34] Extract formatting blocks --- src/lib.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4bfb9c5..51b0fe7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -144,6 +144,13 @@ where Ok(target_block) } + + async fn format(&mut self, block_to_free: usize, medium: &mut M) -> Result<(), StorageError> { + BlockOps::new(medium).format_block(block_to_free).await?; + self.blocks[block_to_free].update_stats_after_erase(); + + Ok(()) + } } /// A mounted storage partition. @@ -733,12 +740,7 @@ where } } - BlockOps::new(&mut self.medium) - .format_block(block_to_free) - .await?; - self.blocks.blocks[block_to_free].update_stats_after_erase(); - - Ok(()) + self.blocks.format(block_to_free, &mut self.medium).await } } From a0a4ae1fce405ca52368ad253742bf6dde168b5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 19 Jun 2023 13:44:05 +0200 Subject: [PATCH 13/34] Return ObjectLocation immediately --- src/lib.rs | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 51b0fe7..2cb1d5a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,7 +61,18 @@ where M: StorageMedium, [(); M::BLOCK_COUNT]:, { - fn find_alloc_block(&self, ty: BlockType, min_free: usize) -> Result { + fn alloc_in(&self, block: usize) -> ObjectLocation { + ObjectLocation { + block, + offset: self.blocks[block].used_bytes(), + } + } + + fn find_alloc_block( + &self, + ty: BlockType, + min_free: usize, + ) -> Result { log::trace!("Storage::find_alloc_block({ty:?}, {min_free})"); self.find_alloc_block_impl(ty, min_free, false) @@ -72,7 +83,7 @@ where ty: BlockType, min_free: usize, allow_gc_block: bool, - ) -> Result { + ) -> Result { log::trace!("Storage::find_alloc_block({ty:?}, {min_free})"); // Try to find a used block with enough free space @@ -81,7 +92,7 @@ where .iter() .position(|info| info.is_type(ty) && !info.is_empty() && info.free_space() >= min_free) { - return Ok(block); + return Ok(self.alloc_in(block)); } // We reserve 2 blocks for GC. @@ -101,7 +112,7 @@ where .filter(|(_, info)| info.is_unassigned() && info.free_space() >= min_free) .min_by_key(|(_, info)| info.erase_count()) { - return Ok(block); + return Ok(self.alloc_in(block)); } } @@ -646,7 +657,7 @@ where // find block with most free space let object_size = M::align(ObjectHeader::byte_count::()) + len; - let block = match self.blocks.find_alloc_block(ty, object_size) { + let location = match self.blocks.find_alloc_block(ty, object_size) { Ok(block) => block, Err(StorageError::InsufficientSpace) => { self.try_to_free_space(ty, object_size).await?; @@ -655,18 +666,13 @@ where Err(e) => return Err(e), }; - if self.blocks.blocks[block].is_unassigned() { + if self.blocks.blocks[location.block].is_unassigned() { BlockOps::new(&mut self.medium) - .set_block_type(block, ty) + .set_block_type(location.block, ty) .await?; - self.blocks.blocks[block].header.set_block_type(ty); + self.blocks.blocks[location.block].header.set_block_type(ty); } - let location = ObjectLocation { - block, - offset: self.blocks.blocks[block].used_bytes(), - }; - log::trace!("Storage::find_new_object_location({ty:?}, {len}) -> {location:?}"); Ok(location) @@ -722,14 +728,10 @@ where ObjectState::Finalized => {} } - let copy_target = self + let copy_location = self .blocks .find_alloc_block_impl(ty, object.total_size(), true) .map_err(|_| StorageError::InsufficientSpace)?; - let copy_location = ObjectLocation { - block: copy_target, - offset: self.blocks.blocks[copy_target].used_bytes(), - }; if ty == BlockType::Data { // When moving a data object, we need to update the file metadata, too. From c0512a0a173222dea37eab1b10b53bd339f6d307 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 19 Jun 2023 13:56:49 +0200 Subject: [PATCH 14/34] Work directly with block info --- src/lib.rs | 35 ++++++++++++++++++++++------------- src/ll/blocks.rs | 25 +++++++++++++++++++++---- 2 files changed, 43 insertions(+), 17 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2cb1d5a..f46b261 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -125,30 +125,29 @@ where ty: BlockType, len: usize, medium: &mut M, - ) -> Result, StorageError> { - let mut target_block = None::<(usize, usize)>; + ) -> Result, usize)>, StorageError> { + let mut target_block = None::<(IndexedBlockInfo, usize)>; // Select block with enough freeable space and minimum erase counter - for (block_idx, info) in self + for info in self .blocks .iter() .copied() .enumerate() - .filter(|(_, block)| block.is_type(ty)) + .filter(|(_, info)| info.is_type(ty)) + .map(|(idx, info)| IndexedBlockInfo(idx, info)) { - let freeable = IndexedBlockInfo(block_idx, info) - .calculate_freeable_space(medium) - .await?; + let freeable = info.calculate_freeable_space(medium).await?; if freeable > len { match target_block { Some((idx, _)) => { - if info.erase_count() < self.blocks[idx].erase_count() { - target_block = Some((block_idx, freeable)); + if info.erase_count() < idx.erase_count() { + target_block = Some((info, freeable)); } } - None => target_block = Some((block_idx, freeable)), + None => target_block = Some((info, freeable)), } } } @@ -162,6 +161,14 @@ where Ok(()) } + + async fn format_indexed( + &mut self, + block_to_free: IndexedBlockInfo, + medium: &mut M, + ) -> Result<(), StorageError> { + self.format(block_to_free.0, medium).await + } } /// A mounted storage partition. @@ -717,9 +724,9 @@ where return Err(StorageError::InsufficientSpace); }; - if freeable != self.blocks.blocks[block_to_free].used_bytes() { + if freeable != block_to_free.used_bytes() { // We need to move objects out of this block - let mut iter = ObjectIterator::new::(block_to_free); + let mut iter = block_to_free.objects(); while let Some(object) = iter.next(&mut self.medium).await? { match object.state() { @@ -742,7 +749,9 @@ where } } - self.blocks.format(block_to_free, &mut self.medium).await + self.blocks + .format_indexed(block_to_free, &mut self.medium) + .await } } diff --git a/src/ll/blocks.rs b/src/ll/blocks.rs index a72125b..885110a 100644 --- a/src/ll/blocks.rs +++ b/src/ll/blocks.rs @@ -280,11 +280,16 @@ impl BlockInfo { } pub struct IndexedBlockInfo(pub usize, pub BlockInfo); + +impl Copy for IndexedBlockInfo {} +impl Clone for IndexedBlockInfo { + fn clone(&self) -> Self { + Self(self.0, self.1) + } +} + impl IndexedBlockInfo { - pub async fn calculate_freeable_space( - &mut self, - medium: &mut M, - ) -> Result { + pub async fn calculate_freeable_space(&self, medium: &mut M) -> Result { let Self(block, _info) = self; let mut iter = ObjectIterator::new::(*block); @@ -302,6 +307,18 @@ impl IndexedBlockInfo { Ok(free_space + deleted) } + + pub fn erase_count(&self) -> u32 { + self.1.erase_count() + } + + pub fn used_bytes(&self) -> usize { + self.1.used_bytes() + } + + pub fn objects(&self) -> ObjectIterator { + ObjectIterator::new::(self.0) + } } pub(crate) struct BlockOps<'a, M> { From cded81e46f7f4d7028bf454373731c554280a369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 19 Jun 2023 14:05:05 +0200 Subject: [PATCH 15/34] Assign type to block while freeing space --- src/lib.rs | 70 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f46b261..cf7afb5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,30 +61,22 @@ where M: StorageMedium, [(); M::BLOCK_COUNT]:, { - fn alloc_in(&self, block: usize) -> ObjectLocation { - ObjectLocation { - block, - offset: self.blocks[block].used_bytes(), - } - } - - fn find_alloc_block( - &self, + async fn allocate_new_object( + &mut self, ty: BlockType, min_free: usize, + medium: &mut M, ) -> Result { - log::trace!("Storage::find_alloc_block({ty:?}, {min_free})"); - - self.find_alloc_block_impl(ty, min_free, false) + self.allocate_object(ty, min_free, false, medium).await } - fn find_alloc_block_impl( + fn allocate_object_impl( &self, ty: BlockType, min_free: usize, allow_gc_block: bool, - ) -> Result { - log::trace!("Storage::find_alloc_block({ty:?}, {min_free})"); + ) -> Result { + log::trace!("Storage::allocate_object({ty:?}, {min_free}, {allow_gc_block:?})"); // Try to find a used block with enough free space if let Some(block) = self @@ -92,7 +84,7 @@ where .iter() .position(|info| info.is_type(ty) && !info.is_empty() && info.free_space() >= min_free) { - return Ok(self.alloc_in(block)); + return Ok(block); } // We reserve 2 blocks for GC. @@ -112,7 +104,7 @@ where .filter(|(_, info)| info.is_unassigned() && info.free_space() >= min_free) .min_by_key(|(_, info)| info.erase_count()) { - return Ok(self.alloc_in(block)); + return Ok(block); } } @@ -120,6 +112,30 @@ where Err(StorageError::InsufficientSpace) } + async fn allocate_object( + &mut self, + ty: BlockType, + min_free: usize, + allow_gc_block: bool, + medium: &mut M, + ) -> Result { + let location = self + .allocate_object_impl(ty, min_free, allow_gc_block) + .map(|block| ObjectLocation { + block, + offset: self.blocks[block].used_bytes(), + })?; + + if self.blocks[location.block].is_unassigned() { + BlockOps::new(medium) + .set_block_type(location.block, ty) + .await?; + self.blocks[location.block].header.set_block_type(ty); + } + + Ok(location) + } + pub(crate) async fn find_block_to_free( &mut self, ty: BlockType, @@ -664,22 +680,21 @@ where // find block with most free space let object_size = M::align(ObjectHeader::byte_count::()) + len; - let location = match self.blocks.find_alloc_block(ty, object_size) { + let location = match self + .blocks + .allocate_new_object(ty, object_size, &mut self.medium) + .await + { Ok(block) => block, Err(StorageError::InsufficientSpace) => { self.try_to_free_space(ty, object_size).await?; - self.blocks.find_alloc_block(ty, object_size)? + self.blocks + .allocate_new_object(ty, object_size, &mut self.medium) + .await? } Err(e) => return Err(e), }; - if self.blocks.blocks[location.block].is_unassigned() { - BlockOps::new(&mut self.medium) - .set_block_type(location.block, ty) - .await?; - self.blocks.blocks[location.block].header.set_block_type(ty); - } - log::trace!("Storage::find_new_object_location({ty:?}, {len}) -> {location:?}"); Ok(location) @@ -737,7 +752,8 @@ where let copy_location = self .blocks - .find_alloc_block_impl(ty, object.total_size(), true) + .allocate_object(ty, object.total_size(), true, &mut self.medium) + .await .map_err(|_| StorageError::InsufficientSpace)?; if ty == BlockType::Data { From 1aeac323af281d7e726660831648c665e7a74b48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 19 Jun 2023 16:18:20 +0200 Subject: [PATCH 16/34] Implement metadata lookup --- src/lib.rs | 78 ++++++++++++++++++++++++++---------------------- src/ll/blocks.rs | 23 +++++++++----- 2 files changed, 58 insertions(+), 43 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index cf7afb5..57a3b6c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,6 +70,15 @@ where self.allocate_object(ty, min_free, false, medium).await } + fn blocks(&self, ty: BlockType) -> impl Iterator> + '_ { + self.blocks + .iter() + .copied() + .enumerate() + .filter(move |(_, info)| info.is_type(ty)) + .map(|(idx, info)| IndexedBlockInfo(idx, info)) + } + fn allocate_object_impl( &self, ty: BlockType, @@ -80,31 +89,21 @@ where // Try to find a used block with enough free space if let Some(block) = self - .blocks - .iter() - .position(|info| info.is_type(ty) && !info.is_empty() && info.free_space() >= min_free) + .blocks(ty) + .find(|info| !info.is_empty() && info.free_space() >= min_free) { - return Ok(block); + return Ok(block.0); } // We reserve 2 blocks for GC. - if self - .blocks - .iter() - .filter(|info| info.is_type(BlockType::Undefined)) - .count() - > 2 - || allow_gc_block - { + if allow_gc_block || self.blocks(BlockType::Undefined).count() > 2 { // Pick a free block. Prioritize lesser used blocks. - if let Some((block, _)) = self - .blocks - .iter() - .enumerate() - .filter(|(_, info)| info.is_unassigned() && info.free_space() >= min_free) - .min_by_key(|(_, info)| info.erase_count()) + if let Some(block) = self + .blocks(BlockType::Undefined) + .filter(|info| info.free_space() >= min_free) + .min_by_key(|info| info.erase_count()) { - return Ok(block); + return Ok(block.0); } } @@ -145,26 +144,21 @@ where let mut target_block = None::<(IndexedBlockInfo, usize)>; // Select block with enough freeable space and minimum erase counter - for info in self - .blocks - .iter() - .copied() - .enumerate() - .filter(|(_, info)| info.is_type(ty)) - .map(|(idx, info)| IndexedBlockInfo(idx, info)) - { + for info in self.blocks(ty) { let freeable = info.calculate_freeable_space(medium).await?; - if freeable > len { - match target_block { - Some((idx, _)) => { - if info.erase_count() < idx.erase_count() { - target_block = Some((info, freeable)); - } - } + if freeable <= len { + continue; + } - None => target_block = Some((info, freeable)), + match target_block { + Some((idx, _)) => { + if info.erase_count() < idx.erase_count() { + target_block = Some((info, freeable)); + } } + + None => target_block = Some((info, freeable)), } } @@ -727,7 +721,19 @@ where &mut self, object: &ObjectInfo, ) -> Result, StorageError> { - todo!() + for block in self.blocks.blocks(BlockType::Metadata) { + let mut objects = block.objects(); + while let Some(meta_object) = objects.next(&mut self.medium).await? { + let mut meta = meta_object.read_metadata(&mut self.medium).await?; + while let Some(loc) = meta.next_object_location(&mut self.medium).await? { + if loc == object.location() { + return Ok(meta_object); + } + } + } + } + + Err(StorageError::NotFound) } async fn try_to_free_space(&mut self, ty: BlockType, len: usize) -> Result<(), StorageError> { diff --git a/src/ll/blocks.rs b/src/ll/blocks.rs index 885110a..1fa744c 100644 --- a/src/ll/blocks.rs +++ b/src/ll/blocks.rs @@ -1,4 +1,7 @@ -use core::marker::PhantomData; +use core::{ + marker::PhantomData, + ops::{Deref, DerefMut}, +}; use crate::{ ll::objects::{ObjectIterator, ObjectState}, @@ -308,16 +311,22 @@ impl IndexedBlockInfo { Ok(free_space + deleted) } - pub fn erase_count(&self) -> u32 { - self.1.erase_count() + pub fn objects(&self) -> ObjectIterator { + ObjectIterator::new::(self.0) } +} - pub fn used_bytes(&self) -> usize { - self.1.used_bytes() +impl Deref for IndexedBlockInfo { + type Target = BlockInfo; + + fn deref(&self) -> &Self::Target { + &self.1 } +} - pub fn objects(&self) -> ObjectIterator { - ObjectIterator::new::(self.0) +impl DerefMut for IndexedBlockInfo { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.1 } } From 55ec7b6556211214683d1a7aa18bd4f238ad171a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 19 Jun 2023 16:42:30 +0200 Subject: [PATCH 17/34] Extract ops specific to block types --- src/lib.rs | 209 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 134 insertions(+), 75 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 57a3b6c..c7a546d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,8 @@ #![feature(generic_const_exprs)] // Eww #![allow(incomplete_features)] +use core::fmt::Debug; + use crate::{ diag::Counters, ll::{ @@ -565,16 +567,13 @@ where let est_page_count = 1 + 1; // TODO: guess the number of data pages needed // this is mutable because we can fail mid-writing - let mut file_meta_location = self - .find_new_object_location( - BlockType::Metadata, - est_page_count * M::align(M::object_location_bytes()), - ) + let mut file_meta_location = MetaObject + .find_new_object_location(self, est_page_count * M::align(M::object_location_bytes())) .await?; // Write file name as data object - let filename_location = self - .find_new_object_location(BlockType::Data, path.len()) + let filename_location = DataObject + .find_new_object_location(self, path.len()) .await?; self.write_object(filename_location, path.as_bytes()) @@ -597,7 +596,7 @@ where // Write data objects while !data.is_empty() { // Write file name as data object - let chunk_location = self.find_new_object_location(BlockType::Data, 0).await?; + let chunk_location = DataObject.find_new_object_location(self, 0).await?; let max_chunk_len = self.blocks.blocks[chunk_location.block].free_space() - ObjectHeader::byte_count::(); @@ -616,9 +615,9 @@ where self.blocks.blocks[file_meta_location.block] .add_used_bytes(meta_writer.total_size()); - let new_file_meta_location = self + let new_file_meta_location = MetaObject .find_new_object_location( - BlockType::Metadata, + self, meta_writer.payload_size() + M::object_location_bytes(), ) .await?; @@ -665,81 +664,85 @@ where Ok(()) } - async fn find_new_object_location( + async fn find_metadata_of_object( &mut self, - ty: BlockType, + object: &ObjectInfo, + ) -> Result, StorageError> { + for block in self.blocks.blocks(BlockType::Metadata) { + let mut objects = block.objects(); + while let Some(meta_object) = objects.next(&mut self.medium).await? { + let mut meta = meta_object.read_metadata(&mut self.medium).await?; + while let Some(loc) = meta.next_object_location(&mut self.medium).await? { + if loc == object.location() { + return Ok(meta_object); + } + } + } + } + + Err(StorageError::NotFound) + } +} + +trait NewObjectAllocator: Debug { + const BLOCK_TYPE: BlockType; + + async fn move_object( + &mut self, + storage: &mut Storage, + object: ObjectInfo, + destination: ObjectLocation, + ) -> Result, StorageError> + where + M: StorageMedium, + [(); M::BLOCK_COUNT]:; + + async fn find_new_object_location( + &mut self, + storage: &mut Storage, len: usize, - ) -> Result { - log::trace!("Storage::find_new_object_location({ty:?}, {len})"); + ) -> Result + where + M: StorageMedium, + [(); M::BLOCK_COUNT]:, + { + log::trace!("Storage::find_new_object_location({self:?}, {len})"); // find block with most free space let object_size = M::align(ObjectHeader::byte_count::()) + len; - let location = match self + let location = match storage .blocks - .allocate_new_object(ty, object_size, &mut self.medium) + .allocate_new_object(Self::BLOCK_TYPE, object_size, &mut storage.medium) .await { Ok(block) => block, Err(StorageError::InsufficientSpace) => { - self.try_to_free_space(ty, object_size).await?; - self.blocks - .allocate_new_object(ty, object_size, &mut self.medium) + self.try_to_free_space(storage, object_size).await?; + storage + .blocks + .allocate_new_object(Self::BLOCK_TYPE, object_size, &mut storage.medium) .await? } Err(e) => return Err(e), }; - log::trace!("Storage::find_new_object_location({ty:?}, {len}) -> {location:?}"); + log::trace!("Storage::find_new_object_location({self:?}, {len}) -> {location:?}"); Ok(location) } - async fn move_data_object( - &mut self, - object: ObjectInfo, - destination: ObjectLocation, - ) -> Result, StorageError> { - let meta = self.find_metadata_of_object(&object).await?; - // let new_meta_location = self - // .find_new_metadata_object_location(meta.total_size()) - // .await?; - - // TODO: copy metadata object while replacing current object location to - // new location - - // TODO: copy object - let copied = object.copy_object(&mut self.medium, destination).await?; - // TODO: finalize metadata object - // TODO: delete old metadata object - // TODO: delete old object - object.delete(&mut self.medium).await?; - - Ok(copied) - } - - async fn find_metadata_of_object( + async fn try_to_free_space( &mut self, - object: &ObjectInfo, - ) -> Result, StorageError> { - for block in self.blocks.blocks(BlockType::Metadata) { - let mut objects = block.objects(); - while let Some(meta_object) = objects.next(&mut self.medium).await? { - let mut meta = meta_object.read_metadata(&mut self.medium).await?; - while let Some(loc) = meta.next_object_location(&mut self.medium).await? { - if loc == object.location() { - return Ok(meta_object); - } - } - } - } - - Err(StorageError::NotFound) - } - - async fn try_to_free_space(&mut self, ty: BlockType, len: usize) -> Result<(), StorageError> { - let Some((block_to_free, freeable)) = self + storage: &mut Storage, + len: usize, + ) -> Result<(), StorageError> + where + M: StorageMedium, + [(); M::BLOCK_COUNT]:, + { + let Some((block_to_free, freeable)) = storage .blocks - .find_block_to_free(ty, len, &mut self.medium) + .find_block_to_free(Self::BLOCK_TYPE, len, &mut storage.medium) .await? else { return Err(StorageError::InsufficientSpace); @@ -749,34 +752,90 @@ where // We need to move objects out of this block let mut iter = block_to_free.objects(); - while let Some(object) = iter.next(&mut self.medium).await? { + while let Some(object) = iter.next(&mut storage.medium).await? { match object.state() { ObjectState::Free | ObjectState::Deleted => continue, ObjectState::Allocated => return Err(StorageError::InsufficientSpace), // TODO: retry in a different object ObjectState::Finalized => {} } - let copy_location = self + let copy_location = storage .blocks - .allocate_object(ty, object.total_size(), true, &mut self.medium) + .allocate_object( + Self::BLOCK_TYPE, + object.total_size(), + true, + &mut storage.medium, + ) .await .map_err(|_| StorageError::InsufficientSpace)?; - if ty == BlockType::Data { - // When moving a data object, we need to update the file metadata, too. - self.move_data_object(object, copy_location).await?; - } else { - object.move_object(&mut self.medium, copy_location).await?; - } + self.move_object(storage, object, copy_location).await?; } } - self.blocks - .format_indexed(block_to_free, &mut self.medium) + storage + .blocks + .format_indexed(block_to_free, &mut storage.medium) .await } } +#[derive(Debug)] +struct DataObject; + +impl NewObjectAllocator for DataObject { + const BLOCK_TYPE: BlockType = BlockType::Data; + + async fn move_object( + &mut self, + storage: &mut Storage, + object: ObjectInfo, + destination: ObjectLocation, + ) -> Result, StorageError> + where + M: StorageMedium, + [(); M::BLOCK_COUNT]:, + { + let meta = storage.find_metadata_of_object(&object).await?; + // let new_meta_location = self + // .find_new_metadata_object_location(meta.total_size()) + // .await?; + + // TODO: copy metadata object while replacing current object location to + // new location + + // TODO: copy object + let copied = object.copy_object(&mut storage.medium, destination).await?; + // TODO: finalize metadata object + // TODO: delete old metadata object + // TODO: delete old object + object.delete(&mut storage.medium).await?; + + Ok(copied) + } +} + +#[derive(Debug)] +struct MetaObject; + +impl NewObjectAllocator for MetaObject { + const BLOCK_TYPE: BlockType = BlockType::Metadata; + + async fn move_object( + &mut self, + storage: &mut Storage, + object: ObjectInfo, + destination: ObjectLocation, + ) -> Result, StorageError> + where + M: StorageMedium, + [(); M::BLOCK_COUNT]:, + { + object.move_object(&mut storage.medium, destination).await + } +} + fn hash_path(path: &str) -> u32 { fxhash::hash32(path.as_bytes()) } From 3aaa19a46cee859bd0d310a7760c7c6b06bcde16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 19 Jun 2023 16:43:46 +0200 Subject: [PATCH 18/34] Find space for modified metadata when moving data object --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c7a546d..a96c3ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -798,9 +798,9 @@ impl NewObjectAllocator for DataObject { [(); M::BLOCK_COUNT]:, { let meta = storage.find_metadata_of_object(&object).await?; - // let new_meta_location = self - // .find_new_metadata_object_location(meta.total_size()) - // .await?; + let new_meta_location = MetaObject + .find_new_object_location(storage, meta.total_size()) + .await?; // TODO: copy metadata object while replacing current object location to // new location From bba83ef80b7335e2d8c33fc807b71d64ed7fd2dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 19 Jun 2023 16:58:13 +0200 Subject: [PATCH 19/34] Copy modified metadata --- src/lib.rs | 52 +++++++++++++++++++++++++++++++++++++++++------ src/ll/objects.rs | 8 ++++++++ 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a96c3ea..d593eb0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -684,6 +684,8 @@ where } } +// Async functions can't be recursive. Splitting out implementation for each block type means +// we can reuse code without recursion. trait NewObjectAllocator: Debug { const BLOCK_TYPE: BlockType; @@ -802,14 +804,52 @@ impl NewObjectAllocator for DataObject { .find_new_object_location(storage, meta.total_size()) .await?; - // TODO: copy metadata object while replacing current object location to - // new location + // copy metadata object while replacing current object location to new location + let mut meta_writer = ObjectWriter::allocate( + new_meta_location, + ObjectType::FileMetadata, + &mut storage.medium, + ) + .await?; + let mut old_object_reader = meta.read_metadata(&mut storage.medium).await?; + + // copy header + meta_writer + .write( + &mut storage.medium, + &old_object_reader.path_hash.to_le_bytes(), + ) + .await?; + let (bytes, byte_count) = old_object_reader.filename_location.into_bytes::(); + meta_writer + .write(&mut storage.medium, &bytes[..byte_count]) + .await?; - // TODO: copy object + // copy object locations + while let Some(loc) = old_object_reader + .next_object_location(&mut storage.medium) + .await? + { + let location = if loc == object.location() { + destination + } else { + loc + }; + + let (bytes, byte_count) = location.into_bytes::(); + meta_writer + .write(&mut storage.medium, &bytes[..byte_count]) + .await?; + } + + // copy data object let copied = object.copy_object(&mut storage.medium, destination).await?; - // TODO: finalize metadata object - // TODO: delete old metadata object - // TODO: delete old object + + // finalize metadata object + meta_writer.finalize(&mut storage.medium).await?; + // delete old metadata object + meta.delete(&mut storage.medium).await?; + // delete old object object.delete(&mut storage.medium).await?; Ok(copied) diff --git a/src/ll/objects.rs b/src/ll/objects.rs index d75e137..a374bd5 100644 --- a/src/ll/objects.rs +++ b/src/ll/objects.rs @@ -793,6 +793,14 @@ impl ObjectInfo { Ok(new) } + pub async fn finalize(mut self, medium: &mut M) -> Result { + self.header + .update_state(medium, ObjectState::Finalized) + .await?; + + Ok(self) + } + pub async fn delete(mut self, medium: &mut M) -> Result<(), StorageError> { self.header.update_state(medium, ObjectState::Deleted).await } From d6e7b0a412087f5323307dc089c3b1b4c82b97b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 19 Jun 2023 17:31:49 +0200 Subject: [PATCH 20/34] Fix over-reading object locations --- src/ll/objects.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/ll/objects.rs b/src/ll/objects.rs index a374bd5..bbbcf5c 100644 --- a/src/ll/objects.rs +++ b/src/ll/objects.rs @@ -278,7 +278,7 @@ impl ObjectLocation { } } -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct ObjectHeader { state: CompositeObjectState, payload_size: usize, // At most block size - header @@ -319,7 +319,7 @@ impl ObjectHeader { location: ObjectLocation, object_type: ObjectType, ) -> Result { - log::trace!("ObjectHeader::allocate({location:?}, {object_type:?})",); + log::debug!("ObjectHeader::allocate({location:?}, {object_type:?})",); let state = CompositeObjectState::allocate(medium, location, object_type).await?; @@ -433,7 +433,13 @@ impl MetadataObjectHeader { log::trace!("MetadataObjectHeader::next_object_location()"); let data_offset = 4 + M::object_location_bytes(); // path hash + filename location - if self.data_object_cursor >= self.object.payload_size - data_offset { + if self.data_object_cursor + >= self + .object + .payload_size::() + .map(|size| size - data_offset) + .unwrap_or(0) + { return Ok(None); } @@ -553,6 +559,11 @@ impl ObjectWriter { } if self.space() < data.len() { + log::debug!( + "Insufficient space ({}) to write data ({})", + self.space(), + data.len() + ); return Err(StorageError::InsufficientSpace); } @@ -761,6 +772,7 @@ impl ObjectInfo { if header.state().is_free() { return Ok(None); } + log::trace!("ObjectInfo::read({location:?}) -> {header:?}"); Ok(Some(Self::with_header(header))) } From a96b80dcbb8b94adbb774e1b85a4699ec3732e19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 19 Jun 2023 17:37:33 +0200 Subject: [PATCH 21/34] Include path hash size in metadata estimation --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d593eb0..5327395 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -566,9 +566,9 @@ where // filename + 1 data page let est_page_count = 1 + 1; // TODO: guess the number of data pages needed - // this is mutable because we can fail mid-writing + // this is mutable because we can fail mid-writing. 4 bytes to store the path hash let mut file_meta_location = MetaObject - .find_new_object_location(self, est_page_count * M::align(M::object_location_bytes())) + .find_new_object_location(self, 4 + est_page_count * M::object_location_bytes()) .await?; // Write file name as data object From 4d59ddc3811173d4401aa8521cf9abea0b086ea1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 19 Jun 2023 18:01:44 +0200 Subject: [PATCH 22/34] Add failing test --- src/lib.rs | 57 ++++++++++++++++++++++++++++++++++++++++++----- src/ll/objects.rs | 12 +++++----- 2 files changed, 58 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5327395..f0bba7b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -668,12 +668,24 @@ where &mut self, object: &ObjectInfo, ) -> Result, StorageError> { + log::trace!("Storage::find_metadata_of_object({:?})", object.location()); for block in self.blocks.blocks(BlockType::Metadata) { let mut objects = block.objects(); while let Some(meta_object) = objects.next(&mut self.medium).await? { + match meta_object.state() { + ObjectState::Free => break, + ObjectState::Allocated => break, + ObjectState::Finalized => {} + ObjectState::Deleted => continue, + } let mut meta = meta_object.read_metadata(&mut self.medium).await?; while let Some(loc) = meta.next_object_location(&mut self.medium).await? { if loc == object.location() { + log::trace!( + "Storage::find_metadata_of_object({:?}) -> {:?}", + object.location(), + meta_object.location() + ); return Ok(meta_object); } } @@ -708,7 +720,7 @@ trait NewObjectAllocator: Debug { M: StorageMedium, [(); M::BLOCK_COUNT]:, { - log::trace!("Storage::find_new_object_location({self:?}, {len})"); + log::trace!("{self:?}::find_new_object_location({self:?}, {len})"); // find block with most free space let object_size = M::align(ObjectHeader::byte_count::()) + len; @@ -742,6 +754,7 @@ trait NewObjectAllocator: Debug { M: StorageMedium, [(); M::BLOCK_COUNT]:, { + log::debug!("{self:?}::try_to_free_space({len})"); let Some((block_to_free, freeable)) = storage .blocks .find_block_to_free(Self::BLOCK_TYPE, len, &mut storage.medium) @@ -799,11 +812,22 @@ impl NewObjectAllocator for DataObject { M: StorageMedium, [(); M::BLOCK_COUNT]:, { + log::trace!("{self:?}::move_object"); + let meta = storage.find_metadata_of_object(&object).await?; let new_meta_location = MetaObject .find_new_object_location(storage, meta.total_size()) .await?; + log::debug!( + "Moving data object {:?} to {destination:?}", + object.location() + ); + log::debug!( + "Moving meta object {:?} to {new_meta_location:?}", + meta.location() + ); + // copy metadata object while replacing current object location to new location let mut meta_writer = ObjectWriter::allocate( new_meta_location, @@ -872,6 +896,7 @@ impl NewObjectAllocator for MetaObject { M: StorageMedium, [(); M::BLOCK_COUNT]:, { + log::trace!("{self:?}::move_object"); object.move_object(&mut storage.medium, destination).await } } @@ -908,7 +933,7 @@ mod test { ram_nor_emulating::NorRamStorage, }; - const LIPSUM: &[u8] = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce in mi scelerisque, porttitor mi amet."; + const LIPSUM: &[u8] = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce i"; pub fn init_test() { _ = simple_logger::SimpleLogger::new() @@ -1107,15 +1132,15 @@ mod test { let mut reader = storage.read("foo").await.expect("Failed to open file"); - let mut buf = [0u8; 100]; + let mut buf = [0u8; 64]; // Read in two chunks to test that the reader resumes with the current byte reader - .read(&mut storage, &mut buf[0..50]) + .read(&mut storage, &mut buf[0..32]) .await .expect("Failed to read file"); reader - .read(&mut storage, &mut buf[50..]) + .read(&mut storage, &mut buf[32..]) .await .expect("Failed to read file"); @@ -1178,7 +1203,27 @@ mod test { .store("bar", LIPSUM, OnCollision::Overwrite) .await .is_err(), - "Lookup returned Ok unexpectedly" + "Store returned Ok unexpectedly" ); } + + #[async_std::test] + async fn can_reuse_space_of_deleted_files() { + init_test(); + + let mut storage = create_default_fs().await; + + storage + .store("foo", LIPSUM, OnCollision::Overwrite) + .await + .expect("Create failed"); + + storage.delete("foo").await.expect("Failed to delete"); + + storage.medium.debug_print(); + storage + .store("bar", LIPSUM, OnCollision::Overwrite) + .await + .expect("Failed to store"); + } } diff --git a/src/ll/objects.rs b/src/ll/objects.rs index bbbcf5c..069e1f4 100644 --- a/src/ll/objects.rs +++ b/src/ll/objects.rs @@ -764,15 +764,12 @@ impl ObjectInfo { async fn read(location: ObjectLocation, medium: &mut M) -> Result, StorageError> { log::trace!("ObjectInfo::read({location:?})"); - if location.offset + BlockHeader::::byte_count() >= M::BLOCK_SIZE { - return Ok(None); - } - let header = ObjectHeader::read(location, medium).await?; + log::trace!("ObjectInfo::read({location:?}) -> {header:?}"); + if header.state().is_free() { return Ok(None); } - log::trace!("ObjectInfo::read({location:?}) -> {header:?}"); Ok(Some(Self::with_header(header))) } @@ -836,10 +833,15 @@ impl ObjectIterator { &mut self, medium: &mut M, ) -> Result>, StorageError> { + if self.location.offset + BlockHeader::::byte_count() >= M::BLOCK_SIZE { + return Ok(None); + } + let info = ObjectInfo::read(self.location, medium).await?; if let Some(info) = info.as_ref() { self.location.offset += M::align(info.total_size()); } + Ok(info) } From be609359e193960d6ad614c3751ede9de529bba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 19 Jun 2023 18:05:45 +0200 Subject: [PATCH 23/34] Delete filename object --- src/lib.rs | 9 +++++---- src/ll/objects.rs | 5 ++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f0bba7b..d1e7689 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -514,10 +514,11 @@ where async fn delete_file_at(&mut self, meta_location: ObjectLocation) -> Result<(), StorageError> { let mut metadata = meta_location.read_metadata(&mut self.medium).await?; - metadata - .object - .update_state(&mut self.medium, ObjectState::Deleted) - .await?; + if let Some(filename_object) = + ObjectInfo::read(metadata.filename_location, &mut self.medium).await? + { + filename_object.delete(&mut self.medium).await?; + } while let Some(location) = metadata.next_object_location(&mut self.medium).await? { let mut header = ObjectHeader::read(location, &mut self.medium).await?; diff --git a/src/ll/objects.rs b/src/ll/objects.rs index 069e1f4..2d3a112 100644 --- a/src/ll/objects.rs +++ b/src/ll/objects.rs @@ -762,7 +762,10 @@ impl ObjectInfo { }) } - async fn read(location: ObjectLocation, medium: &mut M) -> Result, StorageError> { + pub async fn read( + location: ObjectLocation, + medium: &mut M, + ) -> Result, StorageError> { log::trace!("ObjectInfo::read({location:?})"); let header = ObjectHeader::read(location, medium).await?; log::trace!("ObjectInfo::read({location:?}) -> {header:?}"); From e0eb9d9c8ebf970900c41d9f60e6b81d0508a051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 19 Jun 2023 18:13:01 +0200 Subject: [PATCH 24/34] Properly reset block state after erase --- src/lib.rs | 18 +++++++----------- src/ll/blocks.rs | 1 + 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d1e7689..ce9455c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1214,17 +1214,13 @@ mod test { let mut storage = create_default_fs().await; - storage - .store("foo", LIPSUM, OnCollision::Overwrite) - .await - .expect("Create failed"); - - storage.delete("foo").await.expect("Failed to delete"); + for _ in 0..50 { + storage + .store("foo", LIPSUM, OnCollision::Overwrite) + .await + .expect("Create failed"); - storage.medium.debug_print(); - storage - .store("bar", LIPSUM, OnCollision::Overwrite) - .await - .expect("Failed to store"); + storage.delete("foo").await.expect("Failed to delete"); + } } } diff --git a/src/ll/blocks.rs b/src/ll/blocks.rs index 1fa744c..0c07bdd 100644 --- a/src/ll/blocks.rs +++ b/src/ll/blocks.rs @@ -241,6 +241,7 @@ impl BlockInfo { pub fn update_stats_after_erase(&mut self) { self.header.erase_count += 1; + self.header.header = BlockHeaderKind::Known(BlockType::Undefined); self.used_bytes = BlockHeader::::byte_count(); self.allow_alloc = true; } From 13a5b0b38178373bfed9f19d2dd53a4fbc261fec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 19 Jun 2023 18:23:56 +0200 Subject: [PATCH 25/34] Add some logging --- src/lib.rs | 10 +++++++--- src/ll/objects.rs | 10 ++++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ce9455c..3801bde 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -514,6 +514,8 @@ where async fn delete_file_at(&mut self, meta_location: ObjectLocation) -> Result<(), StorageError> { let mut metadata = meta_location.read_metadata(&mut self.medium).await?; + debug_assert_ne!(metadata.object.state(), ObjectState::Free); + if let Some(filename_object) = ObjectInfo::read(metadata.filename_location, &mut self.medium).await? { @@ -522,9 +524,11 @@ where while let Some(location) = metadata.next_object_location(&mut self.medium).await? { let mut header = ObjectHeader::read(location, &mut self.medium).await?; - header - .update_state(&mut self.medium, ObjectState::Deleted) - .await?; + if header.state() != ObjectState::Free { + header + .update_state(&mut self.medium, ObjectState::Deleted) + .await?; + } } metadata diff --git a/src/ll/objects.rs b/src/ll/objects.rs index 2d3a112..6c533e4 100644 --- a/src/ll/objects.rs +++ b/src/ll/objects.rs @@ -88,18 +88,20 @@ impl CompositeObjectState { if current_state > new_state { // Can't go backwards in state + log::error!("Can't change object state from {current_state:?} to {new_state:?}"); return Err(StorageError::InvalidOperation); } if let Self::Allocated(ty, _) = self { // Can't change allocated object type if ty != object_type { + log::error!("Can't change object type from {ty:?} to {object_type:?}"); return Err(StorageError::InvalidOperation); } } let new_data_state = match new_state { - ObjectState::Free => return Err(StorageError::InvalidOperation), + ObjectState::Free => unreachable!(), ObjectState::Allocated => ObjectDataState::Untrusted, ObjectState::Finalized => ObjectDataState::Valid, ObjectState::Deleted => ObjectDataState::Deleted, @@ -207,7 +209,10 @@ impl CompositeObjectState { fn object_type(self) -> Result { match self { Self::Allocated(ty, _) => Ok(ty), - Self::Free => Err(StorageError::InvalidOperation), + Self::Free => { + log::error!("Can't read object of type Free"); + Err(StorageError::InvalidOperation) + } } } } @@ -660,6 +665,7 @@ impl ObjectReader { // We can read data from unfinalized/deleted objects if the caller allows it. } else { // We can only read data from finalized objects. + log::error!("Trying to read {:?} object", object.state()); return Err(StorageError::FsCorrupted); } } From 7657d8a73d1d98456ce5bc00650a3402376abc65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 19 Jun 2023 18:26:52 +0200 Subject: [PATCH 26/34] Simplify --- src/lib.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3801bde..6910a87 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -460,14 +460,8 @@ where async fn lookup(&mut self, path: &str) -> Result { let path_hash = hash_path(path); - for block_idx in self - .blocks - .blocks - .iter() - .enumerate() - .filter_map(|(idx, blk)| blk.is_metadata().then_some(idx)) - { - let mut iter = ObjectIterator::new::(block_idx); + for block in self.blocks.blocks(BlockType::Metadata) { + let mut iter = block.objects(); 'objs: while let Some(object) = iter.next(&mut self.medium).await? { if object.state() != ObjectState::Finalized { From 82e47f2c1485ab9ae4d9274051fdef5a938d6a4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 19 Jun 2023 18:28:44 +0200 Subject: [PATCH 27/34] Offset already has the block header size --- src/ll/objects.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ll/objects.rs b/src/ll/objects.rs index 6c533e4..a2ce793 100644 --- a/src/ll/objects.rs +++ b/src/ll/objects.rs @@ -842,7 +842,7 @@ impl ObjectIterator { &mut self, medium: &mut M, ) -> Result>, StorageError> { - if self.location.offset + BlockHeader::::byte_count() >= M::BLOCK_SIZE { + if self.location.offset >= M::BLOCK_SIZE { return Ok(None); } From 8f5f845dd6d38d8bafc0fbbede19695ea7136e80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Mon, 19 Jun 2023 19:26:04 +0200 Subject: [PATCH 28/34] Add more log messages, print fs contents on error --- src/lib.rs | 14 +++++++++++--- src/ll/objects.rs | 2 ++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6910a87..1406765 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -412,6 +412,7 @@ where self.create_new_file(path, data).await?; if let Some(location) = overwritten { + // TODO: it's possible that location has been moved if we had to free up space. self.delete_file_at(location).await?; } @@ -557,6 +558,7 @@ where async fn create_new_file(&mut self, path: &str, mut data: &[u8]) -> Result<(), StorageError> { if path.contains(&['/', '\\'][..]) { + log::warn!("Path contains invalid characters"); return Err(StorageError::InvalidOperation); } @@ -1216,9 +1218,15 @@ mod test { storage .store("foo", LIPSUM, OnCollision::Overwrite) .await - .expect("Create failed"); - - storage.delete("foo").await.expect("Failed to delete"); + .unwrap_or_else(|e| { + storage.medium.debug_print(); + panic!("Create failed: {e:?}"); + }); + + storage.delete("foo").await.unwrap_or_else(|e| { + storage.medium.debug_print(); + panic!("Failed to delete: {e:?}"); + }); } } } diff --git a/src/ll/objects.rs b/src/ll/objects.rs index a2ce793..c727134 100644 --- a/src/ll/objects.rs +++ b/src/ll/objects.rs @@ -619,6 +619,7 @@ impl ObjectWriter { pub async fn finalize(mut self, medium: &mut M) -> Result, StorageError> { if self.object.state() != ObjectState::Allocated { + log::error!("Can not finalize object in state {:?}", self.object.state()); return Err(StorageError::InvalidOperation); } @@ -631,6 +632,7 @@ impl ObjectWriter { pub async fn delete(mut self, medium: &mut M) -> Result<(), StorageError> { if let ObjectState::Free | ObjectState::Deleted = self.object.state() { + log::error!("Can not delete object in state {:?}", self.object.state()); return Ok(()); } From 88ac6c2b9fd30ba36e0d2f5738ddbdfbd987c9e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Tue, 20 Jun 2023 08:52:00 +0200 Subject: [PATCH 29/34] Don't try to free up space while writing a file --- src/lib.rs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1406765..cef923d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -725,21 +725,10 @@ trait NewObjectAllocator: Debug { // find block with most free space let object_size = M::align(ObjectHeader::byte_count::()) + len; - let location = match storage + let location = storage .blocks .allocate_new_object(Self::BLOCK_TYPE, object_size, &mut storage.medium) - .await - { - Ok(block) => block, - Err(StorageError::InsufficientSpace) => { - self.try_to_free_space(storage, object_size).await?; - storage - .blocks - .allocate_new_object(Self::BLOCK_TYPE, object_size, &mut storage.medium) - .await? - } - Err(e) => return Err(e), - }; + .await?; log::trace!("Storage::find_new_object_location({self:?}, {len}) -> {location:?}"); From 74bcd324c133750ab937935318a891e28ea0ec56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Tue, 20 Jun 2023 09:43:18 +0200 Subject: [PATCH 30/34] Count the maximum number of blocks necessary --- src/lib.rs | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index cef923d..61f4449 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -400,6 +400,11 @@ where if_exists: OnCollision, ) -> Result<(), StorageError> { log::debug!("Storage::store({path}, len = {})", data.len()); + + if self.estimate_data_chunks(data.len()).is_err() { + self.make_space_for(data.len()).await?; + } + let overwritten_location = self.lookup(path).await; let overwritten = match overwritten_location { @@ -458,6 +463,34 @@ where Ok(size) } + fn estimate_data_chunks(&self, mut len: usize) -> Result { + let mut block_count = 0; + + for ty in [BlockType::Data, BlockType::Undefined] { + for block in self.blocks.blocks(ty) { + let space = block + .free_space() + .saturating_sub(ObjectHeader::byte_count::()); + + if space > 0 { + len = len.saturating_sub(space); + block_count += 1; + + if len == 0 { + return Ok(block_count); + } + } + } + } + + Err(StorageError::InsufficientSpace) + } + + async fn make_space_for(&mut self, len: usize) -> Result<(), StorageError> { + // TODO: free up space + Err(StorageError::InsufficientSpace) + } + async fn lookup(&mut self, path: &str) -> Result { let path_hash = hash_path(path); @@ -564,8 +597,8 @@ where let path_hash = hash_path(path); - // filename + 1 data page - let est_page_count = 1 + 1; // TODO: guess the number of data pages needed + // filename + data objects + let est_page_count = 1 + self.estimate_data_chunks(data.len())?; // this is mutable because we can fail mid-writing. 4 bytes to store the path hash let mut file_meta_location = MetaObject From 5ff003ee8dff8d9f719106981c80795d1e533643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Wed, 21 Jun 2023 11:00:50 +0200 Subject: [PATCH 31/34] Rework gc before writing a file --- src/lib.rs | 130 ++++++++++++++++++++++++++++++++--------------- src/ll/blocks.rs | 2 +- 2 files changed, 91 insertions(+), 41 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 61f4449..4832639 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,15 +72,18 @@ where self.allocate_object(ty, min_free, false, medium).await } - fn blocks(&self, ty: BlockType) -> impl Iterator> + '_ { + fn all_blocks(&self) -> impl Iterator> + '_ { self.blocks .iter() .copied() .enumerate() - .filter(move |(_, info)| info.is_type(ty)) .map(|(idx, info)| IndexedBlockInfo(idx, info)) } + fn blocks(&self, ty: BlockType) -> impl Iterator> + '_ { + self.all_blocks().filter(move |info| info.is_type(ty)) + } + fn allocate_object_impl( &self, ty: BlockType, @@ -96,6 +99,9 @@ where { return Ok(block.0); } + if let Some(block) = self.blocks(ty).find(|info| info.free_space() >= min_free) { + return Ok(block.0); + } // We reserve 2 blocks for GC. if allow_gc_block || self.blocks(BlockType::Undefined).count() > 2 { @@ -128,6 +134,7 @@ where })?; if self.blocks[location.block].is_unassigned() { + log::debug!("Setting block {} to {ty:?}", location.block); BlockOps::new(medium) .set_block_type(location.block, ty) .await?; @@ -139,23 +146,19 @@ where pub(crate) async fn find_block_to_free( &mut self, - ty: BlockType, - len: usize, medium: &mut M, ) -> Result, usize)>, StorageError> { let mut target_block = None::<(IndexedBlockInfo, usize)>; // Select block with enough freeable space and minimum erase counter - for info in self.blocks(ty) { + for info in self.all_blocks().filter(|block| !block.is_empty()) { let freeable = info.calculate_freeable_space(medium).await?; - if freeable <= len { - continue; - } - match target_block { - Some((idx, _)) => { - if info.erase_count() < idx.erase_count() { + Some((idx, potential)) => { + if freeable > potential + || (potential == freeable && info.erase_count() < idx.erase_count()) + { target_block = Some((info, freeable)); } } @@ -401,9 +404,7 @@ where ) -> Result<(), StorageError> { log::debug!("Storage::store({path}, len = {})", data.len()); - if self.estimate_data_chunks(data.len()).is_err() { - self.make_space_for(data.len()).await?; - } + self.make_space_for(path.len(), data.len()).await?; let overwritten_location = self.lookup(path).await; @@ -414,15 +415,19 @@ where }; if overwritten.is_none() || if_exists == OnCollision::Overwrite { + if let Some(overwritten) = overwritten { + log::debug!("Overwriting location: {:?}", overwritten); + } + self.create_new_file(path, data).await?; if let Some(location) = overwritten { - // TODO: it's possible that location has been moved if we had to free up space. self.delete_file_at(location).await?; } Ok(()) } else { + log::debug!("File already exists at path: {}", path); Err(StorageError::InvalidOperation) } } @@ -466,14 +471,11 @@ where fn estimate_data_chunks(&self, mut len: usize) -> Result { let mut block_count = 0; - for ty in [BlockType::Data, BlockType::Undefined] { - for block in self.blocks.blocks(ty) { - let space = block - .free_space() - .saturating_sub(ObjectHeader::byte_count::()); - - if space > 0 { - len = len.saturating_sub(space); + for (ty, skip) in [(BlockType::Data, 0), (BlockType::Undefined, 2)] { + for block in self.blocks.blocks(ty).skip(skip) { + let space = block.free_space(); + if space > ObjectHeader::byte_count::() { + len = len.saturating_sub(space - ObjectHeader::byte_count::()); block_count += 1; if len == 0 { @@ -486,9 +488,41 @@ where Err(StorageError::InsufficientSpace) } - async fn make_space_for(&mut self, len: usize) -> Result<(), StorageError> { - // TODO: free up space - Err(StorageError::InsufficientSpace) + async fn make_space_for(&mut self, path_len: usize, len: usize) -> Result<(), StorageError> { + let mut meta_allocated = false; + loop { + let blocks = + match self.estimate_data_chunks(len + path_len + ObjectHeader::byte_count::()) { + Ok(blocks) => blocks, + Err(StorageError::InsufficientSpace) => { + DataObject.try_to_make_space(self).await?; + continue; + } + Err(e) => return Err(e), + }; + + if meta_allocated { + // Hopefully, freeing space didn't free the metadata block. If it did, we'll + // exit with insufficient space error later. + break; + } + + let meta_size = + ObjectHeader::byte_count::() + 4 + (blocks + 1) * M::object_location_bytes(); + + match self + .blocks + .allocate_new_object(BlockType::Metadata, meta_size, &mut self.medium) + .await + { + Ok(_) => meta_allocated = true, + Err(StorageError::InsufficientSpace) => MetaObject.try_to_make_space(self).await?, + Err(e) => return Err(e), + } + } + + log::trace!("Storage::make_space_for({len}) done"); + Ok(()) } async fn lookup(&mut self, path: &str) -> Result { @@ -590,6 +624,8 @@ where } async fn create_new_file(&mut self, path: &str, mut data: &[u8]) -> Result<(), StorageError> { + log::trace!("Storage::create_new_file({:?})", path); + if path.contains(&['/', '\\'][..]) { log::warn!("Path contains invalid characters"); return Err(StorageError::InvalidOperation); @@ -763,37 +799,38 @@ trait NewObjectAllocator: Debug { .allocate_new_object(Self::BLOCK_TYPE, object_size, &mut storage.medium) .await?; - log::trace!("Storage::find_new_object_location({self:?}, {len}) -> {location:?}"); + log::trace!("{self:?}::find_new_object_location({self:?}, {len}) -> {location:?}"); Ok(location) } - async fn try_to_free_space( - &mut self, - storage: &mut Storage, - len: usize, - ) -> Result<(), StorageError> + async fn try_to_make_space(&mut self, storage: &mut Storage) -> Result<(), StorageError> where M: StorageMedium, [(); M::BLOCK_COUNT]:, { - log::debug!("{self:?}::try_to_free_space({len})"); - let Some((block_to_free, freeable)) = storage - .blocks - .find_block_to_free(Self::BLOCK_TYPE, len, &mut storage.medium) + log::debug!("{self:?}::try_to_make_space()"); + let Some((block_to_free, freeable)) = storage.blocks + .find_block_to_free(&mut storage.medium) .await? else { + log::debug!("Could not find a block to free"); return Err(StorageError::InsufficientSpace); }; if freeable != block_to_free.used_bytes() { + log::debug!("{self:?}::try_to_make_space(): Moving objects out of block to free"); // We need to move objects out of this block let mut iter = block_to_free.objects(); while let Some(object) = iter.next(&mut storage.medium).await? { match object.state() { ObjectState::Free | ObjectState::Deleted => continue, - ObjectState::Allocated => return Err(StorageError::InsufficientSpace), // TODO: retry in a different object + ObjectState::Allocated => { + log::warn!("Encountered an allocated object"); + // TODO: retry in a different object + return Err(StorageError::InsufficientSpace); + } ObjectState::Finalized => {} } @@ -837,10 +874,23 @@ impl NewObjectAllocator for DataObject { { log::trace!("{self:?}::move_object"); - let meta = storage.find_metadata_of_object(&object).await?; - let new_meta_location = MetaObject + let mut meta = storage.find_metadata_of_object(&object).await?; + let new_meta_location = match MetaObject .find_new_object_location(storage, meta.total_size()) - .await?; + .await + { + Ok(loc) => loc, + Err(StorageError::InsufficientSpace) => { + MetaObject.try_to_make_space(storage).await?; + let new = MetaObject + .find_new_object_location(storage, meta.total_size()) + .await?; + // Look up again in case it was moved + meta = storage.find_metadata_of_object(&object).await?; + new + } + Err(e) => return Err(e), + }; log::debug!( "Moving data object {:?} to {destination:?}", diff --git a/src/ll/blocks.rs b/src/ll/blocks.rs index 0c07bdd..6dd94fa 100644 --- a/src/ll/blocks.rs +++ b/src/ll/blocks.rs @@ -396,7 +396,7 @@ impl<'a, M: StorageMedium> BlockOps<'a, M> { } if erase { - log::trace!("Erasing block {block}"); + log::debug!("Erasing block {block}"); self.medium.erase(block).await?; } From 0d2384a95f02e05c1338f16f50e5c09c9690a91b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Wed, 21 Jun 2023 11:06:31 +0200 Subject: [PATCH 32/34] Move method back to Storage --- src/lib.rs | 92 +++++++++++++++++++++++++++--------------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4832639..adf9a6c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -491,15 +491,16 @@ where async fn make_space_for(&mut self, path_len: usize, len: usize) -> Result<(), StorageError> { let mut meta_allocated = false; loop { - let blocks = - match self.estimate_data_chunks(len + path_len + ObjectHeader::byte_count::()) { - Ok(blocks) => blocks, - Err(StorageError::InsufficientSpace) => { - DataObject.try_to_make_space(self).await?; - continue; - } - Err(e) => return Err(e), - }; + let blocks = match self + .estimate_data_chunks(len + path_len + M::align(ObjectHeader::byte_count::())) + { + Ok(blocks) => blocks, + Err(StorageError::InsufficientSpace) => { + DataObject.try_to_make_space(self).await?; + continue; + } + Err(e) => return Err(e), + }; if meta_allocated { // Hopefully, freeing space didn't free the metadata block. If it did, we'll @@ -637,13 +638,16 @@ where let est_page_count = 1 + self.estimate_data_chunks(data.len())?; // this is mutable because we can fail mid-writing. 4 bytes to store the path hash - let mut file_meta_location = MetaObject - .find_new_object_location(self, 4 + est_page_count * M::object_location_bytes()) + let mut file_meta_location = self + .find_new_object_location( + BlockType::Metadata, + 4 + est_page_count * M::object_location_bytes(), + ) .await?; // Write file name as data object - let filename_location = DataObject - .find_new_object_location(self, path.len()) + let filename_location = self + .find_new_object_location(BlockType::Data, path.len()) .await?; self.write_object(filename_location, path.as_bytes()) @@ -666,7 +670,7 @@ where // Write data objects while !data.is_empty() { // Write file name as data object - let chunk_location = DataObject.find_new_object_location(self, 0).await?; + let chunk_location = self.find_new_object_location(BlockType::Data, 0).await?; let max_chunk_len = self.blocks.blocks[chunk_location.block].free_space() - ObjectHeader::byte_count::(); @@ -685,9 +689,9 @@ where self.blocks.blocks[file_meta_location.block] .add_used_bytes(meta_writer.total_size()); - let new_file_meta_location = MetaObject + let new_file_meta_location = self .find_new_object_location( - self, + BlockType::Metadata, meta_writer.payload_size() + M::object_location_bytes(), ) .await?; @@ -764,11 +768,30 @@ where Err(StorageError::NotFound) } + + async fn find_new_object_location( + &mut self, + ty: BlockType, + len: usize, + ) -> Result { + log::trace!("Storage::find_new_object_location({ty:?}, {len})"); + + // find block with most free space + let object_size = M::align(ObjectHeader::byte_count::()) + len; + let location = self + .blocks + .allocate_new_object(ty, object_size, &mut self.medium) + .await?; + + log::trace!("Storage::find_new_object_location({ty:?}, {len}) -> {location:?}"); + + Ok(location) + } } // Async functions can't be recursive. Splitting out implementation for each block type means // we can reuse code without recursion. -trait NewObjectAllocator: Debug { +trait ObjectMover: Debug { const BLOCK_TYPE: BlockType; async fn move_object( @@ -781,29 +804,6 @@ trait NewObjectAllocator: Debug { M: StorageMedium, [(); M::BLOCK_COUNT]:; - async fn find_new_object_location( - &mut self, - storage: &mut Storage, - len: usize, - ) -> Result - where - M: StorageMedium, - [(); M::BLOCK_COUNT]:, - { - log::trace!("{self:?}::find_new_object_location({self:?}, {len})"); - - // find block with most free space - let object_size = M::align(ObjectHeader::byte_count::()) + len; - let location = storage - .blocks - .allocate_new_object(Self::BLOCK_TYPE, object_size, &mut storage.medium) - .await?; - - log::trace!("{self:?}::find_new_object_location({self:?}, {len}) -> {location:?}"); - - Ok(location) - } - async fn try_to_make_space(&mut self, storage: &mut Storage) -> Result<(), StorageError> where M: StorageMedium, @@ -859,7 +859,7 @@ trait NewObjectAllocator: Debug { #[derive(Debug)] struct DataObject; -impl NewObjectAllocator for DataObject { +impl ObjectMover for DataObject { const BLOCK_TYPE: BlockType = BlockType::Data; async fn move_object( @@ -875,15 +875,15 @@ impl NewObjectAllocator for DataObject { log::trace!("{self:?}::move_object"); let mut meta = storage.find_metadata_of_object(&object).await?; - let new_meta_location = match MetaObject - .find_new_object_location(storage, meta.total_size()) + let new_meta_location = match storage + .find_new_object_location(BlockType::Metadata, meta.total_size()) .await { Ok(loc) => loc, Err(StorageError::InsufficientSpace) => { MetaObject.try_to_make_space(storage).await?; - let new = MetaObject - .find_new_object_location(storage, meta.total_size()) + let new = storage + .find_new_object_location(BlockType::Metadata, meta.total_size()) .await?; // Look up again in case it was moved meta = storage.find_metadata_of_object(&object).await?; @@ -956,7 +956,7 @@ impl NewObjectAllocator for DataObject { #[derive(Debug)] struct MetaObject; -impl NewObjectAllocator for MetaObject { +impl ObjectMover for MetaObject { const BLOCK_TYPE: BlockType = BlockType::Metadata; async fn move_object( From 06f1f8cbeed0dea0ecb7ba6b9e779ec8a5d0b010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Wed, 21 Jun 2023 11:11:11 +0200 Subject: [PATCH 33/34] Update block usage info --- src/lib.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index adf9a6c..980d272 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -941,9 +941,12 @@ impl ObjectMover for DataObject { // copy data object let copied = object.copy_object(&mut storage.medium, destination).await?; + storage.blocks.blocks[copied.location().block].add_used_bytes(copied.total_size()); // finalize metadata object - meta_writer.finalize(&mut storage.medium).await?; + let meta_info = meta_writer.finalize(&mut storage.medium).await?; + storage.blocks.blocks[meta_info.location().block].add_used_bytes(meta_info.total_size()); + // delete old metadata object meta.delete(&mut storage.medium).await?; // delete old object @@ -970,7 +973,10 @@ impl ObjectMover for MetaObject { [(); M::BLOCK_COUNT]:, { log::trace!("{self:?}::move_object"); - object.move_object(&mut storage.medium, destination).await + let info = object.move_object(&mut storage.medium, destination).await?; + storage.blocks.blocks[destination.block].add_used_bytes(info.total_size()); + + Ok(info) } } From 7bb59064899abe1c8e6c1e6f248674a81d871ea6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Wed, 21 Jun 2023 11:20:41 +0200 Subject: [PATCH 34/34] Run test for all test fs, fix over-read --- src/lib.rs | 41 ++++++++++++++++------------------------- src/ll/objects.rs | 2 +- 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 980d272..9d55340 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -491,9 +491,9 @@ where async fn make_space_for(&mut self, path_len: usize, len: usize) -> Result<(), StorageError> { let mut meta_allocated = false; loop { - let blocks = match self - .estimate_data_chunks(len + path_len + M::align(ObjectHeader::byte_count::())) - { + let blocks = match self.estimate_data_chunks( + M::align(len) + M::align(path_len) + M::align(ObjectHeader::byte_count::()), + ) { Ok(blocks) => blocks, Err(StorageError::InsufficientSpace) => { DataObject.try_to_make_space(self).await?; @@ -1262,6 +1262,19 @@ mod test { assert_file_contents(&mut storage, "foo", b"bar").await; assert_file_contents(&mut storage, "baz", b"asdf").await; } + + async fn can_reuse_space_of_deleted_files( + mut storage: Storage, + ) { + for _ in 0..50 { + storage + .store("foo", LIPSUM, OnCollision::Overwrite) + .await + .expect("Failed to create"); + + storage.delete("foo").await.expect("Failed to delete"); + } + } } #[async_std::test] @@ -1285,26 +1298,4 @@ mod test { "Store returned Ok unexpectedly" ); } - - #[async_std::test] - async fn can_reuse_space_of_deleted_files() { - init_test(); - - let mut storage = create_default_fs().await; - - for _ in 0..50 { - storage - .store("foo", LIPSUM, OnCollision::Overwrite) - .await - .unwrap_or_else(|e| { - storage.medium.debug_print(); - panic!("Create failed: {e:?}"); - }); - - storage.delete("foo").await.unwrap_or_else(|e| { - storage.medium.debug_print(); - panic!("Failed to delete: {e:?}"); - }); - } - } } diff --git a/src/ll/objects.rs b/src/ll/objects.rs index c727134..5302911 100644 --- a/src/ll/objects.rs +++ b/src/ll/objects.rs @@ -844,7 +844,7 @@ impl ObjectIterator { &mut self, medium: &mut M, ) -> Result>, StorageError> { - if self.location.offset >= M::BLOCK_SIZE { + if self.location.offset + ObjectHeader::byte_count::() >= M::BLOCK_SIZE { return Ok(None); }