Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ members = ["codegen", "tools/create-data-file", "tools/dump-data-file"]

[package]
name = "data_bucket"
version = "0.4.0"
version = "0.4.1"
edition = "2021"
authors = ["Handy-caT"]
license = "MIT"
repository = "https://github.com/pathscale/DataBucket"
description = "DataBucket is container for WorkTable's data"

[dependencies]
data_bucket_derive = { path = "codegen", version = "=0.3.15" }
data_bucket_derive = { path = "codegen", version = "=0.3.16" }

eyre = "0.6.12"
derive_more = { version = "1.0.0", features = ["from", "error", "display", "into"] }
Expand All @@ -23,3 +23,10 @@ indexset = { package = "WorkTablesIndex", version = "=0.0.1", features = ["concu
# indexset = { package = "wt-indexset", path = "../indexset", version = "0.12.10", features = ["concurrent", "cdc", "multimap"] }
# indexset = { package = "wt-indexset", version = "0.12.12", features = ["concurrent", "cdc", "multimap"] }
tokio = { version = "1", features = ["full"] }

[features]
default = ["validate-reads"]
# Validate every disk read with bytecheck: a torn page becomes a named
# error instead of undefined behavior. Disable for latency-critical builds
# to compile every read back to unchecked access, exactly as before 0.4.1.
validate-reads = []
2 changes: 1 addition & 1 deletion codegen/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "data_bucket_derive"
version = "0.3.15"
version = "0.3.16"
edition = "2021"
authors = ["Handy-caT"]
license = "MIT"
Expand Down
14 changes: 7 additions & 7 deletions codegen/src/persistable/generator/persistable_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ impl Generator {
rkyv::ser::Serializer<rkyv::util::AlignedVec, rkyv::ser::allocator::ArenaHandle<'a>, rkyv::ser::sharing::Share>,
rkyv::rancor::Error>,
>,
<#ident as rkyv::Archive>::Archived: rkyv::Deserialize<#ident, rkyv::api::high::HighDeserializer<rkyv::rancor::Error>> #archived_bounds,
<#ident as rkyv::Archive>::Archived: rkyv::Deserialize<#ident, rkyv::api::high::HighDeserializer<rkyv::rancor::Error>> + for<'a> rkyv::bytecheck::CheckBytes<rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>> #archived_bounds,
}
} else {
quote! {}
Expand Down Expand Up @@ -90,7 +90,7 @@ impl Generator {
}

fn from_bytes(bytes: &[u8], _version: u32) -> Self {
let archived = unsafe { rkyv::access_unchecked::<<Self as Archive>::Archived>(bytes) };
let archived = data_bucket::access_archived::<<Self as Archive>::Archived>(bytes).expect("torn or corrupt page: the archived bytes fail validation");
rkyv::deserialize::<_, rkyv::rancor::Error>(archived).expect("data should be valid")
}
}
Expand Down Expand Up @@ -214,7 +214,7 @@ impl Generator {
quote! {
let size_length = <#size_type as Default>::default().aligned_size();
let archived =
unsafe { rkyv::access_unchecked::<<#size_type as Archive>::Archived>(&bytes[offset..offset + size_length]) };
data_bucket::access_archived::<<#size_type as Archive>::Archived>(&bytes[offset..offset + size_length]).expect("torn or corrupt page part: a size field fails validation");
let #size_ident =
rkyv::deserialize::<#size_type, rkyv::rancor::Error>(archived).expect("data should be valid");
offset += size_length;
Expand All @@ -240,7 +240,7 @@ impl Generator {
let length = <#ty as Default>::default().aligned_size();
let mut v = rkyv::util::AlignedVec::<4>::new();
v.extend_from_slice(&bytes[offset..offset + length]);
let archived = unsafe { rkyv::access_unchecked::<<#ty as Archive>::Archived>(&v[..]) };
let archived = data_bucket::access_archived::<<#ty as Archive>::Archived>(&v[..]).expect("torn or corrupt page part: a field fails validation");
let #ident = rkyv::deserialize::<_, rkyv::rancor::Error>(archived).expect("data should be valid");
offset += length;
}
Expand Down Expand Up @@ -283,7 +283,7 @@ impl Generator {
let mut v = rkyv::util::AlignedVec::<4>::new();
v.extend_from_slice(&bytes[offset..offset + values_len]);
let archived =
unsafe { rkyv::access_unchecked::<<#ty as Archive>::Archived>(&v[..]) };
data_bucket::access_archived::<<#ty as Archive>::Archived>(&v[..]).expect("torn or corrupt page part: a field fails validation");
let #ident = rkyv::deserialize::<#ty, rkyv::rancor::Error>(archived)
.expect("data should be valid");
offset += values_len;
Expand Down Expand Up @@ -313,7 +313,7 @@ impl Generator {
let mut v = rkyv::util::AlignedVec::<4>::new();
v.extend_from_slice(&bytes[offset..offset + values_len]);
let archived =
unsafe { rkyv::access_unchecked::<<#ty as Archive>::Archived>(&v[..]) };
data_bucket::access_archived::<<#ty as Archive>::Archived>(&v[..]).expect("torn or corrupt page part: a field fails validation");
let #ident = rkyv::deserialize::<#ty, rkyv::rancor::Error>(archived)
.expect("data should be valid");
offset += values_len;
Expand Down Expand Up @@ -343,7 +343,7 @@ impl Generator {
let mut v = rkyv::util::AlignedVec::<4>::new();
v.extend_from_slice(&bytes[offset..offset + values_len]);
let archived =
unsafe { rkyv::access_unchecked::<<#ty as Archive>::Archived>(&v[..]) };
data_bucket::access_archived::<<#ty as Archive>::Archived>(&v[..]).expect("torn or corrupt page part: a field fails validation");
let #ident = rkyv::deserialize::<#ty, rkyv::rancor::Error>(archived)
.expect("data should be valid");
offset += values_len;
Expand Down
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
extern crate core;

// The Persistable derive emits paths through the crate name, and this crate
// uses its own derive: alias ourselves so the generated code resolves here too.
extern crate self as data_bucket;

pub mod link;
pub mod page;
pub mod persistence;
Expand All @@ -19,4 +23,5 @@ pub use page::{
};
pub use persistence::{PersistableIndex, PersistableTable};
pub use space::Id as SpaceId;
pub use util::access_archived;
pub use util::{align, align8, align_vec, Persistable, SizeMeasurable, VariableSizeMeasurable};
51 changes: 39 additions & 12 deletions src/page/index/page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ where
Strategy<Serializer<AlignedVec, ArenaHandle<'a>, Share>, rkyv::rancor::Error>,
> + Send
+ Sync,
<T as Archive>::Archived: Deserialize<T, Strategy<Pool, rkyv::rancor::Error>>,
<T as Archive>::Archived: Deserialize<T, Strategy<Pool, rkyv::rancor::Error>>
+ for<'a> rkyv::bytecheck::CheckBytes<rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>>,
{
type Utility = SizedIndexPageUtility<T>;

Expand All @@ -93,11 +94,12 @@ where

let mut size_bytes = vec![0u8; SizedIndexPageUtility::<T>::size_size()];
file.read_exact(size_bytes.as_mut_slice()).await?;
let archived = unsafe {
rkyv::access_unchecked::<<u16 as Archive>::Archived>(
&size_bytes[0..SizedIndexPageUtility::<T>::size_size()],
)
};
// Validated: this length field steers how much of the page is read
// as index entries, so a torn page must fail here, loudly.
let archived = crate::access_archived::<<u16 as Archive>::Archived>(
&size_bytes[0..SizedIndexPageUtility::<T>::size_size()],
)
.map_err(|error| eyre::eyre!("torn or corrupt index page size field: {error}"))?;
let size =
rkyv::deserialize::<u16, rkyv::rancor::Error>(archived).expect("data should be valid");

Expand Down Expand Up @@ -160,14 +162,21 @@ impl<T: Default + SizeMeasurable> IndexPage<T> {
async fn read_value(file: &mut File) -> eyre::Result<IndexValue<T>>
where
T: Archive,
<T as Archive>::Archived: Deserialize<T, Strategy<Pool, rkyv::rancor::Error>>,
<T as Archive>::Archived: Deserialize<T, Strategy<Pool, rkyv::rancor::Error>>
+ for<'a> rkyv::bytecheck::CheckBytes<
rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>,
>,
<IndexValue<T> as Archive>::Archived: for<'a> rkyv::bytecheck::CheckBytes<
rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>,
>,
{
let mut bytes = vec![0u8; IndexPage::<T>::index_values_value_size()];
file.read_exact(bytes.as_mut_slice()).await?;
let mut v = AlignedVec::<4>::new();
v.extend_from_slice(bytes.as_slice());
let archived =
unsafe { rkyv::access_unchecked::<<IndexValue<T> as Archive>::Archived>(&v[..]) };
// Validated: a torn index entry must be an error, not a dangling link.
let archived = crate::access_archived::<<IndexValue<T> as Archive>::Archived>(&v[..])
.map_err(|error| eyre::eyre!("torn or corrupt index entry: {error}"))?;
Ok(rkyv::deserialize(archived).expect("data should be valid"))
}

Expand All @@ -179,7 +188,13 @@ impl<T: Default + SizeMeasurable> IndexPage<T> {
) -> eyre::Result<IndexValue<T>>
where
T: Archive,
<T as Archive>::Archived: Deserialize<T, Strategy<Pool, rkyv::rancor::Error>>,
<T as Archive>::Archived: Deserialize<T, Strategy<Pool, rkyv::rancor::Error>>
+ for<'a> rkyv::bytecheck::CheckBytes<
rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>,
>,
<IndexValue<T> as Archive>::Archived: for<'a> rkyv::bytecheck::CheckBytes<
rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>,
>,
{
seek_to_page_start(file, page_id.0).await?;
let offset = Self::get_value_offset(size, index);
Expand Down Expand Up @@ -215,7 +230,13 @@ impl<T: Default + SizeMeasurable> IndexPage<T> {
+ for<'a> Serialize<
Strategy<Serializer<AlignedVec, ArenaHandle<'a>, Share>, rkyv::rancor::Error>,
>,
<T as Archive>::Archived: Deserialize<T, Strategy<Pool, rkyv::rancor::Error>>,
<T as Archive>::Archived: Deserialize<T, Strategy<Pool, rkyv::rancor::Error>>
+ for<'a> rkyv::bytecheck::CheckBytes<
rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>,
>,
<IndexValue<T> as Archive>::Archived: for<'a> rkyv::bytecheck::CheckBytes<
rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>,
>,
{
seek_to_page_start(file, page_id.0).await?;

Expand Down Expand Up @@ -249,7 +270,13 @@ impl<T: Default + SizeMeasurable> IndexPage<T> {
+ for<'a> Serialize<
Strategy<Serializer<AlignedVec, ArenaHandle<'a>, Share>, rkyv::rancor::Error>,
>,
<T as Archive>::Archived: Deserialize<T, Strategy<Pool, rkyv::rancor::Error>>,
<T as Archive>::Archived: Deserialize<T, Strategy<Pool, rkyv::rancor::Error>>
+ for<'a> rkyv::bytecheck::CheckBytes<
rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>,
>,
<IndexValue<T> as Archive>::Archived: for<'a> rkyv::bytecheck::CheckBytes<
rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>,
>,
{
seek_to_page_start(file, page_id.0).await?;

Expand Down
77 changes: 52 additions & 25 deletions src/page/index/page_for_unsized.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ where
Strategy<Serializer<AlignedVec, ArenaHandle<'a>, Share>, rkyv::rancor::Error>,
> + Send
+ Sync,
<T as Archive>::Archived: Deserialize<T, Strategy<Pool, rkyv::rancor::Error>>,
<T as Archive>::Archived: Deserialize<T, Strategy<Pool, rkyv::rancor::Error>>
+ for<'a> rkyv::bytecheck::CheckBytes<rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>>,
{
type Utility = UnsizedIndexPageUtility<T>;

Expand All @@ -78,20 +79,22 @@ where

let mut slot_size_bytes = vec![0u8; UnsizedIndexPageUtility::<T>::slots_size_size()];
file.read_exact(slot_size_bytes.as_mut_slice()).await?;
let archived = unsafe {
rkyv::access_unchecked::<<u16 as Archive>::Archived>(
&slot_size_bytes[0..UnsizedIndexPageUtility::<T>::slots_size_size()],
)
};
// Validated: these two length fields steer every later read of the
// page, so a torn page must fail here rather than misdirect them.
let archived = crate::access_archived::<<u16 as Archive>::Archived>(
&slot_size_bytes[0..UnsizedIndexPageUtility::<T>::slots_size_size()],
)
.map_err(|error| eyre::eyre!("torn or corrupt unsized index page (slots size): {error}"))?;
let slots_size =
rkyv::deserialize::<u16, rkyv::rancor::Error>(archived).expect("data should be valid");
let mut node_id_size_bytes = vec![0u8; UnsizedIndexPageUtility::<T>::node_id_size_size()];
file.read_exact(node_id_size_bytes.as_mut_slice()).await?;
let archived = unsafe {
rkyv::access_unchecked::<<u16 as Archive>::Archived>(
&node_id_size_bytes[0..UnsizedIndexPageUtility::<T>::node_id_size_size()],
)
};
let archived = crate::access_archived::<<u16 as Archive>::Archived>(
&node_id_size_bytes[0..UnsizedIndexPageUtility::<T>::node_id_size_size()],
)
.map_err(|error| {
eyre::eyre!("torn or corrupt unsized index page (node id size): {error}")
})?;
let node_id_size =
rkyv::deserialize::<u16, rkyv::rancor::Error>(archived).expect("data should be valid");

Expand Down Expand Up @@ -122,7 +125,8 @@ where
+ for<'a> Serialize<
Strategy<Serializer<AlignedVec, ArenaHandle<'a>, Share>, rkyv::rancor::Error>,
>,
<T as Archive>::Archived: Deserialize<T, Strategy<Pool, rkyv::rancor::Error>>,
<T as Archive>::Archived: Deserialize<T, Strategy<Pool, rkyv::rancor::Error>>
+ for<'a> rkyv::bytecheck::CheckBytes<rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>>,
{
pub fn new(node_id: IndexValue<T>) -> eyre::Result<Self> {
let len = node_id.aligned_size() as u32;
Expand Down Expand Up @@ -203,7 +207,10 @@ where
+ for<'a> Serialize<
Strategy<Serializer<AlignedVec, ArenaHandle<'a>, Share>, rkyv::rancor::Error>,
>,
<T as Archive>::Archived: Deserialize<T, Strategy<Pool, rkyv::rancor::Error>>,
<T as Archive>::Archived: Deserialize<T, Strategy<Pool, rkyv::rancor::Error>>
+ for<'a> rkyv::bytecheck::CheckBytes<
rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>,
>,
{
// We seek to page's end and will write values from tail.
seek_to_page_start(file, page_id.0 + 1).await?;
Expand All @@ -219,14 +226,21 @@ where
async fn read_value(file: &mut File, len: u16) -> eyre::Result<IndexValue<T>>
where
T: Archive,
<T as Archive>::Archived: Deserialize<T, Strategy<Pool, rkyv::rancor::Error>>,
<T as Archive>::Archived: Deserialize<T, Strategy<Pool, rkyv::rancor::Error>>
+ for<'a> rkyv::bytecheck::CheckBytes<
rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>,
>,
<IndexValue<T> as Archive>::Archived: for<'a> rkyv::bytecheck::CheckBytes<
rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>,
>,
{
let mut bytes = vec![0u8; len as usize];
file.read_exact(bytes.as_mut_slice()).await?;
let mut v = AlignedVec::<4>::new();
v.extend_from_slice(bytes.as_slice());
let archived =
unsafe { rkyv::access_unchecked::<<IndexValue<T> as Archive>::Archived>(&v[..]) };
// Validated: a torn index entry must be an error, not a dangling link.
let archived = crate::access_archived::<<IndexValue<T> as Archive>::Archived>(&v[..])
.map_err(|error| eyre::eyre!("torn or corrupt unsized index entry: {error}"))?;
Ok(rkyv::deserialize(archived).expect("data should be valid"))
}

Expand All @@ -238,7 +252,13 @@ where
) -> eyre::Result<IndexValue<T>>
where
T: Archive,
<T as Archive>::Archived: Deserialize<T, Strategy<Pool, rkyv::rancor::Error>>,
<T as Archive>::Archived: Deserialize<T, Strategy<Pool, rkyv::rancor::Error>>
+ for<'a> rkyv::bytecheck::CheckBytes<
rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>,
>,
<IndexValue<T> as Archive>::Archived: for<'a> rkyv::bytecheck::CheckBytes<
rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>,
>,
{
seek_to_page_start(file, page_id.0 + 1).await?;
file.seek(SeekFrom::Current(-(offset as i64))).await?;
Expand Down Expand Up @@ -276,7 +296,11 @@ where
+ for<'a> Serialize<
Strategy<Serializer<AlignedVec, ArenaHandle<'a>, Share>, rkyv::rancor::Error>,
>,
<T as Archive>::Archived: Deserialize<T, Strategy<Pool, rkyv::rancor::Error>>,
<T as Archive>::Archived: Deserialize<T, Strategy<Pool, rkyv::rancor::Error>>
+ for<'a> rkyv::bytecheck::CheckBytes<rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>>,
<IndexValue<T> as Archive>::Archived: for<'a> rkyv::bytecheck::CheckBytes<
rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>,
>,
{
fn as_bytes(&self) -> impl AsRef<[u8]> + Send {
let data_length = DATA_LENGTH as usize;
Expand Down Expand Up @@ -305,16 +329,19 @@ where
}

fn from_bytes(bytes: &[u8], _version: u32) -> Self {
// Validated throughout: `from_bytes` has no error channel, so a torn
// page becomes a named panic here instead of undefined behavior in
// whatever walks the misread entries later.
let slots_size_bytes = &bytes[0..UnsizedIndexPageUtility::<T>::slots_size_size()];
let archived =
unsafe { rkyv::access_unchecked::<<u16 as Archive>::Archived>(slots_size_bytes) };
let archived = crate::access_archived::<<u16 as Archive>::Archived>(slots_size_bytes)
.expect("torn or corrupt unsized index page: slots size fails validation");
let slots_size =
rkyv::deserialize::<u16, rkyv::rancor::Error>(archived).expect("data should be valid");
let node_id_size_bytes = &bytes[UnsizedIndexPageUtility::<T>::slots_size_size()
..UnsizedIndexPageUtility::<T>::node_id_size_size()
+ UnsizedIndexPageUtility::<T>::node_id_size_size()];
let archived =
unsafe { rkyv::access_unchecked::<<u16 as Archive>::Archived>(node_id_size_bytes) };
let archived = crate::access_archived::<<u16 as Archive>::Archived>(node_id_size_bytes)
.expect("torn or corrupt unsized index page: node id size fails validation");
let node_id_size =
rkyv::deserialize::<u16, rkyv::rancor::Error>(archived).expect("data should be valid");
let utility_len = UnsizedIndexPageUtility::<T>::persisted_size(
Expand All @@ -327,9 +354,9 @@ where
let offset = bytes.len() - *offset as usize;
let len = *len as usize;
let value_bytes = &bytes[offset..(offset + len)];
let archived = unsafe {
rkyv::access_unchecked::<<IndexValue<T> as Archive>::Archived>(value_bytes)
};
let archived =
crate::access_archived::<<IndexValue<T> as Archive>::Archived>(value_bytes)
.expect("torn or corrupt unsized index page: an entry fails validation");
let val = rkyv::deserialize::<_, rkyv::rancor::Error>(archived)
.expect("data should be valid");
index_values.push(val)
Expand Down
3 changes: 2 additions & 1 deletion src/page/index/page_for_unsized_cdc_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ where
+ for<'a> Serialize<
Strategy<Serializer<AlignedVec, ArenaHandle<'a>, Share>, rkyv::rancor::Error>,
>,
<T as Archive>::Archived: Deserialize<T, Strategy<Pool, rkyv::rancor::Error>>,
<T as Archive>::Archived: Deserialize<T, Strategy<Pool, rkyv::rancor::Error>>
+ for<'a> rkyv::bytecheck::CheckBytes<rkyv::api::high::HighValidator<'a, rkyv::rancor::Error>>,
{
pub fn apply_change_event(&mut self, event: ChangeEvent<Pair<T, Link>>) -> eyre::Result<()> {
match event {
Expand Down
Loading
Loading