diff --git a/Cargo.lock b/Cargo.lock index 30068ad..b56a7b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -496,6 +496,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + [[package]] name = "jiff" version = "0.2.16" @@ -622,6 +628,15 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "memmap2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +dependencies = [ + "libc", +] + [[package]] name = "namepool" version = "0.1.0" @@ -868,6 +883,15 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -1097,6 +1121,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + [[package]] name = "scopeguard" version = "1.2.0" @@ -1124,7 +1154,7 @@ dependencies = [ "regex", "search-cancel", "serde", - "slab", + "slab-mmap", "tempdir", "thin-vec", "tracing", @@ -1152,6 +1182,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -1172,6 +1212,19 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + [[package]] name = "serde_repr" version = "0.1.20" @@ -1207,6 +1260,19 @@ dependencies = [ "serde", ] +[[package]] +name = "slab-mmap" +version = "0.1.0" +dependencies = [ + "memmap2", + "postcard", + "serde", + "serde-value", + "serde_json", + "slab", + "tempfile", +] + [[package]] name = "smallvec" version = "1.15.1" diff --git a/Cargo.toml b/Cargo.toml index dfdc7d0..f160ed5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,5 +10,6 @@ members = [ "query-segmentation", "cardinal-syntax", "search-cancel", + "slab-mmap", ] exclude = ["cardinal"] diff --git a/cardinal/package-lock.json b/cardinal/package-lock.json index 797a1d1..7a2ca00 100644 --- a/cardinal/package-lock.json +++ b/cardinal/package-lock.json @@ -97,7 +97,6 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -454,7 +453,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -478,7 +476,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -1639,7 +1636,8 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -1699,7 +1697,6 @@ "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -1717,7 +1714,6 @@ "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -1728,7 +1724,6 @@ "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -1867,6 +1862,7 @@ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -1877,6 +1873,7 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -1941,7 +1938,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -2157,7 +2153,8 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/dunder-proto": { "version": "1.0.1", @@ -2555,7 +2552,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.23.2" } @@ -2602,7 +2598,6 @@ "integrity": "sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cssstyle": "^4.0.1", "data-urls": "^5.0.0", @@ -2699,6 +2694,7 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -2839,7 +2835,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -2898,6 +2893,7 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -2942,7 +2938,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -2955,7 +2950,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -2991,7 +2985,8 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-refresh": { "version": "0.18.0", @@ -3361,7 +3356,6 @@ "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -4473,7 +4467,6 @@ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", diff --git a/cardinal/src-tauri/Cargo.lock b/cardinal/src-tauri/Cargo.lock index 6ea342c..c688bea 100644 --- a/cardinal/src-tauri/Cargo.lock +++ b/cardinal/src-tauri/Cargo.lock @@ -2313,6 +2313,15 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "memmap2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.9.1" @@ -3596,7 +3605,7 @@ dependencies = [ "regex", "search-cancel", "serde", - "slab", + "slab-mmap", "thin-vec", "tracing", "typed-num", @@ -3863,8 +3872,14 @@ name = "slab" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "slab-mmap" +version = "0.1.0" dependencies = [ + "memmap2", "serde", + "tempfile", ] [[package]] diff --git a/search-cache/Cargo.toml b/search-cache/Cargo.toml index 7d5b7f1..06b82f3 100644 --- a/search-cache/Cargo.toml +++ b/search-cache/Cargo.toml @@ -12,7 +12,6 @@ query-segmentation = { path = "../query-segmentation" } search-cancel = { path = "../search-cancel" } zstd = { version = "0.13", features = ["zstdmt"] } serde = { version = "1", features = ["derive"] } -slab = { version = "0.4", features = ["serde"] } anyhow = "1.0.97" memchr = "2.7" crossbeam-channel = "0.5.15" @@ -28,6 +27,7 @@ hashbrown = { version = "0.16.0", features = ["serde"] } regex = "1" jiff = "0.2" rayon = "1.9" +slab-mmap = { path = "../slab-mmap" } [dev-dependencies] tempdir = "0.3" diff --git a/search-cache/src/slab.rs b/search-cache/src/slab.rs index 7ad926f..fb4f2d4 100644 --- a/search-cache/src/slab.rs +++ b/search-cache/src/slab.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +use slab_mmap::{Slab, SlabIter}; +use std::io; #[derive(Debug, Copy, Clone, Serialize, Deserialize)] #[repr(transparent)] @@ -53,7 +55,7 @@ impl SlabIndex { #[derive(Debug, Serialize, Deserialize)] #[serde(transparent)] #[repr(transparent)] -pub struct ThinSlab(slab::Slab); +pub struct ThinSlab(Slab); impl Default for ThinSlab { fn default() -> Self { @@ -62,12 +64,31 @@ impl Default for ThinSlab { } impl ThinSlab { + /// Construct a ThinSlab, panicking if the mmap initialization fails. pub fn new() -> Self { - Self(slab::Slab::new()) + Self::try_new().expect("ThinSlab::new failed to initialize memory-mapped slab") } + /// Construct a ThinSlab while propagating I/O failures to the caller. + pub fn try_new() -> io::Result { + Slab::new().map(Self) + } + + /// Inserts a value into the slab. + /// + /// # Panics + /// + /// This method panics if the underlying memory-mapped slab needs to grow and the operation fails due to an I/O error (e.g., disk full, permission denied, etc.). + /// If you need to handle such errors gracefully, use [`try_insert`](Self::try_insert) instead. pub fn insert(&mut self, value: T) -> SlabIndex { - SlabIndex::new(self.0.insert(value)) + self.try_insert(value) + .expect("ThinSlab::insert failed to grow backing slab") + } + + /// Insert a value while allowing callers to handle any I/O failures emitted + /// by the backing slab. + pub fn try_insert(&mut self, value: T) -> io::Result { + self.0.insert(value).map(SlabIndex::new) } pub fn get(&self, index: SlabIndex) -> Option<&T> { @@ -109,7 +130,7 @@ impl std::ops::IndexMut for ThinSlab { } } -pub struct ThinSlabIter<'a, T>(slab::Iter<'a, T>); +pub struct ThinSlabIter<'a, T>(SlabIter<'a, T>); impl<'a, T> Iterator for ThinSlabIter<'a, T> { type Item = (SlabIndex, &'a T); @@ -120,3 +141,28 @@ impl<'a, T> Iterator for ThinSlabIter<'a, T> { .map(|(index, value)| (SlabIndex::new(index), value)) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn thin_slab_try_new_is_empty() { + let slab = ThinSlab::::try_new().expect("ThinSlab::try_new should succeed"); + assert!(slab.is_empty()); + assert_eq!(slab.len(), 0); + } + + #[test] + fn thin_slab_try_insert_round_trips() { + let mut slab = ThinSlab::::try_new().expect("ThinSlab::try_new should succeed"); + let idx_try = slab + .try_insert(7) + .expect("ThinSlab::try_insert should succeed for in-memory data"); + assert_eq!(slab.get(idx_try), Some(&7)); + + let idx_insert = slab.insert(99); + assert_eq!(slab[idx_insert], 99); + assert_ne!(idx_try, idx_insert); + } +} diff --git a/slab-mmap/Cargo.toml b/slab-mmap/Cargo.toml new file mode 100644 index 0000000..8607d37 --- /dev/null +++ b/slab-mmap/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "slab-mmap" +version = "0.1.0" +edition = "2024" + +[dependencies] +memmap2 = "0.9" +tempfile = "3.16" +serde = { version = "1", features = ["derive"] } + +[dev-dependencies] +serde-value = "0.7" +slab = { version = "0.4", features = ["serde"] } +postcard = { version = "1", features = ["use-std"] } +serde_json = "1" diff --git a/slab-mmap/src/builder.rs b/slab-mmap/src/builder.rs new file mode 100644 index 0000000..260a8ea --- /dev/null +++ b/slab-mmap/src/builder.rs @@ -0,0 +1,68 @@ +use super::{Entry, Slab}; +use std::{io, num::NonZeroUsize}; + +/// A helper struct for reconstructing a `Slab` from arbitrary key/value pairs during deserialization. +/// +/// The `Builder` is used to incrementally rebuild a slab by inserting key/value pairs, +/// typically as part of a deserialization process. It ensures that the slab's internal +/// free list is correctly reconstructed by scanning for unoccupied slots after all pairs +/// have been inserted. This guarantees that subsequent allocations from the slab will +/// behave as expected. +/// +/// When inserting a key/value pair, if the key already exists and is occupied, the old +/// value is dropped and replaced with the new one. This matches the behavior of typical +/// deserialization, where the last occurrence of a key takes precedence. Duplicate keys +/// are handled by dropping the previous value to avoid memory leaks. +/// +/// # Usage +/// - Use `Builder::with_capacity` to create a builder with a given capacity. +/// - Call `pair(key, value)` for each key/value to insert. +/// - Call `build()` to finalize and obtain the reconstructed `Slab`. +/// +/// The serialized form is a sparse map from index to payload. During +/// deserialization we may see keys out of order, so we can't rely on the normal +/// push-based slab API. The builder is therefore allowed to poke at the private +/// freelist helpers to recreate exactly the same in-memory shape that the slab +/// would have produced organically. +pub(crate) struct Builder { + slab: Slab, +} + +impl Builder { + pub(crate) fn with_capacity(capacity: NonZeroUsize) -> io::Result { + Ok(Self { + slab: Slab::with_capacity(capacity)?, + }) + } + + pub(crate) fn pair(&mut self, key: usize, value: T) -> io::Result<()> { + self.slab.builder_reserve_slot(key)?; + let entry = self.slab.builder_entry_mut(key); + match entry { + Entry::Occupied(existing) => { + // Overwrite in place if the serialized data contains duplicate keys. + *existing = value; + } + Entry::Vacant(_) => { + *entry = Entry::Occupied(value); + self.slab.builder_increment_len(); + } + } + Ok(()) + } + + pub(crate) fn build(mut self) -> Slab { + let mut next = self.slab.builder_slots(); + for idx in (0..self.slab.builder_slots()).rev() { + let entry = self.slab.builder_entry_mut(idx); + if matches!(entry, Entry::Vacant(_)) { + // Reconstruct the freelist tail-first so that the next vacant + // insert reuses the smallest index, mirroring the runtime behaviour. + *entry = Entry::Vacant(next); + next = idx; + } + } + self.slab.builder_set_next(next); + self.slab + } +} diff --git a/slab-mmap/src/lib.rs b/slab-mmap/src/lib.rs new file mode 100644 index 0000000..df3ed24 --- /dev/null +++ b/slab-mmap/src/lib.rs @@ -0,0 +1,349 @@ +mod builder; +mod serde; + +use memmap2::{MmapMut, MmapOptions}; +use std::{ + fmt, io, + marker::PhantomData, + mem::{self, MaybeUninit}, + num::NonZeroUsize, + slice, +}; +use tempfile::NamedTempFile; + +/// Disk-backed slab that keeps the node payloads in a temporary mmap file so the OS +/// can page the largest structure in and out of memory. +pub struct Slab { + /// Anonymous temporary file that owns the on-disk backing storage. + file: NamedTempFile, + + /// Memory-mapped view of the file; stores the raw `Entry` array. + entries: MmapMut, + /// Number of slots currently mapped. + entries_capacity: NonZeroUsize, + /// Number of slots that have been initialized. + entries_len: usize, + + /// Logical element count (occupied slots only). + len: usize, + /// Head of the freelist (index of the next available slot). + next: usize, + + _marker: PhantomData, +} + +#[derive(Clone)] +enum Entry { + /// Slot is free; stores the index of the next free slot. + Vacant(usize), + /// Slot is occupied by a fully initialized value. + Occupied(T), +} + +pub(crate) const INITIAL_SLOTS: NonZeroUsize = NonZeroUsize::new(1024).unwrap(); + +impl Slab { + pub fn new() -> io::Result { + Self::with_capacity(INITIAL_SLOTS) + } + + fn with_capacity(capacity: NonZeroUsize) -> io::Result { + let mut file = NamedTempFile::new()?; + let mmap = Self::map_file(&mut file, capacity)?; + Ok(Self { + file, + entries: mmap, + len: 0, + next: 0, + entries_capacity: capacity, + entries_len: 0, + _marker: PhantomData, + }) + } + + fn map_file(file: &mut NamedTempFile, slots: NonZeroUsize) -> io::Result { + let bytes = (slots.get() as u64).saturating_mul(mem::size_of::>() as u64); + file.as_file_mut().set_len(bytes)?; + unsafe { MmapOptions::new().map_mut(file.as_file()) } + } + + /// Ensure the mmap can host at least `min_slots` entries. + /// + /// We intentionally copy the classic “double until large enough” strategy from + /// `Vec` to keep amortized O(1) `insert`s. Growing the mmap is expensive + /// (flush + set_len + remap), so avoiding incremental bumps keeps the number + /// of system calls low. + #[inline] + fn ensure_capacity(&mut self, min_capacity: NonZeroUsize) -> io::Result<()> { + if min_capacity <= self.entries_capacity { + return Ok(()); + } + let mut new_capacity = self.entries_capacity; + while new_capacity < min_capacity { + new_capacity = new_capacity.saturating_mul(NonZeroUsize::new(2).unwrap()); + } + self.remap(new_capacity) + } + + /// Flush dirty pages and remap the file with the new capacity. + /// + /// We now bubble up any flushing or mapping failure to the caller so the + /// application can decide whether to retry, fall back, or abort. After + /// remapping we simply update the capacity counters; all the occupied/vacant + /// metadata is still valid because indices remain stable. + #[inline] + fn remap(&mut self, new_capacity: NonZeroUsize) -> io::Result<()> { + assert!(new_capacity.get() >= self.entries_len); + self.entries.flush()?; + self.entries = Self::map_file(&mut self.file, new_capacity)?; + self.entries_capacity = new_capacity; + Ok(()) + } + + fn entries(&self) -> &[MaybeUninit>] { + unsafe { + slice::from_raw_parts( + self.entries.as_ptr().cast::>>(), + self.entries_capacity.get(), + ) + } + } + + fn entries_mut(&mut self) -> &mut [MaybeUninit>] { + unsafe { + slice::from_raw_parts_mut( + self.entries.as_mut_ptr().cast::>>(), + self.entries_capacity.get(), + ) + } + } + + fn entry(&self, index: usize) -> Option<&Entry> { + (index < self.entries_len) + .then(|| unsafe { self.entries().get_unchecked(index).assume_init_ref() }) + } + + fn entry_mut(&mut self, index: usize) -> Option<&mut Entry> { + (index < self.entries_len).then(|| unsafe { + self.entries_mut() + .get_unchecked_mut(index) + .assume_init_mut() + }) + } + + fn write_entry(&mut self, index: usize, entry: Entry) { + unsafe { + self.entries_mut().get_unchecked_mut(index).write(entry); + } + } + + /// Grow to the next power-of-two-ish capacity. + /// + /// This helper exists so both inserts and builder operations share the same + /// policy. Picking a doubling factor matches the behaviour of `Vec`, so + /// benchmark expectations carry over. + fn grow(&mut self) -> io::Result<()> { + let desired = self + .entries_capacity + .saturating_mul(NonZeroUsize::new(2).unwrap()); + self.ensure_capacity(desired) + } + + /// Insert a value, returning its stable index. + /// + /// This is a thin wrapper around `insert_at` so that other APIs (e.g. vacant + /// entry) can reuse the logic. We reuse freelist indices whenever possible; + /// if the freelist is empty we append to the end of the mmap buffer. + pub fn insert(&mut self, value: T) -> io::Result { + let key = self.next; + + self.insert_at(key, value)?; + + Ok(key) + } + + /// Core insertion routine shared by the public API and `VacantEntry`. + /// + /// The branch on `key == entries_len` mirrors the upstream `slab`. When the + /// freelist is empty we simply append and bump `entries_len`, which keeps + /// indices increasing monotonically. Otherwise, we pop from the freelist and + /// store the rescued `next` pointer back into `self.next`. + fn insert_at(&mut self, key: usize, value: T) -> io::Result<()> { + if key == self.entries_len { + if self.entries_len == self.entries_capacity.get() { + self.grow()?; + } + self.write_entry(self.entries_len, Entry::Occupied(value)); + self.entries_len += 1; + self.next = self.entries_len; + } else { + let entry = self + .entry_mut(key) + .expect("slot must exist when reusing keys"); + let next_free = match entry { + Entry::Vacant(next) => *next, + Entry::Occupied(_) => unreachable!("slot unexpectedly occupied"), + }; + *entry = Entry::Occupied(value); + self.next = next_free; + } + self.len += 1; + Ok(()) + } + + pub fn get(&self, index: usize) -> Option<&T> { + self.entry(index).and_then(|entry| match entry { + Entry::Occupied(value) => Some(value), + Entry::Vacant(_) => None, + }) + } + + pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { + self.entry_mut(index).and_then(|entry| match entry { + Entry::Occupied(value) => Some(value), + Entry::Vacant(_) => None, + }) + } + + /// Remove the value at `index` if it exists, pushing the slot onto the freelist. + /// + /// We use `mem::replace` instead of `Option::take` so that we can write the new + /// `Entry::Vacant(next)` atomically; this keeps the freelist consistent even if + /// the caller panics after we return. + pub fn try_remove(&mut self, index: usize) -> Option { + // Cache `next` up front; otherwise the mutable borrow returned by + // `entry_mut` would forbid us from reading another field on `self`. + let next_free = self.next; + if let Some(entry) = self.entry_mut(index) { + let prev = mem::replace(entry, Entry::Vacant(next_free)); + + if let Entry::Occupied(value) = prev { + self.len = self.len.saturating_sub(1); + self.next = index; + return Some(value); + } else { + *entry = prev; + } + } + None + } + + /// Returns the number of occupied slots in the slab. + /// + /// This is the count of elements currently stored, not the total capacity. + pub fn len(&self) -> usize { + self.len + } + + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + pub fn iter(&self) -> SlabIter<'_, T> { + SlabIter { + slab: self, + index: 0, + } + } +} + +impl Drop for Slab { + fn drop(&mut self) { + for i in 0..self.entries_len { + unsafe { + // Dropping every initialized entry is required because the mmap is + // just raw bytes. Invoking `assume_init_drop` is safe even for + // Vacant slots: the `Entry` enum will only drop the payload when it + // holds `Occupied(T)` and act as a no-op otherwise. + self.entries_mut().get_unchecked_mut(i).assume_init_drop(); + } + } + let _ = self.entries.flush(); + } +} + +impl std::ops::Index for Slab { + type Output = T; + + fn index(&self, index: usize) -> &Self::Output { + self.get(index).expect("invalid slab index") + } +} + +impl std::ops::IndexMut for Slab { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + self.get_mut(index).expect("invalid slab index") + } +} + +pub struct SlabIter<'a, T> { + slab: &'a Slab, + index: usize, +} + +impl<'a, T> IntoIterator for &'a Slab { + type Item = (usize, &'a T); + type IntoIter = SlabIter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a, T> Iterator for SlabIter<'a, T> { + type Item = (usize, &'a T); + + fn next(&mut self) -> Option { + // Walk every initialized slot in order and skip vacant holes. This mirrors + // the upstream slab iterator semantics so that serialized indices behave + // identically regardless of the backing store. + while self.index < self.slab.entries_len { + let idx = self.index; + self.index += 1; + if let Some(value) = self.slab.get(idx) { + return Some((idx, value)); + } + } + None + } +} + +impl fmt::Debug for Slab { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Slab") + .field("len", &self.len) + .field("next", &self.next) + .field("slots", &self.entries_len) + .field("capacity", &self.entries_capacity) + .finish() + } +} + +impl Slab { + pub(crate) fn builder_reserve_slot(&mut self, index: usize) -> io::Result<()> { + // The serde builder may request arbitrary keys out of order, so we must + // materialize every slot up to `index` and chain them into the freelist. + self.ensure_capacity(NonZeroUsize::new(index.saturating_add(1)).unwrap())?; + while self.entries_len <= index { + self.write_entry(self.entries_len, Entry::Vacant(self.next)); + self.entries_len += 1; + } + Ok(()) + } + + pub(crate) fn builder_entry_mut(&mut self, index: usize) -> &mut Entry { + self.entry_mut(index).expect("builder ensured slot exists") + } + + pub(crate) fn builder_slots(&self) -> usize { + self.entries_len + } + + pub(crate) fn builder_set_next(&mut self, next: usize) { + self.next = next; + } + + pub(crate) fn builder_increment_len(&mut self) { + self.len += 1; + } +} diff --git a/slab-mmap/src/serde.rs b/slab-mmap/src/serde.rs new file mode 100644 index 0000000..20a2ac4 --- /dev/null +++ b/slab-mmap/src/serde.rs @@ -0,0 +1,65 @@ +use super::{Slab, builder::Builder}; +use crate::INITIAL_SLOTS; +use core::{fmt, marker::PhantomData}; +use serde::{ + de::{Deserialize, Deserializer, Error as DeError, MapAccess, Visitor}, + ser::{Serialize, SerializeMap, Serializer}, +}; +use std::num::NonZeroUsize; + +impl Serialize for Slab +where + T: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut map_serializer = serializer.serialize_map(Some(self.len()))?; + for (key, value) in self { + map_serializer.serialize_key(&key)?; + map_serializer.serialize_value(value)?; + } + map_serializer.end() + } +} + +struct SlabVisitor(PhantomData); + +impl<'de, T> Visitor<'de> for SlabVisitor +where + T: Deserialize<'de>, +{ + type Value = Slab; + + fn expecting(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(fmt, "a map") + } + + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + let size = map.size_hint().unwrap_or_default(); + let size = NonZeroUsize::new(size).unwrap_or(INITIAL_SLOTS); + let mut builder = Builder::with_capacity(size).map_err(A::Error::custom)?; + + while let Some((key, value)) = map.next_entry()? { + builder.pair(key, value).map_err(A::Error::custom)? + } + + Ok(builder.build()) + } +} + +impl<'de, T> Deserialize<'de> for Slab +where + T: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_map(SlabVisitor(PhantomData)) + } +} diff --git a/slab-mmap/tests/basic_tests.rs b/slab-mmap/tests/basic_tests.rs new file mode 100644 index 0000000..068076f --- /dev/null +++ b/slab-mmap/tests/basic_tests.rs @@ -0,0 +1,299 @@ +use slab_mmap::Slab; +use std::fmt::Debug; + +// Generic helper used to exercise different data types +fn test_basic_operations(values: &[T]) +where + T: Clone + Eq + Debug, +{ + let mut slab = Slab::new().unwrap(); + let mut indices = Vec::new(); + + // Test insertions + for value in values { + let idx = slab.insert(value.clone()).unwrap(); + indices.push((idx, value.clone())); + assert_eq!(slab.len(), indices.len()); + } + + // Test reads + for (idx, value) in &indices { + assert_eq!(slab.get(*idx), Some(value)); + assert_eq!(slab[*idx], *value); + } + + // Test mutations + for (idx, _) in &indices { + if let Some(mut_ref) = slab.get_mut(*idx) { + // We could increment numeric types, but here we only ensure the ref is valid + // Because T is generic we avoid changing values; this just checks get_mut works + let _ = mut_ref; + } else { + panic!("Failed to get mutable reference for index {idx}"); + } + } + + // Test removal and reuse + let (first_idx, first_value) = indices[0].clone(); + let first_value_clone = first_value.clone(); + assert_eq!(slab.try_remove(first_idx), Some(first_value_clone)); + assert_eq!(slab.len(), indices.len() - 1); + assert!(slab.get(first_idx).is_none()); + + // Ensure removed slots are reused + let reused_idx = slab.insert(first_value).unwrap(); + assert_eq!(reused_idx, first_idx); + assert_eq!(slab.len(), indices.len()); + let reused_expect = indices[0].1.clone(); + assert_eq!(slab[reused_idx], reused_expect); +} + +#[test] +fn test_i32_operations() { + let values = [1, 2, 3, 4, 5, 100, -1, 0, i32::MAX, i32::MIN]; + test_basic_operations(&values); +} + +#[test] +fn test_string_operations() { + let values = [ + "hello", + "world", + "slab-mmap", + "test", + "", + "very long string to test memory management and serialization", + ]; + test_basic_operations(&values); +} + +#[test] +fn test_vector_operations() { + let values = [ + vec![1, 2, 3], + vec![], + vec![100; 100], + vec![i32::MAX, i32::MIN], + ]; + test_basic_operations(&values); +} + +#[test] +fn test_try_remove_invalid_indices() { + let mut slab = Slab::new().unwrap(); + + assert!(slab.try_remove(0).is_none()); + + let idx = slab.insert("value").unwrap(); + assert_eq!(slab.len(), 1); + + assert_eq!(slab.try_remove(idx), Some("value")); + assert!(slab.try_remove(idx).is_none()); + assert!(slab.try_remove(idx + 1).is_none()); + assert!(slab.is_empty()); +} + +#[test] +fn test_free_list_reuse_is_lifo() { + let mut slab = Slab::new().unwrap(); + + let first = slab.insert("first").unwrap(); + let second = slab.insert("second").unwrap(); + let third = slab.insert("third").unwrap(); + assert_eq!(slab.len(), 3); + + assert_eq!(slab.try_remove(second), Some("second")); + assert_eq!(slab.try_remove(first), Some("first")); + assert_eq!(slab.len(), 1); + + let reused_first = slab.insert("replacement_first").unwrap(); + assert_eq!(reused_first, first); + assert_eq!(slab[reused_first], "replacement_first"); + + let reused_second = slab.insert("replacement_second").unwrap(); + assert_eq!(reused_second, second); + assert_eq!(slab[third], "third"); + assert_eq!(slab.len(), 3); +} + +#[test] +fn test_complex_struct_operations() { + #[derive(Clone, PartialEq, Eq, Debug)] + struct TestStruct { + id: u32, + name: String, + data: Vec, + } + + let values = [ + TestStruct { + id: 1, + name: "test1".to_string(), + data: vec![1, 2, 3], + }, + TestStruct { + id: 2, + name: "test2".to_string(), + data: vec![], + }, + TestStruct { + id: u32::MAX, + name: "".to_string(), + data: vec![42; 100], + }, + ]; + test_basic_operations(&values); +} + +#[test] +fn test_capacity_growth() { + let mut slab = Slab::new().unwrap(); + let initial_capacity = 1024; // Implementation starts at capacity 1024 + + // Insert exactly the initial capacity + for i in 0..initial_capacity { + slab.insert(i).unwrap(); + } + + // Insert one more element to trigger growth + let idx = slab.insert(initial_capacity).unwrap(); + assert!(idx >= initial_capacity); + assert_eq!(slab[idx], initial_capacity); + + // Ensure every element remains accessible + for i in 0..=initial_capacity { + assert_eq!(slab[i], i); + } +} + +#[test] +fn test_index_trait() { + let mut slab = Slab::new().unwrap(); + let idx1 = slab.insert("first").unwrap(); + let idx2 = slab.insert("second").unwrap(); + + // Index trait behavior + assert_eq!(slab[idx1], "first"); + assert_eq!(slab[idx2], "second"); + + // IndexMut trait behavior + slab[idx1] = "modified_first"; + assert_eq!(slab[idx1], "modified_first"); + + // Invalid indices should panic + let _invalid_idx = idx2 + 100; + slab.try_remove(idx2); + + // Note: we can't assert the panic here because the test would fail even though it panics. + // We could use std::panic::catch_unwind, but that's outside this basic suite. +} + +#[test] +fn test_iterator_complete() { + let mut slab = Slab::new().unwrap(); + + // Insert a few elements + let indices = [ + slab.insert("a").unwrap(), + slab.insert("b").unwrap(), + slab.insert("c").unwrap(), + slab.insert("d").unwrap(), + slab.insert("e").unwrap(), + ]; + + // Remove elements to create holes + slab.try_remove(indices[1]); // Remove "b" + slab.try_remove(indices[3]); // Remove "d" + + // Collect iterator results + let mut collected: Vec<_> = slab.iter().collect(); + + // Iterator should visit in index order and skip empty slots + assert_eq!(collected.len(), 3); + + // Sort by index for easier comparison + collected.sort_by_key(|(idx, _)| *idx); + + // Verify output + assert_eq!(collected[0].1, &"a"); + assert_eq!(collected[1].1, &"c"); + assert_eq!(collected[2].1, &"e"); +} + +#[test] +fn test_debug_formatting() { + let mut slab = Slab::new().unwrap(); + slab.insert(42).unwrap(); + slab.insert(100).unwrap(); + + // Debug formatting should not panic + let debug_str = format!("{slab:?}"); + + // Output should mention the expected fields + assert!(debug_str.contains("Slab")); + assert!(debug_str.contains("len")); + assert!(debug_str.contains("next")); + assert!(debug_str.contains("slots")); + assert!(debug_str.contains("capacity")); +} + +#[test] +fn test_new_returns_ok() { + let slab: Slab = Slab::new().unwrap(); + assert!(slab.is_empty()); + assert_eq!(slab.len(), 0); +} + +#[test] +fn test_multiple_removals_and_insertions() { + let mut slab = Slab::new().unwrap(); + let mut keys = Vec::new(); + + // Insert 100 elements + for i in 0..100 { + keys.push(slab.insert(i).unwrap()); + } + + assert_eq!(slab.len(), 100); + + // Remove entries at even positions + for i in 0..50 { + assert_eq!(slab.try_remove(keys[i * 2]), Some(i * 2)); + } + + assert_eq!(slab.len(), 50); + + // Insert 50 more entries to ensure slot reuse + let mut new_keys = Vec::new(); + for i in 100..150 { + new_keys.push(slab.insert(i).unwrap()); + } + + assert_eq!(slab.len(), 100); + + // Check whether the new entries reused freed slots + let mut reused = 0; + for &new_key in &new_keys { + if keys.iter().step_by(2).any(|&k| k == new_key) { + reused += 1; + } + } + + // A meaningful number of slots should be reclaimed + assert!(reused > 0); +} + +#[test] +fn test_get_mut_modification() { + let mut slab = Slab::new().unwrap(); + let idx = slab.insert(Box::new(42)).unwrap(); + + { + // Grab a mutable reference and change it + let value = slab.get_mut(idx).unwrap(); + **value = 100; + } + + // Ensure the change sticks + assert_eq!(**slab.get(idx).unwrap(), 100); +} diff --git a/slab-mmap/tests/boundary_tests.rs b/slab-mmap/tests/boundary_tests.rs new file mode 100644 index 0000000..efe4b68 --- /dev/null +++ b/slab-mmap/tests/boundary_tests.rs @@ -0,0 +1,354 @@ +use serde_json::json; +use slab_mmap::Slab; + +#[test] +fn test_empty_slab_operations() { + let mut slab = Slab::::new().unwrap(); + + // Validate the basic properties of an empty slab + assert!(slab.is_empty()); + assert_eq!(slab.len(), 0); + + // Fetching from an empty slab yields None + assert!(slab.get(0).is_none()); + assert!(slab.get_mut(0).is_none()); + + // Removing from an empty slab yields None + assert!(slab.try_remove(0).is_none()); + + // Iterating an empty slab produces nothing + let collected: Vec<_> = slab.iter().collect(); + assert!(collected.is_empty()); + + // Insert the first element + let idx = slab.insert(42).unwrap(); + assert_eq!(idx, 0); + assert_eq!(slab.len(), 1); + assert_eq!(slab[idx], 42); +} + +#[test] +fn test_very_large_data() { + let mut slab = Slab::new().unwrap(); + + // Create a very large vector + let large_vector = vec![42; 1_000_000]; // ~4 MB of data + + // Insert the chunk + let idx = slab.insert(large_vector.clone()).unwrap(); + + // Verify data integrity + assert_eq!(slab.get(idx).unwrap(), &large_vector); + + // Remove it and verify + let removed = slab.try_remove(idx).unwrap(); + assert_eq!(removed, large_vector); +} + +#[test] +fn test_zero_sized_types() { + // Exercise zero-sized types + let mut slab = Slab::new().unwrap(); + + // Insert several zero-sized entries + let indices = [ + slab.insert(()).unwrap(), + slab.insert(()).unwrap(), + slab.insert(()).unwrap(), + ]; + + // Confirm the assigned indices + assert_eq!(indices[0], 0); + assert_eq!(indices[1], 1); + assert_eq!(indices[2], 2); + + // Ensure we can fetch them + assert!(slab.get(indices[0]).is_some()); + + // Test removal and reuse + slab.try_remove(indices[1]); + let reused_idx = slab.insert(()).unwrap(); + assert_eq!(reused_idx, indices[1]); +} + +#[test] +fn test_edge_case_indices() { + let mut slab = Slab::new().unwrap(); + + // Insert elements + for i in 0..100 { + slab.insert(i).unwrap(); + } + + // Accessing out-of-range indices should fail + assert!(slab.get(1000).is_none()); + assert!(slab.get_mut(1000).is_none()); + assert!(slab.try_remove(1000).is_none()); + + // Reference the theoretical max slot (never reached in practice) + // We no longer cap slots, but this test stresses growth by alternating ops. + + // Alternate insert/remove operations to create sparse indices + for i in 0..50 { + slab.try_remove(i); + } + + // Confirm the holes exist + for i in 0..50 { + assert!(slab.get(i).is_none()); + } + + // Insert again to confirm reuse + for i in 0..50 { + let idx = slab.insert(i + 1000).unwrap(); + assert!(idx < 50); // Should reuse previously freed slots + } +} + +#[test] +fn test_struct_with_drop() { + // Ensure types with Drop clean up correctly + use std::sync::Arc; + + let counter = Arc::new(()); + + #[derive(Clone)] + struct DropTracked { + _inner: Arc<()>, + } + + impl Drop for DropTracked { + fn drop(&mut self) { + // Dropping should reduce the Arc strong count + } + } + + let mut slab = Slab::new().unwrap(); + + // Insert the Arc-backed structs + let indices = [ + slab.insert(DropTracked { + _inner: counter.clone(), + }) + .unwrap(), + slab.insert(DropTracked { + _inner: counter.clone(), + }) + .unwrap(), + slab.insert(DropTracked { + _inner: counter.clone(), + }) + .unwrap(), + ]; + + // Strong count should be 4 (1 original + 3 inserts) + assert_eq!(Arc::strong_count(&counter), 4); + + // Removing one should decrement the count + slab.try_remove(indices[1]); + assert_eq!(Arc::strong_count(&counter), 3); + + // Inserting again increases the count + slab.insert(DropTracked { + _inner: counter.clone(), + }) + .unwrap(); + assert_eq!(Arc::strong_count(&counter), 4); + + // Dropping the slab should drop the remaining elements + drop(slab); + assert_eq!(Arc::strong_count(&counter), 1); +} + +#[test] +fn test_memory_mapping_edge_cases() { + // Exercise memory-mapped boundary behavior + // Note: we stick to high-level effects because the OS details are opaque + + let mut slab = Slab::new().unwrap(); + + // Start small and force gradual growth + let current_capacity = 1024; // Initial capacity + + // Insert until multiple resizes trigger + for i in 0..current_capacity * 4 { + slab.insert(i).unwrap(); + } + + // Ensure every stored value is readable + for i in 0..current_capacity * 4 { + assert_eq!(slab.get(i).unwrap(), &i); + } +} + +#[test] +fn test_saturating_add_edge_case() { + // Exercise the next_slot saturating_add behavior + let mut slab = Slab::new().unwrap(); + + // We cannot reach u32::MAX here but we can rely on saturating_add + // The implementation uses saturating_add to prevent overflow + + // Insert a large number of elements to drive growth + for i in 0..10_000 { + slab.insert(i).unwrap(); + } + + assert_eq!(slab.len(), 10_000); +} + +#[test] +fn test_mixed_data_types_performance() { + // Mix differently sized data variants + #[derive(Clone, PartialEq, Debug)] + enum Data { + Small(i32), + Medium(Vec), + Large(Vec), + } + + let mut slab = Slab::new().unwrap(); + + // Insert the variants + let small_idx = slab.insert(Data::Small(42)).unwrap(); + let medium_idx = slab.insert(Data::Medium(vec![1u8; 100])).unwrap(); + let large_idx = slab.insert(Data::Large(vec![2u8; 10_000])).unwrap(); + + // Ensure each variant remains accessible + match &slab[small_idx] { + Data::Small(v) => assert_eq!(*v, 42), + _ => panic!("unexpected variant for small"), + } + match &slab[medium_idx] { + Data::Medium(v) => assert_eq!(v.len(), 100), + _ => panic!("unexpected variant for medium"), + } + match &slab[large_idx] { + Data::Large(v) => assert_eq!(v.len(), 10_000), + _ => panic!("unexpected variant for large"), + } + + // Remove one and confirm reuse + slab.try_remove(medium_idx); + let new_medium_idx = slab.insert(Data::Medium(vec![3u8; 50])).unwrap(); + assert_eq!(new_medium_idx, medium_idx); + match &slab[new_medium_idx] { + Data::Medium(v) => assert_eq!(v.len(), 50), + _ => panic!("unexpected variant for new medium"), + } +} + +#[test] +fn test_ensure_capacity_edge_cases() { + // Cover ensure_capacity edge cases by forcing repeated growth calls. + + let mut slab = Slab::new().unwrap(); + + // Trigger growth via inserts rather than calling the private helper + let target_capacity = 2048; + + for i in 0..target_capacity { + slab.insert(i).unwrap(); + } + + for i in 0..target_capacity { + assert_eq!(slab.get(i), Some(&i)); + } +} + +#[test] +fn test_read_after_delete_behavior() { + // Validate reads after deletion + let mut slab = Slab::new().unwrap(); + + let idx = slab.insert("hello").unwrap(); + assert_eq!(slab.get(idx), Some(&"hello")); + + // Remove the element + assert_eq!(slab.try_remove(idx), Some("hello")); + + // Ensure nothing can read it afterwards + assert!(slab.get(idx).is_none()); + assert!(slab.get_mut(idx).is_none()); + assert!(slab.try_remove(idx).is_none()); + + // Reinsert at the same index + slab.insert("world").unwrap(); + assert_eq!(slab.get(idx), Some(&"world")); +} + +#[test] +fn test_insert_remove_alternating() { + // Stress alternating insert/remove behavior + let mut slab = Slab::new().unwrap(); + let mut keys = Vec::new(); + + // Perform the alternating sequence + for i in 0..1000 { + if i % 3 == 0 && !keys.is_empty() { + // Remove the oldest key + let key = keys.remove(0); + slab.try_remove(key); + } else { + // Insert a new element + keys.push(slab.insert(i).unwrap()); + } + } + + // Verify the final slab state + assert_eq!(slab.len(), keys.len()); + + // Remaining keys should still be readable + for &key in keys.iter() { + assert!(slab.get(key).is_some()); + } +} + +#[test] +fn test_memory_usage_with_holes() { + // Explore memory usage with many holes + let mut slab = Slab::new().unwrap(); + + // Insert 1000 elements + for i in 0..1000 { + slab.insert(i).unwrap(); + } + + // Remove everything except the last entry + for i in 0..999 { + slab.try_remove(i); + } + + // Confirm only the last entry remains + assert_eq!(slab.len(), 1); + assert_eq!(slab.get(999), Some(&999)); + + // Inserting new entries should reuse freed slots + for i in 1000..1500 { + let idx = slab.insert(i).unwrap(); + assert!(idx < 999); // Should reuse earlier slots + } + + assert_eq!(slab.len(), 501); +} + +#[test] +fn test_sparse_deserialize_rebuilds_free_list() { + let json = json!({ + "0": 10, + "1023": 20, + "2048": 30 + }); + + let mut slab: Slab = serde_json::from_value(json).expect("deserialize sparse slab"); + assert_eq!(slab.len(), 3); + assert_eq!(slab.get(0), Some(&10)); + assert_eq!(slab.get(1023), Some(&20)); + assert_eq!(slab.get(2048), Some(&30)); + assert!(slab.get(1).is_none()); + + let reused_idx = slab.insert(99).unwrap(); + assert_eq!(reused_idx, 1); + assert_eq!(slab.get(reused_idx), Some(&99)); + assert_eq!(slab.len(), 4); +} diff --git a/slab-mmap/tests/concurrency_tests.rs b/slab-mmap/tests/concurrency_tests.rs new file mode 100644 index 0000000..18dc91b --- /dev/null +++ b/slab-mmap/tests/concurrency_tests.rs @@ -0,0 +1,292 @@ +use slab_mmap::Slab; +use std::{ + sync::{Arc, Mutex}, + thread, + vec::Vec, +}; + +#[test] +fn test_multiple_readers() { + // Create a slab and populate it + let slab = Arc::new({ + let mut slab = Slab::new().unwrap(); + for i in 0..1000 { + slab.insert(i).unwrap(); + } + slab + }); + + let mut handles = Vec::new(); + + // Spawn 10 reader threads + for thread_id in 0..10 { + let slab_clone = slab.clone(); + let handle = thread::spawn(move || { + // Each thread reads a different range + let start = (thread_id * 100) % 901; + let end = start + 100; + + for i in start..end { + let value = slab_clone + .get(i) + .unwrap_or_else(|| panic!("Failed to get index {i}")); + assert_eq!(*value, i); + } + }); + handles.push(handle); + } + + // Wait for every thread to finish + for handle in handles { + handle.join().expect("Thread failed"); + } +} + +#[test] +fn test_sequential_write_read() { + // Write sequentially and read afterwards + let shared_slab = Arc::new(Mutex::new(Slab::new().unwrap())); + let keys = Arc::new(Mutex::new(Vec::new())); + + // Write phase + { + let mut slab = shared_slab.lock().unwrap(); + for i in 0..1000 { + let key = slab.insert(i).unwrap(); + keys.lock().unwrap().push(key); + } + } + + // Read phase + let keys_clone = keys.clone(); + let handle = thread::spawn(move || { + let slab = shared_slab.lock().unwrap(); + let keys = keys_clone.lock().unwrap(); + + for (i, &key) in keys.iter().enumerate() { + let value = slab + .get(key) + .unwrap_or_else(|| panic!("Failed to get key {key}")); + assert_eq!(*value, i as u32); + } + }); + + handle.join().expect("Thread failed"); +} + +#[test] +fn test_concurrent_insert_and_remove() { + // Note: this test needs external synchronization because Slab isn't thread-safe + let shared_slab = Arc::new(Mutex::new(Slab::new().unwrap())); + let keys = Arc::new(Mutex::new(Vec::new())); + + let mut handles = Vec::new(); + + // Spawn multiple threads that insert and remove + for thread_id in 0..4 { + let shared_slab_clone = shared_slab.clone(); + let keys_clone = keys.clone(); + + let handle = thread::spawn(move || { + for i in 0..100 { + let value = thread_id * 1000 + i; + + // Insert + let key = { + let mut slab = shared_slab_clone.lock().unwrap(); + let key = slab.insert(value).unwrap(); + keys_clone.lock().unwrap().push(key); + key + }; + + // Brief sleep to allow other threads to run + thread::sleep(std::time::Duration::from_micros(10)); + + // Remove + { + let mut slab = shared_slab_clone.lock().unwrap(); + let removed = slab.try_remove(key); + assert_eq!(removed, Some(value)); + } + } + }); + + handles.push(handle); + } + + // Wait for every thread to finish + for handle in handles { + handle.join().expect("Thread failed"); + } + + // Ensure the slab ends empty + let slab = shared_slab.lock().unwrap(); + assert!(slab.is_empty()); +} + +#[test] +fn test_stress_test_with_mutex() { + // Use a Mutex to model multi-threaded slab access + let shared_slab = Arc::new(Mutex::new(Slab::new().unwrap())); + let operation_count = 10000; + + let mut handles = Vec::new(); + + // Spawn worker threads + for _ in 0..4 { + let shared_slab_clone = shared_slab.clone(); + + let handle = thread::spawn(move || { + let mut local_keys = Vec::new(); + + for i in 0..operation_count { + if i % 3 == 0 && !local_keys.is_empty() { + // Remove + let key = local_keys.pop().unwrap(); + let mut slab = shared_slab_clone.lock().unwrap(); + slab.try_remove(key); + } else { + // Insert + let mut slab = shared_slab_clone.lock().unwrap(); + let key = slab.insert(i).unwrap(); + local_keys.push(key); + } + } + + // Clean up any leftover local keys + for key in local_keys { + let mut slab = shared_slab_clone.lock().unwrap(); + slab.try_remove(key); + } + }); + + handles.push(handle); + } + + // Wait for every thread to finish + for handle in handles { + handle.join().expect("Thread failed"); + } + + // Verify the final state + let slab = shared_slab.lock().unwrap(); + assert!(slab.is_empty()); +} + +#[test] +fn test_multithreaded_iteration() { + // Build a pre-filled slab + let slab = Arc::new({ + let mut slab = Slab::new().unwrap(); + for i in 0..1000 { + slab.insert(i).unwrap(); + } + // Remove some elements to create holes + for i in 0..1000 { + if i % 3 == 0 { + slab.try_remove(i); + } + } + slab + }); + + let mut handles = Vec::new(); + + // Let multiple threads iterate over the slab concurrently + for _ in 0..5 { + let slab_clone = slab.clone(); + + let handle = thread::spawn(move || { + let mut count = 0; + let mut sum = 0; + + for (_, &value) in slab_clone.iter() { + count += 1; + sum += value; + } + + // Validate the iteration results + assert_eq!(count, 666); // Removing 1/3 leaves 666 elements + (count, sum) + }); + + handles.push(handle); + } + + // Collect every thread's results + let mut results = Vec::new(); + for handle in handles { + results.push(handle.join().expect("Thread failed")); + } + + // Ensure every thread observed the same values + for &(count, sum) in &results[1..] { + assert_eq!(count, results[0].0); + assert_eq!(sum, results[0].1); + } +} + +#[test] +fn test_concurrent_with_reuse() { + // Test slot reuse when multiple threads contend + let shared_slab = Arc::new(Mutex::new(Slab::new().unwrap())); + let keys = Arc::new(Mutex::new(Vec::new())); + let total_ops = 1000; + + // Seed the slab with an initial batch + { + let mut slab = shared_slab.lock().unwrap(); + let mut initial_keys = Vec::new(); + for i in 0..100 { + let key = slab.insert(i).unwrap(); + initial_keys.push(key); + } + drop(slab); + *keys.lock().unwrap() = initial_keys; + } + + let mut handles = Vec::new(); + + // Spawn threads that delete and reinsert + for thread_id in 0..4 { + let shared_slab_clone = shared_slab.clone(); + let keys_clone = keys.clone(); + + let handle = thread::spawn(move || { + for i in 0..total_ops { + let base_value = thread_id * total_ops + i; + + // Track active slots through the shared key list + let key = { + let mut keys_vec = keys_clone.lock().unwrap(); + if keys_vec.is_empty() { + continue; + } + let idx = (base_value as usize) % keys_vec.len(); + keys_vec.remove(idx) + }; + + // Remove and insert again, expecting to reuse the freed slot + let new_key = { + let mut slab = shared_slab_clone.lock().unwrap(); + let removed = slab.try_remove(key); + assert!(removed.is_some()); + slab.insert(base_value).unwrap() + }; + + keys_clone.lock().unwrap().push(new_key); + } + }); + + handles.push(handle); + } + + // Wait for every thread to finish + for handle in handles { + handle.join().expect("Thread failed"); + } + + // Verify the final state is consistent + let slab = shared_slab.lock().unwrap(); + assert!(slab.len() <= 100); // Maximum possible number of occupied slots +} diff --git a/slab-mmap/tests/serde_bridge.rs b/slab-mmap/tests/serde_bridge.rs new file mode 100644 index 0000000..83d6c66 --- /dev/null +++ b/slab-mmap/tests/serde_bridge.rs @@ -0,0 +1,151 @@ +use serde::Deserialize; +use serde_value::{Value, ValueDeserializer}; +use slab_mmap::Slab; +use std::collections::BTreeMap; + +fn canonical_bytes_from_map(map: &BTreeMap) -> Vec { + let len = map + .keys() + .copied() + .max() + .map(|m| m as usize + 1) + .unwrap_or(0); + let mut seq: Vec> = vec![None; len]; + for (k, v) in map { + let idx = *k as usize; + let num = match v { + Value::I64(n) => *n as i32, + Value::U64(n) => *n as i32, + Value::I32(n) => *n, + Value::U32(n) => *n as i32, + other => panic!("unexpected value kind in map: {other:?}"), + }; + seq[idx] = Some(num); + } + postcard::to_allocvec(&seq).expect("encode postcard") +} + +// Generate a deterministic mixed pattern of inserts/removes >= 1200 ops. +fn build_patterned_slabs() -> (Slab, slab::Slab) { + let mut mmap = Slab::new().unwrap(); + let mut slab_std = slab::Slab::with_capacity(8); + let mut keys = Vec::new(); + let mut seed: u64 = 0x1234_5678_9abc_def0; + + for step in 0..1500 { + // xorshift64* for deterministic spread + seed ^= seed << 7; + seed ^= seed >> 9; + seed ^= seed << 8; + let do_insert = keys.is_empty() || seed % 3 != 0; + if do_insert { + let value = (step ^ (seed as i32)) & 0x7fff; + let k_m = mmap.insert(value).unwrap(); + let k_s = slab_std.insert(value); + keys.push((k_m, k_s)); + } else { + // Remove a pseudo-random key to leave holes. + let idx = (seed as usize) % keys.len(); + let (km, ks) = keys.swap_remove(idx); + assert_eq!(mmap.try_remove(km), slab_std.try_remove(ks)); + } + } + (mmap, slab_std) +} + +fn canonical_map_from_value(value: Value) -> BTreeMap { + match value { + Value::Map(entries) => entries + .into_iter() + .map(|(k, v)| match k { + Value::U64(i) => (i, v), + Value::I64(i) => (i as u64, v), + other => panic!("unexpected key: {other:?}"), + }) + .collect(), + Value::Seq(seq) => seq + .into_iter() + .enumerate() + .filter_map(|(idx, entry)| match entry { + Value::Option(Some(inner)) => Some((idx as u64, *inner)), + Value::Option(None) => None, + other => panic!("unexpected seq entry: {other:?}"), + }) + .collect(), + other => panic!("unexpected top-level: {other:?}"), + } +} + +fn map_to_value(map: &BTreeMap) -> Value { + Value::Map( + map.iter() + .map(|(k, v)| (Value::U64(*k), v.clone())) + .collect(), + ) +} + +#[test] +fn serde_bridge_round_trip_large_pattern() { + let (mmap, slab_std) = build_patterned_slabs(); + + // Normalize both encodings to maps of index -> value. + let mmap_map = canonical_map_from_value(serde_value::to_value(&mmap).unwrap()); + let slab_map = canonical_map_from_value(serde_value::to_value(&slab_std).unwrap()); + assert_eq!(mmap_map, slab_map, "canonical entry maps must match"); + assert_eq!( + canonical_bytes_from_map(&mmap_map), + canonical_bytes_from_map(&slab_map), + "canonical postcard bytes must match" + ); + + // Convert slab (map encoding) and deserialize into mmap slab. + let map_from_slab = map_to_value(&slab_map); + let mmap_from_slab: Slab = Slab::deserialize(ValueDeserializer::< + serde_value::DeserializerError, + >::new(map_from_slab)) + .expect("decode mmap from slab value"); + let mmap_from_slab_map = + canonical_map_from_value(serde_value::to_value(&mmap_from_slab).unwrap()); + assert_eq!(mmap_from_slab_map, slab_map); + + // Convert mmap map encoding and deserialize into std slab. + let map_from_mmap = map_to_value(&mmap_map); + let slab_from_mmap: slab::Slab = slab::Slab::deserialize(ValueDeserializer::< + serde_value::DeserializerError, + >::new(map_from_mmap)) + .expect("decode std slab from mmap value"); + let slab_from_mmap_map = + canonical_map_from_value(serde_value::to_value(&slab_from_mmap).unwrap()); + assert_eq!(slab_from_mmap_map, mmap_map); +} + +#[test] +fn serde_bridge_empty_and_sparse_extremes() { + // Empty + let mmap_empty = Slab::::new().unwrap(); + let slab_empty = slab::Slab::::new(); + let mmap_map = canonical_map_from_value(serde_value::to_value(&mmap_empty).unwrap()); + let slab_map = canonical_map_from_value(serde_value::to_value(&slab_empty).unwrap()); + assert_eq!(mmap_map, slab_map); + + // Sparse with high index hole coverage (force > 1000 slots) + let mut mmap = Slab::new().unwrap(); + let mut slab_std = slab::Slab::new(); + let mut kept = Vec::new(); + for i in 0..1200 { + let km = mmap.insert(i).unwrap(); + let ks = slab_std.insert(i); + // Drop most of them to create deep holes + if i % 10 == 0 { + kept.push((km, ks)); + } else { + assert_eq!(mmap.try_remove(km), slab_std.try_remove(ks)); + } + } + // Ensure a few survivors remain. + assert!(mmap.len() >= 100); + assert_eq!(mmap.len(), slab_std.len()); + let mmap_map = canonical_map_from_value(serde_value::to_value(&mmap).unwrap()); + let slab_map = canonical_map_from_value(serde_value::to_value(&slab_std).unwrap()); + assert_eq!(mmap_map, slab_map); +} diff --git a/slab-mmap/tests/serde_bridge_huge.rs b/slab-mmap/tests/serde_bridge_huge.rs new file mode 100644 index 0000000..1743175 --- /dev/null +++ b/slab-mmap/tests/serde_bridge_huge.rs @@ -0,0 +1,1376 @@ +use serde::Deserialize; +use serde_value::{Value, ValueDeserializer}; +use slab_mmap::Slab; +use std::collections::BTreeMap; + +#[derive(Clone, Copy, Debug)] +enum Op { + Insert(i32), + RemoveKeyIdx(usize), + RemoveRaw(usize), + Checkpoint, +} + +// Auto-generated to exercise serde bridges across a large patterned sequence. +const OPS: &[Op] = &[ + Op::Insert(0), + Op::Insert(17), + Op::Insert(34), + Op::Insert(51), + Op::Insert(68), + Op::Insert(85), + Op::Insert(102), + Op::Insert(119), + Op::Insert(136), + Op::Insert(153), + Op::Insert(170), + Op::Insert(187), + Op::Insert(204), + Op::Insert(221), + Op::Insert(238), + Op::Insert(255), + Op::Insert(272), + Op::Insert(289), + Op::Insert(306), + Op::Insert(323), + Op::Insert(340), + Op::Insert(357), + Op::Insert(374), + Op::Insert(391), + Op::Insert(408), + Op::Insert(425), + Op::Insert(442), + Op::Insert(459), + Op::Insert(476), + Op::Insert(493), + Op::Insert(510), + Op::Insert(527), + Op::Insert(544), + Op::Insert(561), + Op::Insert(578), + Op::Insert(595), + Op::Insert(612), + Op::Insert(629), + Op::Insert(646), + Op::Insert(663), + Op::Insert(680), + Op::Insert(697), + Op::Insert(714), + Op::Insert(731), + Op::Insert(748), + Op::Insert(765), + Op::Insert(782), + Op::Insert(799), + Op::Insert(816), + Op::Insert(833), + Op::Insert(2345), + Op::Insert(2371), + Op::RemoveRaw(799), + Op::Insert(2324), + Op::Insert(2053), + Op::Checkpoint, + Op::RemoveKeyIdx(2), + Op::RemoveKeyIdx(11), + Op::Insert(2311), + Op::Insert(2711), + Op::Insert(2330), + Op::RemoveKeyIdx(9), + Op::Insert(2701), + Op::RemoveRaw(875), + Op::RemoveRaw(873), + Op::Insert(2911), + Op::RemoveKeyIdx(7), + Op::Insert(2743), + Op::Checkpoint, + Op::Insert(2641), + Op::Insert(2645), + Op::RemoveRaw(943), + Op::Insert(2983), + Op::Checkpoint, + Op::Insert(3145), + Op::Insert(2885), + Op::Insert(3131), + Op::Insert(2981), + Op::Insert(3414), + Op::Insert(3308), + Op::RemoveKeyIdx(58), + Op::RemoveRaw(972), + Op::Insert(3540), + Op::Insert(3291), + Op::Insert(3625), + Op::Insert(3640), + Op::Insert(3468), + Op::RemoveRaw(1030), + Op::Insert(3630), + Op::RemoveRaw(1068), + Op::RemoveRaw(1059), + Op::RemoveRaw(1029), + Op::Insert(3503), + Op::RemoveRaw(1004), + Op::RemoveRaw(1037), + Op::Insert(3746), + Op::RemoveKeyIdx(68), + Op::Checkpoint, + Op::Insert(3967), + Op::Insert(3883), + Op::RemoveKeyIdx(59), + Op::Insert(4265), + Op::Insert(4148), + Op::RemoveRaw(1082), + Op::RemoveRaw(1136), + Op::RemoveKeyIdx(71), + Op::Insert(4230), + Op::RemoveRaw(1144), + Op::Insert(4447), + Op::Checkpoint, + Op::Checkpoint, + Op::Insert(4184), + Op::Insert(4463), + Op::Insert(4225), + Op::Insert(4658), + Op::Insert(4816), + Op::Insert(4666), + Op::Insert(4417), + Op::Insert(4663), + Op::RemoveKeyIdx(72), + Op::Checkpoint, + Op::Insert(4917), + Op::Insert(4811), + Op::RemoveRaw(1196), + Op::RemoveKeyIdx(63), + Op::Insert(5175), + Op::RemoveRaw(1204), + Op::RemoveKeyIdx(58), + Op::Insert(4934), + Op::Insert(5136), + Op::Insert(4997), + Op::Insert(5188), + Op::Checkpoint, + Op::RemoveKeyIdx(71), + Op::RemoveKeyIdx(68), + Op::Insert(5358), + Op::Checkpoint, + Op::RemoveKeyIdx(74), + Op::Insert(5700), + Op::RemoveKeyIdx(74), + Op::Insert(5741), + Op::Insert(5723), + Op::Insert(5562), + Op::Insert(5478), + Op::RemoveKeyIdx(65), + Op::RemoveKeyIdx(63), + Op::Insert(5523), + Op::Checkpoint, + Op::Insert(5542), + Op::Insert(5667), + Op::Insert(5759), + Op::RemoveRaw(1315), + Op::Insert(5844), + Op::RemoveRaw(1374), + Op::Insert(6292), + Op::RemoveRaw(1307), + Op::Insert(5860), + Op::Insert(6348), + Op::Insert(6374), + Op::RemoveRaw(1327), + Op::RemoveKeyIdx(59), + Op::RemoveKeyIdx(67), + Op::Insert(6346), + Op::RemoveKeyIdx(70), + Op::Insert(6079), + Op::RemoveRaw(1434), + Op::Checkpoint, + Op::Insert(6333), + Op::RemoveRaw(1449), + Op::RemoveKeyIdx(68), + Op::Checkpoint, + Op::Insert(6701), + Op::Checkpoint, + Op::RemoveRaw(1439), + Op::Insert(6911), + Op::Insert(6629), + Op::Insert(6919), + Op::RemoveKeyIdx(55), + Op::Insert(6806), + Op::RemoveKeyIdx(58), + Op::Insert(6660), + Op::Checkpoint, + Op::Checkpoint, + Op::Insert(7134), + Op::RemoveKeyIdx(64), + Op::Checkpoint, + Op::Insert(7124), + Op::RemoveKeyIdx(64), + Op::Insert(7099), + Op::RemoveRaw(1505), + Op::Insert(7448), + Op::RemoveRaw(1522), + Op::RemoveKeyIdx(64), + Op::RemoveRaw(1504), + Op::Insert(7453), + Op::Insert(7424), + Op::Insert(7769), + Op::Checkpoint, + Op::Insert(7546), + Op::RemoveKeyIdx(69), + Op::Checkpoint, + Op::RemoveKeyIdx(67), + Op::Insert(7474), + Op::RemoveRaw(1547), + Op::Insert(7999), + Op::RemoveKeyIdx(62), + Op::Insert(7644), + Op::Insert(7813), + Op::Insert(8202), + Op::Insert(8162), + Op::Insert(8100), + Op::Insert(7884), + Op::Insert(8174), + Op::RemoveKeyIdx(72), + Op::Insert(8028), + Op::Insert(8065), + Op::Checkpoint, + Op::RemoveKeyIdx(62), + Op::Insert(8154), + Op::Checkpoint, + Op::RemoveKeyIdx(68), + Op::Checkpoint, + Op::Insert(8390), + Op::Insert(8427), + Op::RemoveRaw(1708), + Op::RemoveKeyIdx(60), + Op::RemoveKeyIdx(70), + Op::Insert(8630), + Op::Insert(8799), + Op::RemoveKeyIdx(67), + Op::RemoveRaw(1689), + Op::Insert(9141), + Op::Insert(8881), + Op::RemoveKeyIdx(69), + Op::Checkpoint, + Op::Checkpoint, + Op::RemoveRaw(1796), + Op::Insert(9044), + Op::Checkpoint, + Op::Insert(9272), + Op::Insert(9441), + Op::RemoveRaw(1800), + Op::RemoveRaw(1791), + Op::Insert(9508), + Op::RemoveKeyIdx(56), + Op::RemoveKeyIdx(66), + Op::RemoveKeyIdx(57), + Op::Insert(9304), + Op::Insert(9517), + Op::Insert(9521), + Op::Insert(9338), + Op::Insert(9760), + Op::Insert(9346), + Op::RemoveRaw(1790), + Op::RemoveKeyIdx(70), + Op::Insert(9754), + Op::RemoveRaw(1805), + Op::Insert(9817), + Op::Insert(9546), + Op::Insert(9682), + Op::Checkpoint, + Op::RemoveRaw(1865), + Op::Insert(9771), + Op::Insert(10050), + Op::Insert(9856), + Op::Insert(9849), + Op::Insert(10304), + Op::Insert(9978), + Op::RemoveKeyIdx(65), + Op::Insert(10283), + Op::Insert(10375), + Op::RemoveRaw(1950), + Op::RemoveKeyIdx(62), + Op::Insert(10398), + Op::RemoveKeyIdx(69), + Op::Insert(10351), + Op::Checkpoint, + Op::RemoveKeyIdx(73), + Op::RemoveKeyIdx(73), + Op::RemoveKeyIdx(60), + Op::Insert(10701), + Op::RemoveKeyIdx(60), + Op::Insert(11006), + Op::Insert(10735), + Op::Insert(10640), + Op::Insert(10677), + Op::RemoveRaw(2018), + Op::RemoveKeyIdx(55), + Op::Insert(11151), + Op::Insert(11287), + Op::Insert(11302), + Op::RemoveKeyIdx(59), + Op::Checkpoint, + Op::Insert(10907), + Op::RemoveRaw(24), + Op::RemoveRaw(8), + Op::RemoveRaw(6), + Op::Insert(11121), + Op::Insert(11103), + Op::Insert(11624), + Op::Checkpoint, + Op::Insert(11610), + Op::Insert(11317), + Op::Insert(11552), + Op::Insert(11512), + Op::Insert(11549), + Op::RemoveKeyIdx(68), + Op::RemoveKeyIdx(57), + Op::Insert(11583), + Op::Insert(12027), + Op::Insert(11723), + Op::Insert(11892), + Op::RemoveKeyIdx(59), + Op::Insert(11922), + Op::Insert(11717), + Op::RemoveKeyIdx(56), + Op::RemoveKeyIdx(70), + Op::Insert(11861), + Op::Insert(11832), + Op::RemoveRaw(128), + Op::RemoveKeyIdx(69), + Op::Insert(11888), + Op::Insert(12035), + Op::Checkpoint, + Op::Insert(12318), + Op::Insert(12256), + Op::Insert(12634), + Op::RemoveKeyIdx(62), + Op::RemoveKeyIdx(61), + Op::Insert(12470), + Op::Insert(12771), + Op::Insert(12324), + Op::Insert(12515), + Op::Insert(12849), + Op::Insert(12358), + Op::Insert(12934), + Op::Insert(12795), + Op::RemoveKeyIdx(58), + Op::Insert(12902), + Op::Insert(13137), + Op::RemoveRaw(275), + Op::Checkpoint, + Op::RemoveKeyIdx(71), + Op::RemoveRaw(255), + Op::Checkpoint, + Op::RemoveKeyIdx(62), + Op::Insert(13011), + Op::Insert(13103), + Op::Insert(13283), + Op::Insert(13210), + Op::Insert(13027), + Op::RemoveKeyIdx(74), + Op::Checkpoint, + Op::RemoveKeyIdx(69), + Op::Insert(13175), + Op::Checkpoint, + Op::Insert(13612), + Op::Insert(13286), + Op::Insert(13312), + Op::RemoveKeyIdx(74), + Op::RemoveKeyIdx(61), + Op::RemoveKeyIdx(64), + Op::RemoveKeyIdx(67), + Op::Insert(13651), + Op::Insert(13545), + Op::RemoveKeyIdx(65), + Op::Insert(13652), + Op::Insert(13832), + Op::Insert(13704), + Op::RemoveRaw(329), + Op::Insert(13778), + Op::RemoveRaw(409), + Op::Insert(14094), + Op::Insert(14362), + Op::Insert(14003), + Op::RemoveKeyIdx(72), + Op::Insert(14253), + Op::RemoveKeyIdx(74), + Op::RemoveRaw(374), + Op::Insert(14078), + Op::RemoveRaw(405), + Op::Insert(14207), + Op::Insert(14717), + Op::RemoveRaw(455), + Op::RemoveKeyIdx(74), + Op::RemoveKeyIdx(72), + Op::RemoveKeyIdx(66), + Op::Insert(14759), + Op::Insert(14719), + Op::Insert(14679), + Op::RemoveRaw(497), + Op::Checkpoint, + Op::Insert(14944), + Op::Insert(14871), + Op::Insert(14941), + Op::Insert(15165), + Op::Insert(14828), + Op::RemoveRaw(532), + Op::RemoveRaw(516), + Op::Insert(15181), + Op::RemoveKeyIdx(58), + Op::Insert(15277), + Op::Checkpoint, + Op::Insert(15010), + Op::Insert(14959), + Op::RemoveKeyIdx(58), + Op::RemoveRaw(535), + Op::RemoveKeyIdx(72), + Op::Insert(15228), + Op::Insert(15232), + Op::RemoveKeyIdx(68), + Op::Insert(15504), + Op::RemoveKeyIdx(64), + Op::Insert(15644), + Op::Insert(15494), + Op::Insert(15839), + Op::Insert(15480), + Op::Insert(15770), + Op::Insert(15983), + Op::Insert(15899), + Op::Insert(15760), + Op::RemoveKeyIdx(56), + Op::RemoveKeyIdx(69), + Op::Checkpoint, + Op::Insert(16106), + Op::RemoveRaw(623), + Op::RemoveRaw(663), + Op::RemoveKeyIdx(67), + Op::Insert(15847), + Op::RemoveRaw(692), + Op::RemoveKeyIdx(70), + Op::Insert(16365), + Op::RemoveRaw(700), + Op::Insert(16164), + Op::Insert(16245), + Op::Insert(16458), + Op::Insert(16286), + Op::Insert(16312), + Op::Checkpoint, + Op::RemoveKeyIdx(70), + Op::Insert(16489), + Op::Insert(16691), + Op::Insert(16750), + Op::RemoveRaw(706), + Op::Insert(16714), + Op::Checkpoint, + Op::Insert(16975), + Op::Insert(16825), + Op::RemoveRaw(801), + Op::RemoveRaw(764), + Op::Insert(17013), + Op::RemoveKeyIdx(64), + Op::RemoveKeyIdx(62), + Op::Insert(17113), + Op::Insert(16864), + Op::Insert(16956), + Op::RemoveRaw(799), + Op::Insert(17503), + Op::Insert(17331), + Op::Insert(17038), + Op::Insert(17020), + Op::Insert(17519), + Op::Checkpoint, + Op::Insert(17307), + Op::RemoveRaw(839), + Op::Insert(17568), + Op::Insert(17462), + Op::Checkpoint, + Op::RemoveKeyIdx(56), + Op::RemoveKeyIdx(70), + Op::Checkpoint, + Op::Insert(18021), + Op::RemoveKeyIdx(71), + Op::Insert(17512), + Op::Insert(17692), + Op::Insert(17674), + Op::RemoveRaw(948), + Op::Insert(17858), + Op::RemoveKeyIdx(69), + Op::Insert(17767), + Op::Insert(18277), + Op::RemoveKeyIdx(74), + Op::RemoveKeyIdx(70), + Op::Insert(18069), + Op::RemoveKeyIdx(55), + Op::Insert(18121), + Op::Insert(18037), + Op::Insert(18448), + Op::Insert(18562), + Op::Insert(18148), + Op::Insert(18625), + Op::Insert(18453), + Op::RemoveRaw(1021), + Op::Insert(18582), + Op::RemoveRaw(1017), + Op::Insert(18458), + Op::Insert(18847), + Op::Checkpoint, + Op::RemoveKeyIdx(71), + Op::Insert(19035), + Op::RemoveRaw(1005), + Op::Checkpoint, + Op::Insert(18783), + Op::Insert(18941), + Op::Insert(18868), + Op::Insert(18927), + Op::Insert(19294), + Op::Insert(18792), + Op::Insert(19038), + Op::Checkpoint, + Op::Insert(19332), + Op::Insert(19479), + Op::RemoveRaw(1107), + Op::Checkpoint, + Op::Insert(19359), + Op::Insert(19429), + Op::Insert(19312), + Op::RemoveRaw(1160), + Op::Insert(19309), + Op::Insert(19731), + Op::Insert(19284), + Op::RemoveKeyIdx(60), + Op::Insert(19622), + Op::Insert(19626), + Op::RemoveKeyIdx(58), + Op::Insert(19909), + Op::Insert(19891), + Op::Insert(19818), + Op::Insert(19844), + Op::Insert(19606), + Op::RemoveRaw(1190), + Op::Insert(19911), + Op::Insert(20245), + Op::Insert(20183), + Op::Insert(20143), + Op::Insert(19883), + Op::Checkpoint, + Op::Insert(20254), + Op::Insert(20390), + Op::RemoveRaw(1221), + Op::RemoveKeyIdx(65), + Op::Insert(20578), + Op::RemoveRaw(1264), + Op::RemoveKeyIdx(68), + Op::Insert(20590), + Op::Insert(20198), + Op::Insert(20356), + Op::Insert(20602), + Op::Insert(20518), + Op::RemoveKeyIdx(74), + Op::Insert(20713), + Op::Insert(20431), + Op::Insert(20567), + Op::RemoveRaw(1249), + Op::RemoveKeyIdx(55), + Op::Insert(21019), + Op::Checkpoint, + Op::Insert(21049), + Op::RemoveKeyIdx(55), + Op::RemoveRaw(1286), + Op::RemoveKeyIdx(65), + Op::Insert(20911), + Op::Insert(21333), + Op::RemoveKeyIdx(73), + Op::Insert(21132), + Op::Insert(21257), + Op::Insert(20997), + Op::Checkpoint, + Op::RemoveKeyIdx(55), + Op::Insert(21053), + Op::RemoveKeyIdx(66), + Op::RemoveKeyIdx(68), + Op::RemoveRaw(1421), + Op::Checkpoint, + Op::Checkpoint, + Op::Checkpoint, + Op::RemoveRaw(1427), + Op::Insert(21624), + Op::Insert(21892), + Op::RemoveKeyIdx(55), + Op::Insert(21548), + Op::RemoveRaw(1452), + Op::Insert(21996), + Op::RemoveRaw(1420), + Op::Insert(22048), + Op::RemoveRaw(1465), + Op::Insert(21847), + Op::Checkpoint, + Op::Insert(22174), + Op::RemoveKeyIdx(64), + Op::Insert(22259), + Op::RemoveRaw(1502), + Op::Insert(22476), + Op::Insert(22392), + Op::Insert(22539), + Op::RemoveRaw(1550), + Op::Insert(22459), + Op::RemoveKeyIdx(70), + Op::Insert(22302), + Op::Insert(22427), + Op::Insert(22783), + Op::RemoveRaw(1552), + Op::Insert(22802), + Op::RemoveRaw(1569), + Op::Checkpoint, + Op::Insert(22627), + Op::RemoveRaw(1528), + Op::RemoveKeyIdx(72), + Op::Insert(22914), + Op::Insert(23094), + Op::RemoveKeyIdx(70), + Op::Insert(22607), + Op::Insert(23062), + Op::Insert(23077), + Op::Insert(23004), + Op::RemoveKeyIdx(55), + Op::RemoveKeyIdx(74), + Op::RemoveRaw(1576), + Op::RemoveRaw(1623), + Op::Insert(23354), + Op::RemoveKeyIdx(59), + Op::RemoveKeyIdx(56), + Op::RemoveKeyIdx(56), + Op::RemoveRaw(1669), + Op::Insert(23385), + Op::RemoveKeyIdx(65), + Op::RemoveKeyIdx(60), + Op::Checkpoint, + Op::Insert(23467), + Op::RemoveRaw(1685), + Op::Insert(23420), + Op::Insert(23743), + Op::RemoveKeyIdx(65), + Op::RemoveRaw(1705), + Op::RemoveRaw(1689), + Op::RemoveRaw(1680), + Op::Insert(23994), + Op::Insert(23690), + Op::Checkpoint, + Op::Insert(23973), + Op::RemoveRaw(1754), + Op::Insert(24146), + Op::Insert(24062), + Op::Insert(24066), + Op::Insert(24026), + Op::Insert(23898), + Op::Insert(23957), + Op::Insert(24280), + Op::RemoveKeyIdx(60), + Op::RemoveRaw(1743), + Op::Checkpoint, + Op::Insert(24186), + Op::RemoveKeyIdx(58), + Op::Insert(24744), + Op::RemoveRaw(1782), + Op::RemoveKeyIdx(73), + Op::Insert(24536), + Op::Checkpoint, + Op::RemoveRaw(1865), + Op::Insert(24922), + Op::RemoveKeyIdx(63), + Op::Insert(25018), + Op::Insert(24835), + Op::Insert(24729), + Op::RemoveRaw(1853), + Op::RemoveRaw(1893), + Op::Insert(24642), + Op::Checkpoint, + Op::Checkpoint, + Op::Insert(24896), + Op::Checkpoint, + Op::Insert(25421), + Op::Insert(25172), + Op::Insert(25143), + Op::RemoveRaw(1945), + Op::RemoveKeyIdx(66), + Op::RemoveKeyIdx(59), + Op::Insert(25115), + Op::RemoveKeyIdx(71), + Op::Insert(25464), + Op::Insert(25325), + Op::RemoveKeyIdx(58), + Op::Insert(25421), + Op::RemoveKeyIdx(59), + Op::RemoveRaw(1967), + Op::RemoveKeyIdx(67), + Op::RemoveKeyIdx(71), + Op::RemoveRaw(1919), + Op::Insert(25896), + Op::Checkpoint, + Op::RemoveKeyIdx(56), + Op::RemoveRaw(1953), + Op::Checkpoint, + Op::RemoveKeyIdx(64), + Op::Insert(26272), + Op::RemoveKeyIdx(70), + Op::RemoveKeyIdx(57), + Op::Insert(26009), + Op::Checkpoint, + Op::RemoveKeyIdx(60), + Op::RemoveKeyIdx(57), + Op::Insert(26300), + Op::Checkpoint, + Op::Insert(26352), + Op::RemoveRaw(2039), + Op::Insert(26470), + Op::Checkpoint, + Op::Checkpoint, + Op::RemoveKeyIdx(66), + Op::RemoveKeyIdx(62), + Op::RemoveRaw(14), + Op::Insert(26637), + Op::Insert(26729), + Op::RemoveKeyIdx(56), + Op::Insert(26517), + Op::Checkpoint, + Op::Insert(26888), + Op::Insert(26859), + Op::Insert(26940), + Op::Insert(27065), + Op::Insert(27080), + Op::RemoveKeyIdx(69), + Op::Insert(26824), + Op::Insert(26938), + Op::Insert(26997), + Op::Insert(27144), + Op::Insert(27401), + Op::RemoveRaw(155), + Op::Insert(27145), + Op::Checkpoint, + Op::Insert(27307), + Op::Insert(27135), + Op::Insert(27667), + Op::Insert(27693), + Op::Insert(27620), + Op::RemoveKeyIdx(69), + Op::RemoveKeyIdx(59), + Op::Insert(27852), + Op::Insert(27383), + Op::Insert(27629), + Op::Insert(27963), + Op::Insert(27956), + Op::Checkpoint, + Op::RemoveKeyIdx(74), + Op::RemoveRaw(247), + Op::Insert(27587), + Op::Checkpoint, + Op::RemoveKeyIdx(73), + Op::Insert(28204), + Op::RemoveKeyIdx(61), + Op::Insert(27750), + Op::Insert(28282), + Op::Insert(28242), + Op::Checkpoint, + Op::Insert(28437), + Op::Insert(28518), + Op::RemoveKeyIdx(68), + Op::Checkpoint, + Op::Checkpoint, + Op::RemoveKeyIdx(69), + Op::RemoveRaw(243), + Op::Insert(28465), + Op::RemoveKeyIdx(62), + Op::Insert(28539), + Op::Insert(28642), + Op::RemoveKeyIdx(55), + Op::RemoveKeyIdx(62), + Op::Insert(28412), + Op::Insert(28955), + Op::Insert(28926), + Op::RemoveRaw(349), + Op::RemoveRaw(361), + Op::Insert(29125), + Op::Checkpoint, + Op::Insert(28869), + Op::RemoveKeyIdx(59), + Op::Insert(28888), + Op::RemoveRaw(342), + Op::RemoveKeyIdx(68), + Op::Insert(28856), + Op::Insert(29410), + Op::RemoveRaw(334), + Op::RemoveKeyIdx(72), + Op::RemoveRaw(407), + Op::Insert(29041), + Op::Insert(29155), + Op::RemoveRaw(408), + Op::Insert(29713), + Op::Insert(29343), + Op::RemoveKeyIdx(59), + Op::Insert(29483), + Op::Insert(29333), + Op::Insert(29667), + Op::Insert(29869), + Op::Insert(29829), + Op::Insert(29712), + Op::RemoveKeyIdx(58), + Op::Insert(29940), + Op::Insert(29999), + Op::Checkpoint, + Op::Insert(30128), + Op::Insert(30022), + Op::Checkpoint, + Op::Insert(30294), + Op::Insert(30100), + Op::Insert(29895), + Op::RemoveKeyIdx(60), + Op::Insert(29881), + Op::Checkpoint, + Op::RemoveKeyIdx(69), + Op::Insert(30036), + Op::Insert(30491), + Op::Insert(30352), + Op::RemoveRaw(550), + Op::Insert(30206), + Op::Checkpoint, + Op::RemoveRaw(565), + Op::Insert(30284), + Op::Checkpoint, + Op::Insert(30336), + Op::Insert(30681), + Op::Insert(30652), + Op::Insert(30436), + Op::RemoveRaw(572), + Op::Insert(30697), + Op::Insert(30855), + Op::Insert(30738), + Op::RemoveKeyIdx(60), + Op::RemoveRaw(639), + Op::Insert(30827), + Op::RemoveKeyIdx(72), + Op::Insert(31044), + Op::RemoveKeyIdx(59), + Op::RemoveRaw(692), + Op::Insert(31210), + Op::Checkpoint, + Op::Insert(31449), + Op::Insert(31200), + Op::RemoveRaw(633), + Op::RemoveRaw(638), + Op::Checkpoint, + Op::Checkpoint, + Op::Insert(31308), + Op::Checkpoint, + Op::Insert(31448), + Op::Insert(31694), + Op::Insert(31445), + Op::RemoveKeyIdx(74), + Op::Insert(31409), + Op::Insert(31842), + Op::RemoveKeyIdx(73), + Op::RemoveRaw(761), + Op::Insert(32052), + Op::Insert(32111), + Op::Checkpoint, + Op::Insert(31910), + Op::Insert(31771), + Op::RemoveRaw(714), + Op::RemoveRaw(810), + Op::RemoveRaw(759), + Op::Insert(32161), + Op::Insert(32000), + Op::RemoveRaw(837), + Op::Checkpoint, + Op::Insert(32386), + Op::RemoveRaw(838), + Op::RemoveRaw(766), + Op::Insert(32244), + Op::RemoveKeyIdx(72), + Op::Insert(32208), + Op::Insert(32674), + Op::RemoveKeyIdx(68), + Op::Insert(32858), + Op::RemoveRaw(857), + Op::Insert(32855), + Op::Insert(32463), + Op::RemoveKeyIdx(64), + Op::RemoveRaw(863), + Op::Insert(32959), + Op::Insert(32534), + Op::Insert(33143), + Op::RemoveKeyIdx(62), + Op::Insert(32777), + Op::RemoveKeyIdx(55), + Op::Insert(33214), + Op::RemoveRaw(896), + Op::RemoveKeyIdx(58), + Op::Checkpoint, + Op::Insert(33065), + Op::RemoveKeyIdx(55), + Op::Insert(33172), + Op::Checkpoint, + Op::RemoveKeyIdx(66), + Op::RemoveRaw(950), + Op::Insert(33452), + Op::RemoveRaw(925), + Op::RemoveKeyIdx(68), + Op::Checkpoint, + Op::RemoveKeyIdx(61), + Op::RemoveKeyIdx(59), + Op::RemoveKeyIdx(55), + Op::Checkpoint, + Op::RemoveRaw(939), + Op::Insert(33785), + Op::Insert(33899), + Op::Insert(33826), + Op::Insert(33643), + Op::Insert(33944), + Op::RemoveKeyIdx(57), + Op::Insert(33974), + Op::Checkpoint, + Op::RemoveKeyIdx(59), + Op::RemoveKeyIdx(72), + Op::RemoveRaw(1015), + Op::RemoveRaw(1069), + Op::Insert(34383), + Op::Insert(34365), + Op::Insert(33962), + Op::RemoveKeyIdx(63), + Op::Insert(34454), + Op::Insert(34293), + Op::RemoveKeyIdx(62), + Op::Insert(34411), + Op::Insert(34646), + Op::Insert(34551), + Op::Insert(34698), + Op::Insert(34647), + Op::RemoveRaw(1071), + Op::RemoveRaw(1167), + Op::Insert(34758), + Op::RemoveKeyIdx(71), + Op::Insert(34458), + Op::RemoveKeyIdx(66), + Op::Insert(34785), + Op::Insert(34668), + Op::Insert(34925), + Op::Checkpoint, + Op::Insert(35241), + Op::RemoveKeyIdx(62), + Op::RemoveKeyIdx(71), + Op::Checkpoint, + Op::Insert(35147), + Op::RemoveRaw(1230), + Op::RemoveKeyIdx(60), + Op::RemoveRaw(1198), + Op::Checkpoint, + Op::RemoveKeyIdx(62), + Op::RemoveKeyIdx(57), + Op::Insert(35098), + Op::Insert(35234), + Op::Insert(35557), + Op::Insert(35495), + Op::Insert(35785), + Op::RemoveRaw(1285), + Op::Insert(35639), + Op::Insert(35764), + Op::RemoveRaw(1272), + Op::RemoveKeyIdx(74), + Op::Insert(35963), + Op::RemoveKeyIdx(60), + Op::RemoveKeyIdx(70), + Op::RemoveKeyIdx(67), + Op::Insert(36078), + Op::Insert(36225), + Op::Checkpoint, + Op::RemoveKeyIdx(70), + Op::Insert(36204), + Op::Insert(36274), + Op::RemoveRaw(1367), + Op::RemoveKeyIdx(58), + Op::Insert(36264), + Op::Insert(36356), + Op::Insert(36316), + Op::Insert(36320), + Op::Insert(36203), + Op::Checkpoint, + Op::Insert(36376), + Op::Insert(36589), + Op::Insert(36351), + Op::Checkpoint, + Op::RemoveKeyIdx(68), + Op::Checkpoint, + Op::RemoveRaw(1430), + Op::Insert(36624), + Op::Insert(36672), + Op::Insert(36742), + Op::Checkpoint, + Op::RemoveKeyIdx(61), + Op::Insert(36941), + Op::Checkpoint, + Op::RemoveRaw(1379), + Op::Checkpoint, + Op::RemoveKeyIdx(67), + Op::RemoveRaw(1401), + Op::Insert(37174), + Op::Insert(36947), + Op::Insert(37116), + Op::Insert(37296), + Op::Insert(37245), + Op::Insert(37469), + Op::Insert(37242), + Op::Insert(37455), + Op::Insert(37085), + Op::Checkpoint, + Op::RemoveKeyIdx(68), + Op::Insert(37361), + Op::Insert(37607), + Op::Insert(37314), + Op::Insert(37362), + Op::RemoveKeyIdx(70), + Op::Insert(37777), + Op::RemoveRaw(1582), + Op::Insert(37620), + Op::RemoveRaw(1536), + Op::Checkpoint, + Op::RemoveKeyIdx(62), + Op::Insert(37735), + Op::Insert(37640), + Op::RemoveKeyIdx(73), + Op::Insert(38099), + Op::RemoveKeyIdx(60), + Op::RemoveKeyIdx(61), + Op::RemoveKeyIdx(56), + Op::Insert(38324), + Op::Insert(38141), + Op::Insert(37991), + Op::Insert(38314), + Op::RemoveKeyIdx(61), + Op::Insert(38190), + Op::Insert(38161), + Op::Insert(38671), + Op::RemoveKeyIdx(62), + Op::Insert(38283), + Op::RemoveKeyIdx(73), + Op::RemoveRaw(1634), + Op::RemoveRaw(1688), + Op::Insert(38398), + Op::Insert(38578), + Op::Insert(38615), + Op::RemoveKeyIdx(72), + Op::Insert(38898), + Op::Insert(38627), + Op::Insert(38895), + Op::Insert(38756), + Op::RemoveKeyIdx(71), + Op::Checkpoint, + Op::Insert(39285), + Op::RemoveRaw(1678), + Op::RemoveRaw(1676), + Op::RemoveRaw(1702), + Op::Insert(39158), + Op::Checkpoint, + Op::RemoveKeyIdx(66), + Op::Insert(39489), + Op::RemoveKeyIdx(57), + Op::RemoveKeyIdx(56), + Op::Insert(39490), + Op::RemoveRaw(1714), + Op::Insert(39740), + Op::Insert(39612), + Op::RemoveKeyIdx(72), + Op::RemoveRaw(1762), + Op::Insert(39371), + Op::RemoveRaw(1765), + Op::Checkpoint, + Op::Insert(39537), + Op::Checkpoint, + Op::Insert(39765), + Op::RemoveRaw(1804), + Op::Insert(39850), + Op::Insert(39953), + Op::RemoveRaw(1847), + Op::Insert(39774), + Op::Insert(39921), + Op::Insert(40068), + Op::Insert(40017), + Op::RemoveKeyIdx(70), + Op::Insert(39948), + Op::Insert(40106), + Op::Insert(39923), + Op::Insert(40532), + Op::RemoveKeyIdx(57), + Op::RemoveRaw(1930), + Op::RemoveRaw(1851), + Op::RemoveKeyIdx(60), + Op::Insert(40552), + Op::Insert(40226), + Op::Insert(40538), + Op::Insert(40652), + Op::Checkpoint, + Op::Insert(40726), + Op::RemoveRaw(1982), + Op::RemoveKeyIdx(58), + Op::Insert(40540), + Op::RemoveRaw(1983), + Op::Insert(40834), + Op::Insert(40915), + Op::RemoveRaw(1949), + Op::RemoveRaw(1919), + Op::RemoveRaw(1987), + Op::Insert(40975), + Op::Insert(41331), + Op::Insert(40928), + Op::RemoveKeyIdx(69), + Op::Insert(41156), + Op::RemoveRaw(2003), + Op::Insert(41131), + Op::Insert(41333), + Op::Insert(41194), + Op::Insert(41132), + Op::Insert(41147), + Op::Insert(41338), + Op::Insert(41573), + Op::RemoveRaw(2001), + Op::Checkpoint, + Op::RemoveKeyIdx(72), + Op::Insert(41688), + Op::RemoveKeyIdx(56), + Op::Insert(41531), + Op::RemoveKeyIdx(59), + Op::RemoveRaw(72), + Op::Insert(41895), + Op::RemoveRaw(89), + Op::Insert(42112), + Op::Insert(41962), + Op::RemoveKeyIdx(64), + Op::RemoveKeyIdx(69), + Op::RemoveKeyIdx(63), + Op::RemoveKeyIdx(56), + Op::Insert(41883), + Op::RemoveRaw(38), + Op::Insert(41902), + Op::RemoveKeyIdx(55), + Op::Checkpoint, + Op::Insert(42409), + Op::RemoveRaw(70), + Op::Insert(42384), + Op::Insert(42102), + Op::Insert(42227), + Op::Insert(42462), + Op::RemoveRaw(158), + Op::RemoveKeyIdx(73), + Op::RemoveRaw(91), + Op::Insert(42313), + Op::Checkpoint, + Op::RemoveRaw(183), + Op::Insert(42776), + Op::RemoveKeyIdx(73), + Op::Insert(42531), + Op::Checkpoint, +]; + +type StdSlab = slab::Slab; + +type Canon = BTreeMap; + +fn numeric_map(map: &Canon) -> BTreeMap { + map.iter() + .map(|(k, v)| { + let n = match v { + Value::I64(n) => *n as i32, + Value::U64(n) => *n as i32, + Value::I32(n) => *n, + Value::U32(n) => *n as i32, + other => panic!("unexpected value kind in map: {other:?}"), + }; + (*k, n) + }) + .collect() +} + +fn canonical_bytes_from_map(map: &Canon) -> Vec { + let len = map + .keys() + .copied() + .max() + .map(|m| m as usize + 1) + .unwrap_or(0); + let mut seq: Vec> = vec![None; len]; + for (k, v) in map { + let idx = *k as usize; + let num = match v { + Value::I64(n) => *n as i32, + Value::U64(n) => *n as i32, + Value::I32(n) => *n, + Value::U32(n) => *n as i32, + other => panic!("unexpected value kind in map: {other:?}"), + }; + seq[idx] = Some(num); + } + postcard::to_allocvec(&seq).expect("encode postcard") +} + +fn canonical_map_from_value(value: Value) -> Canon { + match value { + Value::Map(entries) => entries + .into_iter() + .map(|(k, v)| match k { + Value::U64(i) => (i, v), + Value::I64(i) => (i as u64, v), + other => panic!("unexpected key: {other:?}"), + }) + .collect(), + Value::Seq(seq) => seq + .into_iter() + .enumerate() + .filter_map(|(idx, entry)| match entry { + Value::Option(Some(inner)) => Some((idx as u64, *inner)), + Value::Option(None) => None, + other => panic!("unexpected seq entry: {other:?}"), + }) + .collect(), + other => panic!("unexpected top-level: {other:?}"), + } +} + +fn map_to_value(map: &Canon) -> Value { + Value::Map( + map.iter() + .map(|(k, v)| (Value::U64(*k), v.clone())) + .collect(), + ) +} + +fn apply_ops(ops: &[Op]) -> (Slab, StdSlab) { + let mut mmap = Slab::new().unwrap(); + let mut std = StdSlab::new(); + let mut keys_m = Vec::new(); + let mut keys_s = Vec::new(); + + for (step, op) in ops.iter().enumerate() { + match *op { + Op::Insert(v) => { + let km = mmap.insert(v).unwrap(); + let ks = std.insert(v); + keys_m.push(km); + keys_s.push(ks); + } + Op::RemoveKeyIdx(raw_idx) => { + if keys_m.is_empty() { + continue; + } + let idx = raw_idx % keys_m.len(); + let km = keys_m.swap_remove(idx); + let ks = keys_s.swap_remove(idx); + let rm_m = mmap.try_remove(km); + let rm_s = std.try_remove(ks); + assert_eq!(rm_m, rm_s, "step {step}: remove key idx mismatch"); + } + Op::RemoveRaw(idx) => { + let rm_m = mmap.try_remove(idx); + let rm_s = std.try_remove(idx); + assert_eq!(rm_m, rm_s, "step {step}: remove raw mismatch idx={idx}"); + if let Some(pos) = keys_m.iter().position(|&k| k == idx) { + keys_m.swap_remove(pos); + } + if let Some(pos) = keys_s.iter().position(|&k| k == idx) { + keys_s.swap_remove(pos); + } + } + Op::Checkpoint => { + let snap_m = canonical_map_from_value(serde_value::to_value(&mmap).unwrap()); + let snap_s = canonical_map_from_value(serde_value::to_value(&std).unwrap()); + assert_eq!(snap_m, snap_s, "step {step}: snapshot divergence"); + } + } + } + (mmap, std) +} + +fn decode_mmap_from_slab_map(map: &Canon) -> Slab { + let map_value = map_to_value(map); + Slab::deserialize(ValueDeserializer::::new( + map_value, + )) + .expect("decode mmap from map") +} + +fn decode_std_from_mmap_map(map_value: Value) -> StdSlab { + StdSlab::deserialize(ValueDeserializer::::new( + map_value, + )) + .expect("decode std from map value") +} + +fn assert_bridge_equivalence(mmap: &Slab, std: &StdSlab) { + let mmap_map = canonical_map_from_value(serde_value::to_value(mmap).unwrap()); + let std_map = canonical_map_from_value(serde_value::to_value(std).unwrap()); + assert_eq!(mmap_map, std_map, "final canonical maps mismatch"); + let mmap_nums = numeric_map(&mmap_map); + let std_nums = numeric_map(&std_map); + assert_eq!(mmap_nums, std_nums, "numeric canonical maps mismatch"); + + // postcard canonical bytes (seq representation) must match + let bytes_m = canonical_bytes_from_map(&mmap_map); + let bytes_s = canonical_bytes_from_map(&std_map); + assert_eq!(bytes_m, bytes_s, "postcard canonical bytes mismatch"); + // ensure bytes decode back to the same canonical map + let decoded_seq: Vec> = + postcard::from_bytes(&bytes_m).expect("decode seq from bytes"); + let mut decoded_map: BTreeMap = BTreeMap::new(); + for (idx, val) in decoded_seq.into_iter().enumerate() { + if let Some(v) = val { + decoded_map.insert(idx as u64, v); + } + } + assert_eq!(decoded_map, mmap_nums, "decoded map diverged from original"); + + let mmap_from_std = decode_mmap_from_slab_map(&std_map); + let mmap_from_std_map = + canonical_map_from_value(serde_value::to_value(&mmap_from_std).unwrap()); + assert_eq!(mmap_from_std_map, std_map, "map->mmap mismatch"); + + let map_from_mmap = map_to_value(&mmap_map); + let std_from_mmap = decode_std_from_mmap_map(map_from_mmap); + let std_from_mmap_map = + canonical_map_from_value(serde_value::to_value(&std_from_mmap).unwrap()); + assert_eq!(std_from_mmap_map, mmap_map, "map->std mismatch"); +} + +#[test] +fn serde_bridge_heavy_pattern_large_ops() { + let (mmap, std) = apply_ops(OPS); + assert_bridge_equivalence(&mmap, &std); +} + +#[test] +fn serde_bridge_prefix_windows() { + for window_size in [8usize, 32, 64, 128, 256, OPS.len()] { + let (mmap, std) = apply_ops(&OPS[..window_size]); + assert_bridge_equivalence(&mmap, &std); + } +} + +#[test] +fn serde_bridge_suffix_windows() { + for start in [0usize, OPS.len() / 4, OPS.len() / 2, OPS.len() - 128] { + let (mmap, std) = apply_ops(&OPS[start..]); + assert_bridge_equivalence(&mmap, &std); + } +} + +#[test] +fn serde_bridge_chunked_restarts() { + let mut cursor = 0; + let mut mmap = Slab::new().unwrap(); + let mut std = StdSlab::new(); + while cursor < OPS.len() { + let end = (cursor + 25).min(OPS.len()); + let (chunk_m, _chunk_s) = apply_ops(&OPS[cursor..end]); + let chunk_map = canonical_map_from_value(serde_value::to_value(&chunk_m).unwrap()); + let map_value = map_to_value(&chunk_map); + mmap = Slab::deserialize(ValueDeserializer::::new( + map_value.clone(), + )) + .expect("decode cumulative mmap"); + std = decode_std_from_mmap_map(map_value); + cursor = end; + } + assert_bridge_equivalence(&mmap, &std); +} diff --git a/slab-mmap/tests/serialization_tests.rs b/slab-mmap/tests/serialization_tests.rs new file mode 100644 index 0000000..859f475 --- /dev/null +++ b/slab-mmap/tests/serialization_tests.rs @@ -0,0 +1,298 @@ +use serde::{Deserialize, Serialize}; +use slab_mmap::Slab; +use std::{collections::HashMap, fmt::Debug}; + +// Structure used for complex serialization tests +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +struct ComplexData { + id: u32, + name: String, + values: Vec, + metadata: HashMap, +} + +// Serialization helper +fn roundtrip_serde(slab: &Slab) -> Slab +where + T: Serialize + for<'de> Deserialize<'de> + PartialEq + Debug, +{ + // Serialize to JSON + let json = serde_json::to_string(slab).expect("Failed to serialize slab"); + + // Deserialize from JSON + let deserialized: Slab = serde_json::from_str(&json).expect("Failed to deserialize slab"); + + deserialized +} + +#[test] +fn test_basic_serialization() { + // Serialize basic value types + let mut slab = Slab::new().unwrap(); + + // Insert some data + let idx1 = slab.insert(42).unwrap(); + let idx2 = slab.insert(100).unwrap(); + let idx3 = slab.insert(-1).unwrap(); + + // Round-trip through serialization + let deserialized = roundtrip_serde(&slab); + + // Validate data consistency + assert_eq!(deserialized.len(), slab.len()); + assert_eq!(deserialized.get(idx1), Some(&42)); + assert_eq!(deserialized.get(idx2), Some(&100)); + assert_eq!(deserialized.get(idx3), Some(&-1)); +} + +#[test] +fn test_empty_slab_serialization() { + // Serialize an empty slab + let slab: Slab = Slab::new().unwrap(); + + let deserialized = roundtrip_serde(&slab); + + assert!(deserialized.is_empty()); + assert_eq!(deserialized.len(), 0); +} + +#[test] +fn test_with_holes_serialization() { + // Serialize slabs that contain holes + let mut slab = Slab::new().unwrap(); + + // Insert data + let idx1 = slab.insert("a".to_string()).unwrap(); + let idx2 = slab.insert("b".to_string()).unwrap(); + let idx3 = slab.insert("c".to_string()).unwrap(); + let idx4 = slab.insert("d".to_string()).unwrap(); + + // Remove items to create holes + slab.try_remove(idx2); + slab.try_remove(idx4); + + // Round-trip through serialization + let deserialized = roundtrip_serde(&slab); + + // Validate data consistency + assert_eq!(deserialized.len(), slab.len()); + assert_eq!(deserialized.get(idx1), Some(&"a".to_string())); + assert!(deserialized.get(idx2).is_none()); + assert_eq!(deserialized.get(idx3), Some(&"c".to_string())); + assert!(deserialized.get(idx4).is_none()); + + // Iteration should remain consistent + let original_iter: Vec<_> = slab.iter().collect(); + let deserialized_iter: Vec<_> = deserialized.iter().collect(); + assert_eq!(original_iter, deserialized_iter); +} + +#[test] +fn test_complex_structure_serialization() { + // Serialize complex structures + let mut slab = Slab::new().unwrap(); + + // Build and insert complex data + let data1 = ComplexData { + id: 1, + name: "test1".to_string(), + values: vec![1, 2, 3, 4, 5], + metadata: HashMap::from([ + ("type".to_string(), "example".to_string()), + ("version".to_string(), "1.0".to_string()), + ]), + }; + + let data2 = ComplexData { + id: 2, + name: "test2".to_string(), + values: vec![], + metadata: HashMap::new(), + }; + + let idx1 = slab.insert(data1.clone()).unwrap(); + let idx2 = slab.insert(data2.clone()).unwrap(); + + // Round-trip through serialization + let deserialized = roundtrip_serde(&slab); + + // Validate data consistency + assert_eq!(deserialized.len(), slab.len()); + assert_eq!(deserialized.get(idx1), Some(&data1)); + assert_eq!(deserialized.get(idx2), Some(&data2)); +} + +#[test] +fn test_large_data_serialization() { + // Serialize large data sets + let mut slab = Slab::new().unwrap(); + + // Insert 1000 elements + for i in 0..1000 { + slab.insert(format!("Item {i}")).unwrap(); + } + + // Remove some entries to create holes + for i in 0..1000 { + if i % 5 == 0 { + slab.try_remove(i); + } + } + + // Round-trip through serialization + let deserialized = roundtrip_serde(&slab); + + // Validate data consistency + assert_eq!(deserialized.len(), slab.len()); + + // Spot-check a handful of entries + for i in 0..1000 { + if i % 5 != 0 { + // Elements that were not deleted + assert_eq!(deserialized.get(i), Some(&format!("Item {i}"))); + } else { + // Elements that were deleted + assert!(deserialized.get(i).is_none()); + } + } +} + +#[test] +fn test_nested_slab_serialization() { + // Serialize nested data structures + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct NestedData { + name: String, + numbers: Vec, + } + + let mut slab = Slab::new().unwrap(); + + // Insert nested data + for i in 0..100 { + let nested = NestedData { + name: format!("Nested {i}"), + numbers: (0..i).collect(), + }; + slab.insert(nested).unwrap(); + } + + // Round-trip through serialization + let deserialized = roundtrip_serde(&slab); + + // Validate data consistency + assert_eq!(deserialized.len(), slab.len()); + + // Validate every element + for i in 0..100 { + let original = slab.get(i).unwrap(); + let deserialized_item = deserialized.get(i).unwrap(); + + assert_eq!(original.name, deserialized_item.name); + assert_eq!(original.numbers, deserialized_item.numbers); + } +} + +#[test] +fn test_mixed_types_serialization() { + // Use an enum to cover mixed types + #[derive(Serialize, Deserialize, PartialEq, Debug)] + enum MixedType { + Int(i32), + String(String), + Bool(bool), + List(Vec), + } + + let mut slab = Slab::new().unwrap(); + + // Insert different type variants + slab.insert(MixedType::Int(42)).unwrap(); + slab.insert(MixedType::String("hello".to_string())).unwrap(); + slab.insert(MixedType::Bool(true)).unwrap(); + slab.insert(MixedType::List(vec![1, 2, 3, 4, 5])).unwrap(); + + // Round-trip through serialization + let deserialized = roundtrip_serde(&slab); + + // Validate data consistency + assert_eq!(deserialized.len(), 4); + assert_eq!(deserialized.get(0), Some(&MixedType::Int(42))); + assert_eq!( + deserialized.get(1), + Some(&MixedType::String("hello".to_string())) + ); + assert_eq!(deserialized.get(2), Some(&MixedType::Bool(true))); + assert_eq!( + deserialized.get(3), + Some(&MixedType::List(vec![1, 2, 3, 4, 5])) + ); +} + +#[test] +fn test_serialization_compatibility() { + // Check compatibility with the std slab serialization + use slab::Slab as StdSlab; + + // Create both slabs with matching contents + let mut mmap_slab = Slab::new().unwrap(); + let mut std_slab = StdSlab::new(); + + for i in 0..100 { + mmap_slab.insert(i).unwrap(); + std_slab.insert(i); + } + + // Data serialized from mmap_slab should represent the std slab correctly + let mmap_json = serde_json::to_string(&mmap_slab).expect("Failed to serialize mmap slab"); + let std_json = serde_json::to_string(&std_slab).expect("Failed to serialize std slab"); + + // Note: JSON may differ due to implementation details, but the values should match + // We verify this by deserializing + + let mmap_from_std: Slab = + serde_json::from_str(&std_json).expect("Failed to deserialize from std slab"); + let std_from_mmap: StdSlab = + serde_json::from_str(&mmap_json).expect("Failed to deserialize from mmap slab"); + + // Validate data consistency + assert_eq!(mmap_from_std.len(), 100); + assert_eq!(std_from_mmap.len(), 100); + + for i in 0..100 { + let v = i as i32; + assert_eq!(mmap_from_std.get(i), Some(&v)); + assert_eq!(std_from_mmap.get(i), Some(&v)); + } +} + +#[test] +fn test_serialization_with_large_indexes() { + // Serialize slabs that only retain large indexes + let mut slab = Slab::new().unwrap(); + + // Start by populating some elements + for i in 0..100 { + slab.insert(i).unwrap(); + } + + // Remove most entries, keeping only the tail + for i in 0..95 { + slab.try_remove(i); + } + + // Only slots 95-99 remain populated now + assert_eq!(slab.len(), 5); + + // Round-trip through serialization + let deserialized = roundtrip_serde(&slab); + + // Validate data consistency + assert_eq!(deserialized.len(), 5); + for i in 95..100 { + assert_eq!(deserialized.get(i), Some(&i)); + } + for i in 0..95 { + assert!(deserialized.get(i).is_none()); + } +} diff --git a/slab-mmap/tests/slab.rs b/slab-mmap/tests/slab.rs new file mode 100644 index 0000000..2cc114b --- /dev/null +++ b/slab-mmap/tests/slab.rs @@ -0,0 +1,53 @@ +use slab_mmap::Slab; + +#[test] +fn insert_get_remove_one() { + let mut slab = Slab::new().unwrap(); + assert_eq!(slab.len(), 0); + assert!(slab.is_empty()); + + let key = slab.insert(10).unwrap(); + assert_eq!(slab.len(), 1); + assert_eq!(slab[key], 10); + assert_eq!(slab.get(key), Some(&10)); + + assert_eq!(slab.try_remove(key), Some(10)); + assert_eq!(slab.try_remove(key), None); + assert!(slab.is_empty()); +} + +#[test] +fn slots_are_reused_after_remove() { + let mut slab = Slab::new().unwrap(); + let first = slab.insert("alpha").unwrap(); + let second = slab.insert("beta").unwrap(); + assert_eq!(first, 0); + assert_eq!(second, 1); + + assert_eq!(slab.try_remove(first), Some("alpha")); + let third = slab.insert("gamma").unwrap(); + // We recycle the freed slot. + assert_eq!(third, first); + assert_eq!(slab[third], "gamma"); + assert_eq!(slab.len(), 2); +} + +#[test] +fn get_mut_allows_update() { + let mut slab = Slab::new().unwrap(); + let key = slab.insert(1).unwrap(); + *slab.get_mut(key).unwrap() = 5; + assert_eq!(slab[key], 5); +} + +#[test] +fn iter_skips_holes_and_preserves_order() { + let mut slab = Slab::new().unwrap(); + let a = slab.insert("a").unwrap(); + let b = slab.insert("b").unwrap(); + let c = slab.insert("c").unwrap(); + assert_eq!(slab.try_remove(b), Some("b")); + + let collected: Vec<_> = slab.iter().collect(); + assert_eq!(collected, vec![(a, &"a"), (c, &"c")]); +}