From 5922e63f59381081074f124684d01113db838e21 Mon Sep 17 00:00:00 2001 From: tison Date: Wed, 9 Sep 2026 15:18:01 +0800 Subject: [PATCH 01/14] refactor: organize internal modules by subsystem --- CONTRIBUTING.md | 2 + cache2/src/benchmarking.rs | 24 ++-- cache2/src/cache.rs | 14 +- cache2/src/{config.rs => config/mod.rs} | 2 +- cache2/src/config/runtime.rs | 12 +- cache2/src/config/storage.rs | 28 ++-- cache2/src/{io_backend.rs => io/backend.rs} | 0 cache2/src/{io_engine.rs => io/engine/mod.rs} | 28 ++-- cache2/src/{io_engine => io/engine}/posix.rs | 0 cache2/src/{io_engine => io/engine}/tests.rs | 6 +- cache2/src/{io_engine => io/engine}/uring.rs | 0 cache2/src/io/mod.rs | 18 +++ cache2/src/lib.rs | 17 +-- cache2/src/{ => memory}/eviction.rs | 0 cache2/src/{memory.rs => memory/mod.rs} | 12 +- cache2/src/property_tests.rs | 44 +++---- .../appender.rs} | 32 ++--- .../{file_backend.rs => file_backend/mod.rs} | 106 +++++++-------- cache2/src/region/file_backend/tests.rs | 56 ++++---- .../{region_index.rs => region/index/mod.rs} | 33 +++-- .../src/{index.rs => region/index/packed.rs} | 0 .../index/storage/mod.rs} | 23 ++-- .../index/storage}/page_format.rs | 0 .../{region_manager.rs => region/manager.rs} | 22 ++-- cache2/src/region/mod.rs | 122 ++++++++++-------- .../{region_reader.rs => region/reader.rs} | 42 +++--- .../record/codec.rs} | 20 +-- .../src/{format.rs => region/record/mod.rs} | 10 +- .../recovery/metadata.rs} | 26 ++-- .../{recovery.rs => region/recovery/mod.rs} | 29 +++-- .../runtime}/metrics.rs | 0 .../runtime/mod.rs} | 82 ++++++------ .../runtime}/shutdown_tests.rs | 26 ++-- .../{region_staging.rs => region/staging.rs} | 28 ++-- .../src/{region_store.rs => region/store.rs} | 2 +- 35 files changed, 463 insertions(+), 403 deletions(-) rename cache2/src/{config.rs => config/mod.rs} (99%) rename cache2/src/{io_backend.rs => io/backend.rs} (100%) rename cache2/src/{io_engine.rs => io/engine/mod.rs} (99%) rename cache2/src/{io_engine => io/engine}/posix.rs (100%) rename cache2/src/{io_engine => io/engine}/tests.rs (99%) rename cache2/src/{io_engine => io/engine}/uring.rs (100%) create mode 100644 cache2/src/io/mod.rs rename cache2/src/{ => memory}/eviction.rs (100%) rename cache2/src/{memory.rs => memory/mod.rs} (99%) rename cache2/src/{region_appender.rs => region/appender.rs} (94%) rename cache2/src/region/{file_backend.rs => file_backend/mod.rs} (96%) rename cache2/src/{region_index.rs => region/index/mod.rs} (97%) rename cache2/src/{index.rs => region/index/packed.rs} (100%) rename cache2/src/{index_storage.rs => region/index/storage/mod.rs} (99%) rename cache2/src/{index_storage => region/index/storage}/page_format.rs (100%) rename cache2/src/{region_manager.rs => region/manager.rs} (99%) rename cache2/src/{region_reader.rs => region/reader.rs} (95%) rename cache2/src/{record_codec.rs => region/record/codec.rs} (96%) rename cache2/src/{format.rs => region/record/mod.rs} (96%) rename cache2/src/{region_metadata.rs => region/recovery/metadata.rs} (98%) rename cache2/src/{recovery.rs => region/recovery/mod.rs} (98%) rename cache2/src/{region_runtime => region/runtime}/metrics.rs (100%) rename cache2/src/{region_runtime.rs => region/runtime/mod.rs} (98%) rename cache2/src/{region_runtime => region/runtime}/shutdown_tests.rs (94%) rename cache2/src/{region_staging.rs => region/staging.rs} (98%) rename cache2/src/{region_store.rs => region/store.rs} (99%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e5165c7..7b91953 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,6 +50,8 @@ cargo test --workspace --release --all-features ## Rust Style +Use `module/mod.rs` for modules with child files; keep leaf modules in a single `.rs` file. + Declare restricted visibility at the module boundary and use `pub` for items in that module's API. ## Documentation diff --git a/cache2/src/benchmarking.rs b/cache2/src/benchmarking.rs index 063ef22..750ecdf 100644 --- a/cache2/src/benchmarking.rs +++ b/cache2/src/benchmarking.rs @@ -20,18 +20,18 @@ use std::io; use std::time::Duration; use std::time::Instant; -use crate::index::IndexEntry; -use crate::index::MAX_INDEX_PROBES; -use crate::index::MAX_PACKED_REGION_COUNT; -use crate::index::MAX_REGION_OFFSET; -use crate::index::PackedLocation; -use crate::index_storage::PartitionedIndexStorage; -use crate::index_storage::validated_index_partition_ranges; -use crate::record_codec::hash_key; -use crate::region_index::BenchmarkProbeStats; -use crate::region_index::RegionIndex; -use crate::region_index::reset_benchmark_probe_stats; -use crate::region_index::take_benchmark_probe_stats; +use crate::region::index::BenchmarkProbeStats; +use crate::region::index::IndexEntry; +use crate::region::index::MAX_INDEX_PROBES; +use crate::region::index::MAX_PACKED_REGION_COUNT; +use crate::region::index::MAX_REGION_OFFSET; +use crate::region::index::PackedLocation; +use crate::region::index::RegionIndex; +use crate::region::index::reset_benchmark_probe_stats; +use crate::region::index::storage::PartitionedIndexStorage; +use crate::region::index::storage::validated_index_partition_ranges; +use crate::region::index::take_benchmark_probe_stats; +use crate::region::record::hash_key; use crate::snapshot::CacheIndexSnapshot; const BENCHMARK_HASH_SEED: u64 = 0x6a09_e667_f3bc_c909; diff --git a/cache2/src/cache.rs b/cache2/src/cache.rs index 7812c40..8f6a907 100644 --- a/cache2/src/cache.rs +++ b/cache2/src/cache.rs @@ -40,16 +40,16 @@ use crate::config::storage_geometry; use crate::error::ErrorOperation; use crate::error::Result; use crate::error::from_io; -use crate::recovery::DataSuperblock; -use crate::recovery::PersistentId; -use crate::recovery::RECOVERY_IMAGE_INDEX_OFFSET; -use crate::recovery::recovery_image_index_len; use crate::region::FileRegionBackend; +use crate::region::HybridValueRead; +use crate::region::RegionDataPlane; use crate::region::RegionFiles; +use crate::region::RegionStore; use crate::region::SystemRegionFileSystem; -use crate::region_runtime::HybridValueRead; -use crate::region_runtime::RegionDataPlane; -use crate::region_store::RegionStore; +use crate::region::recovery::DataSuperblock; +use crate::region::recovery::PersistentId; +use crate::region::recovery::RECOVERY_IMAGE_INDEX_OFFSET; +use crate::region::recovery::recovery_image_index_len; use crate::snapshot::CacheSnapshot; use crate::snapshot::DetailedCacheSnapshot; use crate::snapshot::StartupMode; diff --git a/cache2/src/config.rs b/cache2/src/config/mod.rs similarity index 99% rename from cache2/src/config.rs rename to cache2/src/config/mod.rs index 63a4eb3..3b767b4 100644 --- a/cache2/src/config.rs +++ b/cache2/src/config/mod.rs @@ -14,7 +14,7 @@ //! Configuration construction, independent of file paths and runtime handles. -use crate::recovery::DataGeometry; +use crate::region::recovery::DataGeometry; mod runtime; pub use self::runtime::IoEngine; diff --git a/cache2/src/config/runtime.rs b/cache2/src/config/runtime.rs index 724cbd2..95a79ae 100644 --- a/cache2/src/config/runtime.rs +++ b/cache2/src/config/runtime.rs @@ -20,13 +20,13 @@ use super::StorageLayout; use crate::error::ErrorOperation; use crate::error::Result; use crate::error::from_io; -use crate::io_engine::IO_QUEUE_ENTRY_RESERVATION_BYTES; -use crate::io_engine::MAX_IO_REQUESTS_PER_ENGINE; -use crate::io_engine::io_uring_extra_memory_bytes; +use crate::io::engine::IO_QUEUE_ENTRY_RESERVATION_BYTES; +use crate::io::engine::MAX_IO_REQUESTS_PER_ENGINE; +use crate::io::engine::io_uring_extra_memory_bytes; use crate::memory::MemoryStore; -use crate::recovery::DataGeometry; -use crate::region_runtime::ActivityMetrics; -use crate::region_staging::RegionStaging; +use crate::region::ActivityMetrics; +use crate::region::RegionStaging; +use crate::region::recovery::DataGeometry; use crate::resources::CACHE_THREAD_STACK_BYTES; use crate::resources::MAX_CONFIG_COUNT; diff --git a/cache2/src/config/storage.rs b/cache2/src/config/storage.rs index c8b13cb..1d6a618 100644 --- a/cache2/src/config/storage.rs +++ b/cache2/src/config/storage.rs @@ -24,18 +24,18 @@ use super::StorageLayout; use crate::error::ErrorOperation; use crate::error::Result; use crate::error::from_io; -use crate::index::MAX_PACKED_REGION_COUNT; -use crate::index::MAX_PACKED_REGION_SIZE; -use crate::index_storage::IndexStorageError; -use crate::index_storage::validated_index_partition_ranges; -use crate::recovery::DataGeometry; -use crate::recovery::KEY_HASH_ALGORITHM_XXH3_64; -use crate::recovery::RECOVERY_IMAGE_INDEX_OFFSET; -use crate::recovery::STATE_FILE_SIZE; -use crate::recovery::recovery_image_index_len; -use crate::region_metadata::REGION_METADATA_PAGE_SIZE; -use crate::region_metadata::REGION_METADATA_PARTITIONS_PER_PAGE; -use crate::region_metadata::REGION_METADATA_REGIONS_PER_PAGE; +use crate::region::index::MAX_PACKED_REGION_COUNT; +use crate::region::index::MAX_PACKED_REGION_SIZE; +use crate::region::index::storage::IndexStorageError; +use crate::region::index::storage::validated_index_partition_ranges; +use crate::region::recovery::DataGeometry; +use crate::region::recovery::KEY_HASH_ALGORITHM_XXH3_64; +use crate::region::recovery::RECOVERY_IMAGE_INDEX_OFFSET; +use crate::region::recovery::REGION_METADATA_PAGE_SIZE; +use crate::region::recovery::REGION_METADATA_PARTITIONS_PER_PAGE; +use crate::region::recovery::REGION_METADATA_REGIONS_PER_PAGE; +use crate::region::recovery::STATE_FILE_SIZE; +use crate::region::recovery::recovery_image_index_len; const DEFAULT_REGION_SIZE: u64 = 32 * 1024 * 1024; const DEFAULT_EXPECTED_ENTRY_BYTES: u64 = 16 * 1024; @@ -209,8 +209,8 @@ pub fn cache_config( #[cfg(test)] mod tests { use super::*; - use crate::recovery::DataSuperblock; - use crate::recovery::PersistentId; + use crate::region::recovery::DataSuperblock; + use crate::region::recovery::PersistentId; #[test] fn constructed_layouts_encode_at_format_boundaries() { diff --git a/cache2/src/io_backend.rs b/cache2/src/io/backend.rs similarity index 100% rename from cache2/src/io_backend.rs rename to cache2/src/io/backend.rs diff --git a/cache2/src/io_engine.rs b/cache2/src/io/engine/mod.rs similarity index 99% rename from cache2/src/io_engine.rs rename to cache2/src/io/engine/mod.rs index 1c4fa81..910dc8d 100644 --- a/cache2/src/io_engine.rs +++ b/cache2/src/io/engine/mod.rs @@ -46,15 +46,11 @@ use std::time::Instant; use asyncband::semaphore::OwnedSemaphorePermit; use asyncband::semaphore::Semaphore; +use super::backend::IoBackend; #[cfg(unix)] -use crate::config::IoEngine as ConfiguredIoEngine; -#[cfg(unix)] -use crate::config::IoUringPoolConfig; -use crate::io_backend::IoBackend; -#[cfg(unix)] -use crate::io_backend::RuntimeFileBackend; +use super::backend::RuntimeFileBackend; #[cfg(unix)] -use crate::io_backend::RuntimeFileSet; +use super::backend::RuntimeFileSet; #[cfg(all( feature = "io-uring", target_os = "linux", @@ -66,7 +62,7 @@ use crate::io_backend::RuntimeFileSet; target_arch = "powerpc64" ) ))] -use crate::io_backend::RuntimeIoDirection; +use super::backend::RuntimeIoDirection; #[cfg(all( feature = "io-uring", target_os = "linux", @@ -78,8 +74,8 @@ use crate::io_backend::RuntimeIoDirection; target_arch = "powerpc64" ) ))] -use crate::io_backend::RuntimeIoPath; -use crate::io_backend::RuntimeIoStats; +use super::backend::RuntimeIoPath; +use super::backend::RuntimeIoStats; #[cfg(all( feature = "io-uring", target_os = "linux", @@ -91,10 +87,14 @@ use crate::io_backend::RuntimeIoStats; target_arch = "powerpc64" ) ))] -use crate::io_backend::RuntimeIoStatsHandle; -use crate::io_backend::WritePoint; -use crate::io_backend::read_exact_at_uninit_with_progress; -use crate::io_backend::write_all_at_with_progress; +use super::backend::RuntimeIoStatsHandle; +use super::backend::WritePoint; +use super::backend::read_exact_at_uninit_with_progress; +use super::backend::write_all_at_with_progress; +#[cfg(unix)] +use crate::config::IoEngine as ConfiguredIoEngine; +#[cfg(unix)] +use crate::config::IoUringPoolConfig; use crate::resources::BufferLease; use crate::resources::CACHE_THREAD_STACK_BYTES; use crate::snapshot::CacheIoDirectionSnapshot; diff --git a/cache2/src/io_engine/posix.rs b/cache2/src/io/engine/posix.rs similarity index 100% rename from cache2/src/io_engine/posix.rs rename to cache2/src/io/engine/posix.rs diff --git a/cache2/src/io_engine/tests.rs b/cache2/src/io/engine/tests.rs similarity index 99% rename from cache2/src/io_engine/tests.rs rename to cache2/src/io/engine/tests.rs index 771e3fd..3bb1dd3 100644 --- a/cache2/src/io_engine/tests.rs +++ b/cache2/src/io/engine/tests.rs @@ -19,9 +19,9 @@ use std::sync::atomic::AtomicU64; use std::time::Duration; use super::*; -use crate::io_backend::FileBackend; -use crate::io_backend::SyncMode; -use crate::io_backend::SyncPoint; +use crate::io::backend::FileBackend; +use crate::io::backend::SyncMode; +use crate::io::backend::SyncPoint; use crate::resources::ResourceController; use crate::resources::ResourceLimits; use crate::resources::aligned_buffer_capacity; diff --git a/cache2/src/io_engine/uring.rs b/cache2/src/io/engine/uring.rs similarity index 100% rename from cache2/src/io_engine/uring.rs rename to cache2/src/io/engine/uring.rs diff --git a/cache2/src/io/mod.rs b/cache2/src/io/mod.rs new file mode 100644 index 0000000..9086108 --- /dev/null +++ b/cache2/src/io/mod.rs @@ -0,0 +1,18 @@ +// Copyright 2026 ScopeDB, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Positioned file access and bounded owned-buffer execution. + +pub mod backend; +pub mod engine; diff --git a/cache2/src/lib.rs b/cache2/src/lib.rs index 969075a..8b19da3 100644 --- a/cache2/src/lib.rs +++ b/cache2/src/lib.rs @@ -61,25 +61,10 @@ pub use self::snapshot::RegionSnapshot; pub use self::snapshot::StartupMode; mod checksum; -mod eviction; -mod format; mod hashing; -mod index; -mod index_storage; -mod io_backend; -mod io_engine; +mod io; mod memory; -mod record_codec; -mod recovery; mod region; -mod region_appender; -mod region_index; -mod region_manager; -mod region_metadata; -mod region_reader; -mod region_runtime; -mod region_staging; -mod region_store; mod resources; #[cfg(test)] diff --git a/cache2/src/eviction.rs b/cache2/src/memory/eviction.rs similarity index 100% rename from cache2/src/eviction.rs rename to cache2/src/memory/eviction.rs diff --git a/cache2/src/memory.rs b/cache2/src/memory/mod.rs similarity index 99% rename from cache2/src/memory.rs rename to cache2/src/memory/mod.rs index 46bfeda..7dc9ab4 100644 --- a/cache2/src/memory.rs +++ b/cache2/src/memory/mod.rs @@ -28,16 +28,18 @@ use std::sync::atomic::AtomicU64; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; +use self::eviction::DetachedPolicy; +use self::eviction::EvictionState; +use self::eviction::MAX_POLICY_SCAN_STEPS; +use self::eviction::MAX_POLICY_SLOT_INDEX; +use self::eviction::PolicySlot; use crate::config::L1EvictionPolicy; -use crate::eviction::DetachedPolicy; -use crate::eviction::EvictionState; -use crate::eviction::MAX_POLICY_SCAN_STEPS; -use crate::eviction::MAX_POLICY_SLOT_INDEX; -use crate::eviction::PolicySlot; use crate::hashing::FixedPrehashedMap; use crate::hashing::route_hash; use crate::snapshot::CacheL1Snapshot; +mod eviction; + /// Charged retained-value ownership. Fixed entry, policy, and directory /// storage is planned and allocated separately during open. const MEMORY_ENTRY_OVERHEAD_BYTES: usize = 64; diff --git a/cache2/src/property_tests.rs b/cache2/src/property_tests.rs index 8e25987..2122c37 100644 --- a/cache2/src/property_tests.rs +++ b/cache2/src/property_tests.rs @@ -22,29 +22,29 @@ use quickcheck::QuickCheck; use crate::checksum::Crc32c; use crate::checksum::crc32c; -use crate::format::MAX_KEY_SIZE; -use crate::format::RECORD_ALIGNMENT; -use crate::format::RECORD_HEADER_SIZE; -use crate::format::RecordHeader; use crate::hashing::FixedPrehashedMap; -use crate::index::IndexEntry; -use crate::index::PackedLocation; -use crate::index::record_size_class_upper_bound; -use crate::index_storage::INDEX_IMAGE_SLOT_SIZE; -use crate::index_storage::IndexSlot; -use crate::index_storage::PartitionedIndexStorage; -use crate::record_codec::RecordPayload; -use crate::record_codec::encode_reinsert_into_hashed; -use crate::record_codec::encode_value_into_hashed; -use crate::record_codec::required_record_bytes; -use crate::recovery::DataSuperblock; -use crate::recovery::RECOVERY_PAGE_SIZE; -use crate::recovery::RecoveryImageHeader; -use crate::recovery::StateRecord; -use crate::region_index::ReclaimIndexAction; -use crate::region_index::RegionIndex; -use crate::region_manager::RegionAppendReservation; -use crate::region_metadata::RegionMetadata; +use crate::region::index::IndexEntry; +use crate::region::index::PackedLocation; +use crate::region::index::ReclaimIndexAction; +use crate::region::index::RegionIndex; +use crate::region::index::record_size_class_upper_bound; +use crate::region::index::storage::INDEX_IMAGE_SLOT_SIZE; +use crate::region::index::storage::IndexSlot; +use crate::region::index::storage::PartitionedIndexStorage; +use crate::region::manager::RegionAppendReservation; +use crate::region::record::MAX_KEY_SIZE; +use crate::region::record::RECORD_ALIGNMENT; +use crate::region::record::RECORD_HEADER_SIZE; +use crate::region::record::RecordHeader; +use crate::region::record::RecordPayload; +use crate::region::record::encode_reinsert_into_hashed; +use crate::region::record::encode_value_into_hashed; +use crate::region::record::required_record_bytes; +use crate::region::recovery::DataSuperblock; +use crate::region::recovery::RECOVERY_PAGE_SIZE; +use crate::region::recovery::RecoveryImageHeader; +use crate::region::recovery::RegionMetadata; +use crate::region::recovery::StateRecord; const MAX_PROPERTY_INPUT_BYTES: usize = 16 * 1024; const MAX_PROPERTY_MAP_ENTRIES: usize = 64; diff --git a/cache2/src/region_appender.rs b/cache2/src/region/appender.rs similarity index 94% rename from cache2/src/region_appender.rs rename to cache2/src/region/appender.rs index 2850ca7..39833a5 100644 --- a/cache2/src/region_appender.rs +++ b/cache2/src/region/appender.rs @@ -21,18 +21,18 @@ use std::fmt; use std::io; -use crate::io_backend::DIRECT_IO_ALIGNMENT; -use crate::io_backend::WritePoint; -use crate::io_engine::BoundedIoRequest; -use crate::io_engine::IoBuffer; -use crate::io_engine::IoEngine; -use crate::io_engine::IoOperation; -use crate::io_engine::OperationKind; -use crate::io_engine::RequestId; -use crate::io_engine::submit_cache_io; -use crate::recovery::DATA_REGION_AREA_OFFSET; -use crate::recovery::DataGeometry; -use crate::region_manager::RegionWriteSpan; +use super::manager::RegionWriteSpan; +use super::recovery::DATA_REGION_AREA_OFFSET; +use super::recovery::DataGeometry; +use crate::io::backend::DIRECT_IO_ALIGNMENT; +use crate::io::backend::WritePoint; +use crate::io::engine::BoundedIoRequest; +use crate::io::engine::IoBuffer; +use crate::io::engine::IoEngine; +use crate::io::engine::IoOperation; +use crate::io::engine::OperationKind; +use crate::io::engine::RequestId; +use crate::io::engine::submit_cache_io; pub struct RegionSpanSubmitError { pub error: io::Error, @@ -242,10 +242,10 @@ mod tests { use std::sync::Mutex; use super::*; - use crate::io_backend::IoBackend; - use crate::io_backend::SyncMode; - use crate::io_backend::SyncPoint; - use crate::io_engine::BackendIoEngine; + use crate::io::backend::IoBackend; + use crate::io::backend::SyncMode; + use crate::io::backend::SyncPoint; + use crate::io::engine::BackendIoEngine; use crate::resources::BufferLease; #[derive(Default)] diff --git a/cache2/src/region/file_backend.rs b/cache2/src/region/file_backend/mod.rs similarity index 96% rename from cache2/src/region/file_backend.rs rename to cache2/src/region/file_backend/mod.rs index 43b8541..32c2a64 100644 --- a/cache2/src/region/file_backend.rs +++ b/cache2/src/region/file_backend/mod.rs @@ -30,67 +30,67 @@ use super::RegionHealthLatch; use super::RegionManagerAuthority; use super::RegionShard; use super::guarded_index_result; +use super::index::MAX_INDEX_PARTITIONS; +use super::index::RegionIndex; +use super::index::storage::IndexImageBinding; +use super::index::storage::IndexPartitionRange; +use super::index::storage::IndexPhysicalStats; +use super::index::storage::PartitionedIndexStorage; +use super::index::storage::canonical_index_partition_ranges; use super::index_storage_io_error; +use super::manager::RegionManager; +use super::recovery::DataSuperblock; +use super::recovery::DataSuperblockProbe; +use super::recovery::PartitionMetadataRecord; +use super::recovery::PersistentId; +use super::recovery::RECOVERY_IMAGE_INDEX_OFFSET; +use super::recovery::RECOVERY_PAGE_SIZE; +use super::recovery::REGION_METADATA_PAGE_SIZE; +use super::recovery::REGION_METADATA_PARTITIONS_PER_PAGE; +use super::recovery::REGION_METADATA_REGIONS_PER_PAGE; +use super::recovery::RecoveryImageHeader; +use super::recovery::RecoveryImageHeaderProbe; +use super::recovery::RecoveryState; +use super::recovery::RegionMetadata; +use super::recovery::RegionMetadataError; +use super::recovery::RegionMetadataRecord; +use super::recovery::RegionMetadataRoot; +use super::recovery::RegionMetadataState; +use super::recovery::STATE_FILE_SIZE; +use super::recovery::STATE_SLOT_COUNT; +use super::recovery::SelectedState; +use super::recovery::StateBinding; +use super::recovery::StatePageWrite; +use super::recovery::StateRecord; +use super::recovery::StateSelectionError; +use super::recovery::clean_image_matches; +use super::recovery::latest_state; +use super::recovery::prepare_next_state; +use super::recovery::prepare_running_barrier; +use super::recovery::recovery_image_index_len; use super::region_metadata_io_error; +#[cfg(test)] +use super::runtime::HybridValueRead; +use super::runtime::RegionDataPlane; +use super::store::RecoveryPlan; +use super::store::RegionBackend; +use super::store::RegionStore; use crate::config::CacheConfig; use crate::config::IoMode; #[cfg(test)] use crate::config::RuntimeOptions; #[cfg(test)] use crate::config::cache_config; -use crate::index::MAX_INDEX_PARTITIONS; -use crate::index_storage::IndexImageBinding; -use crate::index_storage::IndexPartitionRange; -use crate::index_storage::IndexPhysicalStats; -use crate::index_storage::PartitionedIndexStorage; -use crate::index_storage::canonical_index_partition_ranges; -use crate::io_backend::ControlIoBackend; -use crate::io_backend::FileBackend; -use crate::io_backend::IoBackend; -use crate::io_backend::RuntimeFileSet; -use crate::io_backend::SyncMode; -use crate::io_backend::SyncPoint; -use crate::io_backend::WritePoint; -use crate::io_backend::read_at_bounded; -use crate::io_backend::read_exact_at; -use crate::io_backend::write_all_at; -use crate::recovery::DataSuperblock; -use crate::recovery::DataSuperblockProbe; -use crate::recovery::PersistentId; -use crate::recovery::RECOVERY_IMAGE_INDEX_OFFSET; -use crate::recovery::RECOVERY_PAGE_SIZE; -use crate::recovery::RecoveryImageHeader; -use crate::recovery::RecoveryImageHeaderProbe; -use crate::recovery::RecoveryState; -use crate::recovery::STATE_FILE_SIZE; -use crate::recovery::STATE_SLOT_COUNT; -use crate::recovery::SelectedState; -use crate::recovery::StateBinding; -use crate::recovery::StatePageWrite; -use crate::recovery::StateRecord; -use crate::recovery::StateSelectionError; -use crate::recovery::clean_image_matches; -use crate::recovery::latest_state; -use crate::recovery::prepare_next_state; -use crate::recovery::prepare_running_barrier; -use crate::recovery::recovery_image_index_len; -use crate::region_index::RegionIndex; -use crate::region_manager::RegionManager; -use crate::region_metadata::PartitionMetadataRecord; -use crate::region_metadata::REGION_METADATA_PAGE_SIZE; -use crate::region_metadata::REGION_METADATA_PARTITIONS_PER_PAGE; -use crate::region_metadata::REGION_METADATA_REGIONS_PER_PAGE; -use crate::region_metadata::RegionMetadata; -use crate::region_metadata::RegionMetadataError; -use crate::region_metadata::RegionMetadataRecord; -use crate::region_metadata::RegionMetadataRoot; -use crate::region_metadata::RegionMetadataState; -#[cfg(test)] -use crate::region_runtime::HybridValueRead; -use crate::region_runtime::RegionDataPlane; -use crate::region_store::RecoveryPlan; -use crate::region_store::RegionBackend; -use crate::region_store::RegionStore; +use crate::io::backend::ControlIoBackend; +use crate::io::backend::FileBackend; +use crate::io::backend::IoBackend; +use crate::io::backend::RuntimeFileSet; +use crate::io::backend::SyncMode; +use crate::io::backend::SyncPoint; +use crate::io::backend::WritePoint; +use crate::io::backend::read_at_bounded; +use crate::io::backend::read_exact_at; +use crate::io::backend::write_all_at; #[cfg(test)] use crate::snapshot::CacheSnapshot; #[cfg(test)] diff --git a/cache2/src/region/file_backend/tests.rs b/cache2/src/region/file_backend/tests.rs index adab151..413dcf2 100644 --- a/cache2/src/region/file_backend/tests.rs +++ b/cache2/src/region/file_backend/tests.rs @@ -27,28 +27,28 @@ use std::time::Instant; use super::*; use crate::config::ReadAdmission; -use crate::index::IndexEntry; -use crate::index::PackedLocation; -use crate::index_storage::INDEX_IMAGE_SLOTS_PER_PAGE; -use crate::index_storage::IndexSlot; -use crate::io_backend::testing::FaultAction; -use crate::io_backend::testing::FaultBackend; -use crate::io_backend::testing::FaultEvent; -use crate::io_backend::testing::FaultHandle; -use crate::io_engine::BackendIoEngine; -use crate::io_engine::IoEngine; -use crate::record_codec::hash_key; -use crate::record_codec::required_record_bytes; -use crate::recovery::DATA_REGION_AREA_OFFSET; -use crate::recovery::DataGeometry; -use crate::recovery::PersistentId; +use crate::io::backend::testing::FaultAction; +use crate::io::backend::testing::FaultBackend; +use crate::io::backend::testing::FaultEvent; +use crate::io::backend::testing::FaultHandle; +use crate::io::engine::BackendIoEngine; +use crate::io::engine::IoEngine; use crate::region::RegionStageValue; -use crate::region_reader::ReadCandidate; -use crate::region_reader::ReadCompletion; -use crate::region_reader::ReadPlan; -use crate::region_reader::plan_read; -use crate::region_staging::RegionStaging; -use crate::region_staging::StagedRecord; +use crate::region::index::IndexEntry; +use crate::region::index::PackedLocation; +use crate::region::index::storage::INDEX_IMAGE_SLOTS_PER_PAGE; +use crate::region::index::storage::IndexSlot; +use crate::region::reader::ReadCandidate; +use crate::region::reader::ReadCompletion; +use crate::region::reader::ReadPlan; +use crate::region::reader::plan_read; +use crate::region::record::hash_key; +use crate::region::record::required_record_bytes; +use crate::region::recovery::DATA_REGION_AREA_OFFSET; +use crate::region::recovery::DataGeometry; +use crate::region::recovery::PersistentId; +use crate::region::staging::RegionStaging; +use crate::region::staging::StagedRecord; use crate::resources::ResourceController; use crate::resources::ResourceLimits; use crate::snapshot::StartupMode; @@ -125,7 +125,7 @@ fn state_page_reads_stop_after_the_interrupted_retry_budget() { .iter() .filter(|event| **event == FaultEvent::Read) .count(), - crate::io_backend::MAX_INTERRUPTED_RETRIES + 1 + crate::io::backend::MAX_INTERRUPTED_RETRIES + 1 ); } @@ -335,7 +335,7 @@ fn run_crash_child(case: &str, files: RegionFiles) -> ! { "open" => { let _store = RegionStore::open(4096, FileRegionBackend::for_test(files, data, 4096)).unwrap(); - crate::io_backend::testing::kill_process(); + crate::io::backend::testing::kill_process(); } "write" | "drain" => { let store = @@ -344,7 +344,7 @@ fn run_crash_child(case: &str, files: RegionFiles) -> ! { if case == "drain" { store.drain().unwrap(); } - crate::io_backend::testing::kill_process(); + crate::io::backend::testing::kill_process(); } "warm-data" | "warm-image" | "clean-state" => { let (file_system, faults, _) = FaultRegionFileSystem::new(); @@ -833,7 +833,7 @@ fn completed_record_publication_does_not_enter_region_manager() { let record = StagedRecord::new( 7, IndexEntry { - location: crate::index::PackedLocation::new(0, 0, 64).unwrap(), + location: crate::region::index::PackedLocation::new(0, 0, 64).unwrap(), }, 1, ); @@ -1114,7 +1114,7 @@ fn same_hash_candidate_requires_full_key() { let wrong_length_location = PackedLocation::new( current.entry.location.region_id(), current.entry.location.offset(), - current.entry.location.record_len() + crate::format::RECORD_ALIGNMENT, + current.entry.location.record_len() + crate::region::record::RECORD_ALIGNMENT, ) .unwrap(); let wrong_length = ReadCandidate { @@ -1430,11 +1430,11 @@ fn complete_warm_image_maps_without_rebuilding_index_slots() { let directory = TestDirectory::new(); let config = INDEX_IMAGE_SLOTS_PER_PAGE + 8; let data = test_data_superblock_with_regions(REGION_SHARDS + 1); - let value = IndexSlot::from_state(crate::index_storage::IndexSlotState::Value { + let value = IndexSlot::from_state(crate::region::index::storage::IndexSlotState::Value { fingerprint: 7, displacement: 0, entry: IndexEntry { - location: crate::index::PackedLocation::new(0, 0, 32).unwrap(), + location: crate::region::index::PackedLocation::new(0, 0, 32).unwrap(), }, }); diff --git a/cache2/src/region_index.rs b/cache2/src/region/index/mod.rs similarity index 97% rename from cache2/src/region_index.rs rename to cache2/src/region/index/mod.rs index 19a0f97..0ccfa37 100644 --- a/cache2/src/region_index.rs +++ b/cache2/src/region/index/mod.rs @@ -28,16 +28,31 @@ use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; +use self::storage::IndexPartitionWriteGuard; +use self::storage::IndexSlotState; +use self::storage::IndexStorageError; +use self::storage::PartitionedIndexStorage; use crate::hashing::route_hash; -use crate::index::INDEX_CANDIDATES; -use crate::index::IndexEntry; -use crate::index::PackedLocation; -use crate::index_storage::IndexPartitionWriteGuard; -use crate::index_storage::IndexSlotState; -use crate::index_storage::IndexStorageError; -use crate::index_storage::PartitionedIndexStorage; use crate::snapshot::CacheIndexSnapshot; +mod packed; +pub use self::packed::INDEX_CANDIDATES; +pub use self::packed::IndexEntry; +pub use self::packed::MAX_INDEX_PARTITIONS; +#[cfg(feature = "benchmarking")] +pub use self::packed::MAX_INDEX_PROBES; +pub use self::packed::MAX_PACKED_REGION_COUNT; +pub use self::packed::MAX_PACKED_REGION_SIZE; +pub use self::packed::MAX_RECORD_LEN; +#[cfg(feature = "benchmarking")] +pub use self::packed::MAX_REGION_OFFSET; +pub use self::packed::PackedLocation; +pub use self::packed::PackedLocationError; +pub use self::packed::index_partition_for; +pub use self::packed::record_size_class_upper_bound; + +pub mod storage; + const CANDIDATE_OFFSETS: [usize; INDEX_CANDIDATES] = [0, 23, 61, 97]; const FINGERPRINT_MASK: u16 = (1 << 14) - 1; const REFERENCE_WORD_BITS: usize = u64::BITS as usize; @@ -692,8 +707,8 @@ fn candidate_offset(displacement: usize, slot_count: usize) -> usize { #[cfg(test)] mod tests { use super::*; - use crate::index_storage::IndexPhysicalStats; - use crate::record_codec::hash_key; + use crate::region::index::storage::IndexPhysicalStats; + use crate::region::record::hash_key; fn entry(region_id: u32, offset: u32) -> IndexEntry { IndexEntry { diff --git a/cache2/src/index.rs b/cache2/src/region/index/packed.rs similarity index 100% rename from cache2/src/index.rs rename to cache2/src/region/index/packed.rs diff --git a/cache2/src/index_storage.rs b/cache2/src/region/index/storage/mod.rs similarity index 99% rename from cache2/src/index_storage.rs rename to cache2/src/region/index/storage/mod.rs index f8d148b..2e6ac44 100644 --- a/cache2/src/index_storage.rs +++ b/cache2/src/region/index/storage/mod.rs @@ -44,13 +44,13 @@ use self::page_format::put_u32; use self::page_format::put_u64; use self::page_format::read_u64; use self::page_format::validate_page_header; -use crate::index::INDEX_CANDIDATES; -use crate::index::IndexEntry; -use crate::index::MAX_INDEX_PARTITIONS; -use crate::index::PackedLocation; -use crate::index::PackedLocationError; -use crate::index::index_partition_for; -use crate::index::record_size_class_upper_bound; +use super::INDEX_CANDIDATES; +use super::IndexEntry; +use super::MAX_INDEX_PARTITIONS; +use super::PackedLocation; +use super::PackedLocationError; +use super::index_partition_for; +use super::record_size_class_upper_bound; mod page_format; pub use self::page_format::INDEX_IMAGE_PAGE_HEADER_SIZE; @@ -278,7 +278,8 @@ impl IndexSlot { entry, } => { let location = entry.location; - let offset_units = u64::from(location.offset() / crate::format::RECORD_ALIGNMENT); + let offset_units = + u64::from(location.offset() / crate::region::record::RECORD_ALIGNMENT); Self { encoded: u64::from(location.region_id()) | (offset_units << SLOT_OFFSET_SHIFT) @@ -311,7 +312,7 @@ impl IndexSlot { .ok_or(IndexSlotSemanticError::NonCanonicalMarker)?; let region_id = ((self.encoded >> SLOT_REGION_SHIFT) & SLOT_REGION_MASK) as u32; let offset_units = ((self.encoded >> SLOT_OFFSET_SHIFT) & SLOT_OFFSET_MASK) as u32; - let offset = offset_units * crate::format::RECORD_ALIGNMENT; + let offset = offset_units * crate::region::record::RECORD_ALIGNMENT; let location = PackedLocation::new(region_id, offset, record_len) .map_err(IndexSlotSemanticError::InvalidLocation)?; Ok(IndexSlotState::Value { @@ -1882,7 +1883,7 @@ mod tests { fn sample_slot(seed: u64) -> IndexSlot { let location = PackedLocation::new( (seed % 64) as u32, - ((seed % 128) * u64::from(crate::format::RECORD_ALIGNMENT)) as u32, + ((seed % 128) * u64::from(crate::region::record::RECORD_ALIGNMENT)) as u32, 32, ) .unwrap(); @@ -2288,7 +2289,7 @@ mod tests { .unwrap(); assert_golden( &encoded, - include_str!("fixtures/format_v1/index_page.golden"), + include_str!("../../../fixtures/format_v1/index_page.golden"), ); } diff --git a/cache2/src/index_storage/page_format.rs b/cache2/src/region/index/storage/page_format.rs similarity index 100% rename from cache2/src/index_storage/page_format.rs rename to cache2/src/region/index/storage/page_format.rs diff --git a/cache2/src/region_manager.rs b/cache2/src/region/manager.rs similarity index 99% rename from cache2/src/region_manager.rs rename to cache2/src/region/manager.rs index 73b1eb7..bd7d634 100644 --- a/cache2/src/region_manager.rs +++ b/cache2/src/region/manager.rs @@ -21,15 +21,15 @@ use std::collections::VecDeque; -use crate::format::RECORD_ALIGNMENT; -use crate::io_backend::DIRECT_IO_ALIGNMENT; -use crate::recovery::PersistentId; -use crate::region_metadata::PartitionMetadataRecord; -use crate::region_metadata::RegionMetadata; -use crate::region_metadata::RegionMetadataError; -use crate::region_metadata::RegionMetadataRecord; -use crate::region_metadata::RegionMetadataRoot; -use crate::region_metadata::RegionMetadataState; +use super::record::RECORD_ALIGNMENT; +use super::recovery::PartitionMetadataRecord; +use super::recovery::PersistentId; +use super::recovery::RegionMetadata; +use super::recovery::RegionMetadataError; +use super::recovery::RegionMetadataRecord; +use super::recovery::RegionMetadataRoot; +use super::recovery::RegionMetadataState; +use crate::io::backend::DIRECT_IO_ALIGNMENT; use crate::snapshot::RegionSnapshot; const UNASSIGNED_REGION: u32 = u32::MAX; @@ -1280,8 +1280,8 @@ fn try_unassigned_queue( #[cfg(test)] mod tests { use super::*; - use crate::index_storage::INDEX_IMAGE_SLOTS_PER_PAGE; - use crate::index_storage::canonical_index_partition_ranges; + use crate::region::index::storage::INDEX_IMAGE_SLOTS_PER_PAGE; + use crate::region::index::storage::canonical_index_partition_ranges; fn id(byte: u8) -> PersistentId { PersistentId::from_bytes([byte; 16]).unwrap() diff --git a/cache2/src/region/mod.rs b/cache2/src/region/mod.rs index b86da0d..0329887 100644 --- a/cache2/src/region/mod.rs +++ b/cache2/src/region/mod.rs @@ -24,52 +24,51 @@ use std::sync::atomic::AtomicU8; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; -use crate::checksum::crc32c; -use crate::format::RECORD_ALIGNMENT; -use crate::format::RECORD_HEADER_SIZE; -use crate::format::RecordHeader; -use crate::hashing::route_hash; +use self::appender::submit_span; #[cfg(test)] -use crate::index::IndexEntry; -use crate::index::PackedLocation; -use crate::index_storage::INDEX_IMAGE_PAGE_SIZE; -use crate::index_storage::IndexStorageError; -use crate::index_storage::WARM_IMAGE_WRITE_BATCH_BYTES; -use crate::index_storage::canonical_index_partition_ranges; -use crate::io_engine::IoEngine; -use crate::io_engine::ReadSlot; -use crate::record_codec::RecordEncodeError; -use crate::record_codec::RecordPayload; -use crate::record_codec::encode_reinsert_into_hashed; -use crate::record_codec::encode_value_into_hashed; +use self::index::IndexEntry; +use self::index::PackedLocation; +use self::index::ReclaimIndexAction; +use self::index::RegionIndex; +use self::index::heat_memory_bytes; +use self::index::storage::INDEX_IMAGE_PAGE_SIZE; +use self::index::storage::IndexStorageError; +use self::index::storage::WARM_IMAGE_WRITE_BATCH_BYTES; +use self::index::storage::canonical_index_partition_ranges; +use self::manager::RegionManager; +use self::manager::RegionMutationError; +use self::manager::RegionReclaimReceipt; +use self::reader::PendingRead; +use self::reader::ReadCandidate; +use self::reader::ReadCompletion; +use self::reader::ReadPlan; #[cfg(test)] -use crate::record_codec::hash_key; -use crate::recovery::DATA_REGION_AREA_OFFSET; -use crate::recovery::recovery_image_index_len; -use crate::region_appender::submit_span; -use crate::region_index::ReclaimIndexAction; -use crate::region_index::RegionIndex; -use crate::region_index::heat_memory_bytes; -use crate::region_manager::RegionManager; -use crate::region_manager::RegionMutationError; -use crate::region_manager::RegionReclaimReceipt; -use crate::region_metadata::REGION_METADATA_PAGE_SIZE; -use crate::region_metadata::REGION_METADATA_PARTITIONS_PER_PAGE; -use crate::region_metadata::REGION_METADATA_REGIONS_PER_PAGE; -use crate::region_metadata::RegionMetadataError; -use crate::region_reader::PendingRead; -use crate::region_reader::ReadCandidate; -use crate::region_reader::ReadCompletion; -use crate::region_reader::ReadPlan; +use self::reader::plan_read; +use self::reader::submit_read; +use self::record::RECORD_ALIGNMENT; +use self::record::RECORD_HEADER_SIZE; +use self::record::RecordEncodeError; +use self::record::RecordHeader; +use self::record::RecordPayload; +use self::record::encode_reinsert_into_hashed; +use self::record::encode_value_into_hashed; #[cfg(test)] -use crate::region_reader::plan_read; -use crate::region_reader::submit_read; -use crate::region_staging::RegionStaging; -use crate::region_staging::StageAppend; -use crate::region_staging::StagedRecord; -use crate::region_staging::StagedWrite; -use crate::region_staging::StagingEncodeError; -use crate::region_staging::StagingError; +use self::record::hash_key; +use self::recovery::DATA_REGION_AREA_OFFSET; +use self::recovery::REGION_METADATA_PAGE_SIZE; +use self::recovery::REGION_METADATA_PARTITIONS_PER_PAGE; +use self::recovery::REGION_METADATA_REGIONS_PER_PAGE; +use self::recovery::RegionMetadataError; +use self::recovery::recovery_image_index_len; +use self::staging::StageAppend; +use self::staging::StagedRecord; +use self::staging::StagedWrite; +use self::staging::StagingEncodeError; +use self::staging::StagingError; +use crate::checksum::crc32c; +use crate::hashing::route_hash; +use crate::io::engine::IoEngine; +use crate::io::engine::ReadSlot; use crate::resources::BufferLease; use crate::snapshot::CacheIndexSnapshot; use crate::snapshot::RegionSnapshot; @@ -79,6 +78,25 @@ pub use self::file_backend::FileRegionBackend; pub use self::file_backend::RegionFiles; pub use self::file_backend::SystemRegionFileSystem; +pub mod index; +pub mod manager; +pub mod record; +pub mod recovery; + +mod runtime; +pub use self::runtime::ActivityMetrics; +pub use self::runtime::HybridValueRead; +pub use self::runtime::RegionDataPlane; + +mod staging; +pub use self::staging::RegionStaging; + +mod store; +pub use self::store::RegionStore; + +mod appender; +mod reader; + const REGION_HEALTHY: u8 = 0; const REGION_MISS_ONLY: u8 = 1; /// One-way health fence shared by the live, frozen, and prepared-clean owners. @@ -363,8 +381,8 @@ impl FileRegionCore { ..RegionReclaimStats::default() }; let alignment = u64::from(RECORD_ALIGNMENT); - let raw_budget = - (receipt.used_offset / 8).saturating_sub(crate::io_backend::DIRECT_IO_ALIGNMENT as u64); + let raw_budget = (receipt.used_offset / 8) + .saturating_sub(crate::io::backend::DIRECT_IO_ALIGNMENT as u64); let mut reinsert_budget = raw_budget - raw_budget % alignment; while offset < bytes.len() { let header_end = offset.checked_add(RECORD_HEADER_SIZE).ok_or_else(|| { @@ -603,7 +621,7 @@ impl FileRegionCore { fn read_value( &self, engine: &dyn IoEngine, - geometry: crate::recovery::DataGeometry, + geometry: crate::region::recovery::DataGeometry, buffer: BufferLease, hash_seed: u64, key: &[u8], @@ -938,14 +956,14 @@ impl FileRegionCore { staging: &RegionStaging, engine: &dyn IoEngine, shard_id: usize, - ) -> io::Result> { + ) -> io::Result> { let shard_mutation = self.lock_shard_mutation(shard_id)?; let geometry_for = |manager: &RegionManager| { let region_count = u32::try_from(manager.regions().len()).map_err(|_| { self.health.enter_miss_only(); io::Error::new(io::ErrorKind::InvalidData, "Region count is too large") })?; - let data_file_len = crate::recovery::DataGeometry::expected_file_len( + let data_file_len = crate::region::recovery::DataGeometry::expected_file_len( manager.region_size(), region_count, ) @@ -953,7 +971,7 @@ impl FileRegionCore { self.health.enter_miss_only(); io::Error::new(io::ErrorKind::InvalidData, "data geometry overflow") })?; - Ok::<_, io::Error>(crate::recovery::DataGeometry { + Ok::<_, io::Error>(crate::region::recovery::DataGeometry { data_file_len, region_size: manager.region_size(), region_count, @@ -1043,7 +1061,7 @@ impl FileRegionCore { } }; let completion = flight.wait(engine); - let crate::region_appender::RegionSpanCompletion { + let crate::region::appender::RegionSpanCompletion { span, result, buffer, @@ -1157,8 +1175,8 @@ impl FileRegionCore { fn fail_staged_span( &self, staging: &RegionStaging, - span: crate::region_manager::RegionWriteSpan, - buffer: Option, + span: crate::region::manager::RegionWriteSpan, + buffer: Option, records: Vec, ) { self.health.enter_miss_only(); diff --git a/cache2/src/region_reader.rs b/cache2/src/region/reader.rs similarity index 95% rename from cache2/src/region_reader.rs rename to cache2/src/region/reader.rs index 0288d59..9d7ea22 100644 --- a/cache2/src/region_reader.rs +++ b/cache2/src/region/reader.rs @@ -23,20 +23,20 @@ use std::io; use std::ops::Range; -use crate::format::RECORD_ALIGNMENT; -use crate::index::IndexEntry; -use crate::io_engine::BoundedIoRequest; -use crate::io_engine::IoBuffer; -use crate::io_engine::IoCompletion; -use crate::io_engine::IoDeadlineExceeded; -use crate::io_engine::IoEngine; -use crate::io_engine::IoOperation; -use crate::io_engine::OperationKind; -use crate::io_engine::ReadSlot; -use crate::io_engine::RequestId; -use crate::io_engine::submit_cache_read; -use crate::recovery::DATA_REGION_AREA_OFFSET; -use crate::recovery::DataGeometry; +use super::index::IndexEntry; +use super::record::RECORD_ALIGNMENT; +use super::recovery::DATA_REGION_AREA_OFFSET; +use super::recovery::DataGeometry; +use crate::io::engine::BoundedIoRequest; +use crate::io::engine::IoBuffer; +use crate::io::engine::IoCompletion; +use crate::io::engine::IoDeadlineExceeded; +use crate::io::engine::IoEngine; +use crate::io::engine::IoOperation; +use crate::io::engine::OperationKind; +use crate::io::engine::ReadSlot; +use crate::io::engine::RequestId; +use crate::io::engine::submit_cache_read; use crate::resources::BufferLease; const _READ_ALIGNMENT: usize = 4096; @@ -319,12 +319,12 @@ mod tests { use std::sync::Mutex; use super::*; - use crate::index::PackedLocation; - use crate::io_backend::IoBackend; - use crate::io_backend::SyncMode; - use crate::io_backend::SyncPoint; - use crate::io_backend::WritePoint; - use crate::io_engine::BackendIoEngine; + use crate::io::backend::IoBackend; + use crate::io::backend::SyncMode; + use crate::io::backend::SyncPoint; + use crate::io::backend::WritePoint; + use crate::io::engine::BackendIoEngine; + use crate::region::index::PackedLocation; use crate::resources::ResourceController; use crate::resources::ResourceLimits; @@ -380,7 +380,7 @@ mod tests { } } - fn entry(location: crate::index::PackedLocation) -> IndexEntry { + fn entry(location: crate::region::index::PackedLocation) -> IndexEntry { IndexEntry { location } } diff --git a/cache2/src/record_codec.rs b/cache2/src/region/record/codec.rs similarity index 96% rename from cache2/src/record_codec.rs rename to cache2/src/region/record/codec.rs index e5b4f03..1194329 100644 --- a/cache2/src/record_codec.rs +++ b/cache2/src/region/record/codec.rs @@ -23,16 +23,16 @@ use std::fmt; use hashcrew::xxhash::xxh3_64_with_seed; +use super::MAX_KEY_SIZE; +use super::RECORD_ALIGNMENT; +use super::RECORD_HEADER_SIZE; +use super::RecordHeader; use crate::checksum::Crc32c; -use crate::format::MAX_KEY_SIZE; -use crate::format::RECORD_ALIGNMENT; -use crate::format::RECORD_HEADER_SIZE; -use crate::format::RecordHeader; -use crate::index::IndexEntry; -use crate::index::MAX_RECORD_LEN; -use crate::index::PackedLocation; -use crate::index::PackedLocationError; -use crate::region_manager::RegionAppendReservation; +use crate::region::index::IndexEntry; +use crate::region::index::MAX_RECORD_LEN; +use crate::region::index::PackedLocation; +use crate::region::index::PackedLocationError; +use crate::region::manager::RegionAppendReservation; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum RecordEncodeError { @@ -301,7 +301,7 @@ mod tests { required_record_bytes(key_len, value_len).unwrap() as usize, expected ); - assert!(!expected.is_multiple_of(crate::io_backend::DIRECT_IO_ALIGNMENT)); + assert!(!expected.is_multiple_of(crate::io::backend::DIRECT_IO_ALIGNMENT)); } #[test] diff --git a/cache2/src/format.rs b/cache2/src/region/record/mod.rs similarity index 96% rename from cache2/src/format.rs rename to cache2/src/region/record/mod.rs index 12e5ef8..2c70750 100644 --- a/cache2/src/format.rs +++ b/cache2/src/region/record/mod.rs @@ -20,6 +20,14 @@ use crate::checksum::Crc32c; use crate::checksum::crc32c; +mod codec; +pub use self::codec::RecordEncodeError; +pub use self::codec::RecordPayload; +pub use self::codec::encode_reinsert_into_hashed; +pub use self::codec::encode_value_into_hashed; +pub use self::codec::hash_key; +pub use self::codec::required_record_bytes; + pub const RECORD_FORMAT_VERSION: u16 = 1; pub const RECORD_HEADER_SIZE: usize = 48; @@ -215,7 +223,7 @@ mod tests { encoded[RECORD_HEADER_SIZE..RECORD_HEADER_SIZE + payload.len()].copy_from_slice(&payload); let golden = assert_golden( &encoded, - include_str!("fixtures/format_v1/value_record.golden"), + include_str!("../../fixtures/format_v1/value_record.golden"), ); assert_eq!( RecordHeader::decode(&golden[..RECORD_HEADER_SIZE]), diff --git a/cache2/src/region_metadata.rs b/cache2/src/region/recovery/metadata.rs similarity index 98% rename from cache2/src/region_metadata.rs rename to cache2/src/region/recovery/metadata.rs index 3a2d357..06b0729 100644 --- a/cache2/src/region_metadata.rs +++ b/cache2/src/region/recovery/metadata.rs @@ -20,19 +20,19 @@ use std::fmt; +use super::DataSuperblock; +use super::PersistentId; +use super::RECOVERY_PAGE_SIZE; +use super::RecoveryImageHeader; use crate::checksum::Crc32c; -use crate::index::MAX_INDEX_PARTITIONS; -use crate::index::MAX_PACKED_REGION_COUNT; -use crate::index::MAX_PACKED_REGION_SIZE; -use crate::index_storage::INDEX_IMAGE_PAGE_SIZE; -use crate::index_storage::INDEX_IMAGE_SLOTS_PER_PAGE; -use crate::index_storage::IndexStorageError; -use crate::index_storage::canonical_index_partition_ranges; -use crate::index_storage::validated_index_partition_ranges; -use crate::recovery::DataSuperblock; -use crate::recovery::PersistentId; -use crate::recovery::RECOVERY_PAGE_SIZE; -use crate::recovery::RecoveryImageHeader; +use crate::region::index::MAX_INDEX_PARTITIONS; +use crate::region::index::MAX_PACKED_REGION_COUNT; +use crate::region::index::MAX_PACKED_REGION_SIZE; +use crate::region::index::storage::INDEX_IMAGE_PAGE_SIZE; +use crate::region::index::storage::INDEX_IMAGE_SLOTS_PER_PAGE; +use crate::region::index::storage::IndexStorageError; +use crate::region::index::storage::canonical_index_partition_ranges; +use crate::region::index::storage::validated_index_partition_ranges; pub const REGION_METADATA_PAGE_SIZE: usize = RECOVERY_PAGE_SIZE; const REGION_METADATA_PAGE_HEADER_SIZE: usize = 64; @@ -1355,7 +1355,7 @@ mod tests { let encoded = expected.encode().unwrap(); let golden = assert_golden( &encoded, - include_str!("fixtures/format_v1/region_metadata.golden"), + include_str!("../../fixtures/format_v1/region_metadata.golden"), ); assert_eq!(RegionMetadata::decode(&golden).unwrap(), expected); } diff --git a/cache2/src/recovery.rs b/cache2/src/region/recovery/mod.rs similarity index 98% rename from cache2/src/recovery.rs rename to cache2/src/region/recovery/mod.rs index 3116a32..8fb7cc9 100644 --- a/cache2/src/recovery.rs +++ b/cache2/src/region/recovery/mod.rs @@ -20,14 +20,25 @@ //! `CLEAN`. This module performs no I/O; callers must write the returned page //! to the selected slot and provide the required `fdatasync` barrier. +use super::index::MAX_PACKED_REGION_COUNT; +use super::index::MAX_PACKED_REGION_SIZE; +use super::index::storage::INDEX_IMAGE_PAGE_SIZE; +use super::index::storage::INDEX_IMAGE_SLOTS_PER_PAGE; +use super::record::RECORD_ALIGNMENT; +use super::record::RECORD_FORMAT_VERSION; use crate::checksum::Crc32c; use crate::checksum::crc32c; -use crate::format::RECORD_ALIGNMENT; -use crate::format::RECORD_FORMAT_VERSION; -use crate::index::MAX_PACKED_REGION_COUNT; -use crate::index::MAX_PACKED_REGION_SIZE; -use crate::index_storage::INDEX_IMAGE_PAGE_SIZE; -use crate::index_storage::INDEX_IMAGE_SLOTS_PER_PAGE; + +mod metadata; +pub use self::metadata::PartitionMetadataRecord; +pub use self::metadata::REGION_METADATA_PAGE_SIZE; +pub use self::metadata::REGION_METADATA_PARTITIONS_PER_PAGE; +pub use self::metadata::REGION_METADATA_REGIONS_PER_PAGE; +pub use self::metadata::RegionMetadata; +pub use self::metadata::RegionMetadataError; +pub use self::metadata::RegionMetadataRecord; +pub use self::metadata::RegionMetadataRoot; +pub use self::metadata::RegionMetadataState; const RECOVERY_FORMAT_VERSION: u16 = 1; pub const RECOVERY_PAGE_SIZE: usize = 4 * 1024; @@ -1009,7 +1020,7 @@ mod tests { let data = data_superblock(); let data_golden = assert_golden( &data.encode().unwrap(), - include_str!("fixtures/format_v1/data_superblock.golden"), + include_str!("../../fixtures/format_v1/data_superblock.golden"), ); assert_eq!( DataSuperblock::probe(&data_golden), @@ -1022,7 +1033,7 @@ mod tests { let clean = record(19, RecoveryState::Clean); let clean_golden = assert_golden( &clean.encode().unwrap(), - include_str!("fixtures/format_v1/clean_state.golden"), + include_str!("../../fixtures/format_v1/clean_state.golden"), ); assert_eq!(StateRecord::decode(&clean_golden), Some(clean)); } @@ -1033,7 +1044,7 @@ mod tests { let header = image_header(); let image_golden = assert_golden( &header.encode().unwrap(), - include_str!("fixtures/format_v1/recovery_image_header.golden"), + include_str!("../../fixtures/format_v1/recovery_image_header.golden"), ); assert_eq!( RecoveryImageHeader::probe(&image_golden), diff --git a/cache2/src/region_runtime/metrics.rs b/cache2/src/region/runtime/metrics.rs similarity index 100% rename from cache2/src/region_runtime/metrics.rs rename to cache2/src/region/runtime/metrics.rs diff --git a/cache2/src/region_runtime.rs b/cache2/src/region/runtime/mod.rs similarity index 98% rename from cache2/src/region_runtime.rs rename to cache2/src/region/runtime/mod.rs index 2f0e6e0..a1252d4 100644 --- a/cache2/src/region_runtime.rs +++ b/cache2/src/region/runtime/mod.rs @@ -36,6 +36,27 @@ use asyncband::semaphore::Semaphore; use asyncband::watch; use self::metrics::RuntimeMetrics; +use super::FileRegionCore; +use super::RegionStageValue; +use super::RegionValueRead; +#[cfg(test)] +use super::index::storage::INDEX_IMAGE_PAGE_SIZE; +#[cfg(test)] +use super::index::storage::INDEX_IMAGE_SLOTS_PER_PAGE; +use super::reader::PendingRead; +use super::reader::ReadCompletion; +use super::reader::ReadPlan; +use super::reader::plan_read; +use super::record::MAX_KEY_SIZE; +use super::record::hash_key; +use super::record::required_record_bytes; +#[cfg(test)] +use super::recovery::DataGeometry; +use super::recovery::DataSuperblock; +#[cfg(test)] +use super::runtime_fixed_memory_bytes; +use super::staging::RegionStaging; +use super::staging::StagingError; use crate::config::CacheConfig; use crate::config::IoMode; use crate::config::IoPoolTopology; @@ -47,42 +68,21 @@ use crate::config::read_io_wait_capacity; use crate::config::read_io_wait_timeout; use crate::config::reserved_memory_bytes; use crate::config::storage_geometry; -use crate::format::MAX_KEY_SIZE; use crate::hashing::route_hash; -#[cfg(test)] -use crate::index_storage::INDEX_IMAGE_PAGE_SIZE; -#[cfg(test)] -use crate::index_storage::INDEX_IMAGE_SLOTS_PER_PAGE; -use crate::io_backend::RuntimeFileSet; -use crate::io_engine::IoBuffer; -use crate::io_engine::IoEngine; -use crate::io_engine::IoOperation; -use crate::io_engine::ReadSlot; -use crate::io_engine::ReadSlotWaiter; -use crate::io_engine::build_file_engine; -use crate::io_engine::submit_cache_io; +use crate::io::backend::RuntimeFileSet; +use crate::io::engine::IoBuffer; +use crate::io::engine::IoEngine; +use crate::io::engine::IoOperation; +use crate::io::engine::ReadSlot; +use crate::io::engine::ReadSlotWaiter; +use crate::io::engine::build_file_engine; +use crate::io::engine::submit_cache_io; use crate::memory::MemoryLookup; #[cfg(test)] use crate::memory::MemoryMetricsSnapshot; use crate::memory::MemoryReadToken; use crate::memory::MemoryStore; use crate::memory::MemoryValue; -use crate::record_codec::hash_key; -use crate::record_codec::required_record_bytes; -#[cfg(test)] -use crate::recovery::DataGeometry; -use crate::recovery::DataSuperblock; -use crate::region::FileRegionCore; -use crate::region::RegionStageValue; -use crate::region::RegionValueRead; -#[cfg(test)] -use crate::region::runtime_fixed_memory_bytes; -use crate::region_reader::PendingRead; -use crate::region_reader::ReadCompletion; -use crate::region_reader::ReadPlan; -use crate::region_reader::plan_read; -use crate::region_staging::RegionStaging; -use crate::region_staging::StagingError; use crate::resources::BufferLease; use crate::resources::CACHE_THREAD_STACK_BYTES; #[cfg(test)] @@ -2153,9 +2153,9 @@ mod tests { use std::task::Waker; use super::*; - use crate::io_backend::FileBackend; - use crate::io_backend::IoBackend; - use crate::io_engine::BackendIoEngine; + use crate::io::backend::FileBackend; + use crate::io::backend::IoBackend; + use crate::io::engine::BackendIoEngine; static LANE_TEST_ID: AtomicU64 = AtomicU64::new(1); @@ -2452,13 +2452,13 @@ mod tests { fn completion_timeouts_follow_read_wait_mode() { use crate::config::IoEngine; use crate::config::PosixIoConfig; - use crate::index::IndexEntry; - use crate::index::PackedLocation; - use crate::recovery::DATA_REGION_AREA_OFFSET; - use crate::recovery::PersistentId; use crate::region::FileRegionBackend; use crate::region::RegionFiles; - use crate::region_store::RegionStore; + use crate::region::index::IndexEntry; + use crate::region::index::PackedLocation; + use crate::region::recovery::DATA_REGION_AREA_OFFSET; + use crate::region::recovery::PersistentId; + use crate::region::store::RegionStore; let id = LANE_TEST_ID.fetch_add(1, Ordering::Relaxed); let path = std::env::temp_dir().join(format!( @@ -2552,17 +2552,17 @@ mod tests { region_size: 512 * 1024, region_count: 10, }; - let value_len = geometry.region_size as usize - crate::format::RECORD_HEADER_SIZE; + let value_len = geometry.region_size as usize - crate::region::record::RECORD_HEADER_SIZE; let record_len = required_record_bytes(0, value_len).unwrap(); assert_eq!(u64::from(record_len), geometry.region_size); - let entry = crate::index::IndexEntry { - location: crate::index::PackedLocation::new(0, 0, record_len).unwrap(), + let entry = crate::region::index::IndexEntry { + location: crate::region::index::PackedLocation::new(0, 0, record_len).unwrap(), }; assert_eq!( plan_read( geometry, 1, - crate::region_reader::ReadCandidate { + crate::region::reader::ReadCandidate { entry, region_generation: 1, }, diff --git a/cache2/src/region_runtime/shutdown_tests.rs b/cache2/src/region/runtime/shutdown_tests.rs similarity index 94% rename from cache2/src/region_runtime/shutdown_tests.rs rename to cache2/src/region/runtime/shutdown_tests.rs index 83d9451..4e3c767 100644 --- a/cache2/src/region_runtime/shutdown_tests.rs +++ b/cache2/src/region/runtime/shutdown_tests.rs @@ -15,17 +15,17 @@ use std::sync::atomic::AtomicBool; use super::*; -use crate::io_backend::IoBackend; -use crate::io_backend::SyncMode; -use crate::io_backend::SyncPoint; -use crate::io_backend::WritePoint; -use crate::io_engine::BackendIoEngine; -use crate::io_engine::CompletionState; -use crate::io_engine::EngineIoSnapshot; -use crate::io_engine::IoRequest; -use crate::io_engine::ReadSlotWaiter; -use crate::io_engine::RequestId; -use crate::io_engine::SubmitError; +use crate::io::backend::IoBackend; +use crate::io::backend::SyncMode; +use crate::io::backend::SyncPoint; +use crate::io::backend::WritePoint; +use crate::io::engine::BackendIoEngine; +use crate::io::engine::CompletionState; +use crate::io::engine::EngineIoSnapshot; +use crate::io::engine::IoRequest; +use crate::io::engine::ReadSlotWaiter; +use crate::io::engine::RequestId; +use crate::io::engine::SubmitError; #[derive(Default)] struct BlockedReadState { @@ -203,10 +203,10 @@ fn submitted_read_must_not_pin_close() { fn assert_close_does_not_wait_for_read(submit_before_close: bool) { use crate::config::PosixIoConfig; - use crate::recovery::PersistentId; use crate::region::FileRegionBackend; use crate::region::RegionFiles; - use crate::region_store::RegionStore; + use crate::region::recovery::PersistentId; + use crate::region::store::RegionStore; let root = std::env::temp_dir().join(format!( "cache2-close-race-{}-{submit_before_close}", std::process::id() diff --git a/cache2/src/region_staging.rs b/cache2/src/region/staging.rs similarity index 98% rename from cache2/src/region_staging.rs rename to cache2/src/region/staging.rs index b2ac60b..02c99ae 100644 --- a/cache2/src/region_staging.rs +++ b/cache2/src/region/staging.rs @@ -20,19 +20,19 @@ use std::fmt; use std::sync::Mutex; use std::sync::MutexGuard; -use crate::format::RECORD_ALIGNMENT; -use crate::format::RECORD_HEADER_SIZE; -use crate::format::RecordHeader; -use crate::index::IndexEntry; -use crate::index::MAX_RECORD_LEN; -use crate::index::PackedLocation; -use crate::io_backend::DIRECT_IO_ALIGNMENT; -use crate::io_engine::IoBuffer; -use crate::recovery::DATA_REGION_AREA_OFFSET; -use crate::recovery::RECOVERY_PAGE_SIZE; -use crate::region_manager::RegionAppendReservation; -use crate::region_manager::RegionPaddingReceipt; -use crate::region_manager::RegionWriteSpan; +use super::index::IndexEntry; +use super::index::MAX_RECORD_LEN; +use super::index::PackedLocation; +use super::manager::RegionAppendReservation; +use super::manager::RegionPaddingReceipt; +use super::manager::RegionWriteSpan; +use super::record::RECORD_ALIGNMENT; +use super::record::RECORD_HEADER_SIZE; +use super::record::RecordHeader; +use super::recovery::DATA_REGION_AREA_OFFSET; +use super::recovery::RECOVERY_PAGE_SIZE; +use crate::io::backend::DIRECT_IO_ALIGNMENT; +use crate::io::engine::IoBuffer; use crate::resources::BUFFER_ALIGNMENT; use crate::resources::BufferLease; use crate::resources::ResourceBuildError; @@ -904,7 +904,7 @@ mod tests { use std::time::Duration; use super::*; - use crate::index::PackedLocation; + use crate::region::index::PackedLocation; use crate::resources::ResourceLimits; fn resources(memory_limit_bytes: usize) -> ResourceController { diff --git a/cache2/src/region_store.rs b/cache2/src/region/store.rs similarity index 99% rename from cache2/src/region_store.rs rename to cache2/src/region/store.rs index f67b641..2ff39e3 100644 --- a/cache2/src/region_store.rs +++ b/cache2/src/region/store.rs @@ -25,7 +25,7 @@ use std::io; -use crate::index_storage::validated_index_partition_ranges; +use super::index::storage::validated_index_partition_ranges; use crate::snapshot::StartupMode; /// Result of inspecting the latest valid state record. From 7c9d9adc8b16f6d4c9809934b75f313bd4be4fdb Mon Sep 17 00:00:00 2001 From: tison Date: Wed, 9 Sep 2026 19:12:41 +0800 Subject: [PATCH 02/14] test: colocate golden fixtures with their format modules Keep each versioned golden file beside its encoding and decoding module. Preserve fixture bytes and keep only the shared parser and assertions in fixtures.rs. --- CONTRIBUTING.md | 2 ++ cache2/src/{fixtures/mod.rs => fixtures.rs} | 9 ++++++++- cache2/src/fixtures/format_v1/README.md | 5 ----- .../index/storage}/format_v1/index_page.golden | 0 cache2/src/region/index/storage/mod.rs | 5 +---- .../record}/format_v1/value_record.golden | 0 cache2/src/region/record/mod.rs | 5 +---- .../recovery}/format_v1/clean_state.golden | 0 .../recovery}/format_v1/data_superblock.golden | 0 .../recovery}/format_v1/recovery_image_header.golden | 0 .../recovery}/format_v1/region_metadata.golden | 0 cache2/src/region/recovery/metadata.rs | 5 +---- cache2/src/region/recovery/mod.rs | 6 +++--- 13 files changed, 16 insertions(+), 21 deletions(-) rename cache2/src/{fixtures/mod.rs => fixtures.rs} (84%) delete mode 100644 cache2/src/fixtures/format_v1/README.md rename cache2/src/{fixtures => region/index/storage}/format_v1/index_page.golden (100%) rename cache2/src/{fixtures => region/record}/format_v1/value_record.golden (100%) rename cache2/src/{fixtures => region/recovery}/format_v1/clean_state.golden (100%) rename cache2/src/{fixtures => region/recovery}/format_v1/data_superblock.golden (100%) rename cache2/src/{fixtures => region/recovery}/format_v1/recovery_image_header.golden (100%) rename cache2/src/{fixtures => region/recovery}/format_v1/region_metadata.golden (100%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7b91953..17845d3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,6 +16,8 @@ The repository separates published code from development-only consumers: Keep unit tests beside the implementation when they need private access. Behavior visible to callers belongs in `tests-integration/tests`. +Keep versioned format fixtures beside the module that owns their encoding and decoding. Share only the fixture parsing and assertion helpers. + ## Repository workflows The `.cargo/config.toml` alias maps `cargo x` to the `x` package in `xtask/`. Use these commands before opening a pull request: diff --git a/cache2/src/fixtures/mod.rs b/cache2/src/fixtures.rs similarity index 84% rename from cache2/src/fixtures/mod.rs rename to cache2/src/fixtures.rs index fb5fef5..3839388 100644 --- a/cache2/src/fixtures/mod.rs +++ b/cache2/src/fixtures.rs @@ -12,7 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Versioned byte fixtures for private persistent-format tests. +//! Shared byte assertions for module-local persistent-format fixtures. +//! +//! Golden fixtures pin versioned on-disk bytes. Changes require an explicit format-version +//! decision; tests never regenerate them. Each fixture lives beside the module that owns its +//! format. +//! +//! The sparse representation starts with the complete byte length. Each following line contains a +//! hexadecimal offset and hexadecimal bytes; unspecified bytes are zero. /// Checks every byte, including zero padding, and returns the committed bytes /// for decoder compatibility checks. diff --git a/cache2/src/fixtures/format_v1/README.md b/cache2/src/fixtures/format_v1/README.md deleted file mode 100644 index b4fbce5..0000000 --- a/cache2/src/fixtures/format_v1/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Format 1 golden fixtures - -These fixtures pin the version 1 on-disk bytes. Changes require an explicit format-version decision; tests never regenerate them. - -The sparse representation starts with the complete byte length. Each following line contains a hexadecimal offset and hexadecimal bytes; unspecified bytes are zero. diff --git a/cache2/src/fixtures/format_v1/index_page.golden b/cache2/src/region/index/storage/format_v1/index_page.golden similarity index 100% rename from cache2/src/fixtures/format_v1/index_page.golden rename to cache2/src/region/index/storage/format_v1/index_page.golden diff --git a/cache2/src/region/index/storage/mod.rs b/cache2/src/region/index/storage/mod.rs index 2e6ac44..e9102a1 100644 --- a/cache2/src/region/index/storage/mod.rs +++ b/cache2/src/region/index/storage/mod.rs @@ -2287,10 +2287,7 @@ mod tests { source .write_warm_image(&mut encoded, binding(0x1122_3344_5566_7788)) .unwrap(); - assert_golden( - &encoded, - include_str!("../../../fixtures/format_v1/index_page.golden"), - ); + assert_golden(&encoded, include_str!("format_v1/index_page.golden")); } #[test] diff --git a/cache2/src/fixtures/format_v1/value_record.golden b/cache2/src/region/record/format_v1/value_record.golden similarity index 100% rename from cache2/src/fixtures/format_v1/value_record.golden rename to cache2/src/region/record/format_v1/value_record.golden diff --git a/cache2/src/region/record/mod.rs b/cache2/src/region/record/mod.rs index 2c70750..ee9798c 100644 --- a/cache2/src/region/record/mod.rs +++ b/cache2/src/region/record/mod.rs @@ -221,10 +221,7 @@ mod tests { let mut encoded = vec![0_u8; record_len as usize]; encoded[..RECORD_HEADER_SIZE].copy_from_slice(&header.encode()); encoded[RECORD_HEADER_SIZE..RECORD_HEADER_SIZE + payload.len()].copy_from_slice(&payload); - let golden = assert_golden( - &encoded, - include_str!("../../fixtures/format_v1/value_record.golden"), - ); + let golden = assert_golden(&encoded, include_str!("format_v1/value_record.golden")); assert_eq!( RecordHeader::decode(&golden[..RECORD_HEADER_SIZE]), Some(header) diff --git a/cache2/src/fixtures/format_v1/clean_state.golden b/cache2/src/region/recovery/format_v1/clean_state.golden similarity index 100% rename from cache2/src/fixtures/format_v1/clean_state.golden rename to cache2/src/region/recovery/format_v1/clean_state.golden diff --git a/cache2/src/fixtures/format_v1/data_superblock.golden b/cache2/src/region/recovery/format_v1/data_superblock.golden similarity index 100% rename from cache2/src/fixtures/format_v1/data_superblock.golden rename to cache2/src/region/recovery/format_v1/data_superblock.golden diff --git a/cache2/src/fixtures/format_v1/recovery_image_header.golden b/cache2/src/region/recovery/format_v1/recovery_image_header.golden similarity index 100% rename from cache2/src/fixtures/format_v1/recovery_image_header.golden rename to cache2/src/region/recovery/format_v1/recovery_image_header.golden diff --git a/cache2/src/fixtures/format_v1/region_metadata.golden b/cache2/src/region/recovery/format_v1/region_metadata.golden similarity index 100% rename from cache2/src/fixtures/format_v1/region_metadata.golden rename to cache2/src/region/recovery/format_v1/region_metadata.golden diff --git a/cache2/src/region/recovery/metadata.rs b/cache2/src/region/recovery/metadata.rs index 06b0729..30e7820 100644 --- a/cache2/src/region/recovery/metadata.rs +++ b/cache2/src/region/recovery/metadata.rs @@ -1353,10 +1353,7 @@ mod tests { fn complete_metadata_matches_committed_golden_bytes() { let expected = sample(); let encoded = expected.encode().unwrap(); - let golden = assert_golden( - &encoded, - include_str!("../../fixtures/format_v1/region_metadata.golden"), - ); + let golden = assert_golden(&encoded, include_str!("format_v1/region_metadata.golden")); assert_eq!(RegionMetadata::decode(&golden).unwrap(), expected); } diff --git a/cache2/src/region/recovery/mod.rs b/cache2/src/region/recovery/mod.rs index 8fb7cc9..720ecf8 100644 --- a/cache2/src/region/recovery/mod.rs +++ b/cache2/src/region/recovery/mod.rs @@ -1020,7 +1020,7 @@ mod tests { let data = data_superblock(); let data_golden = assert_golden( &data.encode().unwrap(), - include_str!("../../fixtures/format_v1/data_superblock.golden"), + include_str!("format_v1/data_superblock.golden"), ); assert_eq!( DataSuperblock::probe(&data_golden), @@ -1033,7 +1033,7 @@ mod tests { let clean = record(19, RecoveryState::Clean); let clean_golden = assert_golden( &clean.encode().unwrap(), - include_str!("../../fixtures/format_v1/clean_state.golden"), + include_str!("format_v1/clean_state.golden"), ); assert_eq!(StateRecord::decode(&clean_golden), Some(clean)); } @@ -1044,7 +1044,7 @@ mod tests { let header = image_header(); let image_golden = assert_golden( &header.encode().unwrap(), - include_str!("../../fixtures/format_v1/recovery_image_header.golden"), + include_str!("format_v1/recovery_image_header.golden"), ); assert_eq!( RecoveryImageHeader::probe(&image_golden), From b0ae60575d14827ba06b80060496ceb80bb2770b Mon Sep 17 00:00:00 2001 From: tison Date: Wed, 9 Sep 2026 19:13:32 +0800 Subject: [PATCH 03/14] refactor: make imports explicit across the workspace Use imports for referenced symbols and start intra-crate paths at crate. Reserve parent glob imports for tests, and keep short qualifiers or explicit aliases where they clarify symbol origins. Document the conventions in CONTRIBUTING.md. --- CONTRIBUTING.md | 4 + benchmarks/cache/main.rs | 24 ++-- benchmarks/cache_soak/main.rs | 33 +++-- benchmarks/mixed_workloads/main.rs | 21 ++- benchmarks/recovery_scale/main.rs | 29 ++-- benchmarks/region_index_turnover/main.rs | 3 +- benchmarks/src/report.rs | 18 +-- cache2/src/benchmarking.rs | 3 +- cache2/src/cache.rs | 18 ++- cache2/src/checksum.rs | 2 +- cache2/src/config/runtime.rs | 28 ++-- cache2/src/config/storage.rs | 8 +- cache2/src/error.rs | 3 +- cache2/src/fixtures.rs | 4 +- cache2/src/hashing.rs | 7 +- cache2/src/io/backend.rs | 40 ++++-- cache2/src/io/engine/mod.rs | 70 ++++----- cache2/src/io/engine/posix.rs | 63 ++++++-- cache2/src/io/engine/tests.rs | 50 ++++--- cache2/src/io/engine/uring.rs | 60 +++++++- cache2/src/memory/eviction.rs | 11 +- cache2/src/memory/mod.rs | 24 ++-- cache2/src/region/appender.rs | 11 +- cache2/src/region/file_backend/mod.rs | 113 ++++++++------- cache2/src/region/file_backend/tests.rs | 105 ++++++++------ cache2/src/region/index/mod.rs | 6 +- cache2/src/region/index/packed.rs | 3 +- cache2/src/region/index/storage/mod.rs | 53 ++++--- .../src/region/index/storage/page_format.rs | 12 +- cache2/src/region/manager.rs | 16 +-- cache2/src/region/mod.rs | 43 +++--- cache2/src/region/reader.rs | 17 ++- cache2/src/region/record/codec.rs | 17 ++- cache2/src/region/recovery/metadata.rs | 17 ++- cache2/src/region/recovery/mod.rs | 12 +- cache2/src/region/runtime/metrics.rs | 13 +- cache2/src/region/runtime/mod.rs | 134 ++++++++++-------- cache2/src/region/runtime/shutdown_tests.rs | 15 +- cache2/src/region/staging.rs | 36 ++--- cache2/src/region/store.rs | 2 +- cache2/src/resources.rs | 5 +- tests-integration/tests/cache.rs | 58 ++++---- tests-integration/tests/error.rs | 14 +- xtask/src/main.rs | 8 +- 44 files changed, 729 insertions(+), 504 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 17845d3..0384934 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,6 +56,10 @@ Use `module/mod.rs` for modules with child files; keep leaf modules in a single Declare restricted visibility at the module boundary and use `pub` for items in that module's API. +Use imports for referenced symbols. Keep a short module qualifier or use an explicit alias when a bare name would obscure its origin or conflict with another symbol, such as `io::Error` or `ConfiguredIoEngine`. + +Start intra-crate imports at `crate`; reserve `use super::*` for test modules. + ## Documentation Keep each Markdown prose paragraph and list item on one source line. diff --git a/benchmarks/cache/main.rs b/benchmarks/cache/main.rs index 3f4bee2..bf13b41 100644 --- a/benchmarks/cache/main.rs +++ b/benchmarks/cache/main.rs @@ -13,11 +13,16 @@ // limitations under the License. use std::env; +use std::fmt; +use std::fs; use std::hint::black_box; use std::io; +use std::ops::Range; use std::path::Path; use std::path::PathBuf; +use std::process; use std::sync::Arc; +use std::sync::Barrier as ThreadBarrier; use std::thread; use std::time::Duration; use std::time::Instant; @@ -44,6 +49,8 @@ use cache2::RuntimeOptions; use cache2::StartupMode; use cache2::StorageOptions; use cache2::Value; +use tokio::runtime::Builder as TokioRuntimeBuilder; +use tokio::time; const MIB: usize = 1024 * 1024; const REGION_BYTES: usize = 32 * MIB; @@ -317,10 +324,7 @@ impl BenchFiles { .unwrap_or_default() .as_nanos(); Self { - data: directory.join(format!( - "cache2-bench-{}-{timestamp}.cache", - std::process::id() - )), + data: directory.join(format!("cache2-bench-{}-{timestamp}.cache", process::id())), } } } @@ -333,7 +337,7 @@ impl Drop for BenchFiles { sidecar(&self.data, ".image"), sidecar(&self.data, ".image.next"), ] { - let _ = std::fs::remove_file(path); + let _ = fs::remove_file(path); } } } @@ -381,14 +385,14 @@ fn main() -> io::Result<()> { result .as_ref() .err() - .map(|error| error as &dyn std::fmt::Display), + .map(|error| error as &dyn fmt::Display), ); result } fn run_benchmark() -> io::Result<()> { let config = BenchConfig::from_env()?; - let runtime = tokio::runtime::Builder::new_multi_thread() + let runtime = TokioRuntimeBuilder::new_multi_thread() .worker_threads(config.clients.max(2)) .thread_name("cache2-benchmark") .enable_time() @@ -751,7 +755,7 @@ fn concurrent_writes( value_bytes: usize, clients: usize, ) -> io::Result { - let barrier = Arc::new(std::sync::Barrier::new(clients + 1)); + let barrier = Arc::new(ThreadBarrier::new(clients + 1)); thread::scope(|scope| { let mut handles = Vec::with_capacity(clients); for client in 0..clients { @@ -804,7 +808,7 @@ fn concurrent_writes( async fn concurrent_reads( cache: Arc, - key_range: std::ops::Range, + key_range: Range, operations: usize, clients: usize, expected_tier: CacheTier, @@ -974,7 +978,7 @@ async fn read_l1_eventually(cache: &Cache, key_ordinal: usize, client: usize) -> ))); } attempts += 1; - tokio::time::sleep(RETRY_DELAY).await; + time::sleep(RETRY_DELAY).await; } } diff --git a/benchmarks/cache_soak/main.rs b/benchmarks/cache_soak/main.rs index 3ffff1e..34ee76e 100644 --- a/benchmarks/cache_soak/main.rs +++ b/benchmarks/cache_soak/main.rs @@ -12,10 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::cmp::min; use std::env; +use std::fmt; +use std::fs; use std::io; +use std::mem::MaybeUninit; use std::path::Path; use std::path::PathBuf; +use std::process; use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; @@ -49,6 +54,9 @@ use logforth::append::Stderr; use logforth::bridge::log::LogBridge; use logforth::filter::rustlog::RustLogFilterBuilder; use logforth::layout::JsonLayout; +use tokio::runtime::Builder as TokioRuntimeBuilder; +use tokio::runtime::Handle as TokioHandle; +use tokio::runtime::Runtime as TokioRuntime; const MIB: usize = 1024 * 1024; const REGION_BYTES: usize = 32 * MIB; @@ -243,10 +251,7 @@ impl SoakFiles { .unwrap_or_default() .as_nanos(); Self { - data: directory.join(format!( - "cache2-soak-{}-{timestamp}.cache", - std::process::id() - )), + data: directory.join(format!("cache2-soak-{}-{timestamp}.cache", process::id())), cleanup_on_drop: AtomicBool::new(false), } } @@ -263,7 +268,7 @@ impl SoakFiles { sidecar(&self.data, ".image.next"), ] .into_iter() - .try_fold(0_u64, |total, path| match std::fs::metadata(path) { + .try_fold(0_u64, |total, path| match fs::metadata(path) { Ok(metadata) => total .checked_add(metadata.len()) .ok_or_else(|| invalid("logical disk byte count overflow")), @@ -288,7 +293,7 @@ impl Drop for SoakFiles { sidecar(&self.data, ".image"), sidecar(&self.data, ".image.next"), ] { - let _ = std::fs::remove_file(path); + let _ = fs::remove_file(path); } } } @@ -359,7 +364,7 @@ fn main() -> io::Result<()> { result .as_ref() .err() - .map(|error| error as &dyn std::fmt::Display), + .map(|error| error as &dyn fmt::Display), ); result } @@ -367,7 +372,7 @@ fn main() -> io::Result<()> { fn run_benchmark() -> io::Result<()> { init_logforth()?; let config = SoakConfig::from_env()?; - let runtime = tokio::runtime::Builder::new_multi_thread() + let runtime = TokioRuntimeBuilder::new_multi_thread() .worker_threads(config.readers.max(2)) .thread_name("cache2-soak") .enable_time() @@ -499,7 +504,7 @@ fn run_benchmark() -> io::Result<()> { .ok_or_else(|| invalid("soak sample deadline is too far in the future"))?; let mut sample_error = None; while Instant::now() < deadline && !stop.load(Ordering::Acquire) { - let wake_at = std::cmp::min(next_sample, deadline); + let wake_at = min(next_sample, deadline); if let Some(remaining) = wake_at.checked_duration_since(Instant::now()) { thread::sleep(remaining); } @@ -643,7 +648,7 @@ fn init_logforth() -> io::Result<()> { } fn open_cache( - runtime: &tokio::runtime::Runtime, + runtime: &TokioRuntime, files: &SoakFiles, config: &CacheConfig, ) -> io::Result { @@ -770,7 +775,7 @@ fn run_reader( next_read: &AtomicU64, stop: &AtomicBool, counters: &SoakCounters, - runtime: &tokio::runtime::Handle, + runtime: &TokioHandle, ) -> io::Result<()> { let reader_id = u64::try_from(reader_id).map_err(|_| invalid("reader id exceeds u64"))?; while !stop.load(Ordering::Acquire) { @@ -809,7 +814,7 @@ fn run_reader( } fn verify_warm_reopen( - runtime: &tokio::runtime::Runtime, + runtime: &TokioRuntime, cache: &Cache, expected: &[AtomicU64], value_size_count: u64, @@ -1137,7 +1142,7 @@ fn pace(interval: Duration) { #[cfg(unix)] fn peak_rss_bytes() -> u64 { - let mut usage = std::mem::MaybeUninit::::zeroed(); + let mut usage = MaybeUninit::::zeroed(); // SAFETY: `usage` points to writable storage for one `rusage` value. if unsafe { libc::getrusage(libc::RUSAGE_SELF, usage.as_mut_ptr()) } != 0 { return 0; @@ -1158,7 +1163,7 @@ fn peak_rss_bytes() -> u64 { #[cfg(target_os = "linux")] fn current_rss_bytes() -> io::Result { - let status = std::fs::read_to_string("/proc/self/status")?; + let status = fs::read_to_string("/proc/self/status")?; let kib = status .lines() .find_map(|line| line.strip_prefix("VmRSS:")) diff --git a/benchmarks/mixed_workloads/main.rs b/benchmarks/mixed_workloads/main.rs index b6956a9..9b706bf 100644 --- a/benchmarks/mixed_workloads/main.rs +++ b/benchmarks/mixed_workloads/main.rs @@ -13,10 +13,14 @@ // limitations under the License. use std::env; +use std::f64::consts::TAU; +use std::fmt; +use std::fs; use std::hint::black_box; use std::io; use std::path::Path; use std::path::PathBuf; +use std::process; use std::sync::Arc; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; @@ -33,6 +37,8 @@ use benchmarks::report::emit_cache_report; use cache2::Cache; use cache2::CacheConfig; use cache2::CacheHealth; +use cache2::CacheSnapshot; +use cache2::DetailedCacheSnapshot; use cache2::ErrorKind as CacheErrorKind; use cache2::IoEngine; use cache2::IoMode; @@ -42,6 +48,7 @@ use cache2::L1EvictionPolicy; use cache2::PosixIoConfig; use cache2::RuntimeOptions; use cache2::StorageOptions; +use tokio::runtime::Builder as TokioRuntimeBuilder; const MIB: usize = 1024 * 1024; const MAX_KEY_BYTES: usize = 64; @@ -453,7 +460,7 @@ impl BenchFiles { data: directory.join(format!( "cache2-mixed-workload-{}-{}-{timestamp}.cache", scenario.slug(), - std::process::id() + process::id() )), } } @@ -467,7 +474,7 @@ impl Drop for BenchFiles { sidecar(&self.data, ".image"), sidecar(&self.data, ".image.next"), ] { - let _ = std::fs::remove_file(file); + let _ = fs::remove_file(file); } } } @@ -582,7 +589,7 @@ fn main() -> io::Result<()> { .max() .unwrap_or(2) .max(2); - let runtime = tokio::runtime::Builder::new_multi_thread() + let runtime = TokioRuntimeBuilder::new_multi_thread() .worker_threads(runtime_threads) .thread_name("cache2-mixed-workload") .enable_time() @@ -602,7 +609,7 @@ async fn run_scenario(config: EffectiveConfig) -> io::Result<()> { result .as_ref() .err() - .map(|error| error as &dyn std::fmt::Display), + .map(|error| error as &dyn fmt::Display), ); result } @@ -854,7 +861,7 @@ fn sample_normal_key(left: usize, right: usize, rng: &mut DeterministicRng) -> u let standard_deviation = (right - left) as f64 * 0.25; for _ in 0..NORMAL_SAMPLE_ATTEMPTS { let radius = (-2.0 * rng.open_unit_f64().ln()).sqrt(); - let angle = std::f64::consts::TAU * rng.open_unit_f64(); + let angle = TAU * rng.open_unit_f64(); let sampled = (mean + standard_deviation * radius * angle.cos()).round(); if sampled >= left as f64 && sampled <= right as f64 { return sampled as usize; @@ -961,7 +968,7 @@ fn should_sample(operation: Operation, result: &WorkloadResult, interval: usize) } } -fn validate_snapshot(result: &WorkloadResult, snapshot: &cache2::CacheSnapshot) -> io::Result<()> { +fn validate_snapshot(result: &WorkloadResult, snapshot: &CacheSnapshot) -> io::Result<()> { let cache_hits = snapshot.l1_hits.saturating_add(snapshot.l2_hits); if snapshot.health != CacheHealth::Running || snapshot.io_failures != 0 { return Err(io::Error::other( @@ -993,7 +1000,7 @@ fn report( result: &WorkloadResult, workload_elapsed: Duration, drain_elapsed: Duration, - detailed: &cache2::DetailedCacheSnapshot, + detailed: &DetailedCacheSnapshot, ) { let snapshot = detailed.summary; let seconds = workload_elapsed.as_secs_f64(); diff --git a/benchmarks/recovery_scale/main.rs b/benchmarks/recovery_scale/main.rs index 0e553dc..76f2d9c 100644 --- a/benchmarks/recovery_scale/main.rs +++ b/benchmarks/recovery_scale/main.rs @@ -13,9 +13,13 @@ // limitations under the License. use std::env; +use std::fmt; +use std::fs; use std::io; +use std::mem::MaybeUninit; use std::path::Path; use std::path::PathBuf; +use std::process; use std::thread; use std::time::Duration; use std::time::Instant; @@ -33,6 +37,7 @@ use cache2::PosixIoConfig; use cache2::RuntimeOptions; use cache2::StartupMode; use cache2::StorageOptions; +use tokio::runtime::Builder as TokioRuntimeBuilder; const MIB: usize = 1024 * 1024; const WRITE_RETRY_TIMEOUT: Duration = Duration::from_secs(30); @@ -116,7 +121,7 @@ impl ScaleFiles { Self { data: directory.join(format!( "cache2-recovery-scale-{}-{timestamp}.cache", - std::process::id() + process::id() )), cleanup_on_drop: false, } @@ -129,7 +134,7 @@ impl ScaleFiles { fn logical_bytes(&self) -> io::Result { self.paths() .into_iter() - .try_fold(0_u64, |total, path| match std::fs::metadata(path) { + .try_fold(0_u64, |total, path| match fs::metadata(path) { Ok(metadata) => total .checked_add(metadata.len()) .ok_or_else(|| invalid("logical file size overflow")), @@ -144,7 +149,7 @@ impl ScaleFiles { self.paths() .into_iter() - .try_fold(0_u64, |total, path| match std::fs::metadata(path) { + .try_fold(0_u64, |total, path| match fs::metadata(path) { Ok(metadata) => total .checked_add(metadata.blocks().saturating_mul(512)) .ok_or_else(|| invalid("allocated file size overflow")), @@ -178,7 +183,7 @@ impl Drop for ScaleFiles { return; } for path in self.paths() { - let _ = std::fs::remove_file(path); + let _ = fs::remove_file(path); } } } @@ -190,14 +195,14 @@ fn main() -> io::Result<()> { result .as_ref() .err() - .map(|error| error as &dyn std::fmt::Display), + .map(|error| error as &dyn fmt::Display), ); result } fn run_benchmark() -> io::Result<()> { let config = ScaleConfig::from_env()?; - let runtime = tokio::runtime::Builder::new_current_thread() + let runtime = TokioRuntimeBuilder::new_current_thread() .enable_time() .build()?; runtime.block_on(run(config)) @@ -279,11 +284,7 @@ async fn run(config: ScaleConfig) -> io::Result<()> { Ok(()) } -async fn verify_sentinels( - cache: &cache2::Cache, - keys: &[[u8; 16]], - value_bytes: usize, -) -> io::Result<()> { +async fn verify_sentinels(cache: &Cache, keys: &[[u8; 16]], value_bytes: usize) -> io::Result<()> { let started = Instant::now(); for (ordinal, key) in keys.iter().enumerate() { let observed = cache @@ -307,7 +308,7 @@ async fn verify_sentinels( Ok(()) } -fn put_eventually(cache: &cache2::Cache, key: &[u8], value: &[u8]) -> io::Result<()> { +fn put_eventually(cache: &Cache, key: &[u8], value: &[u8]) -> io::Result<()> { let deadline = Instant::now() + WRITE_RETRY_TIMEOUT; loop { match cache.put(key, value) { @@ -357,7 +358,7 @@ fn emit(phase: &str, operation: &str, elapsed: Duration, operations: u64, bytes: #[cfg(unix)] fn peak_rss_bytes() -> u64 { - let mut usage = std::mem::MaybeUninit::::zeroed(); + let mut usage = MaybeUninit::::zeroed(); // SAFETY: `usage` points to writable storage for one `rusage` value. if unsafe { libc::getrusage(libc::RUSAGE_SELF, usage.as_mut_ptr()) } != 0 { return 0; @@ -383,7 +384,7 @@ fn peak_rss_bytes() -> u64 { #[cfg(target_os = "linux")] fn current_rss_bytes() -> u64 { - std::fs::read_to_string("/proc/self/status") + fs::read_to_string("/proc/self/status") .ok() .and_then(|status| { status.lines().find_map(|line| { diff --git a/benchmarks/region_index_turnover/main.rs b/benchmarks/region_index_turnover/main.rs index f108590..122abb3 100644 --- a/benchmarks/region_index_turnover/main.rs +++ b/benchmarks/region_index_turnover/main.rs @@ -13,6 +13,7 @@ // limitations under the License. use std::env; +use std::fmt; use std::io; use benchmarks::report::JobReport; @@ -28,7 +29,7 @@ fn main() -> io::Result<()> { result .as_ref() .err() - .map(|error| error as &dyn std::fmt::Display), + .map(|error| error as &dyn fmt::Display), ); result } diff --git a/benchmarks/src/report.rs b/benchmarks/src/report.rs index 4a07e63..810027d 100644 --- a/benchmarks/src/report.rs +++ b/benchmarks/src/report.rs @@ -14,7 +14,11 @@ //! Bounded benchmark measurements and fio-style reporting. +use std::array; +use std::env::consts::ARCH; +use std::env::consts::OS; use std::fmt; +use std::mem::MaybeUninit; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; use std::time::Duration; @@ -142,7 +146,7 @@ pub struct AtomicLatencyHistogram { impl Default for AtomicLatencyHistogram { fn default() -> Self { Self { - buckets: std::array::from_fn(|_| AtomicU64::new(0)), + buckets: array::from_fn(|_| AtomicU64::new(0)), minimum_ns: AtomicU64::new(u64::MAX), maximum_ns: AtomicU64::new(0), } @@ -160,7 +164,7 @@ impl AtomicLatencyHistogram { /// Takes a non-transactional snapshot suitable for periodic reporting. pub fn snapshot(&self) -> LatencyHistogram { - let buckets = std::array::from_fn(|index| self.buckets[index].load(Ordering::Relaxed)); + let buckets = array::from_fn(|index| self.buckets[index].load(Ordering::Relaxed)); let samples = buckets.iter().copied().sum(); LatencyHistogram { buckets, @@ -359,15 +363,11 @@ impl RunReporter { println!("C² benchmark report"); println!( " benchmark={}, scenario={}, os={}, arch={}", - benchmark, - scenario, - std::env::consts::OS, - std::env::consts::ARCH, + benchmark, scenario, OS, ARCH, ); println!( "report version=1 type=header benchmark={benchmark} scenario={scenario} os={} arch={}", - std::env::consts::OS, - std::env::consts::ARCH, + OS, ARCH, ); Self { benchmark, @@ -594,7 +594,7 @@ struct ProcessUsage { impl ProcessUsage { #[cfg(unix)] fn capture() -> Self { - let mut usage = std::mem::MaybeUninit::::zeroed(); + let mut usage = MaybeUninit::::zeroed(); // SAFETY: `usage` points to writable storage for one `rusage` value. if unsafe { libc::getrusage(libc::RUSAGE_SELF, usage.as_mut_ptr()) } != 0 { return Self::default(); diff --git a/cache2/src/benchmarking.rs b/cache2/src/benchmarking.rs index 750ecdf..1466292 100644 --- a/cache2/src/benchmarking.rs +++ b/cache2/src/benchmarking.rs @@ -15,6 +15,7 @@ //! Internal benchmark entry points. This module is available only with the //! `benchmarking` feature and is not part of the supported cache API. +use std::error::Error as StdError; use std::hint::black_box; use std::io; use std::time::Duration; @@ -512,7 +513,7 @@ fn out_of_memory(target: &'static str) -> io::Error { ) } -fn index_error(error: impl std::error::Error + Send + Sync + 'static) -> io::Error { +fn index_error(error: impl StdError + Send + Sync + 'static) -> io::Error { io::Error::other(error) } diff --git a/cache2/src/cache.rs b/cache2/src/cache.rs index 8f6a907..3bf221c 100644 --- a/cache2/src/cache.rs +++ b/cache2/src/cache.rs @@ -23,6 +23,7 @@ use std::io; use std::ops::Deref; use std::path::Path; use std::path::PathBuf; +use std::process; use std::sync::Arc; use std::sync::Mutex; use std::sync::atomic::AtomicBool; @@ -33,6 +34,9 @@ use std::time::Instant; use std::time::SystemTime; use std::time::UNIX_EPOCH; +use tokio::runtime::Handle as TokioHandle; +use tokio::task::JoinError; + use crate::config::CacheConfig; use crate::config::KEY_HASH_SEED; use crate::config::storage_fingerprint; @@ -119,7 +123,7 @@ pub struct Cache { startup: StartupMode, path: PathBuf, logical_disk_peak_bytes: u64, - tokio_handle: tokio::runtime::Handle, + tokio_handle: TokioHandle, } impl fmt::Debug for Cache { @@ -142,7 +146,7 @@ impl Cache { /// device support, runtime binding, or worker startup failures. Configuration /// has already been checked by [`CacheConfig::new`]. pub async fn open(path: impl AsRef, config: CacheConfig) -> Result { - let handle = tokio::runtime::Handle::try_current().map_err(|error| { + let handle = TokioHandle::try_current().map_err(|error| { from_io( ErrorOperation::Open, io::Error::new(io::ErrorKind::InvalidInput, error.to_string()), @@ -161,7 +165,7 @@ impl Cache { pub async fn open_with_handle( path: impl AsRef, config: CacheConfig, - tokio_handle: tokio::runtime::Handle, + tokio_handle: TokioHandle, ) -> Result { let path = path.as_ref().to_path_buf(); let cache_handle = tokio_handle.clone(); @@ -181,7 +185,7 @@ impl Cache { fn open_blocking( path: PathBuf, config: CacheConfig, - tokio_handle: tokio::runtime::Handle, + tokio_handle: TokioHandle, started: Instant, ) -> io::Result { let capacity_bytes = config.storage().capacity_bytes(); @@ -226,7 +230,7 @@ impl Cache { fn open_blocking_inner( path: PathBuf, config: CacheConfig, - tokio_handle: tokio::runtime::Handle, + tokio_handle: TokioHandle, ) -> io::Result { let format_data = DataSuperblock { generation: 1, @@ -554,7 +558,7 @@ fn log_cache_close(path: &Path, mode: &'static str, elapsed: Duration, result: & } } -fn blocking_task_error(operation: &'static str, error: tokio::task::JoinError) -> io::Error { +fn blocking_task_error(operation: &'static str, error: JoinError) -> io::Error { io::Error::other(format!("{operation} task failed: {error}")) } @@ -576,7 +580,7 @@ fn next_persistent_id() -> PersistentId { .unwrap_or_default() .as_nanos(); let mut bytes = now.to_le_bytes(); - let mix = counter ^ u64::from(std::process::id()).rotate_left(32); + let mix = counter ^ u64::from(process::id()).rotate_left(32); for (target, source) in bytes[8..].iter_mut().zip(mix.to_le_bytes()) { *target ^= source; } diff --git a/cache2/src/checksum.rs b/cache2/src/checksum.rs index 40cdfbe..8b0a15d 100644 --- a/cache2/src/checksum.rs +++ b/cache2/src/checksum.rs @@ -57,7 +57,7 @@ impl Default for Crc32c { #[cfg(test)] mod tests { - use super::crc32c; + use crate::checksum::crc32c; #[test] fn matches_the_crc32c_check_value() { diff --git a/cache2/src/config/runtime.rs b/cache2/src/config/runtime.rs index 95a79ae..7eb4a5a 100644 --- a/cache2/src/config/runtime.rs +++ b/cache2/src/config/runtime.rs @@ -13,10 +13,11 @@ // limitations under the License. use std::io; +use std::mem::size_of; use std::time::Duration; -use super::CacheConfig; -use super::StorageLayout; +use crate::config::CacheConfig; +use crate::config::StorageLayout; use crate::error::ErrorOperation; use crate::error::Result; use crate::error::from_io; @@ -27,6 +28,8 @@ use crate::memory::MemoryStore; use crate::region::ActivityMetrics; use crate::region::RegionStaging; use crate::region::recovery::DataGeometry; +use crate::region::runtime_fixed_memory_bytes; +use crate::resources::BUFFER_ALIGNMENT; use crate::resources::CACHE_THREAD_STACK_BYTES; use crate::resources::MAX_CONFIG_COUNT; @@ -387,12 +390,12 @@ pub enum ReadAdmission { /// Maximum wait, greater than zero and no longer than five seconds. timeout: Duration, /// Maximum queued readers, from one through 65536. `None` follows the - /// aggregate read in-flight limit when [`super::CacheConfig`] is built. + /// aggregate read in-flight limit when [`CacheConfig`] is built. max_waiters: Option, }, } -/// Process-local resource choices, checked together by [`super::CacheConfig::new`]. +/// Process-local resource choices, checked together by [`CacheConfig::new`]. /// /// These values may change across opens. Warm recovery rebinds append shards /// from recovered Active and Free Regions when the requested topology fits. @@ -516,10 +519,9 @@ impl CacheConfig { runtime.l1_shards, runtime.l1_eviction_policy, )?; - let fixed_bytes = - crate::region::runtime_fixed_memory_bytes(index_slots, geometry.region_count)? - .checked_add(l1_metadata_bytes) - .ok_or_else(|| invalid_config("fixed memory requirements overflow"))?; + let fixed_bytes = runtime_fixed_memory_bytes(index_slots, geometry.region_count)? + .checked_add(l1_metadata_bytes) + .ok_or_else(|| invalid_config("fixed memory requirements overflow"))?; let (reserved_memory_bytes, minimum_memory_bytes) = runtime.memory_requirements(geometry, fixed_bytes)?; if minimum_memory_bytes > runtime.managed_memory_limit_bytes { @@ -638,7 +640,7 @@ impl RuntimeOptions { || self.write_flush_threshold_bytes > MAX_WRITE_FLUSH_THRESHOLD_BYTES || !self .write_flush_threshold_bytes - .is_multiple_of(crate::resources::BUFFER_ALIGNMENT) + .is_multiple_of(BUFFER_ALIGNMENT) { return Err(io::Error::new( io::ErrorKind::InvalidInput, @@ -748,7 +750,7 @@ fn runtime_topology_memory_bytes(config: &RuntimeOptions) -> Option { .checked_add(config.l1_shards)? .checked_add(reclaim.max_in_flight)? .checked_mul(RUNTIME_CONTROL_RESERVATION_BYTES)?; - let metrics = shard_count.checked_mul(std::mem::size_of::())?; + let metrics = shard_count.checked_mul(size_of::())?; stacks .checked_add(queue)? .checked_add(uring)? @@ -805,7 +807,7 @@ mod tests { #[test] fn optional_read_wait_queue_is_memory_accounted() { let base = RuntimeOptions { - io_engine: crate::config::IoEngine::Posix(crate::config::PosixIoConfig::new(7, 4, 1)), + io_engine: IoEngine::Posix(PosixIoConfig::new(7, 4, 1)), ..RuntimeOptions::default() }; let no_wait = runtime_topology_memory_bytes(&base).unwrap(); @@ -835,7 +837,7 @@ mod tests { }; let (_, base_minimum) = base.memory_requirements(geometry, 0).unwrap(); let (_, parallel_minimum) = RuntimeOptions { - io_engine: crate::config::IoEngine::Posix(crate::config::PosixIoConfig::new(4, 4, 2)), + io_engine: IoEngine::Posix(PosixIoConfig::new(4, 4, 2)), ..base } .memory_requirements(geometry, 0) @@ -929,7 +931,7 @@ mod tests { fn io_poll_requires_direct_mode() { let pool = IoUringPoolConfig::default().with_io_poll(true); let mut config = RuntimeOptions { - io_engine: IoEngine::IoUring(crate::config::IoUringConfig::new( + io_engine: IoEngine::IoUring(IoUringConfig::new( pool, IoUringPoolConfig::default(), IoUringPoolConfig::new(1, 1), diff --git a/cache2/src/config/storage.rs b/cache2/src/config/storage.rs index 1d6a618..ad643c1 100644 --- a/cache2/src/config/storage.rs +++ b/cache2/src/config/storage.rs @@ -17,10 +17,10 @@ use std::io; #[cfg(test)] -use super::CacheConfig; +use crate::config::CacheConfig; #[cfg(test)] -use super::RuntimeOptions; -use super::StorageLayout; +use crate::config::RuntimeOptions; +use crate::config::StorageLayout; use crate::error::ErrorOperation; use crate::error::Result; use crate::error::from_io; @@ -70,7 +70,7 @@ impl StorageOptions { /// Checks the inputs and computes an immutable layout without opening files. /// Use [`StorageLayout::peak_disk_bytes`] to compare a candidate with a disk - /// budget, then pass the chosen layout to [`super::CacheConfig::new`]. + /// budget, then pass the chosen layout to [`crate::CacheConfig::new`]. /// /// # Errors /// diff --git a/cache2/src/error.rs b/cache2/src/error.rs index 1244818..6a06147 100644 --- a/cache2/src/error.rs +++ b/cache2/src/error.rs @@ -15,9 +15,10 @@ use std::error::Error as StdError; use std::fmt; use std::io; +use std::result; /// A result returned by a public C² operation. -pub type Result = std::result::Result; +pub type Result = result::Result; /// Stable, actionable classification for a C² failure. /// diff --git a/cache2/src/fixtures.rs b/cache2/src/fixtures.rs index 3839388..ecafeb7 100644 --- a/cache2/src/fixtures.rs +++ b/cache2/src/fixtures.rs @@ -21,6 +21,8 @@ //! The sparse representation starts with the complete byte length. Each following line contains a //! hexadecimal offset and hexadecimal bytes; unspecified bytes are zero. +use std::str; + /// Checks every byte, including zero padding, and returns the committed bytes /// for decoder compatibility checks. #[track_caller] @@ -59,7 +61,7 @@ fn sparse_golden(input: &str) -> Vec { .as_chunks::<2>() .0 .iter() - .map(|pair| u8::from_str_radix(std::str::from_utf8(pair).unwrap(), 16).unwrap()) + .map(|pair| u8::from_str_radix(str::from_utf8(pair).unwrap(), 16).unwrap()) .collect::>(); let output = output.as_mut().expect("golden length must come first"); output[offset..offset + bytes.len()].copy_from_slice(&bytes); diff --git a/cache2/src/hashing.rs b/cache2/src/hashing.rs index 290d62e..1a87d49 100644 --- a/cache2/src/hashing.rs +++ b/cache2/src/hashing.rs @@ -13,6 +13,7 @@ // limitations under the License. use std::io; +use std::mem::size_of; const EMPTY_VALUE: u32 = u32::MAX; const DELETED_VALUE: u32 = u32::MAX - 1; @@ -61,7 +62,7 @@ impl FixedPrehashedMap { pub fn allocation_bytes(maximum_entries: usize) -> io::Result { Self::slot_count(maximum_entries)? - .checked_mul(std::mem::size_of::()) + .checked_mul(size_of::()) .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "fixed map is too large")) } @@ -237,7 +238,7 @@ mod tests { #[test] fn fixed_map_slot_is_one_u64() { - assert_eq!(std::mem::size_of::(), 8); + assert_eq!(size_of::(), 8); } #[test] @@ -247,7 +248,7 @@ mod tests { assert_eq!(FixedPrehashedMap::slot_count(40_960).unwrap(), 81_920); assert_eq!( FixedPrehashedMap::allocation_bytes(40_960).unwrap(), - 40_960 * 2 * std::mem::size_of::() + 40_960 * 2 * size_of::() ); } diff --git a/cache2/src/io/backend.rs b/cache2/src/io/backend.rs index 10ab216..418d081 100644 --- a/cache2/src/io/backend.rs +++ b/cache2/src/io/backend.rs @@ -19,9 +19,15 @@ //! record, superblock, or barrier operation without changing the cache //! algorithm. +#[cfg(test)] +use std::env; +#[cfg(test)] +use std::fs; use std::fs::File; use std::fs::OpenOptions; use std::io; +#[cfg(test)] +use std::mem::MaybeUninit; #[cfg(unix)] use std::os::fd::AsRawFd; #[cfg(unix)] @@ -31,10 +37,15 @@ use std::os::unix::fs::MetadataExt; #[cfg(unix)] use std::os::unix::fs::OpenOptionsExt; use std::path::Path; +#[cfg(test)] +use std::process; +use std::slice; use std::sync::Arc; use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; +#[cfg(test)] +use std::thread; use crate::config::IoMode; use crate::snapshot::CacheIoPathSnapshot; @@ -346,7 +357,7 @@ pub trait IoBackend: Send + Sync { // before constructing the mutable slice required by `read_at`. unsafe { buffer.write_bytes(0, length); - self.read_at(std::slice::from_raw_parts_mut(buffer, length), offset) + self.read_at(slice::from_raw_parts_mut(buffer, length), offset) } } fn write_at(&self, point: WritePoint, buffer: &[u8], offset: u64) -> io::Result; @@ -979,10 +990,7 @@ mod tests { impl TestFile { fn new(label: &str) -> Self { let nonce = NEXT_PATH.fetch_add(1, Ordering::Relaxed); - Self(std::env::temp_dir().join(format!( - "cache2-{label}-{}-{nonce}.cache", - std::process::id() - ))) + Self(env::temp_dir().join(format!("cache2-{label}-{}-{nonce}.cache", process::id()))) } fn open(&self) -> File { @@ -998,7 +1006,7 @@ mod tests { impl Drop for TestFile { fn drop(&mut self) { - let _ = std::fs::remove_file(&self.0); + let _ = fs::remove_file(&self.0); } } @@ -1060,7 +1068,7 @@ mod tests { MAX_INTERRUPTED_RETRIES + 1 ); - let mut uninitialized = std::mem::MaybeUninit::::uninit(); + let mut uninitialized = MaybeUninit::::uninit(); let backend = InterruptedBackend::default(); let (result, transferred) = read_exact_at_uninit_with_progress(&backend, uninitialized.as_mut_ptr(), 1, 0); @@ -1269,7 +1277,7 @@ mod tests { let alias = TestFile::new("control-alias"); let other = TestFile::new("control-other"); drop(primary.open()); - std::fs::hard_link(&primary.0, &alias.0).unwrap(); + fs::hard_link(&primary.0, &alias.0).unwrap(); let primary = FileBackend::open(&primary.0).unwrap(); let alias = FileBackend::open(&alias.0).unwrap(); @@ -1302,10 +1310,10 @@ mod tests { #[test] fn one_fault_handle_controls_multiple_recovery_files() { - use super::testing::FaultAction; - use super::testing::FaultBackend; - use super::testing::FaultEvent; - use super::testing::FaultHandle; + use crate::io::backend::testing::FaultAction; + use crate::io::backend::testing::FaultBackend; + use crate::io::backend::testing::FaultEvent; + use crate::io::backend::testing::FaultHandle; let state = TestFile::new("shared-fault-state"); let image = TestFile::new("shared-fault-image"); @@ -1348,10 +1356,12 @@ mod tests { #[test] fn cache_open_rejects_symbolic_links() { + use std::os::unix::fs::symlink; + let target = TestFile::new("symlink-target"); let link = TestFile::new("symlink-link"); drop(target.open()); - std::os::unix::fs::symlink(&target.0, &link.0).unwrap(); + symlink(&target.0, &link.0).unwrap(); assert!(FileBackend::open(&link.0).is_err()); } @@ -1589,10 +1599,10 @@ pub mod testing { // run user code in the target process. if unsafe { kill(getpid(), SIGKILL) } == 0 { loop { - std::thread::park(); + thread::park(); } } - std::process::abort() + process::abort() } #[cfg(unix)] diff --git a/cache2/src/io/engine/mod.rs b/cache2/src/io/engine/mod.rs index 910dc8d..a48161b 100644 --- a/cache2/src/io/engine/mod.rs +++ b/cache2/src/io/engine/mod.rs @@ -18,6 +18,7 @@ //! buffer is returned only with the target operation's completion, which is //! the lifetime rule required by both positioned I/O workers and `io_uring`. +use std::error::Error as StdError; use std::fmt; use std::future::Future; use std::io; @@ -32,10 +33,8 @@ use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicU64; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; -use std::sync::mpsc::Receiver; use std::sync::mpsc::SyncSender; use std::sync::mpsc::TrySendError; -use std::sync::mpsc::{self}; use std::task::Context; use std::task::Poll; use std::task::Waker; @@ -45,24 +44,17 @@ use std::time::Instant; use asyncband::semaphore::OwnedSemaphorePermit; use asyncband::semaphore::Semaphore; +use tokio::runtime::Handle as TokioHandle; +use tokio::time; +use tokio::time::Instant as TokioInstant; -use super::backend::IoBackend; #[cfg(unix)] -use super::backend::RuntimeFileBackend; +use crate::config::IoEngine as ConfiguredIoEngine; #[cfg(unix)] -use super::backend::RuntimeFileSet; -#[cfg(all( - feature = "io-uring", - target_os = "linux", - any( - target_arch = "x86_64", - target_arch = "aarch64", - target_arch = "riscv64", - target_arch = "loongarch64", - target_arch = "powerpc64" - ) -))] -use super::backend::RuntimeIoDirection; +use crate::config::IoUringPoolConfig; +use crate::io::backend::IoBackend; +#[cfg(unix)] +use crate::io::backend::RuntimeFileSet; #[cfg(all( feature = "io-uring", target_os = "linux", @@ -74,8 +66,7 @@ use super::backend::RuntimeIoDirection; target_arch = "powerpc64" ) ))] -use super::backend::RuntimeIoPath; -use super::backend::RuntimeIoStats; +use crate::io::backend::RuntimeIoDirection; #[cfg(all( feature = "io-uring", target_os = "linux", @@ -87,16 +78,10 @@ use super::backend::RuntimeIoStats; target_arch = "powerpc64" ) ))] -use super::backend::RuntimeIoStatsHandle; -use super::backend::WritePoint; -use super::backend::read_exact_at_uninit_with_progress; -use super::backend::write_all_at_with_progress; -#[cfg(unix)] -use crate::config::IoEngine as ConfiguredIoEngine; -#[cfg(unix)] -use crate::config::IoUringPoolConfig; +use crate::io::backend::RuntimeIoPath; +use crate::io::backend::RuntimeIoStats; +use crate::io::backend::WritePoint; use crate::resources::BufferLease; -use crate::resources::CACHE_THREAD_STACK_BYTES; use crate::snapshot::CacheIoDirectionSnapshot; mod posix; @@ -325,8 +310,8 @@ impl fmt::Display for IoBufferError { } } -impl std::error::Error for IoBufferError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { +impl StdError for IoBufferError { + fn source(&self) -> Option<&(dyn StdError + 'static)> { Some(&self.error) } } @@ -582,8 +567,8 @@ impl fmt::Display for SubmitError { } } -impl std::error::Error for SubmitError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { +impl StdError for SubmitError { + fn source(&self) -> Option<&(dyn StdError + 'static)> { Some(&self.error) } } @@ -820,13 +805,13 @@ impl BoundedIoRequest { pub async fn wait_async( self, engine: Arc, - tokio_handle: &tokio::runtime::Handle, + tokio_handle: &TokioHandle, ) -> Result { let mut request = AsyncRequestGuard::new(self.request, engine); - let deadline = tokio::time::Instant::from_std(self.deadline); + let deadline = TokioInstant::from_std(self.deadline); let completion = { let _entered = tokio_handle.enter(); - tokio::time::timeout_at(deadline, request.request_mut()) + time::timeout_at(deadline, request.request_mut()) } .await; if let Ok(completion) = completion { @@ -837,7 +822,7 @@ impl BoundedIoRequest { let cancel_error = request.cancel().err(); let completion = { let _entered = tokio_handle.enter(); - tokio::time::timeout(self.cancel_grace, request.request_mut()) + time::timeout(self.cancel_grace, request.request_mut()) } .await; match completion { @@ -1116,13 +1101,13 @@ impl ReadSlotAdmission { async fn acquire_until( &self, deadline: Instant, - tokio_handle: &tokio::runtime::Handle, + tokio_handle: &TokioHandle, ) -> io::Result { self.ensure_open()?; let acquire = Arc::clone(&self.slots).acquire_owned(1); { let _entered = tokio_handle.enter(); - tokio::time::timeout_at(tokio::time::Instant::from_std(deadline), acquire) + time::timeout_at(TokioInstant::from_std(deadline), acquire) } .await .map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "L2 read wait deadline expired")) @@ -1153,7 +1138,7 @@ impl ReadSlotWaiter { pub async fn reserve_until( self, deadline: Instant, - tokio_handle: &tokio::runtime::Handle, + tokio_handle: &TokioHandle, ) -> io::Result { let admission = self .shared @@ -1455,6 +1440,9 @@ impl RuntimeShared { ) ))] fn finish_quarantined(&self, task: Task, status: CompletionStatus, bytes_transferred: usize) { + #[cfg(not(test))] + use std::mem::forget; + let Task { request_id, operation, @@ -1469,7 +1457,7 @@ impl RuntimeShared { // intentional LeakSanitizer finding. lock_unpoisoned(&self.quarantined_buffers).push(buffer); #[cfg(not(test))] - std::mem::forget(buffer); + forget(buffer); } self.publish_completion( request_id, @@ -2052,7 +2040,7 @@ fn update_peak(peak: &AtomicUsize, value: usize) { peak.fetch_max(value, Ordering::Relaxed); } -fn add_duration_ns(counter: &AtomicU64, duration: std::time::Duration) { +fn add_duration_ns(counter: &AtomicU64, duration: Duration) { const MAX_DURATION_CAS_ATTEMPTS: usize = 8; let nanos = duration.as_nanos().min(u128::from(u64::MAX)) as u64; let mut current = counter.load(Ordering::Relaxed); diff --git a/cache2/src/io/engine/posix.rs b/cache2/src/io/engine/posix.rs index 10379dc..201efbc 100644 --- a/cache2/src/io/engine/posix.rs +++ b/cache2/src/io/engine/posix.rs @@ -12,7 +12,47 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::*; +use std::io; +use std::panic; +use std::panic::AssertUnwindSafe; +use std::sync::Arc; +use std::sync::Condvar; +use std::sync::Mutex; +use std::sync::RwLock; +use std::sync::atomic::AtomicBool; +use std::sync::atomic::AtomicU64; +use std::sync::atomic::Ordering; +use std::sync::mpsc; +use std::sync::mpsc::Receiver; +use std::thread; +use std::time::Instant; + +use crate::io::backend::IoBackend; +#[cfg(unix)] +use crate::io::backend::RuntimeFileBackend; +#[cfg(unix)] +use crate::io::backend::RuntimeFileSet; +use crate::io::backend::read_exact_at_uninit_with_progress; +use crate::io::backend::write_all_at_with_progress; +use crate::io::engine::BackendIoEngine; +use crate::io::engine::CompletionState; +use crate::io::engine::CompletionStatus; +use crate::io::engine::DriverCommand; +use crate::io::engine::EngineIoSnapshot; +use crate::io::engine::IoEngine; +use crate::io::engine::IoOperation; +use crate::io::engine::IoRequest; +use crate::io::engine::ReadSlot; +use crate::io::engine::ReadSlotWaiter; +use crate::io::engine::RequestId; +use crate::io::engine::RuntimeInner; +use crate::io::engine::RuntimeShared; +use crate::io::engine::ShutdownPhase; +use crate::io::engine::ShutdownState; +use crate::io::engine::SubmitError; +use crate::io::engine::SubmitState; +use crate::io::engine::lock_unpoisoned; +use crate::resources::CACHE_THREAD_STACK_BYTES; impl BackendIoEngine { #[cfg(unix)] @@ -99,7 +139,7 @@ impl BackendIoEngine { let worker_backend = Arc::clone(&backend); let worker_shared = Arc::clone(&shared); let worker_receiver = Arc::clone(&receiver); - let spawn_result = std::thread::Builder::new() + let spawn_result = thread::Builder::new() .name(format!("cache2-sync-io-{worker_index}")) .stack_size(CACHE_THREAD_STACK_BYTES) .spawn(move || backend_driver(worker_backend, worker_shared, worker_receiver)); @@ -233,16 +273,15 @@ fn backend_driver( shared.finish(task, CompletionStatus::Cancelled, 0); continue; } - let (status, transferred) = - std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - execute_backend(backend.as_ref(), &mut task.operation) - })) - .unwrap_or_else(|_| { - ( - CompletionStatus::Failed(io::Error::other("I/O backend panicked")), - 0, - ) - }); + let (status, transferred) = panic::catch_unwind(AssertUnwindSafe(|| { + execute_backend(backend.as_ref(), &mut task.operation) + })) + .unwrap_or_else(|_| { + ( + CompletionStatus::Failed(io::Error::other("I/O backend panicked")), + 0, + ) + }); shared.finish(task, status, transferred); } DriverCommand::Cancel(request_id) => { diff --git a/cache2/src/io/engine/tests.rs b/cache2/src/io/engine/tests.rs index 3bb1dd3..f3804e2 100644 --- a/cache2/src/io/engine/tests.rs +++ b/cache2/src/io/engine/tests.rs @@ -12,13 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::env; +use std::fs; use std::fs::File; use std::fs::OpenOptions; use std::path::PathBuf; +use std::process; use std::sync::atomic::AtomicU64; +use std::sync::mpsc; +use std::thread; use std::time::Duration; +use tokio::runtime::Handle as TokioHandle; +use tokio::task; +use tokio::task::JoinHandle as TokioJoinHandle; +use tokio::time; + use super::*; +use crate::config::PosixIoConfig; use crate::io::backend::FileBackend; use crate::io::backend::SyncMode; use crate::io::backend::SyncPoint; @@ -39,7 +50,7 @@ async fn wait_for_registered_read_waiters(engine: &BackendIoEngine, expected: us if actual == expected { return; } - tokio::task::yield_now().await; + task::yield_now().await; } panic!("expected {expected} registered read waiters"); } @@ -48,18 +59,18 @@ async fn spawn_registered_read_slot_waiter( engine: &BackendIoEngine, timeout: Duration, expected_waiters: usize, -) -> tokio::task::JoinHandle> { +) -> TokioJoinHandle> { let slot_waiter = engine.read_slot_waiter(); let waiter = tokio::spawn(async move { slot_waiter - .reserve_until(Instant::now() + timeout, &tokio::runtime::Handle::current()) + .reserve_until(Instant::now() + timeout, &TokioHandle::current()) .await }); wait_for_registered_read_waiters(engine, expected_waiters).await; waiter } -async fn read_wait_error(waiter: tokio::task::JoinHandle>) -> io::Error { +async fn read_wait_error(waiter: TokioJoinHandle>) -> io::Error { match waiter.await.unwrap() { Ok(_) => panic!("read waiter unexpectedly reserved a slot"), Err(error) => error, @@ -73,8 +84,7 @@ struct TestFile { impl TestFile { fn new() -> Self { let id = FILE_ID.fetch_add(1, Ordering::Relaxed); - let path = - std::env::temp_dir().join(format!("cache2-io-engine-{}-{id}.bin", std::process::id())); + let path = env::temp_dir().join(format!("cache2-io-engine-{}-{id}.bin", process::id())); Self { path } } @@ -95,7 +105,7 @@ impl TestFile { impl Drop for TestFile { fn drop(&mut self) { - let _ = std::fs::remove_file(&self.path); + let _ = fs::remove_file(&self.path); } } @@ -378,7 +388,7 @@ async fn async_request_is_woken_by_driver_completion() { .unwrap(); let completion = request - .wait_async(Arc::clone(&engine), &tokio::runtime::Handle::current()) + .wait_async(Arc::clone(&engine), &TokioHandle::current()) .await .unwrap(); @@ -400,10 +410,10 @@ async fn dropping_async_wait_requests_bounded_cancellation() { let waiter_engine = Arc::clone(&engine); let waiter = tokio::spawn(async move { request - .wait_async(waiter_engine, &tokio::runtime::Handle::current()) + .wait_async(waiter_engine, &TokioHandle::current()) .await }); - tokio::task::yield_now().await; + task::yield_now().await; assert!(backend.wait_for_entered(1)); waiter.abort(); @@ -429,10 +439,10 @@ async fn read_slot_waits_for_cancelled_request_to_release_physical_capacity() { let request_engine = Arc::clone(&engine); let request_waiter = tokio::spawn(async move { request - .wait_async(request_engine, &tokio::runtime::Handle::current()) + .wait_async(request_engine, &TokioHandle::current()) .await }); - tokio::task::yield_now().await; + task::yield_now().await; assert!(backend.wait_for_entered(1)); request_waiter.abort(); @@ -441,10 +451,10 @@ async fn read_slot_waits_for_cancelled_request_to_release_physical_capacity() { let slot_waiter = engine.read_slot_waiter(); let deadline = Instant::now() + Duration::from_secs(1); - let tokio_handle = tokio::runtime::Handle::current(); + let tokio_handle = TokioHandle::current(); let mut reservation = Box::pin(slot_waiter.reserve_until(deadline, &tokio_handle)); assert!( - tokio::time::timeout(Duration::from_millis(20), reservation.as_mut()) + time::timeout(Duration::from_millis(20), reservation.as_mut()) .await .is_err(), "caller cancellation must not publish physical capacity" @@ -510,7 +520,7 @@ async fn queued_read_reservations_are_fifo() { drop(held); let first_slot = first.await.unwrap().unwrap(); - tokio::task::yield_now().await; + task::yield_now().await; assert!( !second.is_finished(), "the second waiter bypassed the first" @@ -532,7 +542,7 @@ async fn queued_reads_use_every_released_engine_slot() { drop(held); let first_slot = first.await.unwrap().unwrap(); - let second_slot = tokio::time::timeout(Duration::from_millis(20), second) + let second_slot = time::timeout(Duration::from_millis(20), second) .await .expect("an idle second engine slot was blocked by the queue head") .unwrap() @@ -596,7 +606,7 @@ async fn async_read_deadline_keeps_other_slots_available() { assert!(backend.wait_for_entered(1)); let timeout = request - .wait_async(Arc::clone(&engine), &tokio::runtime::Handle::current()) + .wait_async(Arc::clone(&engine), &TokioHandle::current()) .await .unwrap_err(); let (error, buffer) = timeout.into_buffer(); @@ -760,7 +770,7 @@ fn configured_posix_engine_shares_its_worker_capacity() { files, 4, 4, - ConfiguredIoEngine::Posix(crate::config::PosixIoConfig::new(4, 4, 1)), + ConfiguredIoEngine::Posix(PosixIoConfig::new(4, 4, 1)), None, false, false, @@ -880,7 +890,7 @@ fn submit_wait_blocks_at_engine_capacity_and_resumes() { let (_, waiting_operation) = rejected.into_parts(); let (started_sender, started_receiver) = mpsc::sync_channel(1); let (sender, receiver) = mpsc::sync_channel(1); - let submitter = std::thread::spawn(move || { + let submitter = thread::spawn(move || { started_sender.send(()).unwrap(); sender .send(waiting_engine.submit_wait(waiting_operation)) @@ -924,7 +934,7 @@ fn controlled_slot_wait_observes_cancel_wake_and_absolute_deadline() { let waiting_operation = IoOperation::read(read_buffer(&resources, 1), 1); let (started_sender, started_receiver) = mpsc::sync_channel(1); let (result_sender, result_receiver) = mpsc::sync_channel(1); - let submitter = std::thread::spawn(move || { + let submitter = thread::spawn(move || { started_sender.send(()).unwrap(); result_sender .send(waiting_engine.submit_wait_controlled( diff --git a/cache2/src/io/engine/uring.rs b/cache2/src/io/engine/uring.rs index b502851..12ab3db 100644 --- a/cache2/src/io/engine/uring.rs +++ b/cache2/src/io/engine/uring.rs @@ -14,10 +14,27 @@ use std::collections::HashMap; use std::collections::VecDeque; +use std::io; use std::io::Read; use std::io::Write; +use std::mem; use std::os::fd::AsRawFd; use std::os::unix::net::UnixStream; +use std::panic; +use std::panic::AssertUnwindSafe; +use std::sync::Arc; +use std::sync::Condvar; +use std::sync::Mutex; +use std::sync::RwLock; +use std::sync::atomic::AtomicBool; +use std::sync::atomic::AtomicU64; +#[cfg(test)] +use std::sync::atomic::AtomicUsize; +use std::sync::atomic::Ordering; +use std::sync::mpsc; +use std::sync::mpsc::Receiver; +use std::thread; +use std::time::Instant; use hashcrew::xxhash::Xxh3_64Builder; use io_uring::IoUring; @@ -26,7 +43,38 @@ use io_uring::opcode; use io_uring::squeue; use io_uring::types; -use super::*; +use crate::config::IoUringPoolConfig; +use crate::io::backend::RuntimeFileSet; +use crate::io::backend::RuntimeIoPath; +use crate::io::backend::RuntimeIoStatsHandle; +use crate::io::engine::CompletionState; +use crate::io::engine::CompletionStatus; +use crate::io::engine::DriverCommand; +use crate::io::engine::DriverWake; +use crate::io::engine::EngineIoSnapshot; +#[cfg(test)] +use crate::io::engine::IO_QUEUE_ENTRY_RESERVATION_BYTES; +#[cfg(test)] +use crate::io::engine::IoBuffer; +use crate::io::engine::IoEngine; +use crate::io::engine::IoOperation; +use crate::io::engine::IoRequest; +#[cfg(test)] +use crate::io::engine::MAX_IO_REQUESTS_PER_ENGINE; +use crate::io::engine::OperationKind; +use crate::io::engine::ReadSlot; +use crate::io::engine::ReadSlotWaiter; +use crate::io::engine::RequestId; +use crate::io::engine::RuntimeInner; +use crate::io::engine::RuntimeShared; +use crate::io::engine::ShutdownPhase; +use crate::io::engine::ShutdownState; +use crate::io::engine::SubmitError; +use crate::io::engine::SubmitState; +use crate::io::engine::Task; +#[cfg(test)] +use crate::io::engine::io_uring_extra_memory_bytes; +use crate::resources::CACHE_THREAD_STACK_BYTES; const CANCEL_CQE_BIT: u64 = 1_u64 << 63; const INTERNAL_CQE_BIT: u64 = 1_u64 << 62; @@ -83,7 +131,7 @@ impl UringIoEngine { pub fn new_with_files( files: RuntimeFileSet, max_in_flight: usize, - config: crate::config::IoUringPoolConfig, + config: IoUringPoolConfig, statistics_enabled: bool, read_wait_enabled: bool, ) -> io::Result { @@ -165,7 +213,7 @@ impl UringIoEngine { let submit_state = Arc::new(RwLock::new(SubmitState { accepting: true })); let worker_shared = Arc::clone(&shared); let worker_submit_state = Arc::clone(&submit_state); - let worker = std::thread::Builder::new() + let worker = thread::Builder::new() .name("cache2-uring-io".into()) .stack_size(CACHE_THREAD_STACK_BYTES) .spawn(move || { @@ -352,7 +400,7 @@ fn uring_driver( io_poll, shutting_down: false, }; - let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| driver.run())) + let result = panic::catch_unwind(AssertUnwindSafe(|| driver.run())) .unwrap_or_else(|_| Err(io::Error::other("io_uring driver panicked"))); if let Err(error) = &result { driver.stop_accepting_and_fail_all(error); @@ -860,7 +908,7 @@ impl UringDriver { // and never issue LOCK_UN on another duplicate. self.shared.mark_unfenced_writes(); if let Some(files) = self.files.take() { - std::mem::forget(files); + mem::forget(files); } } @@ -946,7 +994,7 @@ impl UringDriver { if !self.has_active_target() { return; } - std::thread::yield_now(); + thread::yield_now(); } } diff --git a/cache2/src/memory/eviction.rs b/cache2/src/memory/eviction.rs index 56ff824..a4ce814 100644 --- a/cache2/src/memory/eviction.rs +++ b/cache2/src/memory/eviction.rs @@ -18,6 +18,7 @@ //! the optional CLOCK or S3-FIFO metadata and chooses bounded victims. use std::io; +use std::mem::size_of; use crate::config::L1EvictionPolicy; use crate::hashing::FixedPrehashedMap; @@ -329,7 +330,7 @@ impl ClockState { fn allocation_bytes(maximum_entries: usize) -> io::Result { maximum_entries - .checked_mul(std::mem::size_of::()) + .checked_mul(size_of::()) .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "CLOCK is too large")) } @@ -529,7 +530,7 @@ impl S3FifoState { fn allocation_bytes(maximum_entries: usize) -> io::Result { let links = maximum_entries - .checked_mul(std::mem::size_of::()) + .checked_mul(size_of::()) .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "S3-FIFO is too large"))?; links .checked_add(GhostQueue::allocation_bytes(maximum_entries)?) @@ -717,12 +718,12 @@ impl GhostQueue { fn allocation_bytes(maximum_entries: usize) -> io::Result { let hashes = maximum_entries - .checked_mul(std::mem::size_of::()) + .checked_mul(size_of::()) .ok_or_else(|| { io::Error::new(io::ErrorKind::InvalidInput, "ghost queue is too large") })?; let links = maximum_entries - .checked_mul(std::mem::size_of::()) + .checked_mul(size_of::()) .ok_or_else(|| { io::Error::new(io::ErrorKind::InvalidInput, "ghost queue is too large") })?; @@ -1075,7 +1076,7 @@ mod tests { #[test] fn ghost_storage_uses_twenty_bytes_per_entry() { const ENTRIES: usize = 5; - assert_eq!(std::mem::size_of::(), 12); + assert_eq!(size_of::(), 12); assert_eq!( GhostQueue::allocation_bytes(ENTRIES).unwrap() - FixedPrehashedMap::allocation_bytes(ENTRIES).unwrap(), diff --git a/cache2/src/memory/mod.rs b/cache2/src/memory/mod.rs index 7dc9ab4..56b9305 100644 --- a/cache2/src/memory/mod.rs +++ b/cache2/src/memory/mod.rs @@ -18,15 +18,21 @@ //! immediately, may be discarded at any time, and use a small bounded eviction //! policy. +use std::hint::spin_loop; use std::io; +use std::mem::size_of; use std::ops::Deref; use std::sync::Arc; +#[cfg(test)] +use std::sync::Barrier; use std::sync::Mutex; use std::sync::MutexGuard; use std::sync::TryLockError; use std::sync::atomic::AtomicU64; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; +#[cfg(test)] +use std::thread; use self::eviction::DetachedPolicy; use self::eviction::EvictionState; @@ -49,7 +55,7 @@ const MEMORY_ENTRY_OVERHEAD_BYTES: usize = 64; const MAX_L1_ENTRY_BYTES: usize = 256 * 1024; /// A sequence trailer uses the spare tail of the fixed entry-overhead charge /// so compact resident slots do not enlarge the hot Arc allocation. -const MEMORY_VALUE_SEQNO_BYTES: usize = std::mem::size_of::(); +const MEMORY_VALUE_SEQNO_BYTES: usize = size_of::(); /// Full-key collision work stays bounded for entries sharing one directory /// fingerprint, including distinct keys with the same 64-bit cache hash. const MAX_SAME_HASH_ENTRIES: usize = 8; @@ -861,10 +867,10 @@ impl MemoryStore { .min(shard_capacity / MEMORY_ENTRY_OVERHEAD_BYTES) .min(MAX_POLICY_SLOT_INDEX.saturating_add(1)); let fixed_slots = shard_entries - .checked_mul(std::mem::size_of::>()) + .checked_mul(size_of::>()) .and_then(|bytes| { shard_entries - .checked_mul(std::mem::size_of::()) + .checked_mul(size_of::()) .and_then(|policy| bytes.checked_add(policy)) }) .ok_or_else(|| invalid_memory_plan("L1 slot memory plan overflow"))?; @@ -940,7 +946,7 @@ impl MemoryStore { Ok(shard) => break shard, Err(TryLockError::WouldBlock) if attempts < MAX_L1_LOOKUP_LOCK_ATTEMPTS => { attempts += 1; - std::hint::spin_loop(); + spin_loop(); } Err(TryLockError::WouldBlock | TryLockError::Poisoned(_)) => { return MemoryLookup::Miss(MemoryReadToken { shard_id }); @@ -1111,9 +1117,9 @@ mod tests { #[test] fn memory_entry_slot_uses_two_machine_words() { - let expected = 2 * std::mem::size_of::(); - assert_eq!(std::mem::size_of::(), expected); - assert_eq!(std::mem::size_of::>(), expected); + let expected = 2 * size_of::(); + assert_eq!(size_of::(), expected); + assert_eq!(size_of::>(), expected); } #[test] @@ -1444,8 +1450,8 @@ mod tests { let clones = (0..8).map(|_| retained.clone()).collect::>(); assert!(!store.publish(22, b"b", &[2; 300], 2)); - let barrier = Arc::new(std::sync::Barrier::new(clones.len() + 1)); - std::thread::scope(|scope| { + let barrier = Arc::new(Barrier::new(clones.len() + 1)); + thread::scope(|scope| { for value in clones { let barrier = Arc::clone(&barrier); scope.spawn(move || { diff --git a/cache2/src/region/appender.rs b/cache2/src/region/appender.rs index 39833a5..5c45ef1 100644 --- a/cache2/src/region/appender.rs +++ b/cache2/src/region/appender.rs @@ -18,12 +18,10 @@ //! disposable-cache protocol establishes durability once, when publishing a //! CLEAN image, and deliberately has no per-span sync. +use std::error::Error as StdError; use std::fmt; use std::io; -use super::manager::RegionWriteSpan; -use super::recovery::DATA_REGION_AREA_OFFSET; -use super::recovery::DataGeometry; use crate::io::backend::DIRECT_IO_ALIGNMENT; use crate::io::backend::WritePoint; use crate::io::engine::BoundedIoRequest; @@ -33,6 +31,9 @@ use crate::io::engine::IoOperation; use crate::io::engine::OperationKind; use crate::io::engine::RequestId; use crate::io::engine::submit_cache_io; +use crate::region::manager::RegionWriteSpan; +use crate::region::recovery::DATA_REGION_AREA_OFFSET; +use crate::region::recovery::DataGeometry; pub struct RegionSpanSubmitError { pub error: io::Error, @@ -57,8 +58,8 @@ impl fmt::Display for RegionSpanSubmitError { } } -impl std::error::Error for RegionSpanSubmitError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { +impl StdError for RegionSpanSubmitError { + fn source(&self) -> Option<&(dyn StdError + 'static)> { Some(&self.error) } } diff --git a/cache2/src/region/file_backend/mod.rs b/cache2/src/region/file_backend/mod.rs index 32c2a64..8e6ef9f 100644 --- a/cache2/src/region/file_backend/mod.rs +++ b/cache2/src/region/file_backend/mod.rs @@ -14,6 +14,8 @@ //! File ownership, recovery, and lifecycle adapter for the Region core. +use std::fmt; +use std::fs; use std::fs::File; use std::io::Write; use std::io::{self}; @@ -24,57 +26,9 @@ use std::sync::Arc; use std::sync::Mutex; use std::sync::atomic::AtomicU64; -use super::FileRegionCore; -use super::RegionAccessState; -use super::RegionHealthLatch; -use super::RegionManagerAuthority; -use super::RegionShard; -use super::guarded_index_result; -use super::index::MAX_INDEX_PARTITIONS; -use super::index::RegionIndex; -use super::index::storage::IndexImageBinding; -use super::index::storage::IndexPartitionRange; -use super::index::storage::IndexPhysicalStats; -use super::index::storage::PartitionedIndexStorage; -use super::index::storage::canonical_index_partition_ranges; -use super::index_storage_io_error; -use super::manager::RegionManager; -use super::recovery::DataSuperblock; -use super::recovery::DataSuperblockProbe; -use super::recovery::PartitionMetadataRecord; -use super::recovery::PersistentId; -use super::recovery::RECOVERY_IMAGE_INDEX_OFFSET; -use super::recovery::RECOVERY_PAGE_SIZE; -use super::recovery::REGION_METADATA_PAGE_SIZE; -use super::recovery::REGION_METADATA_PARTITIONS_PER_PAGE; -use super::recovery::REGION_METADATA_REGIONS_PER_PAGE; -use super::recovery::RecoveryImageHeader; -use super::recovery::RecoveryImageHeaderProbe; -use super::recovery::RecoveryState; -use super::recovery::RegionMetadata; -use super::recovery::RegionMetadataError; -use super::recovery::RegionMetadataRecord; -use super::recovery::RegionMetadataRoot; -use super::recovery::RegionMetadataState; -use super::recovery::STATE_FILE_SIZE; -use super::recovery::STATE_SLOT_COUNT; -use super::recovery::SelectedState; -use super::recovery::StateBinding; -use super::recovery::StatePageWrite; -use super::recovery::StateRecord; -use super::recovery::StateSelectionError; -use super::recovery::clean_image_matches; -use super::recovery::latest_state; -use super::recovery::prepare_next_state; -use super::recovery::prepare_running_barrier; -use super::recovery::recovery_image_index_len; -use super::region_metadata_io_error; #[cfg(test)] -use super::runtime::HybridValueRead; -use super::runtime::RegionDataPlane; -use super::store::RecoveryPlan; -use super::store::RegionBackend; -use super::store::RegionStore; +use tokio::runtime::Handle as TokioHandle; + use crate::config::CacheConfig; use crate::config::IoMode; #[cfg(test)] @@ -91,6 +45,57 @@ use crate::io::backend::WritePoint; use crate::io::backend::read_at_bounded; use crate::io::backend::read_exact_at; use crate::io::backend::write_all_at; +use crate::region::FileRegionCore; +use crate::region::RegionAccessState; +use crate::region::RegionHealthLatch; +use crate::region::RegionManagerAuthority; +use crate::region::RegionShard; +use crate::region::guarded_index_result; +use crate::region::index::MAX_INDEX_PARTITIONS; +use crate::region::index::RegionIndex; +use crate::region::index::storage::IndexImageBinding; +use crate::region::index::storage::IndexPartitionRange; +use crate::region::index::storage::IndexPhysicalStats; +use crate::region::index::storage::PartitionedIndexStorage; +use crate::region::index::storage::canonical_index_partition_ranges; +use crate::region::index_storage_io_error; +use crate::region::manager::RegionManager; +use crate::region::recovery::DataSuperblock; +use crate::region::recovery::DataSuperblockProbe; +use crate::region::recovery::PartitionMetadataRecord; +use crate::region::recovery::PersistentId; +use crate::region::recovery::RECOVERY_IMAGE_INDEX_OFFSET; +use crate::region::recovery::RECOVERY_PAGE_SIZE; +use crate::region::recovery::REGION_METADATA_PAGE_SIZE; +use crate::region::recovery::REGION_METADATA_PARTITIONS_PER_PAGE; +use crate::region::recovery::REGION_METADATA_REGIONS_PER_PAGE; +use crate::region::recovery::RecoveryImageHeader; +use crate::region::recovery::RecoveryImageHeaderProbe; +use crate::region::recovery::RecoveryState; +use crate::region::recovery::RegionMetadata; +use crate::region::recovery::RegionMetadataError; +use crate::region::recovery::RegionMetadataRecord; +use crate::region::recovery::RegionMetadataRoot; +use crate::region::recovery::RegionMetadataState; +use crate::region::recovery::STATE_FILE_SIZE; +use crate::region::recovery::STATE_SLOT_COUNT; +use crate::region::recovery::SelectedState; +use crate::region::recovery::StateBinding; +use crate::region::recovery::StatePageWrite; +use crate::region::recovery::StateRecord; +use crate::region::recovery::StateSelectionError; +use crate::region::recovery::clean_image_matches; +use crate::region::recovery::latest_state; +use crate::region::recovery::prepare_next_state; +use crate::region::recovery::prepare_running_barrier; +use crate::region::recovery::recovery_image_index_len; +use crate::region::region_metadata_io_error; +#[cfg(test)] +use crate::region::runtime::HybridValueRead; +use crate::region::runtime::RegionDataPlane; +use crate::region::store::RecoveryPlan; +use crate::region::store::RegionBackend; +use crate::region::store::RegionStore; #[cfg(test)] use crate::snapshot::CacheSnapshot; #[cfg(test)] @@ -263,7 +268,7 @@ impl RegionStore> { async fn get_value_async( &self, key: &[u8], - tokio_handle: &tokio::runtime::Handle, + tokio_handle: &TokioHandle, ) -> io::Result> { self.runtime()? .data_plane()? @@ -339,7 +344,7 @@ impl RegionFileSystem for SystemRegionFileSystem { } fn remove_file(&self, path: &Path) -> io::Result<()> { - match std::fs::remove_file(path) { + match fs::remove_file(path) { Ok(()) => Ok(()), Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(()), Err(error) => Err(error), @@ -347,7 +352,7 @@ impl RegionFileSystem for SystemRegionFileSystem { } fn rename(&self, source: &Path, destination: &Path) -> io::Result<()> { - std::fs::rename(source, destination) + fs::rename(source, destination) } fn sync_parent(&self, path: &Path) -> io::Result<()> { @@ -470,7 +475,7 @@ where reason: &'static str, index_slots: usize, index_mapping_bytes: u64, - error: &impl std::fmt::Display, + error: &impl fmt::Display, ) { log::warn!( target: "cache2::recovery", diff --git a/cache2/src/region/file_backend/tests.rs b/cache2/src/region/file_backend/tests.rs index 413dcf2..8546e19 100644 --- a/cache2/src/region/file_backend/tests.rs +++ b/cache2/src/region/file_backend/tests.rs @@ -12,8 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::env; +use std::fs; +use std::future::Future; +use std::future::poll_fn; #[cfg(unix)] use std::os::unix::process::ExitStatusExt; +use std::pin::Pin; +use std::process; #[cfg(unix)] use std::process::Command; #[cfg(unix)] @@ -22,15 +28,25 @@ use std::sync::Arc; use std::sync::Mutex; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; +use std::sync::mpsc; +use std::task::Poll; +use std::thread; use std::time::Duration; use std::time::Instant; +use tokio::runtime::Builder as TokioRuntimeBuilder; + use super::*; +use crate::config::IoEngine as ConfiguredIoEngine; +use crate::config::MAX_WRITE_FLUSH_THRESHOLD_BYTES; +use crate::config::PosixIoConfig; use crate::config::ReadAdmission; +use crate::io::backend::MAX_INTERRUPTED_RETRIES; use crate::io::backend::testing::FaultAction; use crate::io::backend::testing::FaultBackend; use crate::io::backend::testing::FaultEvent; use crate::io::backend::testing::FaultHandle; +use crate::io::backend::testing::kill_process; use crate::io::engine::BackendIoEngine; use crate::io::engine::IoEngine; use crate::region::RegionStageValue; @@ -38,10 +54,12 @@ use crate::region::index::IndexEntry; use crate::region::index::PackedLocation; use crate::region::index::storage::INDEX_IMAGE_SLOTS_PER_PAGE; use crate::region::index::storage::IndexSlot; +use crate::region::index::storage::IndexSlotState; use crate::region::reader::ReadCandidate; use crate::region::reader::ReadCompletion; use crate::region::reader::ReadPlan; use crate::region::reader::plan_read; +use crate::region::record::RECORD_ALIGNMENT; use crate::region::record::hash_key; use crate::region::record::required_record_bytes; use crate::region::recovery::DATA_REGION_AREA_OFFSET; @@ -65,20 +83,18 @@ fn eventually_admitted(mut put: impl FnMut() -> io::Result) -> T { Instant::now() < deadline, "write buffer did not make progress" ); - std::thread::yield_now(); + thread::yield_now(); } Err(error) => panic!("cache write failed: {error}"), } } } -async fn assert_pending(mut future: std::pin::Pin<&mut F>, message: &str) { - std::future::poll_fn( - |context| match std::future::Future::poll(future.as_mut(), context) { - std::task::Poll::Pending => std::task::Poll::Ready(()), - std::task::Poll::Ready(_) => panic!("{message}"), - }, - ) +async fn assert_pending(mut future: Pin<&mut F>, message: &str) { + poll_fn(|context| match Future::poll(future.as_mut(), context) { + Poll::Pending => Poll::Ready(()), + Poll::Ready(_) => panic!("{message}"), + }) .await; } @@ -90,10 +106,9 @@ struct TestDirectory { impl TestDirectory { fn new() -> Self { let ordinal = NEXT_TEST_DIRECTORY.fetch_add(1, Ordering::Relaxed); - let root = - std::env::temp_dir().join(format!("cache2-region-{}-{ordinal}", std::process::id())); - let _ = std::fs::remove_dir_all(&root); - std::fs::create_dir(&root).unwrap(); + let root = env::temp_dir().join(format!("cache2-region-{}-{ordinal}", process::id())); + let _ = fs::remove_dir_all(&root); + fs::create_dir(&root).unwrap(); let files = RegionFiles::new( root.join("data"), root.join("state"), @@ -105,7 +120,7 @@ impl TestDirectory { impl Drop for TestDirectory { fn drop(&mut self) { - let _ = std::fs::remove_dir_all(&self.root); + let _ = fs::remove_dir_all(&self.root); } } @@ -125,7 +140,7 @@ fn state_page_reads_stop_after_the_interrupted_retry_budget() { .iter() .filter(|event| **event == FaultEvent::Read) .count(), - crate::io::backend::MAX_INTERRUPTED_RETRIES + 1 + MAX_INTERRUPTED_RETRIES + 1 ); } @@ -193,7 +208,7 @@ impl RegionFileSystem for FaultRegionFileSystem { } fn remove_file(&self, path: &Path) -> io::Result<()> { - match std::fs::remove_file(path) { + match fs::remove_file(path) { Ok(()) => Ok(()), Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(()), Err(error) => Err(error), @@ -202,7 +217,7 @@ impl RegionFileSystem for FaultRegionFileSystem { fn rename(&self, source: &Path, destination: &Path) -> io::Result<()> { self.file_system.check(FileSystemFault::Rename)?; - std::fs::rename(source, destination) + fs::rename(source, destination) } fn sync_parent(&self, path: &Path) -> io::Result<()> { @@ -262,8 +277,8 @@ fn external_process_kill_recovery_contract() { const CHILD_CASE: &str = "CACHE2_CRASH_CHILD_CASE"; const CHILD_ROOT: &str = "CACHE2_CRASH_CHILD_ROOT"; - if let Ok(case) = std::env::var(CHILD_CASE) { - let root = PathBuf::from(std::env::var_os(CHILD_ROOT).expect("child root is set")); + if let Ok(case) = env::var(CHILD_CASE) { + let root = PathBuf::from(env::var_os(CHILD_ROOT).expect("child root is set")); let files = RegionFiles::new( root.join("data"), root.join("state"), @@ -291,7 +306,7 @@ fn external_process_kill_recovery_contract() { initial.drain().unwrap(); initial.close_warm().unwrap(); - let status = Command::new(std::env::current_exe().unwrap()) + let status = Command::new(env::current_exe().unwrap()) .arg("--exact") .arg("region::file_backend::tests::external_process_kill_recovery_contract") .arg("--ignored") @@ -335,7 +350,7 @@ fn run_crash_child(case: &str, files: RegionFiles) -> ! { "open" => { let _store = RegionStore::open(4096, FileRegionBackend::for_test(files, data, 4096)).unwrap(); - crate::io::backend::testing::kill_process(); + kill_process(); } "write" | "drain" => { let store = @@ -344,7 +359,7 @@ fn run_crash_child(case: &str, files: RegionFiles) -> ! { if case == "drain" { store.drain().unwrap(); } - crate::io::backend::testing::kill_process(); + kill_process(); } "warm-data" | "warm-image" | "clean-state" => { let (file_system, faults, _) = FaultRegionFileSystem::new(); @@ -398,7 +413,7 @@ fn configured_read_wait_is_bounded_and_cancel_safe() { let directory = TestDirectory::new(); let data = production_data_superblock(512 * 1024); let runtime_config = RuntimeOptions { - io_engine: crate::config::IoEngine::Posix(crate::config::PosixIoConfig::new(2, 4, 1)), + io_engine: ConfiguredIoEngine::Posix(PosixIoConfig::new(2, 4, 1)), l1_capacity_bytes: 0, statistics: true, read_admission: ReadAdmission::Wait { @@ -417,7 +432,7 @@ fn configured_read_wait_is_bounded_and_cancel_safe() { ), ) .unwrap(); - let tokio_runtime = tokio::runtime::Builder::new_multi_thread() + let tokio_runtime = TokioRuntimeBuilder::new_multi_thread() .worker_threads(2) .enable_time() .build() @@ -482,7 +497,7 @@ fn queued_l2_read_does_not_pin_warm_close() { let directory = TestDirectory::new(); let data = production_data_superblock(512 * 1024); let runtime_config = RuntimeOptions { - io_engine: crate::config::IoEngine::Posix(crate::config::PosixIoConfig::new(1, 4, 1)), + io_engine: ConfiguredIoEngine::Posix(PosixIoConfig::new(1, 4, 1)), l1_capacity_bytes: 0, read_admission: ReadAdmission::Wait { timeout: Duration::from_secs(1), @@ -500,7 +515,7 @@ fn queued_l2_read_does_not_pin_warm_close() { ), ) .unwrap(); - let tokio_runtime = tokio::runtime::Builder::new_current_thread() + let tokio_runtime = TokioRuntimeBuilder::new_current_thread() .enable_time() .build() .unwrap(); @@ -647,7 +662,7 @@ fn poisoned_runtime_gates_stop_workers_and_reject_warm_close() { let directory = TestDirectory::new(); let data = production_data_superblock(512 * 1024); let runtime_config = RuntimeOptions { - io_engine: crate::config::IoEngine::Posix(crate::config::PosixIoConfig::new(1, 1, 1)), + io_engine: ConfiguredIoEngine::Posix(PosixIoConfig::new(1, 1, 1)), l1_capacity_bytes: 0, managed_memory_limit_bytes: 32 * 1024 * 1024, write_flush_threshold_bytes: 128 * 1024, @@ -756,7 +771,7 @@ fn foreground_stage_fixture() -> (DataSuperblock, FileRegionRuntime, RegionStagi let resources = data_path_resources(); let staging = RegionStaging::try_new( 1, - crate::config::MAX_WRITE_FLUSH_THRESHOLD_BYTES, + MAX_WRITE_FLUSH_THRESHOLD_BYTES, data.geometry.region_size, &resources, ) @@ -788,9 +803,9 @@ fn foreground_stage_rejects_busy_shard_without_reserving_then_stages_once() { let mutation = runtime.core.shards[0].mutation.lock().unwrap(); let hash = hash_key(data.hash_seed, b"key"); let record_bytes = required_record_bytes(b"key".len(), b"value".len()).unwrap(); - let (sender, receiver) = std::sync::mpsc::sync_channel(1); + let (sender, receiver) = mpsc::sync_channel(1); let core = Arc::clone(&runtime.core); - let writer = std::thread::spawn(move || { + let writer = thread::spawn(move || { let result = core.try_stage_value(&staging, 0, hash, record_bytes, b"key", b"value"); sender.send((result, staging)).unwrap(); }); @@ -833,19 +848,19 @@ fn completed_record_publication_does_not_enter_region_manager() { let record = StagedRecord::new( 7, IndexEntry { - location: crate::region::index::PackedLocation::new(0, 0, 64).unwrap(), + location: PackedLocation::new(0, 0, 64).unwrap(), }, 1, ); - let (sender, receiver) = std::sync::mpsc::sync_channel(1); + let (sender, receiver) = mpsc::sync_channel(1); let publisher_core = Arc::clone(&core); - let publisher = std::thread::spawn(move || { + let publisher = thread::spawn(move || { sender .send(publisher_core.publish_completed_records(&[record])) .unwrap(); }); - let published = receiver.recv_timeout(std::time::Duration::from_secs(1)); + let published = receiver.recv_timeout(Duration::from_secs(1)); drop(manager); publisher.join().unwrap(); published.unwrap().unwrap(); @@ -864,7 +879,7 @@ fn completed_owned_span_publishes_index_without_a_steady_state_sync() { let resources = data_path_resources(); let staging = RegionStaging::try_new( 1, - crate::config::MAX_WRITE_FLUSH_THRESHOLD_BYTES, + MAX_WRITE_FLUSH_THRESHOLD_BYTES, data.geometry.region_size, &resources, ) @@ -1026,7 +1041,7 @@ fn same_hash_candidate_requires_full_key() { let resources = data_path_resources(); let staging = RegionStaging::try_new( 1, - crate::config::MAX_WRITE_FLUSH_THRESHOLD_BYTES, + MAX_WRITE_FLUSH_THRESHOLD_BYTES, data.geometry.region_size, &resources, ) @@ -1114,7 +1129,7 @@ fn same_hash_candidate_requires_full_key() { let wrong_length_location = PackedLocation::new( current.entry.location.region_id(), current.entry.location.offset(), - current.entry.location.record_len() + crate::region::record::RECORD_ALIGNMENT, + current.entry.location.record_len() + RECORD_ALIGNMENT, ) .unwrap(); let wrong_length = ReadCandidate { @@ -1168,7 +1183,7 @@ fn failed_span_write_never_publishes_and_latches_miss_only() { let resources = data_path_resources(); let staging = RegionStaging::try_new( 1, - crate::config::MAX_WRITE_FLUSH_THRESHOLD_BYTES, + MAX_WRITE_FLUSH_THRESHOLD_BYTES, data.geometry.region_size, &resources, ) @@ -1430,11 +1445,11 @@ fn complete_warm_image_maps_without_rebuilding_index_slots() { let directory = TestDirectory::new(); let config = INDEX_IMAGE_SLOTS_PER_PAGE + 8; let data = test_data_superblock_with_regions(REGION_SHARDS + 1); - let value = IndexSlot::from_state(crate::region::index::storage::IndexSlotState::Value { + let value = IndexSlot::from_state(IndexSlotState::Value { fingerprint: 7, displacement: 0, entry: IndexEntry { - location: crate::region::index::PackedLocation::new(0, 0, 32).unwrap(), + location: PackedLocation::new(0, 0, 32).unwrap(), }, }); @@ -1747,8 +1762,8 @@ fn data_and_state_inode_alias_is_rejected_without_truncation() { let config = 8; let data = test_data_superblock(); let marker = b"do-not-truncate"; - std::fs::write(&directory.files.data, marker).unwrap(); - std::fs::hard_link(&directory.files.data, &directory.files.state).unwrap(); + fs::write(&directory.files.data, marker).unwrap(); + fs::hard_link(&directory.files.data, &directory.files.state).unwrap(); let opened = RegionStore::open( config, @@ -1758,7 +1773,7 @@ fn data_and_state_inode_alias_is_rejected_without_truncation() { opened, Err(error) if error.kind() == io::ErrorKind::InvalidInput )); - assert_eq!(std::fs::read(&directory.files.data).unwrap(), marker); + assert_eq!(fs::read(&directory.files.data).unwrap(), marker); } #[test] @@ -1767,7 +1782,7 @@ fn recovery_temporary_path_cannot_name_the_data_or_state_file() { let marker = b"keep-data"; let image = directory.root.join("recovery"); let data_path = directory.root.join("recovery.next"); - std::fs::write(&data_path, marker).unwrap(); + fs::write(&data_path, marker).unwrap(); let files = RegionFiles::new(&data_path, directory.root.join("state"), image); let opened = RegionStore::open( @@ -1782,14 +1797,14 @@ fn recovery_temporary_path_cannot_name_the_data_or_state_file() { opened, Err(error) if error.kind() == io::ErrorKind::InvalidInput )); - assert_eq!(std::fs::read(data_path).unwrap(), marker); + assert_eq!(fs::read(data_path).unwrap(), marker); } #[test] fn recovery_sidecars_must_share_one_directory() { let directory = TestDirectory::new(); let other = directory.root.join("other"); - std::fs::create_dir(&other).unwrap(); + fs::create_dir(&other).unwrap(); let files = RegionFiles::new( directory.root.join("data"), other.join("state"), diff --git a/cache2/src/region/index/mod.rs b/cache2/src/region/index/mod.rs index 0ccfa37..84f0846 100644 --- a/cache2/src/region/index/mod.rs +++ b/cache2/src/region/index/mod.rs @@ -21,9 +21,11 @@ //! There are no probe chains, //! tombstones, generation tables, retries, or request-time allocations. +use std::array; #[cfg(feature = "benchmarking")] use std::cell::Cell; use std::io; +use std::mem::size_of; use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; @@ -60,7 +62,7 @@ const REFERENCE_WORD_BITS: usize = u64::BITS as usize; pub fn heat_memory_bytes(slot_count: usize) -> Option { let bitmap_bytes = slot_count .div_ceil(REFERENCE_WORD_BITS) - .checked_mul(std::mem::size_of::())?; + .checked_mul(size_of::())?; bitmap_bytes.checked_mul(2) } @@ -670,7 +672,7 @@ fn fingerprint(hash: u64) -> u16 { fn candidate_slots(hash: u64, slot_count: usize) -> [usize; INDEX_CANDIDATES] { let home = route_hash(hash.rotate_left(32), slot_count); - std::array::from_fn(|displacement| slot_from_home(home, displacement, slot_count)) + array::from_fn(|displacement| slot_from_home(home, displacement, slot_count)) } fn slot_from_home(home: usize, displacement: usize, slot_count: usize) -> usize { diff --git a/cache2/src/region/index/packed.rs b/cache2/src/region/index/packed.rs index 1e0fab6..1abf4f1 100644 --- a/cache2/src/region/index/packed.rs +++ b/cache2/src/region/index/packed.rs @@ -14,6 +14,7 @@ //! Shared packed-location and index-entry primitives for the index. +use std::error::Error as StdError; use std::fmt; const REGION_BITS: u32 = 20; @@ -208,7 +209,7 @@ impl fmt::Display for PackedLocationError { } } -impl std::error::Error for PackedLocationError {} +impl StdError for PackedLocationError {} #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct IndexEntry { diff --git a/cache2/src/region/index/storage/mod.rs b/cache2/src/region/index/storage/mod.rs index e9102a1..8ce4e7c 100644 --- a/cache2/src/region/index/storage/mod.rs +++ b/cache2/src/region/index/storage/mod.rs @@ -21,13 +21,25 @@ //! runtime mutations become private copy-on-write pages. use std::cell::UnsafeCell; +#[cfg(test)] +use std::env; +use std::error::Error as StdError; use std::fmt; +#[cfg(test)] +use std::fs; use std::fs::File; use std::io::Write; use std::io::{self}; #[cfg(any(target_os = "linux", target_os = "macos"))] use std::os::fd::AsRawFd; +#[cfg(test)] +use std::panic; +#[cfg(test)] +use std::panic::AssertUnwindSafe; +#[cfg(test)] +use std::process; use std::ptr; +use std::slice; use std::sync::Arc; use std::sync::RwLock; use std::sync::RwLockReadGuard; @@ -44,13 +56,14 @@ use self::page_format::put_u32; use self::page_format::put_u64; use self::page_format::read_u64; use self::page_format::validate_page_header; -use super::INDEX_CANDIDATES; -use super::IndexEntry; -use super::MAX_INDEX_PARTITIONS; -use super::PackedLocation; -use super::PackedLocationError; -use super::index_partition_for; -use super::record_size_class_upper_bound; +use crate::region::index::INDEX_CANDIDATES; +use crate::region::index::IndexEntry; +use crate::region::index::MAX_INDEX_PARTITIONS; +use crate::region::index::PackedLocation; +use crate::region::index::PackedLocationError; +use crate::region::index::index_partition_for; +use crate::region::index::record_size_class_upper_bound; +use crate::region::record::RECORD_ALIGNMENT; mod page_format; pub use self::page_format::INDEX_IMAGE_PAGE_HEADER_SIZE; @@ -278,8 +291,7 @@ impl IndexSlot { entry, } => { let location = entry.location; - let offset_units = - u64::from(location.offset() / crate::region::record::RECORD_ALIGNMENT); + let offset_units = u64::from(location.offset() / RECORD_ALIGNMENT); Self { encoded: u64::from(location.region_id()) | (offset_units << SLOT_OFFSET_SHIFT) @@ -312,7 +324,7 @@ impl IndexSlot { .ok_or(IndexSlotSemanticError::NonCanonicalMarker)?; let region_id = ((self.encoded >> SLOT_REGION_SHIFT) & SLOT_REGION_MASK) as u32; let offset_units = ((self.encoded >> SLOT_OFFSET_SHIFT) & SLOT_OFFSET_MASK) as u32; - let offset = offset_units * crate::region::record::RECORD_ALIGNMENT; + let offset = offset_units * RECORD_ALIGNMENT; let location = PackedLocation::new(region_id, offset, record_len) .map_err(IndexSlotSemanticError::InvalidLocation)?; Ok(IndexSlotState::Value { @@ -560,8 +572,8 @@ impl fmt::Display for IndexStorageError { } } -impl std::error::Error for IndexStorageError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { +impl StdError for IndexStorageError { + fn source(&self) -> Option<&(dyn StdError + 'static)> { match self { Self::Io(error) => Some(error), Self::InvalidArgument(_) @@ -1058,9 +1070,8 @@ impl IndexStorageCore { .ok_or(IndexStorageError::SizeOverflow)?; // SAFETY: `offset` and the fixed page length are inside `image_len` by // construction, and the mapping remains alive for this borrow. - let page = unsafe { - std::slice::from_raw_parts(self.data_ptr().add(offset), INDEX_IMAGE_PAGE_SIZE) - }; + let page = + unsafe { slice::from_raw_parts(self.data_ptr().add(offset), INDEX_IMAGE_PAGE_SIZE) }; let page: &[u8; INDEX_IMAGE_PAGE_SIZE] = page .try_into() .expect("fixed mapped page has the Index Image page size"); @@ -1550,7 +1561,7 @@ impl PartitionedIndexStorage { #[cfg(test)] pub fn poison_hash_partition_for_test(&self, hash: u64) { let partition = index_partition_for(hash, self.partitions.len()); - let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + let result = panic::catch_unwind(AssertUnwindSafe(|| { let _guard = self.partitions[partition].write().unwrap(); panic!("poison index partition for test"); })); @@ -1860,10 +1871,8 @@ mod tests { impl TestFile { fn create() -> Self { let id = NEXT_TEST_FILE.fetch_add(1, Ordering::Relaxed); - let path = std::env::temp_dir().join(format!( - "cache2-index-image-{}-{id}.tmp", - std::process::id() - )); + let path = + env::temp_dir().join(format!("cache2-index-image-{}-{id}.tmp", process::id())); let file = OpenOptions::new() .create_new(true) .read(true) @@ -1876,14 +1885,14 @@ mod tests { impl Drop for TestFile { fn drop(&mut self) { - let _ = std::fs::remove_file(&self.path); + let _ = fs::remove_file(&self.path); } } fn sample_slot(seed: u64) -> IndexSlot { let location = PackedLocation::new( (seed % 64) as u32, - ((seed % 128) * u64::from(crate::region::record::RECORD_ALIGNMENT)) as u32, + ((seed % 128) * u64::from(RECORD_ALIGNMENT)) as u32, 32, ) .unwrap(); diff --git a/cache2/src/region/index/storage/page_format.rs b/cache2/src/region/index/storage/page_format.rs index 89334f5..54cc1ae 100644 --- a/cache2/src/region/index/storage/page_format.rs +++ b/cache2/src/region/index/storage/page_format.rs @@ -12,10 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::CorruptPageReason; -use super::IndexImageBinding; -use super::IndexStorageError; +use std::mem::size_of; + use crate::checksum::Crc32c; +use crate::region::index::storage::CorruptPageReason; +use crate::region::index::storage::IndexImageBinding; +use crate::region::index::storage::IndexStorageError; pub const INDEX_IMAGE_PAGE_SIZE: usize = 4096; pub const INDEX_IMAGE_PAGE_HEADER_SIZE: usize = 64; @@ -197,8 +199,8 @@ pub fn validate_page_header( pub fn page_checksum(page: &[u8; INDEX_IMAGE_PAGE_SIZE]) -> u32 { let mut checksum = Crc32c::new(); checksum.update(&page[..PAGE_CHECKSUM_OFFSET]); - checksum.update(&[0_u8; std::mem::size_of::()]); - checksum.update(&page[PAGE_CHECKSUM_OFFSET + std::mem::size_of::()..]); + checksum.update(&[0_u8; size_of::()]); + checksum.update(&page[PAGE_CHECKSUM_OFFSET + size_of::()..]); checksum.finish() } diff --git a/cache2/src/region/manager.rs b/cache2/src/region/manager.rs index bd7d634..da5f187 100644 --- a/cache2/src/region/manager.rs +++ b/cache2/src/region/manager.rs @@ -21,15 +21,15 @@ use std::collections::VecDeque; -use super::record::RECORD_ALIGNMENT; -use super::recovery::PartitionMetadataRecord; -use super::recovery::PersistentId; -use super::recovery::RegionMetadata; -use super::recovery::RegionMetadataError; -use super::recovery::RegionMetadataRecord; -use super::recovery::RegionMetadataRoot; -use super::recovery::RegionMetadataState; use crate::io::backend::DIRECT_IO_ALIGNMENT; +use crate::region::record::RECORD_ALIGNMENT; +use crate::region::recovery::PartitionMetadataRecord; +use crate::region::recovery::PersistentId; +use crate::region::recovery::RegionMetadata; +use crate::region::recovery::RegionMetadataError; +use crate::region::recovery::RegionMetadataRecord; +use crate::region::recovery::RegionMetadataRoot; +use crate::region::recovery::RegionMetadataState; use crate::snapshot::RegionSnapshot; const UNASSIGNED_REGION: u32 = u32::MAX; diff --git a/cache2/src/region/mod.rs b/cache2/src/region/mod.rs index 0329887..142a782 100644 --- a/cache2/src/region/mod.rs +++ b/cache2/src/region/mod.rs @@ -14,7 +14,9 @@ //! Steady-state Region authority and bounded request-path operations. +use std::fmt; use std::io; +use std::mem::size_of; use std::ops::Range; use std::sync::Arc; use std::sync::Mutex; @@ -67,8 +69,13 @@ use self::staging::StagingEncodeError; use self::staging::StagingError; use crate::checksum::crc32c; use crate::hashing::route_hash; +use crate::io::backend::DIRECT_IO_ALIGNMENT; +use crate::io::engine::IoBuffer; use crate::io::engine::IoEngine; use crate::io::engine::ReadSlot; +use crate::region::appender::RegionSpanCompletion; +use crate::region::manager::RegionWriteSpan; +use crate::region::recovery::DataGeometry; use crate::resources::BufferLease; use crate::snapshot::CacheIndexSnapshot; use crate::snapshot::RegionSnapshot; @@ -129,7 +136,7 @@ impl RegionHealthLatch { } } - fn enter_miss_only_with_error(&self, reason: &'static str, error: &impl std::fmt::Display) { + fn enter_miss_only_with_error(&self, reason: &'static str, error: &impl fmt::Display) { if self.transition_to_miss_only() { log::warn!( target: "cache2::health", @@ -381,8 +388,7 @@ impl FileRegionCore { ..RegionReclaimStats::default() }; let alignment = u64::from(RECORD_ALIGNMENT); - let raw_budget = (receipt.used_offset / 8) - .saturating_sub(crate::io::backend::DIRECT_IO_ALIGNMENT as u64); + let raw_budget = (receipt.used_offset / 8).saturating_sub(DIRECT_IO_ALIGNMENT as u64); let mut reinsert_budget = raw_budget - raw_budget % alignment; while offset < bytes.len() { let header_end = offset.checked_add(RECORD_HEADER_SIZE).ok_or_else(|| { @@ -536,7 +542,7 @@ impl FileRegionCore { self.health.enter_miss_only(); } - pub fn enter_miss_only_with_error(&self, reason: &'static str, error: &impl std::fmt::Display) { + pub fn enter_miss_only_with_error(&self, reason: &'static str, error: &impl fmt::Display) { self.health.enter_miss_only_with_error(reason, error); } @@ -621,7 +627,7 @@ impl FileRegionCore { fn read_value( &self, engine: &dyn IoEngine, - geometry: crate::region::recovery::DataGeometry, + geometry: DataGeometry, buffer: BufferLease, hash_seed: u64, key: &[u8], @@ -956,22 +962,21 @@ impl FileRegionCore { staging: &RegionStaging, engine: &dyn IoEngine, shard_id: usize, - ) -> io::Result> { + ) -> io::Result> { let shard_mutation = self.lock_shard_mutation(shard_id)?; let geometry_for = |manager: &RegionManager| { let region_count = u32::try_from(manager.regions().len()).map_err(|_| { self.health.enter_miss_only(); io::Error::new(io::ErrorKind::InvalidData, "Region count is too large") })?; - let data_file_len = crate::region::recovery::DataGeometry::expected_file_len( - manager.region_size(), - region_count, - ) - .ok_or_else(|| { - self.health.enter_miss_only(); - io::Error::new(io::ErrorKind::InvalidData, "data geometry overflow") - })?; - Ok::<_, io::Error>(crate::region::recovery::DataGeometry { + let data_file_len = + DataGeometry::expected_file_len(manager.region_size(), region_count).ok_or_else( + || { + self.health.enter_miss_only(); + io::Error::new(io::ErrorKind::InvalidData, "data geometry overflow") + }, + )?; + Ok::<_, io::Error>(DataGeometry { data_file_len, region_size: manager.region_size(), region_count, @@ -1061,7 +1066,7 @@ impl FileRegionCore { } }; let completion = flight.wait(engine); - let crate::region::appender::RegionSpanCompletion { + let RegionSpanCompletion { span, result, buffer, @@ -1175,8 +1180,8 @@ impl FileRegionCore { fn fail_staged_span( &self, staging: &RegionStaging, - span: crate::region::manager::RegionWriteSpan, - buffer: Option, + span: RegionWriteSpan, + buffer: Option, records: Vec, ) { self.health.enter_miss_only(); @@ -1207,7 +1212,7 @@ pub fn runtime_fixed_memory_bytes(index_slots: usize, region_count: u32) -> io:: ) })?; let index_page_state_bytes = (index_bytes / INDEX_IMAGE_PAGE_SIZE) - .checked_mul(std::mem::size_of::()) + .checked_mul(size_of::()) .ok_or_else(|| { io::Error::new( io::ErrorKind::InvalidInput, diff --git a/cache2/src/region/reader.rs b/cache2/src/region/reader.rs index 9d7ea22..f0c0cf7 100644 --- a/cache2/src/region/reader.rs +++ b/cache2/src/region/reader.rs @@ -22,11 +22,10 @@ use std::io; use std::ops::Range; +use std::sync::Arc; + +use tokio::runtime::Handle as TokioHandle; -use super::index::IndexEntry; -use super::record::RECORD_ALIGNMENT; -use super::recovery::DATA_REGION_AREA_OFFSET; -use super::recovery::DataGeometry; use crate::io::engine::BoundedIoRequest; use crate::io::engine::IoBuffer; use crate::io::engine::IoCompletion; @@ -37,6 +36,10 @@ use crate::io::engine::OperationKind; use crate::io::engine::ReadSlot; use crate::io::engine::RequestId; use crate::io::engine::submit_cache_read; +use crate::region::index::IndexEntry; +use crate::region::record::RECORD_ALIGNMENT; +use crate::region::recovery::DATA_REGION_AREA_OFFSET; +use crate::region::recovery::DataGeometry; use crate::resources::BufferLease; const _READ_ALIGNMENT: usize = 4096; @@ -99,8 +102,8 @@ impl PendingRead { pub async fn wait_async( self, - engine: std::sync::Arc, - tokio_handle: &tokio::runtime::Handle, + engine: Arc, + tokio_handle: &TokioHandle, ) -> ReadCompletion { let Self { plan, @@ -380,7 +383,7 @@ mod tests { } } - fn entry(location: crate::region::index::PackedLocation) -> IndexEntry { + fn entry(location: PackedLocation) -> IndexEntry { IndexEntry { location } } diff --git a/cache2/src/region/record/codec.rs b/cache2/src/region/record/codec.rs index 1194329..a9f6538 100644 --- a/cache2/src/region/record/codec.rs +++ b/cache2/src/region/record/codec.rs @@ -19,20 +19,23 @@ //! no allocation. Payload preparation computes the CRC before the append //! transaction copies the borrowed key and value into staging. +use std::error::Error as StdError; use std::fmt; use hashcrew::xxhash::xxh3_64_with_seed; -use super::MAX_KEY_SIZE; -use super::RECORD_ALIGNMENT; -use super::RECORD_HEADER_SIZE; -use super::RecordHeader; use crate::checksum::Crc32c; +#[cfg(test)] +use crate::io::backend::DIRECT_IO_ALIGNMENT; use crate::region::index::IndexEntry; use crate::region::index::MAX_RECORD_LEN; use crate::region::index::PackedLocation; use crate::region::index::PackedLocationError; use crate::region::manager::RegionAppendReservation; +use crate::region::record::MAX_KEY_SIZE; +use crate::region::record::RECORD_ALIGNMENT; +use crate::region::record::RECORD_HEADER_SIZE; +use crate::region::record::RecordHeader; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum RecordEncodeError { @@ -74,8 +77,8 @@ impl fmt::Display for RecordEncodeError { } } -impl std::error::Error for RecordEncodeError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { +impl StdError for RecordEncodeError { + fn source(&self) -> Option<&(dyn StdError + 'static)> { match self { Self::InvalidLocation(error) => Some(error), _ => None, @@ -301,7 +304,7 @@ mod tests { required_record_bytes(key_len, value_len).unwrap() as usize, expected ); - assert!(!expected.is_multiple_of(crate::io::backend::DIRECT_IO_ALIGNMENT)); + assert!(!expected.is_multiple_of(DIRECT_IO_ALIGNMENT)); } #[test] diff --git a/cache2/src/region/recovery/metadata.rs b/cache2/src/region/recovery/metadata.rs index 30e7820..2f4de0f 100644 --- a/cache2/src/region/recovery/metadata.rs +++ b/cache2/src/region/recovery/metadata.rs @@ -18,12 +18,11 @@ //! manager or index mapping becomes visible. Index slots remain independently //! lazy-validated; this section contains only O(regions + index partitions) state. +use std::error::Error as StdError; use std::fmt; +use std::mem; +use std::result; -use super::DataSuperblock; -use super::PersistentId; -use super::RECOVERY_PAGE_SIZE; -use super::RecoveryImageHeader; use crate::checksum::Crc32c; use crate::region::index::MAX_INDEX_PARTITIONS; use crate::region::index::MAX_PACKED_REGION_COUNT; @@ -33,6 +32,10 @@ use crate::region::index::storage::INDEX_IMAGE_SLOTS_PER_PAGE; use crate::region::index::storage::IndexStorageError; use crate::region::index::storage::canonical_index_partition_ranges; use crate::region::index::storage::validated_index_partition_ranges; +use crate::region::recovery::DataSuperblock; +use crate::region::recovery::PersistentId; +use crate::region::recovery::RECOVERY_PAGE_SIZE; +use crate::region::recovery::RecoveryImageHeader; pub const REGION_METADATA_PAGE_SIZE: usize = RECOVERY_PAGE_SIZE; const REGION_METADATA_PAGE_HEADER_SIZE: usize = 64; @@ -230,9 +233,9 @@ impl fmt::Display for RegionMetadataError { } } -impl std::error::Error for RegionMetadataError {} +impl StdError for RegionMetadataError {} -type Result = std::result::Result; +type Result = result::Result; impl RegionMetadata { pub fn encoded_len(&self) -> Result { @@ -643,7 +646,7 @@ fn validate_regions(root: RegionMetadataRoot, regions: &[RegionMetadataRecord]) RegionMetadataState::Sealed => (&mut sealed_seen, root.sealed_region_count), }; if region.queue_ordinal >= state_count - || std::mem::replace(&mut seen[region.queue_ordinal as usize], 1) != 0 + || mem::replace(&mut seen[region.queue_ordinal as usize], 1) != 0 { return Err(RegionMetadataError::InvalidField("region_queue_ordinal")); } diff --git a/cache2/src/region/recovery/mod.rs b/cache2/src/region/recovery/mod.rs index 720ecf8..c10040d 100644 --- a/cache2/src/region/recovery/mod.rs +++ b/cache2/src/region/recovery/mod.rs @@ -20,14 +20,14 @@ //! `CLEAN`. This module performs no I/O; callers must write the returned page //! to the selected slot and provide the required `fdatasync` barrier. -use super::index::MAX_PACKED_REGION_COUNT; -use super::index::MAX_PACKED_REGION_SIZE; -use super::index::storage::INDEX_IMAGE_PAGE_SIZE; -use super::index::storage::INDEX_IMAGE_SLOTS_PER_PAGE; -use super::record::RECORD_ALIGNMENT; -use super::record::RECORD_FORMAT_VERSION; use crate::checksum::Crc32c; use crate::checksum::crc32c; +use crate::region::index::MAX_PACKED_REGION_COUNT; +use crate::region::index::MAX_PACKED_REGION_SIZE; +use crate::region::index::storage::INDEX_IMAGE_PAGE_SIZE; +use crate::region::index::storage::INDEX_IMAGE_SLOTS_PER_PAGE; +use crate::region::record::RECORD_ALIGNMENT; +use crate::region::record::RECORD_FORMAT_VERSION; mod metadata; pub use self::metadata::PartitionMetadataRecord; diff --git a/cache2/src/region/runtime/metrics.rs b/cache2/src/region/runtime/metrics.rs index 68988bd..1d4adfc 100644 --- a/cache2/src/region/runtime/metrics.rs +++ b/cache2/src/region/runtime/metrics.rs @@ -13,14 +13,17 @@ // limitations under the License. use std::io; +use std::sync::atomic::AtomicU8; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; use std::time::Duration; -use super::LIFECYCLE_DRAINING; -use super::LIFECYCLE_FAILED; use crate::hashing::route_hash; use crate::memory::MemoryMetricsSnapshot; +use crate::region::RegionReclaimStats; +use crate::region::runtime::LIFECYCLE_DRAINING; +use crate::region::runtime::LIFECYCLE_FAILED; +use crate::region::runtime::LIFECYCLE_RUNNING; use crate::resources::ManagedMemorySnapshot; use crate::snapshot::CacheHealth; use crate::snapshot::CacheIoSnapshot; @@ -31,7 +34,7 @@ static NEXT_METRICS_EPOCH: AtomicU64 = AtomicU64::new(1); pub struct RuntimeMetrics { metrics_epoch: u64, - pub lifecycle: std::sync::atomic::AtomicU8, + pub lifecycle: AtomicU8, activity: Box<[ActivityMetrics]>, l2_read_overloads: AtomicU64, l2_read_wait_ns: AtomicU64, @@ -94,7 +97,7 @@ impl RuntimeMetrics { activity.resize_with(shard_count, ActivityMetrics::new); Ok(Self { metrics_epoch: NEXT_METRICS_EPOCH.fetch_add(1, Ordering::Relaxed), - lifecycle: std::sync::atomic::AtomicU8::new(super::LIFECYCLE_RUNNING), + lifecycle: AtomicU8::new(LIFECYCLE_RUNNING), activity: activity.into_boxed_slice(), l2_read_overloads: AtomicU64::new(0), l2_read_wait_ns: AtomicU64::new(0), @@ -143,7 +146,7 @@ impl RuntimeMetrics { self.l2_read_wait_ns.fetch_add(nanos, Ordering::Relaxed); } - pub fn record_reclaim(&self, stats: crate::region::RegionReclaimStats) { + pub fn record_reclaim(&self, stats: RegionReclaimStats) { Self::increment(&self.reclaimed_regions); self.reclaimed_bytes .fetch_add(stats.bytes_read, Ordering::Relaxed); diff --git a/cache2/src/region/runtime/mod.rs b/cache2/src/region/runtime/mod.rs index a1252d4..e83d053 100644 --- a/cache2/src/region/runtime/mod.rs +++ b/cache2/src/region/runtime/mod.rs @@ -20,13 +20,24 @@ //! device path. A fixed age deadline publishes partial batches without adding //! a durability sync; CLEAN remains the only steady-state durability boundary. +#[cfg(test)] +use std::env; +#[cfg(test)] +use std::fs; use std::io; +use std::mem; +use std::panic; +use std::panic::AssertUnwindSafe; +#[cfg(test)] +use std::process; use std::sync::Arc; use std::sync::Condvar; use std::sync::Mutex; +use std::sync::MutexGuard; use std::sync::atomic::AtomicU8; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; +use std::thread; use std::thread::JoinHandle; use std::time::Duration; use std::time::Instant; @@ -34,30 +45,13 @@ use std::time::Instant; use asyncband::semaphore::OwnedSemaphorePermit; use asyncband::semaphore::Semaphore; use asyncband::watch; +use tokio::runtime::Handle as TokioHandle; +#[cfg(test)] +use tokio::task; use self::metrics::RuntimeMetrics; -use super::FileRegionCore; -use super::RegionStageValue; -use super::RegionValueRead; -#[cfg(test)] -use super::index::storage::INDEX_IMAGE_PAGE_SIZE; -#[cfg(test)] -use super::index::storage::INDEX_IMAGE_SLOTS_PER_PAGE; -use super::reader::PendingRead; -use super::reader::ReadCompletion; -use super::reader::ReadPlan; -use super::reader::plan_read; -use super::record::MAX_KEY_SIZE; -use super::record::hash_key; -use super::record::required_record_bytes; -#[cfg(test)] -use super::recovery::DataGeometry; -use super::recovery::DataSuperblock; -#[cfg(test)] -use super::runtime_fixed_memory_bytes; -use super::staging::RegionStaging; -use super::staging::StagingError; use crate::config::CacheConfig; +use crate::config::IoEngine as ConfiguredIoEngine; use crate::config::IoMode; use crate::config::IoPoolTopology; #[cfg(test)] @@ -83,6 +77,35 @@ use crate::memory::MemoryMetricsSnapshot; use crate::memory::MemoryReadToken; use crate::memory::MemoryStore; use crate::memory::MemoryValue; +use crate::region::FileRegionCore; +use crate::region::RegionStageValue; +use crate::region::RegionValueRead; +#[cfg(test)] +use crate::region::index::IndexEntry; +#[cfg(test)] +use crate::region::index::PackedLocation; +#[cfg(test)] +use crate::region::index::storage::INDEX_IMAGE_PAGE_SIZE; +#[cfg(test)] +use crate::region::index::storage::INDEX_IMAGE_SLOTS_PER_PAGE; +use crate::region::reader::PendingRead; +#[cfg(test)] +use crate::region::reader::ReadCandidate; +use crate::region::reader::ReadCompletion; +use crate::region::reader::ReadPlan; +use crate::region::reader::plan_read; +use crate::region::record::MAX_KEY_SIZE; +#[cfg(test)] +use crate::region::record::RECORD_HEADER_SIZE; +use crate::region::record::hash_key; +use crate::region::record::required_record_bytes; +#[cfg(test)] +use crate::region::recovery::DataGeometry; +use crate::region::recovery::DataSuperblock; +#[cfg(test)] +use crate::region::runtime_fixed_memory_bytes; +use crate::region::staging::RegionStaging; +use crate::region::staging::StagingError; use crate::resources::BufferLease; use crate::resources::CACHE_THREAD_STACK_BYTES; #[cfg(test)] @@ -346,7 +369,7 @@ impl PendingGet { } } - async fn wait_async(self, tokio_handle: &tokio::runtime::Handle) -> CompletedGet { + async fn wait_async(self, tokio_handle: &TokioHandle) -> CompletedGet { let Self { engine, read, @@ -362,7 +385,7 @@ impl PendingGet { } impl WaitingGet { - async fn reserve_async(self, tokio_handle: &tokio::runtime::Handle) -> io::Result { + async fn reserve_async(self, tokio_handle: &TokioHandle) -> io::Result { let Self { engine, slot_waiter, @@ -718,7 +741,7 @@ impl ShardControl { self.async_changed.send_replace(()); } - fn lock(&self) -> io::Result> { + fn lock(&self) -> io::Result> { self.state.lock().map_err(|_| poisoned_runtime_error()) } } @@ -904,7 +927,7 @@ impl RegionDataPlane { pub async fn get_async( &self, key: &[u8], - tokio_handle: &tokio::runtime::Handle, + tokio_handle: &TokioHandle, ) -> io::Result> { match self.prepare_get(key)? { PreparedGet::Complete(value) => Ok(value), @@ -1287,7 +1310,7 @@ impl RegionDataPlane { #[cfg(test)] pub fn poison_shard_for_test(&self, shard_id: usize) { let shard = self.shared.shards.get(shard_id).expect("test shard exists"); - let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + let result = panic::catch_unwind(AssertUnwindSafe(|| { let _state = shard.state.lock().unwrap(); panic!("poison shard gate"); })); @@ -1465,7 +1488,7 @@ fn start_running( })?; for shard_id in 0..shard_count { let worker_shared = Arc::clone(&shared); - match std::thread::Builder::new() + match thread::Builder::new() .name(format!("cache2-shard-{shard_id}")) .stack_size(CACHE_THREAD_STACK_BYTES) .spawn(move || shard_worker(worker_shared, shard_id)) @@ -1488,7 +1511,7 @@ fn start_running( } for (worker_id, buffer) in reclaim_buffers.into_iter().enumerate() { let reclaim_shared = Arc::clone(&shared); - match std::thread::Builder::new() + match thread::Builder::new() .name(format!("cache2-reclaim-{worker_id}")) .stack_size(CACHE_THREAD_STACK_BYTES) .spawn(move || reclaim_worker(reclaim_shared, buffer, worker_id, reclaim_worker_count)) @@ -1535,7 +1558,7 @@ fn build_engine_pool( engines .try_reserve_exact(engine_count) .map_err(|_| io::Error::new(io::ErrorKind::OutOfMemory, "cannot allocate I/O workers"))?; - let posix_workers = if matches!(config.io_engine, crate::config::IoEngine::Posix(_)) { + let posix_workers = if matches!(config.io_engine, ConfiguredIoEngine::Posix(_)) { topology.max_in_flight } else { 1 @@ -1561,7 +1584,7 @@ fn build_engine_pool( fn shard_worker(shared: Arc, shard_id: usize) { let control = Arc::clone(&shared.shards[shard_id]); - let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + let result = panic::catch_unwind(AssertUnwindSafe(|| { shard_worker_result(&shared, shard_id, &control) })); let error = match result { @@ -1637,7 +1660,7 @@ fn reclaim_worker( worker_count: usize, ) { let mut buffer = Some(buffer); - let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + let result = panic::catch_unwind(AssertUnwindSafe(|| { reclaim_worker_result(&shared, &mut buffer, worker_id, worker_count) })); let error = match result { @@ -1913,7 +1936,7 @@ fn wait_for_shard_work( if let Some(failure) = &state.failure { return Err(failure.to_error()); } - let flags = std::mem::take(&mut state.wake_flags); + let flags = mem::take(&mut state.wake_flags); let drain_generation = if state.drain_requested > state.drain_completed { state.drain_requested } else { @@ -2065,7 +2088,7 @@ fn stop_running(mut owner: RunningOwner) -> io::Result { // has no trustworthy future fence and remains process-lifetime state. if unfenced { for engine in owner.shared.engines() { - std::mem::forget(Arc::clone(engine)); + mem::forget(Arc::clone(engine)); } } else { for engine in owner.shared.engines() { @@ -2087,7 +2110,7 @@ fn stop_running(mut owner: RunningOwner) -> io::Result { fn reap_engine_after_target_fence(engine: &Arc) { let reaper_engine = Arc::clone(engine); - let spawn = std::thread::Builder::new() + let spawn = thread::Builder::new() .name("cache2-io-reaper".to_owned()) .stack_size(CACHE_THREAD_STACK_BYTES) .spawn(move || { @@ -2097,7 +2120,7 @@ fn reap_engine_after_target_fence(engine: &Arc) { // The original owner is still alive while this fallback clone is // created, so a failed thread spawn cannot synchronously run the // engine's blocking Drop path. - std::mem::forget(Arc::clone(engine)); + mem::forget(Arc::clone(engine)); } } @@ -2174,10 +2197,7 @@ mod tests { #[test] fn read_lane_uses_one_bounded_alternate_on_primary_pressure() { let id = LANE_TEST_ID.fetch_add(1, Ordering::Relaxed); - let path = std::env::temp_dir().join(format!( - "cache2-read-lane-{}-{id}.cache", - std::process::id() - )); + let path = env::temp_dir().join(format!("cache2-read-lane-{}-{id}.cache", process::id())); let backend: Arc = Arc::new(FileBackend::open(&path).unwrap()); let engines: Box<[Arc]> = vec![ Arc::new(BackendIoEngine::new(Arc::clone(&backend), 1).unwrap()) as Arc, @@ -2210,15 +2230,15 @@ mod tests { } drop(engines); drop(backend); - std::fs::remove_file(path).unwrap(); + fs::remove_file(path).unwrap(); } #[test] fn hot_read_route_rotates_pressure_fallback_across_all_lanes() { let id = LANE_TEST_ID.fetch_add(1, Ordering::Relaxed); - let path = std::env::temp_dir().join(format!( + let path = env::temp_dir().join(format!( "cache2-read-lane-rotation-{}-{id}.cache", - std::process::id() + process::id() )); let backend: Arc = Arc::new(FileBackend::open(&path).unwrap()); let engines: Box<[Arc]> = (0..4) @@ -2244,7 +2264,7 @@ mod tests { } drop(engines); drop(backend); - std::fs::remove_file(path).unwrap(); + fs::remove_file(path).unwrap(); } #[test] @@ -2276,13 +2296,13 @@ mod tests { let mutation = gate.try_enter().unwrap(); let drain = gate.begin_drain().unwrap(); let closing_gate = Arc::clone(&gate); - let close = std::thread::spawn(move || { + let close = thread::spawn(move || { closing_gate.start_close(); closing_gate.wait_quiescent().unwrap(); }); while gate.state.load(Ordering::Acquire) & MUTATION_CLOSED == 0 { - std::thread::yield_now(); + thread::yield_now(); } drop(mutation); drain.wait().unwrap(); @@ -2318,7 +2338,7 @@ mod tests { let drain = drain_gate.begin_drain().unwrap(); drain.wait_async().await; }); - tokio::task::yield_now().await; + task::yield_now().await; assert!(gate.try_enter().is_none()); drop(mutation); @@ -2335,7 +2355,7 @@ mod tests { let drain = drain_gate.begin_drain().unwrap(); drain.wait_async().await; }); - tokio::task::yield_now().await; + task::yield_now().await; assert!(gate.try_enter().is_none()); drain.abort(); @@ -2419,7 +2439,7 @@ mod tests { for _ in 0..2 { let control = Arc::clone(&control); let ready = Arc::clone(&ready); - workers.push(std::thread::spawn(move || { + workers.push(thread::spawn(move || { let mut observed_generation = 0; ready.wait(); let notified = control.wait(&mut observed_generation).unwrap(); @@ -2461,10 +2481,8 @@ mod tests { use crate::region::store::RegionStore; let id = LANE_TEST_ID.fetch_add(1, Ordering::Relaxed); - let path = std::env::temp_dir().join(format!( - "cache2-completion-timeout-{}-{id}", - std::process::id() - )); + let path = + env::temp_dir().join(format!("cache2-completion-timeout-{}-{id}", process::id())); let files = RegionFiles::new( path.with_extension("cache"), path.with_extension("state"), @@ -2541,8 +2559,8 @@ mod tests { assert_eq!(snapshot.l2_read_overloads, 1); } } - std::fs::remove_file(files.data).unwrap(); - std::fs::remove_file(files.state).unwrap(); + fs::remove_file(files.data).unwrap(); + fs::remove_file(files.state).unwrap(); } #[test] @@ -2552,17 +2570,17 @@ mod tests { region_size: 512 * 1024, region_count: 10, }; - let value_len = geometry.region_size as usize - crate::region::record::RECORD_HEADER_SIZE; + let value_len = geometry.region_size as usize - RECORD_HEADER_SIZE; let record_len = required_record_bytes(0, value_len).unwrap(); assert_eq!(u64::from(record_len), geometry.region_size); - let entry = crate::region::index::IndexEntry { - location: crate::region::index::PackedLocation::new(0, 0, record_len).unwrap(), + let entry = IndexEntry { + location: PackedLocation::new(0, 0, record_len).unwrap(), }; assert_eq!( plan_read( geometry, 1, - crate::region::reader::ReadCandidate { + ReadCandidate { entry, region_generation: 1, }, diff --git a/cache2/src/region/runtime/shutdown_tests.rs b/cache2/src/region/runtime/shutdown_tests.rs index 4e3c767..3ec8eac 100644 --- a/cache2/src/region/runtime/shutdown_tests.rs +++ b/cache2/src/region/runtime/shutdown_tests.rs @@ -13,6 +13,7 @@ // limitations under the License. use std::sync::atomic::AtomicBool; +use std::sync::mpsc; use super::*; use crate::io::backend::IoBackend; @@ -207,11 +208,11 @@ fn assert_close_does_not_wait_for_read(submit_before_close: bool) { use crate::region::RegionFiles; use crate::region::recovery::PersistentId; use crate::region::store::RegionStore; - let root = std::env::temp_dir().join(format!( + let root = env::temp_dir().join(format!( "cache2-close-race-{}-{submit_before_close}", - std::process::id() + process::id() )); - std::fs::create_dir_all(&root).unwrap(); + fs::create_dir_all(&root).unwrap(); let files = RegionFiles::new(root.join("data"), root.join("state"), root.join("image")); let data = DataSuperblock { generation: 1, @@ -228,7 +229,7 @@ fn assert_close_does_not_wait_for_read(submit_before_close: bool) { let config = RuntimeOptions { append_shards: 1, l1_capacity_bytes: 0, - io_engine: crate::config::IoEngine::Posix(PosixIoConfig::new(1, 1, 1)), + io_engine: ConfiguredIoEngine::Posix(PosixIoConfig::new(1, 1, 1)), ..RuntimeOptions::default() }; let mut store = RegionStore::open( @@ -257,8 +258,8 @@ fn assert_close_does_not_wait_for_read(submit_before_close: bool) { shared.reclaim_engines = Box::new([]); shared.shards = Box::new([]); let shared = Arc::clone(&plane.shared); - let (tx, rx) = std::sync::mpsc::channel(); - let thread = std::thread::spawn(move || { + let (tx, rx) = mpsc::channel(); + let thread = thread::spawn(move || { let result = stop_running(RunningOwner { shared, shard_workers: vec![], @@ -271,7 +272,7 @@ fn assert_close_does_not_wait_for_read(submit_before_close: bool) { thread.join().unwrap(); engine.shutdown().unwrap(); assert!(!engine.inject.load(Ordering::Acquire)); - std::fs::remove_dir_all(root).unwrap(); + fs::remove_dir_all(root).unwrap(); assert!( matches!(result, Ok(Ok(false))), "close synchronously joined a blocked read" diff --git a/cache2/src/region/staging.rs b/cache2/src/region/staging.rs index 02c99ae..d343551 100644 --- a/cache2/src/region/staging.rs +++ b/cache2/src/region/staging.rs @@ -17,22 +17,26 @@ //! Region manager receipts are the only span authority. use std::fmt; +use std::mem; +use std::mem::size_of; use std::sync::Mutex; use std::sync::MutexGuard; +#[cfg(test)] +use std::thread; -use super::index::IndexEntry; -use super::index::MAX_RECORD_LEN; -use super::index::PackedLocation; -use super::manager::RegionAppendReservation; -use super::manager::RegionPaddingReceipt; -use super::manager::RegionWriteSpan; -use super::record::RECORD_ALIGNMENT; -use super::record::RECORD_HEADER_SIZE; -use super::record::RecordHeader; -use super::recovery::DATA_REGION_AREA_OFFSET; -use super::recovery::RECOVERY_PAGE_SIZE; use crate::io::backend::DIRECT_IO_ALIGNMENT; use crate::io::engine::IoBuffer; +use crate::region::index::IndexEntry; +use crate::region::index::MAX_RECORD_LEN; +use crate::region::index::PackedLocation; +use crate::region::manager::RegionAppendReservation; +use crate::region::manager::RegionPaddingReceipt; +use crate::region::manager::RegionWriteSpan; +use crate::region::record::RECORD_ALIGNMENT; +use crate::region::record::RECORD_HEADER_SIZE; +use crate::region::record::RecordHeader; +use crate::region::recovery::DATA_REGION_AREA_OFFSET; +use crate::region::recovery::RECOVERY_PAGE_SIZE; use crate::resources::BUFFER_ALIGNMENT; use crate::resources::BufferLease; use crate::resources::ResourceBuildError; @@ -259,7 +263,7 @@ impl RegionStaging { pub fn reservation_bytes(shard_count: usize, chunk_bytes: usize) -> Option { let buffers_per_shard = chunk_bytes.checked_mul(2)?; let records_per_shard = MAX_STAGING_RECORDS - .checked_mul(std::mem::size_of::())? + .checked_mul(size_of::())? .checked_mul(2)?; buffers_per_shard .checked_add(records_per_shard)? @@ -713,7 +717,7 @@ impl RegionStaging { state.failed = true; StagingError::Invariant("staging lost its second record vector") })?; - let records = std::mem::replace(&mut state.fill.records, replacement_records); + let records = mem::replace(&mut state.fill.records, replacement_records); state.fill.reset(replacement_buffer); state.submitted = Some(span); drop(state); @@ -975,13 +979,13 @@ mod tests { #[test] fn seal_moves_the_aligned_fill_lease_and_keeps_filling_the_second_buffer() { - assert_eq!(std::mem::size_of::(), 32); + assert_eq!(size_of::(), 32); let resources = resources(4 * 1024 * 1024); let staging = RegionStaging::try_new(1, 4096, 64 * 1024, &resources).unwrap(); assert_eq!(staging.chunk_bytes(), 4096); assert_eq!( resources.managed_memory_snapshot().current_bytes, - 2 * 4096 + 2 * MAX_STAGING_RECORDS * std::mem::size_of::() + 2 * 4096 + 2 * MAX_STAGING_RECORDS * size_of::() ); let (first, first_record) = reservation(0, 64, 11); @@ -1107,7 +1111,7 @@ mod tests { let (entered_tx, entered_rx) = mpsc::sync_channel(0); let (release_tx, release_rx) = mpsc::sync_channel(0); let encoder_staging = Arc::clone(&staging); - let encoder = std::thread::spawn(move || { + let encoder = thread::spawn(move || { encoder_staging.encode_reserved(receipt, |target| { entered_tx.send(()).unwrap(); release_rx.recv().unwrap(); diff --git a/cache2/src/region/store.rs b/cache2/src/region/store.rs index 2ff39e3..37bfe87 100644 --- a/cache2/src/region/store.rs +++ b/cache2/src/region/store.rs @@ -25,7 +25,7 @@ use std::io; -use super::index::storage::validated_index_partition_ranges; +use crate::region::index::storage::validated_index_partition_ranges; use crate::snapshot::StartupMode; /// Result of inspecting the latest valid state record. diff --git a/cache2/src/resources.rs b/cache2/src/resources.rs index 22fb429..da47262 100644 --- a/cache2/src/resources.rs +++ b/cache2/src/resources.rs @@ -23,6 +23,7 @@ use std::alloc::alloc; use std::alloc::dealloc; use std::fmt; use std::ptr::NonNull; +use std::slice; use std::sync::Arc; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; @@ -207,7 +208,7 @@ impl BufferLease { } // SAFETY: the allocation holds `initialized` initialized bytes and the // returned shared slice cannot mutate the exclusively leased buffer. - Ok(unsafe { std::slice::from_raw_parts(buffer.ptr.as_ptr(), length) }) + Ok(unsafe { slice::from_raw_parts(buffer.ptr.as_ptr(), length) }) } pub fn prepared_mut(&mut self, length: usize) -> Result<&mut [u8], ()> { @@ -306,7 +307,7 @@ impl AlignedBuffer { debug_assert!(length <= self.initialized); // SAFETY: the allocation holds `initialized` initialized bytes, this // mutable borrow is exclusive, and `length <= initialized`. - unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), length) } + unsafe { slice::from_raw_parts_mut(self.ptr.as_ptr(), length) } } fn deallocate(&mut self) { diff --git a/tests-integration/tests/cache.rs b/tests-integration/tests/cache.rs index 8547b11..ab267ef 100644 --- a/tests-integration/tests/cache.rs +++ b/tests-integration/tests/cache.rs @@ -12,11 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::env; +use std::fs; +use std::fs::OpenOptions; +use std::io; use std::io::Read; use std::io::Seek; use std::io::SeekFrom; use std::io::Write; +use std::path::Path; use std::path::PathBuf; +use std::process; use std::sync::Arc; use std::sync::Barrier; use std::sync::atomic::AtomicBool; @@ -30,19 +36,26 @@ use std::time::Instant; use cache2::Cache; use cache2::CacheConfig; use cache2::CacheHealth; +use cache2::CacheIoSnapshot; use cache2::CacheTier; +use cache2::DetailedCacheSnapshot; use cache2::ErrorKind; use cache2::ErrorOperation; use cache2::IoEngine; #[cfg(not(target_os = "linux"))] use cache2::IoMode; +#[cfg(not(target_os = "linux"))] +use cache2::IoUringConfig; use cache2::L1EvictionPolicy; use cache2::PosixIoConfig; use cache2::ReadAdmission; +use cache2::Result; use cache2::RuntimeOptions; use cache2::StartupMode; use cache2::StorageLayout; use cache2::StorageOptions; +use tokio::runtime::Builder as TokioRuntimeBuilder; +use tokio::runtime::Handle as TokioHandle; static NEXT_FILE: AtomicU64 = AtomicU64::new(1); @@ -91,8 +104,7 @@ struct TestCache { impl TestCache { fn new(name: &str) -> Self { let id = NEXT_FILE.fetch_add(1, Ordering::Relaxed); - let data = - std::env::temp_dir().join(format!("cache2-{name}-{}-{id}.cache", std::process::id())); + let data = env::temp_dir().join(format!("cache2-{name}-{}-{id}.cache", process::id())); Self { data } } @@ -120,17 +132,17 @@ impl Drop for TestCache { self.sidecar(".image"), self.sidecar(".image.next"), ] { - let _ = std::fs::remove_file(path); + let _ = fs::remove_file(path); } } } -fn rewrite_page_version(path: &std::path::Path, offset: u64, version: u16) { +fn rewrite_page_version(path: &Path, offset: u64, version: u16) { const PAGE_BYTES: usize = 4096; const VERSION_OFFSET: usize = 8; const CRC_OFFSET: usize = PAGE_BYTES - 4; - let mut file = std::fs::OpenOptions::new() + let mut file = OpenOptions::new() .read(true) .write(true) .open(path) @@ -147,7 +159,7 @@ fn rewrite_page_version(path: &std::path::Path, offset: u64, version: u16) { file.sync_all().unwrap(); } -fn eventually_admitted(mut put: impl FnMut() -> cache2::Result) -> T { +fn eventually_admitted(mut put: impl FnMut() -> Result) -> T { let deadline = Instant::now() + Duration::from_secs(2); loop { match put() { @@ -157,14 +169,14 @@ fn eventually_admitted(mut put: impl FnMut() -> cache2::Result) -> T { Instant::now() < deadline, "write buffer did not make progress" ); - std::thread::yield_now(); + thread::yield_now(); } Err(error) => panic!("cache write failed: {error}"), } } } -async fn completed_reclaim_snapshot(cache: &cache2::Cache) -> cache2::DetailedCacheSnapshot { +async fn completed_reclaim_snapshot(cache: &Cache) -> DetailedCacheSnapshot { for ordinal in 0_u64..128 { eventually_admitted(|| cache.put(ordinal.to_le_bytes(), vec![ordinal as u8; 8 * 1024])); } @@ -200,14 +212,12 @@ async fn completed_reclaim_snapshot(cache: &cache2::Cache) -> cache2::DetailedCa #[test] fn explicit_tokio_handle_works_from_a_runtime_without_time_enabled() { let files = TestCache::new("explicit-tokio-handle"); - let cache_runtime = tokio::runtime::Builder::new_multi_thread() + let cache_runtime = TokioRuntimeBuilder::new_multi_thread() .worker_threads(2) .enable_time() .build() .unwrap(); - let caller_runtime = tokio::runtime::Builder::new_current_thread() - .build() - .unwrap(); + let caller_runtime = TokioRuntimeBuilder::new_current_thread().build().unwrap(); let config = test_config(1); let minimum_memory_bytes = config.minimum_memory_bytes(); let config = CacheConfig::new( @@ -359,7 +369,7 @@ async fn concurrent_open_reports_structured_busy_error() { let error = Cache::open(&files.data, test_config(1)).await.unwrap_err(); assert_eq!(error.kind(), ErrorKind::Busy); assert_eq!(error.operation(), ErrorOperation::Open); - assert_eq!(error.io_kind(), std::io::ErrorKind::WouldBlock); + assert_eq!(error.io_kind(), io::ErrorKind::WouldBlock); cache.close_fast().await.unwrap(); } @@ -504,7 +514,7 @@ async fn l1_bypass_may_remain_stale_after_region_completion() { fn unavailable_io_engine_is_rejected_before_file_creation() { let files = TestCache::new("unavailable-io-engine"); let runtime = RuntimeOptions { - io_engine: IoEngine::IoUring(cache2::IoUringConfig::default()), + io_engine: IoEngine::IoUring(IoUringConfig::default()), write_flush_threshold_bytes: 128 * 1024, statistics: false, ..test_runtime_options(1, 2) @@ -513,7 +523,7 @@ fn unavailable_io_engine_is_rejected_before_file_creation() { let error = CacheConfig::new(test_storage(), runtime).unwrap_err(); assert_eq!(error.kind(), ErrorKind::Unsupported); assert_eq!(error.operation(), ErrorOperation::BuildConfig); - assert_eq!(error.io_kind(), std::io::ErrorKind::Unsupported); + assert_eq!(error.io_kind(), io::ErrorKind::Unsupported); files.assert_absent(); } @@ -531,7 +541,7 @@ fn unavailable_direct_io_is_rejected_before_file_creation() { let error = CacheConfig::new(test_storage(), runtime).unwrap_err(); assert_eq!(error.kind(), ErrorKind::Unsupported); assert_eq!(error.operation(), ErrorOperation::BuildConfig); - assert_eq!(error.io_kind(), std::io::ErrorKind::Unsupported); + assert_eq!(error.io_kind(), io::ErrorKind::Unsupported); files.assert_absent(); } @@ -720,7 +730,7 @@ async fn concurrent_mixed_mutations_never_return_wrong_key_or_future_values() { let writers_left = AtomicUsize::new(WRITERS); let start = AtomicBool::new(false); let hits = AtomicU64::new(0); - let runtime = tokio::runtime::Handle::current(); + let runtime = TokioHandle::current(); thread::scope(|scope| { for writer in 0..WRITERS { @@ -818,7 +828,7 @@ async fn public_key_and_record_size_limits_are_enforced() { let error = cache.put(&oversized_key, b"value").unwrap_err(); assert_eq!(error.kind(), ErrorKind::InvalidInput); assert_eq!(error.operation(), ErrorOperation::Put); - assert_eq!(error.io_kind(), std::io::ErrorKind::InvalidInput); + assert_eq!(error.io_kind(), io::ErrorKind::InvalidInput); let error = cache.put(b"too-large", &oversized_value).unwrap_err(); assert_eq!(error.kind(), ErrorKind::InvalidInput); @@ -864,8 +874,8 @@ async fn cold_start_removes_stale_recovery_files() { let files = TestCache::new("stale-recovery-files"); let image = files.sidecar(".image"); let temporary = files.sidecar(".image.next"); - std::fs::write(&image, b"stale image").unwrap(); - std::fs::write(&temporary, b"stale temporary image").unwrap(); + fs::write(&image, b"stale image").unwrap(); + fs::write(&temporary, b"stale temporary image").unwrap(); let cache = Cache::open(&files.data, test_config(2)).await.unwrap(); assert!(!image.exists()); @@ -924,7 +934,7 @@ async fn reported_peak_disk_bytes_covers_atomic_warm_publication() { let image = files.sidecar(".image"); let temporary = files.sidecar(".image.next"); - std::fs::copy(&image, &temporary).unwrap(); + fs::copy(&image, &temporary).unwrap(); let logical_bytes = [ files.data.clone(), files.sidecar(".state"), @@ -932,7 +942,7 @@ async fn reported_peak_disk_bytes_covers_atomic_warm_publication() { temporary, ] .into_iter() - .map(|path| std::fs::metadata(path).unwrap().len()) + .map(|path| fs::metadata(path).unwrap().len()) .sum::(); assert_eq!(logical_bytes, peak_disk_bytes); @@ -1068,7 +1078,7 @@ async fn cache_snapshot_reports_tier_activity_and_resets_on_open() { assert_eq!(fresh.l1_hits, 0); assert_eq!(fresh.l2_hits, 0); assert_ne!(fresh.metrics_epoch, before_close.metrics_epoch); - assert_eq!(fresh.io, cache2::CacheIoSnapshot::default()); + assert_eq!(fresh.io, CacheIoSnapshot::default()); assert_eq!( reopened.get("key").await.unwrap().unwrap().tier(), CacheTier::L2 @@ -1148,7 +1158,7 @@ async fn read_io_failure_is_counted_and_latches_miss_only() { cache.put("key", vec![9_u8; 16 * 1024]).unwrap(); cache.drain().await.unwrap(); - std::fs::OpenOptions::new() + OpenOptions::new() .write(true) .open(&files.data) .unwrap() diff --git a/tests-integration/tests/error.rs b/tests-integration/tests/error.rs index a09221b..74a8005 100644 --- a/tests-integration/tests/error.rs +++ b/tests-integration/tests/error.rs @@ -13,7 +13,9 @@ // limitations under the License. use std::error::Error as _; +use std::io; +use cache2::Error; use cache2::ErrorKind; use cache2::ErrorOperation; use cache2::StorageOptions; @@ -24,7 +26,7 @@ fn storage_construction_errors_expose_structured_context() { assert_eq!(error.kind(), ErrorKind::InvalidInput); assert_eq!(error.operation(), ErrorOperation::BuildStorage); - assert_eq!(error.io_kind(), std::io::ErrorKind::InvalidInput); + assert_eq!(error.io_kind(), io::ErrorKind::InvalidInput); assert!(error.raw_os_error().is_none()); assert!(error.source().is_some()); assert!(error.to_string().contains("build_storage")); @@ -34,14 +36,14 @@ fn storage_construction_errors_expose_structured_context() { fn default_io_conversion_preserves_the_original_error() { let error = StorageOptions::new(1).build().unwrap_err(); let message = error.as_io_error().to_string(); - let error = std::io::Error::from(error); + let error = io::Error::from(error); - assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput); + assert_eq!(error.kind(), io::ErrorKind::InvalidInput); assert_eq!(error.to_string(), message); assert!( error .get_ref() - .and_then(|source| source.downcast_ref::()) + .and_then(|source| source.downcast_ref::()) .is_none() ); } @@ -53,10 +55,10 @@ fn contextual_io_conversion_keeps_structured_source() { .unwrap_err() .into_io_error_with_context(); - assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput); + assert_eq!(error.kind(), io::ErrorKind::InvalidInput); let source = error .get_ref() - .and_then(|source| source.downcast_ref::()) + .and_then(|source| source.downcast_ref::()) .expect("structured error remains in the source chain"); assert_eq!(source.operation(), ErrorOperation::BuildStorage); } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 9301cdb..7d9046f 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -15,7 +15,9 @@ use std::env; use std::ffi::OsStr; use std::ffi::OsString; +use std::iter; use std::path::Path; +use std::process; use std::process::Command as StdCommand; use cargo_metadata::Metadata; @@ -182,7 +184,7 @@ impl CommandLint { command_run("taplo", ["format", "--check"]); command_run("hawkeye", ["check"]); } - command_run("typos", std::iter::empty::<&str>()); + command_run("typos", iter::empty::<&str>()); let mut docs = nightly_cargo(); docs.env("RUSTDOCFLAGS", "-D warnings -D missing_docs --cfg docsrs"); @@ -274,14 +276,14 @@ fn run(mut command: StdCommand) { println!("{command:?}"); match command.status() { Ok(status) if status.success() => {} - Ok(status) => std::process::exit(status.code().unwrap_or(1)), + Ok(status) => process::exit(status.code().unwrap_or(1)), Err(error) => fail(&format!("failed to run {command:?}: {error}")), } } fn fail(message: &str) -> ! { eprintln!("{message}"); - std::process::exit(2) + process::exit(2) } #[cfg(test)] From 947a731d0dc79ad6d97c927972e2aee7e726d228 Mon Sep 17 00:00:00 2001 From: tison Date: Wed, 9 Sep 2026 19:37:58 +0800 Subject: [PATCH 04/14] refactor: prefer clear paths over import aliases Use direct paths where aliases or conditional imports obscure symbol origins, retain conventional short qualifiers, and give rustdoc links concise labels with explicit targets. --- benchmarks/cache/main.rs | 52 ++++++++-------- benchmarks/cache_soak/main.rs | 64 +++++++++---------- benchmarks/mixed_workloads/main.rs | 36 +++++------ benchmarks/recovery_scale/main.rs | 21 +++---- benchmarks/region_index_turnover/main.rs | 5 +- cache2/src/benchmarking.rs | 3 +- cache2/src/cache.rs | 58 +++++++++--------- cache2/src/config/mod.rs | 2 +- cache2/src/config/storage.rs | 2 +- cache2/src/error.rs | 68 +++++++++++---------- cache2/src/fixtures.rs | 4 +- cache2/src/io/backend.rs | 25 +++----- cache2/src/io/engine/mod.rs | 39 +++++------- cache2/src/io/engine/posix.rs | 3 +- cache2/src/io/engine/tests.rs | 45 ++++++-------- cache2/src/io/engine/uring.rs | 5 +- cache2/src/memory/mod.rs | 4 +- cache2/src/region/appender.rs | 5 +- cache2/src/region/file_backend/mod.rs | 5 +- cache2/src/region/file_backend/tests.rs | 31 ++++------ cache2/src/region/index/packed.rs | 3 +- cache2/src/region/index/storage/mod.rs | 25 +++----- cache2/src/region/reader.rs | 4 +- cache2/src/region/record/codec.rs | 5 +- cache2/src/region/recovery/metadata.rs | 3 +- cache2/src/region/runtime/mod.rs | 58 ++++++++---------- cache2/src/region/runtime/shutdown_tests.rs | 12 ++-- cache2/src/region/staging.rs | 4 +- examples/src/logforth.rs | 3 +- tests-integration/tests/cache.rs | 30 +++++---- tests-integration/tests/error.rs | 3 +- xtask/src/main.rs | 26 ++++---- 32 files changed, 283 insertions(+), 370 deletions(-) diff --git a/benchmarks/cache/main.rs b/benchmarks/cache/main.rs index bf13b41..9697740 100644 --- a/benchmarks/cache/main.rs +++ b/benchmarks/cache/main.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::env; use std::fmt; use std::fs; use std::hint::black_box; @@ -20,10 +19,7 @@ use std::io; use std::ops::Range; use std::path::Path; use std::path::PathBuf; -use std::process; use std::sync::Arc; -use std::sync::Barrier as ThreadBarrier; -use std::thread; use std::time::Duration; use std::time::Instant; use std::time::SystemTime; @@ -37,7 +33,6 @@ use benchmarks::report::emit_cache_report; use cache2::Cache; use cache2::CacheConfig; use cache2::CacheTier; -use cache2::ErrorKind as CacheErrorKind; use cache2::IoEngine; use cache2::IoMode; use cache2::IoUringConfig; @@ -49,8 +44,6 @@ use cache2::RuntimeOptions; use cache2::StartupMode; use cache2::StorageOptions; use cache2::Value; -use tokio::runtime::Builder as TokioRuntimeBuilder; -use tokio::time; const MIB: usize = 1024 * 1024; const REGION_BYTES: usize = 32 * MIB; @@ -115,7 +108,7 @@ impl BenchConfig { let reclaim_workers = env_usize("CACHE_BENCH_RECLAIM_WORKERS", 1)?; let clients = env_usize("CACHE_BENCH_CLIENTS", 8)?; let write_clients = env_usize("CACHE_BENCH_WRITE_CLIENTS", 4)?; - let io_engine = match env::var("CACHE_BENCH_IO_ENGINE") + let io_engine = match std::env::var("CACHE_BENCH_IO_ENGINE") .unwrap_or_else(|_| "posix".to_owned()) .as_str() { @@ -141,7 +134,7 @@ impl BenchConfig { )), value => return Err(invalid(format!("unsupported I/O engine: {value}"))), }; - let io_mode = match env::var("CACHE_BENCH_IO_MODE") + let io_mode = match std::env::var("CACHE_BENCH_IO_MODE") .unwrap_or_else(|_| "buffered".to_owned()) .as_str() { @@ -149,7 +142,7 @@ impl BenchConfig { "direct" => IoMode::Direct, value => return Err(invalid(format!("unsupported I/O mode: {value}"))), }; - let l1_eviction_policy = match env::var("CACHE_BENCH_L1_EVICTION") + let l1_eviction_policy = match std::env::var("CACHE_BENCH_L1_EVICTION") .unwrap_or_else(|_| "clock".to_owned()) .as_str() { @@ -160,9 +153,9 @@ impl BenchConfig { } }; let statistics_enabled = env_bool("CACHE_BENCH_STATS", false)?; - let directory = env::var_os("CACHE_BENCH_DIR") + let directory = std::env::var_os("CACHE_BENCH_DIR") .map(PathBuf::from) - .unwrap_or_else(env::temp_dir); + .unwrap_or_else(std::env::temp_dir); if entries == 0 || read_ops == 0 @@ -324,7 +317,10 @@ impl BenchFiles { .unwrap_or_default() .as_nanos(); Self { - data: directory.join(format!("cache2-bench-{}-{timestamp}.cache", process::id())), + data: directory.join(format!( + "cache2-bench-{}-{timestamp}.cache", + std::process::id() + )), } } } @@ -392,7 +388,7 @@ fn main() -> io::Result<()> { fn run_benchmark() -> io::Result<()> { let config = BenchConfig::from_env()?; - let runtime = TokioRuntimeBuilder::new_multi_thread() + let runtime = tokio::runtime::Builder::new_multi_thread() .worker_threads(config.clients.max(2)) .thread_name("cache2-benchmark") .enable_time() @@ -755,8 +751,8 @@ fn concurrent_writes( value_bytes: usize, clients: usize, ) -> io::Result { - let barrier = Arc::new(ThreadBarrier::new(clients + 1)); - thread::scope(|scope| { + let barrier = Arc::new(std::sync::Barrier::new(clients + 1)); + std::thread::scope(|scope| { let mut handles = Vec::with_capacity(clients); for client in 0..clients { let cache = Arc::clone(&cache); @@ -978,7 +974,7 @@ async fn read_l1_eventually(cache: &Cache, key_ordinal: usize, client: usize) -> ))); } attempts += 1; - time::sleep(RETRY_DELAY).await; + tokio::time::sleep(RETRY_DELAY).await; } } @@ -989,7 +985,7 @@ fn put_eventually(cache: &Cache, key: &[u8], value: &[u8]) -> io::Result<(u64, u attempts = attempts.saturating_add(1); match cache.put(key, value) { Ok(receipt) => return Ok((receipt, attempts)), - Err(error) if error.kind() == CacheErrorKind::Overloaded => { + Err(error) if error.kind() == cache2::ErrorKind::Overloaded => { if Instant::now() >= deadline { return Err(io::Error::new( io::ErrorKind::TimedOut, @@ -997,9 +993,9 @@ fn put_eventually(cache: &Cache, key: &[u8], value: &[u8]) -> io::Result<(u64, u )); } if attempts <= WRITE_YIELD_RETRIES { - thread::yield_now(); + std::thread::yield_now(); } else { - thread::sleep(RETRY_DELAY); + std::thread::sleep(RETRY_DELAY); } } Err(error) => return Err(error.into()), @@ -1169,7 +1165,7 @@ fn require_minimum_rate(name: &str, measurement: &Measurement) -> io::Result<()> } fn env_optional_f64(name: &str) -> io::Result> { - match env::var(name) { + match std::env::var(name) { Ok(value) => { let parsed = value .parse::() @@ -1181,37 +1177,37 @@ fn env_optional_f64(name: &str) -> io::Result> { } Ok(Some(parsed)) } - Err(env::VarError::NotPresent) => Ok(None), + Err(std::env::VarError::NotPresent) => Ok(None), Err(error) => Err(invalid(format!("cannot read {name}: {error}"))), } } fn env_usize(name: &str, default: usize) -> io::Result { - match env::var(name) { + match std::env::var(name) { Ok(value) => value .parse() .map_err(|_| invalid(format!("{name} must be an unsigned integer"))), - Err(env::VarError::NotPresent) => Ok(default), + Err(std::env::VarError::NotPresent) => Ok(default), Err(error) => Err(invalid(format!("cannot read {name}: {error}"))), } } fn env_u32(name: &str, default: u32) -> io::Result { - match env::var(name) { + match std::env::var(name) { Ok(value) => value .parse() .map_err(|_| invalid(format!("{name} must be an unsigned integer"))), - Err(env::VarError::NotPresent) => Ok(default), + Err(std::env::VarError::NotPresent) => Ok(default), Err(error) => Err(invalid(format!("cannot read {name}: {error}"))), } } fn env_bool(name: &str, default: bool) -> io::Result { - match env::var(name) { + match std::env::var(name) { Ok(value) if value == "true" || value == "1" => Ok(true), Ok(value) if value == "false" || value == "0" => Ok(false), Ok(_) => Err(invalid(format!("{name} must be true, false, 1, or 0"))), - Err(env::VarError::NotPresent) => Ok(default), + Err(std::env::VarError::NotPresent) => Ok(default), Err(error) => Err(invalid(format!("cannot read {name}: {error}"))), } } diff --git a/benchmarks/cache_soak/main.rs b/benchmarks/cache_soak/main.rs index 34ee76e..2700694 100644 --- a/benchmarks/cache_soak/main.rs +++ b/benchmarks/cache_soak/main.rs @@ -13,18 +13,15 @@ // limitations under the License. use std::cmp::min; -use std::env; use std::fmt; use std::fs; use std::io; use std::mem::MaybeUninit; use std::path::Path; use std::path::PathBuf; -use std::process; use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; -use std::thread; use std::time::Duration; use std::time::Instant; use std::time::SystemTime; @@ -39,7 +36,6 @@ use cache2::Cache; use cache2::CacheConfig; use cache2::CacheHealth; use cache2::DetailedCacheSnapshot; -use cache2::ErrorKind as CacheErrorKind; use cache2::IoEngine; use cache2::IoMode; use cache2::IoUringConfig; @@ -54,9 +50,6 @@ use logforth::append::Stderr; use logforth::bridge::log::LogBridge; use logforth::filter::rustlog::RustLogFilterBuilder; use logforth::layout::JsonLayout; -use tokio::runtime::Builder as TokioRuntimeBuilder; -use tokio::runtime::Handle as TokioHandle; -use tokio::runtime::Runtime as TokioRuntime; const MIB: usize = 1024 * 1024; const REGION_BYTES: usize = 32 * MIB; @@ -164,9 +157,9 @@ impl SoakConfig { )?; let io_mode = parse_io_mode("CACHE_SOAK_IO_MODE")?; let l1_eviction_policy = parse_l1_eviction_policy("CACHE_SOAK_L1_EVICTION")?; - let directory = env::var_os("CACHE_SOAK_DIR") + let directory = std::env::var_os("CACHE_SOAK_DIR") .map(PathBuf::from) - .unwrap_or_else(env::temp_dir); + .unwrap_or_else(std::env::temp_dir); if duration.is_zero() || sample_period.is_zero() || value_bytes.is_empty() @@ -251,7 +244,10 @@ impl SoakFiles { .unwrap_or_default() .as_nanos(); Self { - data: directory.join(format!("cache2-soak-{}-{timestamp}.cache", process::id())), + data: directory.join(format!( + "cache2-soak-{}-{timestamp}.cache", + std::process::id() + )), cleanup_on_drop: AtomicBool::new(false), } } @@ -372,7 +368,7 @@ fn main() -> io::Result<()> { fn run_benchmark() -> io::Result<()> { init_logforth()?; let config = SoakConfig::from_env()?; - let runtime = TokioRuntimeBuilder::new_multi_thread() + let runtime = tokio::runtime::Builder::new_multi_thread() .worker_threads(config.readers.max(2)) .thread_name("cache2-soak") .enable_time() @@ -451,7 +447,7 @@ fn run_benchmark() -> io::Result<()> { files.data.display(), ); - thread::scope(|scope| -> io::Result<()> { + std::thread::scope(|scope| -> io::Result<()> { let mut workers = Vec::with_capacity(client_count); for _ in 0..config.writers { workers.push(scope.spawn(|| { @@ -506,7 +502,7 @@ fn run_benchmark() -> io::Result<()> { while Instant::now() < deadline && !stop.load(Ordering::Acquire) { let wake_at = min(next_sample, deadline); if let Some(remaining) = wake_at.checked_duration_since(Instant::now()) { - thread::sleep(remaining); + std::thread::sleep(remaining); } let now = Instant::now(); if now >= next_sample && now < deadline { @@ -648,7 +644,7 @@ fn init_logforth() -> io::Result<()> { } fn open_cache( - runtime: &TokioRuntime, + runtime: &tokio::runtime::Runtime, files: &SoakFiles, config: &CacheConfig, ) -> io::Result { @@ -682,8 +678,8 @@ fn populate_for_warm_reopen( loop { match cache.put(key, &value[..value_bytes]) { Ok(_) => break, - Err(error) if error.kind() == CacheErrorKind::Overloaded => { - thread::sleep(OVERLOAD_DELAY); + Err(error) if error.kind() == cache2::ErrorKind::Overloaded => { + std::thread::sleep(OVERLOAD_DELAY); } Err(error) => return Err(error.into()), } @@ -734,10 +730,10 @@ fn run_writer( record_latency(&counters.put_latency, put_started); counters.writes.fetch_add(1, Ordering::Relaxed); } - Err(error) if error.kind() == CacheErrorKind::Overloaded => { + Err(error) if error.kind() == cache2::ErrorKind::Overloaded => { record_latency(&counters.put_latency, put_started); counters.write_rejections.fetch_add(1, Ordering::Relaxed); - thread::sleep(OVERLOAD_DELAY); + std::thread::sleep(OVERLOAD_DELAY); continue; } Err(error) => return Err(error.into()), @@ -752,10 +748,10 @@ fn run_writer( record_latency(&counters.delete_latency, delete_started); counters.deletes.fetch_add(1, Ordering::Relaxed); } - Err(error) if error.kind() == CacheErrorKind::Overloaded => { + Err(error) if error.kind() == cache2::ErrorKind::Overloaded => { record_latency(&counters.delete_latency, delete_started); counters.delete_rejections.fetch_add(1, Ordering::Relaxed); - thread::sleep(OVERLOAD_DELAY); + std::thread::sleep(OVERLOAD_DELAY); } Err(error) => return Err(error.into()), } @@ -775,7 +771,7 @@ fn run_reader( next_read: &AtomicU64, stop: &AtomicBool, counters: &SoakCounters, - runtime: &TokioHandle, + runtime: &tokio::runtime::Handle, ) -> io::Result<()> { let reader_id = u64::try_from(reader_id).map_err(|_| invalid("reader id exceeds u64"))?; while !stop.load(Ordering::Acquire) { @@ -814,7 +810,7 @@ fn run_reader( } fn verify_warm_reopen( - runtime: &TokioRuntime, + runtime: &tokio::runtime::Runtime, cache: &Cache, expected: &[AtomicU64], value_size_count: u64, @@ -1136,7 +1132,7 @@ fn record_latency(histogram: &AtomicLatencyHistogram, started: Option) fn pace(interval: Duration) { if !interval.is_zero() { - thread::sleep(interval); + std::thread::sleep(interval); } } @@ -1185,11 +1181,11 @@ fn peak_rss_bytes() -> u64 { } fn env_u64(name: &str, default: u64) -> io::Result { - match env::var(name) { + match std::env::var(name) { Ok(value) => value .parse() .map_err(|_| invalid(format!("{name} must be an unsigned integer"))), - Err(env::VarError::NotPresent) => Ok(default), + Err(std::env::VarError::NotPresent) => Ok(default), Err(error) => Err(invalid(format!("cannot read {name}: {error}"))), } } @@ -1201,7 +1197,7 @@ fn env_usize(name: &str, default: usize) -> io::Result { } fn env_usize_list(name: &str, default: &[usize]) -> io::Result> { - match env::var(name) { + match std::env::var(name) { Ok(value) => value .split(',') .map(|item| { @@ -1210,7 +1206,7 @@ fn env_usize_list(name: &str, default: &[usize]) -> io::Result> { }) .collect::>>() .map(Vec::into_boxed_slice), - Err(env::VarError::NotPresent) => Ok(default.to_vec().into_boxed_slice()), + Err(std::env::VarError::NotPresent) => Ok(default.to_vec().into_boxed_slice()), Err(error) => Err(invalid(format!("cannot read {name}: {error}"))), } } @@ -1221,22 +1217,22 @@ fn env_u32(name: &str, default: u32) -> io::Result { } fn env_optional_u32(name: &str) -> io::Result> { - match env::var(name) { + match std::env::var(name) { Ok(value) => value .parse::() .map(Some) .map_err(|_| invalid(format!("{name} must be an unsigned integer"))), - Err(env::VarError::NotPresent) => Ok(None), + Err(std::env::VarError::NotPresent) => Ok(None), Err(error) => Err(invalid(format!("cannot read {name}: {error}"))), } } fn env_bool(name: &str, default: bool) -> io::Result { - match env::var(name) { + match std::env::var(name) { Ok(value) if value == "true" || value == "1" => Ok(true), Ok(value) if value == "false" || value == "0" => Ok(false), Ok(_) => Err(invalid(format!("{name} must be true, false, 1, or 0"))), - Err(env::VarError::NotPresent) => Ok(default), + Err(std::env::VarError::NotPresent) => Ok(default), Err(error) => Err(invalid(format!("cannot read {name}: {error}"))), } } @@ -1247,7 +1243,7 @@ fn parse_io_engine( write_workers: usize, reclaim_workers: usize, ) -> io::Result { - match env::var(name) + match std::env::var(name) .unwrap_or_else(|_| "posix".to_owned()) .as_str() { @@ -1324,7 +1320,7 @@ fn io_uring_write_pool(write_workers: usize) -> io::Result { } fn parse_io_mode(name: &str) -> io::Result { - match env::var(name) + match std::env::var(name) .unwrap_or_else(|_| "buffered".to_owned()) .as_str() { @@ -1335,7 +1331,7 @@ fn parse_io_mode(name: &str) -> io::Result { } fn parse_l1_eviction_policy(name: &str) -> io::Result { - match env::var(name) + match std::env::var(name) .unwrap_or_else(|_| "clock".to_owned()) .as_str() { diff --git a/benchmarks/mixed_workloads/main.rs b/benchmarks/mixed_workloads/main.rs index 9b706bf..21b945c 100644 --- a/benchmarks/mixed_workloads/main.rs +++ b/benchmarks/mixed_workloads/main.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::env; use std::f64::consts::TAU; use std::fmt; use std::fs; @@ -20,7 +19,6 @@ use std::hint::black_box; use std::io; use std::path::Path; use std::path::PathBuf; -use std::process; use std::sync::Arc; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; @@ -39,7 +37,6 @@ use cache2::CacheConfig; use cache2::CacheHealth; use cache2::CacheSnapshot; use cache2::DetailedCacheSnapshot; -use cache2::ErrorKind as CacheErrorKind; use cache2::IoEngine; use cache2::IoMode; use cache2::IoUringConfig; @@ -48,7 +45,6 @@ use cache2::L1EvictionPolicy; use cache2::PosixIoConfig; use cache2::RuntimeOptions; use cache2::StorageOptions; -use tokio::runtime::Builder as TokioRuntimeBuilder; const MIB: usize = 1024 * 1024; const MAX_KEY_BYTES: usize = 64; @@ -239,7 +235,7 @@ impl HarnessConfig { let reclaim_workers = env_usize("CACHE_WORKLOAD_RECLAIM_WORKERS", 1)?; let latency_sample_interval = env_usize("CACHE_WORKLOAD_LATENCY_SAMPLE_INTERVAL", 16)?; let seed = env_u64("CACHE_WORKLOAD_SEED", DEFAULT_SEED)?; - let io_engine = match env::var("CACHE_WORKLOAD_IO_ENGINE") + let io_engine = match std::env::var("CACHE_WORKLOAD_IO_ENGINE") .unwrap_or_else(|_| "posix".to_owned()) .as_str() { @@ -265,7 +261,7 @@ impl HarnessConfig { )), value => return Err(invalid(format!("unsupported I/O engine: {value}"))), }; - let io_mode = match env::var("CACHE_WORKLOAD_IO_MODE") + let io_mode = match std::env::var("CACHE_WORKLOAD_IO_MODE") .unwrap_or_else(|_| "buffered".to_owned()) .as_str() { @@ -273,7 +269,7 @@ impl HarnessConfig { "direct" => IoMode::Direct, value => return Err(invalid(format!("unsupported I/O mode: {value}"))), }; - let l1_eviction_policy = match env::var("CACHE_WORKLOAD_L1_EVICTION") + let l1_eviction_policy = match std::env::var("CACHE_WORKLOAD_L1_EVICTION") .unwrap_or_else(|_| "clock".to_owned()) .as_str() { @@ -281,9 +277,9 @@ impl HarnessConfig { "s3-fifo" => L1EvictionPolicy::S3Fifo, value => return Err(invalid(format!("unsupported L1 eviction policy: {value}"))), }; - let directory = env::var_os("CACHE_WORKLOAD_DIR") + let directory = std::env::var_os("CACHE_WORKLOAD_DIR") .map(PathBuf::from) - .unwrap_or_else(env::temp_dir); + .unwrap_or_else(std::env::temp_dir); if operations_per_thread == Some(0) || threads == Some(0) @@ -460,7 +456,7 @@ impl BenchFiles { data: directory.join(format!( "cache2-mixed-workload-{}-{}-{timestamp}.cache", scenario.slug(), - process::id() + std::process::id() )), } } @@ -589,7 +585,7 @@ fn main() -> io::Result<()> { .max() .unwrap_or(2) .max(2); - let runtime = TokioRuntimeBuilder::new_multi_thread() + let runtime = tokio::runtime::Builder::new_multi_thread() .worker_threads(runtime_threads) .thread_name("cache2-mixed-workload") .enable_time() @@ -751,7 +747,7 @@ async fn run_worker( )); } Ok(None) => result.misses = result.misses.saturating_add(1), - Err(error) if error.kind() == CacheErrorKind::Overloaded => { + Err(error) if error.kind() == cache2::ErrorKind::Overloaded => { result.get_overloaded = result.get_overloaded.saturating_add(1); } Err(error) => return Err(error.into()), @@ -788,7 +784,7 @@ async fn run_worker( )); } Ok(None) => result.misses = result.misses.saturating_add(1), - Err(error) if error.kind() == CacheErrorKind::Overloaded => { + Err(error) if error.kind() == cache2::ErrorKind::Overloaded => { result.get_overloaded = result.get_overloaded.saturating_add(1); } Err(error) => return Err(error.into()), @@ -815,7 +811,7 @@ async fn run_worker( .accepted_value_bytes .saturating_add(value_size as u64); } - Err(error) if error.kind() == CacheErrorKind::Overloaded => { + Err(error) if error.kind() == cache2::ErrorKind::Overloaded => { result.set_overloaded = result.set_overloaded.saturating_add(1); } Err(error) => return Err(error.into()), @@ -830,7 +826,7 @@ async fn run_worker( Ok(_) => { result.delete_accepted = result.delete_accepted.saturating_add(1); } - Err(error) if error.kind() == CacheErrorKind::Overloaded => { + Err(error) if error.kind() == cache2::ErrorKind::Overloaded => { result.delete_overloaded = result.delete_overloaded.saturating_add(1); } Err(error) => return Err(error.into()), @@ -1174,7 +1170,7 @@ fn report_latency(scenario: Scenario, operation: &str, latency: &LatencyHistogra } fn parse_scenarios() -> io::Result> { - let value = env::var("CACHE_WORKLOAD_SCENARIO").unwrap_or_else(|_| "all".to_owned()); + let value = std::env::var("CACHE_WORKLOAD_SCENARIO").unwrap_or_else(|_| "all".to_owned()); if value == "all" { return Ok(Scenario::ALL.into()); } @@ -1239,22 +1235,22 @@ fn mixed(mut value: u64) -> u64 { } fn env_optional_usize(name: &str) -> io::Result> { - match env::var(name) { + match std::env::var(name) { Ok(value) => value .parse::() .map(Some) .map_err(|_| invalid(format!("{name} must be an unsigned integer"))), - Err(env::VarError::NotPresent) => Ok(None), + Err(std::env::VarError::NotPresent) => Ok(None), Err(error) => Err(invalid(format!("cannot read {name}: {error}"))), } } fn env_u64(name: &str, default: u64) -> io::Result { - match env::var(name) { + match std::env::var(name) { Ok(value) => value .parse() .map_err(|_| invalid(format!("{name} must be an unsigned integer"))), - Err(env::VarError::NotPresent) => Ok(default), + Err(std::env::VarError::NotPresent) => Ok(default), Err(error) => Err(invalid(format!("cannot read {name}: {error}"))), } } diff --git a/benchmarks/recovery_scale/main.rs b/benchmarks/recovery_scale/main.rs index 76f2d9c..ce4717b 100644 --- a/benchmarks/recovery_scale/main.rs +++ b/benchmarks/recovery_scale/main.rs @@ -12,15 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::env; use std::fmt; use std::fs; use std::io; use std::mem::MaybeUninit; use std::path::Path; use std::path::PathBuf; -use std::process; -use std::thread; use std::time::Duration; use std::time::Instant; use std::time::SystemTime; @@ -30,14 +27,12 @@ use benchmarks::report::JobReport; use benchmarks::report::RunReporter; use cache2::Cache; use cache2::CacheConfig; -use cache2::ErrorKind as CacheErrorKind; use cache2::IoEngine; use cache2::IoMode; use cache2::PosixIoConfig; use cache2::RuntimeOptions; use cache2::StartupMode; use cache2::StorageOptions; -use tokio::runtime::Builder as TokioRuntimeBuilder; const MIB: usize = 1024 * 1024; const WRITE_RETRY_TIMEOUT: Duration = Duration::from_secs(30); @@ -67,9 +62,9 @@ impl ScaleConfig { .ok_or_else(|| invalid("recovery benchmark managed memory limit is too large"))?; let sentinel_count = env_usize("CACHE_RECOVERY_SENTINELS", 1_024)?; let value_bytes = env_usize("CACHE_RECOVERY_VALUE_BYTES", 1_024)?; - let directory = env::var_os("CACHE_RECOVERY_DIR") + let directory = std::env::var_os("CACHE_RECOVERY_DIR") .map(PathBuf::from) - .unwrap_or_else(env::temp_dir); + .unwrap_or_else(std::env::temp_dir); if expected_entries == 0 || sentinel_count == 0 || value_bytes < 8 || !directory.is_dir() { return Err(invalid( "expected entries and sentinels must be positive, values must be at least 8 bytes, and the benchmark directory must exist", @@ -121,7 +116,7 @@ impl ScaleFiles { Self { data: directory.join(format!( "cache2-recovery-scale-{}-{timestamp}.cache", - process::id() + std::process::id() )), cleanup_on_drop: false, } @@ -202,7 +197,7 @@ fn main() -> io::Result<()> { fn run_benchmark() -> io::Result<()> { let config = ScaleConfig::from_env()?; - let runtime = TokioRuntimeBuilder::new_current_thread() + let runtime = tokio::runtime::Builder::new_current_thread() .enable_time() .build()?; runtime.block_on(run(config)) @@ -313,14 +308,14 @@ fn put_eventually(cache: &Cache, key: &[u8], value: &[u8]) -> io::Result<()> { loop { match cache.put(key, value) { Ok(_) => return Ok(()), - Err(error) if error.kind() == CacheErrorKind::Overloaded => { + Err(error) if error.kind() == cache2::ErrorKind::Overloaded => { if Instant::now() >= deadline { return Err(io::Error::new( io::ErrorKind::TimedOut, "recovery benchmark write did not enter bounded staging", )); } - thread::sleep(Duration::from_micros(50)); + std::thread::sleep(Duration::from_micros(50)); } Err(error) => return Err(error.into()), } @@ -423,11 +418,11 @@ fn sentinel_key(ordinal: usize) -> [u8; 16] { } fn env_u64(name: &str, default: u64) -> io::Result { - match env::var(name) { + match std::env::var(name) { Ok(value) => value .parse() .map_err(|_| invalid(format!("{name} must be an unsigned integer"))), - Err(env::VarError::NotPresent) => Ok(default), + Err(std::env::VarError::NotPresent) => Ok(default), Err(error) => Err(invalid(format!("cannot read {name}: {error}"))), } } diff --git a/benchmarks/region_index_turnover/main.rs b/benchmarks/region_index_turnover/main.rs index 122abb3..93550ac 100644 --- a/benchmarks/region_index_turnover/main.rs +++ b/benchmarks/region_index_turnover/main.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::env; use std::fmt; use std::io; @@ -135,11 +134,11 @@ fn report_phase(turn: usize, phase: &str, measurement: RegionIndexTurnoverPhase) } fn env_usize(name: &str, default: usize) -> io::Result { - match env::var(name) { + match std::env::var(name) { Ok(value) => value .parse() .map_err(|_| invalid(format!("{name} must be an unsigned integer"))), - Err(env::VarError::NotPresent) => Ok(default), + Err(std::env::VarError::NotPresent) => Ok(default), Err(error) => Err(invalid(format!("cannot read {name}: {error}"))), } } diff --git a/cache2/src/benchmarking.rs b/cache2/src/benchmarking.rs index 1466292..750ecdf 100644 --- a/cache2/src/benchmarking.rs +++ b/cache2/src/benchmarking.rs @@ -15,7 +15,6 @@ //! Internal benchmark entry points. This module is available only with the //! `benchmarking` feature and is not part of the supported cache API. -use std::error::Error as StdError; use std::hint::black_box; use std::io; use std::time::Duration; @@ -513,7 +512,7 @@ fn out_of_memory(target: &'static str) -> io::Error { ) } -fn index_error(error: impl StdError + Send + Sync + 'static) -> io::Error { +fn index_error(error: impl std::error::Error + Send + Sync + 'static) -> io::Error { io::Error::other(error) } diff --git a/cache2/src/cache.rs b/cache2/src/cache.rs index 3bf221c..1a47a9e 100644 --- a/cache2/src/cache.rs +++ b/cache2/src/cache.rs @@ -23,7 +23,6 @@ use std::io; use std::ops::Deref; use std::path::Path; use std::path::PathBuf; -use std::process; use std::sync::Arc; use std::sync::Mutex; use std::sync::atomic::AtomicBool; @@ -34,7 +33,6 @@ use std::time::Instant; use std::time::SystemTime; use std::time::UNIX_EPOCH; -use tokio::runtime::Handle as TokioHandle; use tokio::task::JoinError; use crate::config::CacheConfig; @@ -123,7 +121,7 @@ pub struct Cache { startup: StartupMode, path: PathBuf, logical_disk_peak_bytes: u64, - tokio_handle: TokioHandle, + tokio_handle: tokio::runtime::Handle, } impl fmt::Debug for Cache { @@ -146,7 +144,7 @@ impl Cache { /// device support, runtime binding, or worker startup failures. Configuration /// has already been checked by [`CacheConfig::new`]. pub async fn open(path: impl AsRef, config: CacheConfig) -> Result { - let handle = TokioHandle::try_current().map_err(|error| { + let handle = tokio::runtime::Handle::try_current().map_err(|error| { from_io( ErrorOperation::Open, io::Error::new(io::ErrorKind::InvalidInput, error.to_string()), @@ -165,7 +163,7 @@ impl Cache { pub async fn open_with_handle( path: impl AsRef, config: CacheConfig, - tokio_handle: TokioHandle, + tokio_handle: tokio::runtime::Handle, ) -> Result { let path = path.as_ref().to_path_buf(); let cache_handle = tokio_handle.clone(); @@ -185,7 +183,7 @@ impl Cache { fn open_blocking( path: PathBuf, config: CacheConfig, - tokio_handle: TokioHandle, + tokio_handle: tokio::runtime::Handle, started: Instant, ) -> io::Result { let capacity_bytes = config.storage().capacity_bytes(); @@ -230,7 +228,7 @@ impl Cache { fn open_blocking_inner( path: PathBuf, config: CacheConfig, - tokio_handle: TokioHandle, + tokio_handle: tokio::runtime::Handle, ) -> io::Result { let format_data = DataSuperblock { generation: 1, @@ -273,10 +271,10 @@ impl Cache { /// /// # Errors /// - /// Returns [`crate::ErrorKind::InvalidInput`] for an oversized key or - /// record and [`crate::ErrorKind::Overloaded`] when bounded mutation - /// admission is busy. Returns [`crate::ErrorKind::Unavailable`] after close - /// starts. Runtime and device failures use their corresponding structured + /// Returns [`ErrorKind::InvalidInput`](crate::ErrorKind::InvalidInput) for an oversized key or + /// record and [`ErrorKind::Overloaded`](crate::ErrorKind::Overloaded) when bounded mutation + /// admission is busy. Returns [`ErrorKind::Unavailable`](crate::ErrorKind::Unavailable) after + /// close starts. Runtime and device failures use their corresponding structured /// classifications. pub fn put(&self, key: impl AsRef<[u8]>, value: impl AsRef<[u8]>) -> Result { self.ensure_open(ErrorOperation::Put)?; @@ -297,7 +295,7 @@ impl Cache { /// /// Uses the same input, overload, runtime, and device classifications as /// [`Self::put`], including unavailable after close starts, with - /// [`crate::ErrorOperation::PutL2`] as its context. + /// [`ErrorOperation::PutL2`](crate::ErrorOperation::PutL2) as its context. pub fn put_l2(&self, key: impl AsRef<[u8]>, value: impl AsRef<[u8]>) -> Result { self.ensure_open(ErrorOperation::PutL2)?; public_result( @@ -313,9 +311,9 @@ impl Cache { /// /// # Errors /// - /// Returns [`crate::ErrorKind::InvalidInput`] for an oversized key and - /// [`crate::ErrorKind::Overloaded`] when bounded mutation admission is - /// busy. Returns [`crate::ErrorKind::Unavailable`] after close starts. + /// Returns [`ErrorKind::InvalidInput`](crate::ErrorKind::InvalidInput) for an oversized key and + /// [`ErrorKind::Overloaded`](crate::ErrorKind::Overloaded) when bounded mutation admission is + /// busy. Returns [`ErrorKind::Unavailable`](crate::ErrorKind::Unavailable) after close starts. /// Runtime and device failures remain explicit. pub fn delete(&self, key: impl AsRef<[u8]>) -> Result { self.ensure_open(ErrorOperation::Delete)?; @@ -333,8 +331,8 @@ impl Cache { /// /// # Errors /// - /// Returns [`crate::ErrorKind::Overloaded`] for explicit read pressure when - /// waiting is enabled. Cache data and device failures that can safely fail + /// Returns [`ErrorKind::Overloaded`](crate::ErrorKind::Overloaded) for explicit read pressure + /// when waiting is enabled. Cache data and device failures that can safely fail /// open transition reads to misses instead of surfacing an application /// error. pub async fn get(&self, key: impl AsRef<[u8]> + Send) -> Result> { @@ -356,9 +354,9 @@ impl Cache { /// /// # Errors /// - /// Returns [`crate::ErrorKind::Overloaded`] if another drain is active, or - /// [`crate::ErrorKind::Unavailable`] after close starts. Accepted work that - /// cannot complete returns a structured runtime/device failure. + /// Returns [`ErrorKind::Overloaded`](crate::ErrorKind::Overloaded) if another drain is active, + /// or [`ErrorKind::Unavailable`](crate::ErrorKind::Unavailable) after close starts. + /// Accepted work that cannot complete returns a structured runtime/device failure. pub async fn drain(&self) -> Result<()> { self.ensure_open(ErrorOperation::Drain)?; public_result(ErrorOperation::Drain, self.data_plane.drain_async().await) @@ -370,7 +368,7 @@ impl Cache { /// /// # Errors /// - /// Returns [`crate::ErrorKind::Unavailable`] after close starts, or a + /// Returns [`ErrorKind::Unavailable`](crate::ErrorKind::Unavailable) after close starts, or a /// structured runtime failure if the snapshot cannot be read. pub fn snapshot(&self) -> Result { self.ensure_open(ErrorOperation::Snapshot)?; @@ -386,7 +384,7 @@ impl Cache { /// /// # Errors /// - /// Returns [`crate::ErrorKind::Unavailable`] after close starts, or a + /// Returns [`ErrorKind::Unavailable`](crate::ErrorKind::Unavailable) after close starts, or a /// structured runtime failure if any diagnostic partition cannot be /// sampled. pub fn detailed_snapshot(&self) -> Result { @@ -406,9 +404,9 @@ impl Cache { /// /// # Errors /// - /// Returns [`crate::ErrorKind::Unavailable`] if close already started, or a - /// structured runtime, worker, or filesystem failure with - /// [`crate::ErrorOperation::CloseFast`]. + /// Returns [`ErrorKind::Unavailable`](crate::ErrorKind::Unavailable) if close already started, + /// or a structured runtime, worker, or filesystem failure with + /// [`ErrorOperation::CloseFast`](crate::ErrorOperation::CloseFast). pub fn close_fast(&self) -> impl Future> + Send + 'static { self.close(false) } @@ -421,10 +419,10 @@ impl Cache { /// /// # Errors /// - /// Returns [`crate::ErrorKind::Unavailable`] if close already started, or a - /// structured runtime, worker, filesystem, or device failure with - /// [`crate::ErrorOperation::CloseWarm`]. A failed warm close does not - /// publish a recoverable image. + /// Returns [`ErrorKind::Unavailable`](crate::ErrorKind::Unavailable) if close already started, + /// or a structured runtime, worker, filesystem, or device failure with + /// [`ErrorOperation::CloseWarm`](crate::ErrorOperation::CloseWarm). A failed warm close does + /// not publish a recoverable image. pub fn close_warm(&self) -> impl Future> + Send + 'static { self.close(true) } @@ -580,7 +578,7 @@ fn next_persistent_id() -> PersistentId { .unwrap_or_default() .as_nanos(); let mut bytes = now.to_le_bytes(); - let mix = counter ^ u64::from(process::id()).rotate_left(32); + let mix = counter ^ u64::from(std::process::id()).rotate_left(32); for (target, source) in bytes[8..].iter_mut().zip(mix.to_le_bytes()) { *target ^= source; } diff --git a/cache2/src/config/mod.rs b/cache2/src/config/mod.rs index 3b767b4..58773d0 100644 --- a/cache2/src/config/mod.rs +++ b/cache2/src/config/mod.rs @@ -38,7 +38,7 @@ pub use self::storage::StorageOptions; #[cfg(test)] pub use self::storage::cache_config; -/// Complete, immutable configuration for opening a [`crate::Cache`]. +/// Complete, immutable configuration for opening a [`Cache`](crate::Cache). /// /// Construction checks runtime settings against the storage layout and managed /// memory limit. It performs bounded calculations without opening files, starting diff --git a/cache2/src/config/storage.rs b/cache2/src/config/storage.rs index ad643c1..4fbf1e7 100644 --- a/cache2/src/config/storage.rs +++ b/cache2/src/config/storage.rs @@ -70,7 +70,7 @@ impl StorageOptions { /// Checks the inputs and computes an immutable layout without opening files. /// Use [`StorageLayout::peak_disk_bytes`] to compare a candidate with a disk - /// budget, then pass the chosen layout to [`crate::CacheConfig::new`]. + /// budget, then pass the chosen layout to [`CacheConfig::new`](crate::CacheConfig::new). /// /// # Errors /// diff --git a/cache2/src/error.rs b/cache2/src/error.rs index 6a06147..e803bf7 100644 --- a/cache2/src/error.rs +++ b/cache2/src/error.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::error::Error as StdError; use std::fmt; use std::io; use std::result; @@ -75,29 +74,30 @@ impl fmt::Display for ErrorKind { #[non_exhaustive] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum ErrorOperation { - /// [`crate::CacheConfig::new`]. + /// [`CacheConfig::new`](crate::CacheConfig::new). BuildConfig, - /// [`crate::StorageOptions::build`]. + /// [`StorageOptions::build`](crate::StorageOptions::build). BuildStorage, - /// [`crate::Cache::open`] or [`crate::Cache::open_with_handle`]. + /// [`Cache::open`](crate::Cache::open) or + /// [`Cache::open_with_handle`](crate::Cache::open_with_handle). Open, - /// [`crate::Cache::put`]. + /// [`Cache::put`](crate::Cache::put). Put, - /// [`crate::Cache::put_l2`]. + /// [`Cache::put_l2`](crate::Cache::put_l2). PutL2, - /// [`crate::Cache::delete`]. + /// [`Cache::delete`](crate::Cache::delete). Delete, - /// [`crate::Cache::get`]. + /// [`Cache::get`](crate::Cache::get). Get, - /// [`crate::Cache::drain`]. + /// [`Cache::drain`](crate::Cache::drain). Drain, - /// [`crate::Cache::snapshot`]. + /// [`Cache::snapshot`](crate::Cache::snapshot). Snapshot, - /// [`crate::Cache::detailed_snapshot`]. + /// [`Cache::detailed_snapshot`](crate::Cache::detailed_snapshot). DetailedSnapshot, - /// [`crate::Cache::close_fast`]. + /// [`Cache::close_fast`](crate::Cache::close_fast). CloseFast, - /// [`crate::Cache::close_warm`]. + /// [`Cache::close_warm`](crate::Cache::close_warm). CloseWarm, } @@ -131,7 +131,7 @@ impl fmt::Display for ErrorOperation { /// /// The classification and operation are stable programmatic fields. The /// wrapped [`io::Error`] retains the detailed cause, its raw OS error when one -/// exists, and the complete [`StdError::source`] chain. +/// exists, and the complete [`Error::source`](std::error::Error::source) chain. #[doc = include_str!("../ERRORS.md")] #[derive(Debug)] pub struct Error { @@ -200,8 +200,8 @@ impl fmt::Display for Error { } } -impl StdError for Error { - fn source(&self) -> Option<&(dyn StdError + 'static)> { +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.source) } } @@ -221,26 +221,28 @@ pub fn from_io(operation: ErrorOperation, source: io::Error) -> Error { } fn classify(operation: ErrorOperation, source: &io::Error) -> ErrorKind { - use io::ErrorKind as IoKind; - match source.kind() { - IoKind::InvalidInput if source.raw_os_error().is_some() => ErrorKind::Io, - IoKind::InvalidInput if accepts_caller_input(operation) => ErrorKind::InvalidInput, - IoKind::InvalidInput => ErrorKind::Internal, - IoKind::Unsupported => ErrorKind::Unsupported, - IoKind::WouldBlock | IoKind::AlreadyExists if operation == ErrorOperation::Open => { + io::ErrorKind::InvalidInput if source.raw_os_error().is_some() => ErrorKind::Io, + io::ErrorKind::InvalidInput if accepts_caller_input(operation) => ErrorKind::InvalidInput, + io::ErrorKind::InvalidInput => ErrorKind::Internal, + io::ErrorKind::Unsupported => ErrorKind::Unsupported, + io::ErrorKind::WouldBlock | io::ErrorKind::AlreadyExists + if operation == ErrorOperation::Open => + { ErrorKind::Busy } - IoKind::WouldBlock if source.raw_os_error().is_some() => ErrorKind::Io, - IoKind::WouldBlock if has_bounded_admission(operation) => ErrorKind::Overloaded, - IoKind::WouldBlock | IoKind::AlreadyExists => ErrorKind::Internal, - IoKind::TimedOut if operation == ErrorOperation::Get => ErrorKind::Overloaded, - IoKind::TimedOut => ErrorKind::Io, - IoKind::OutOfMemory if operation == ErrorOperation::Get => ErrorKind::Overloaded, - IoKind::OutOfMemory => ErrorKind::ResourceExhausted, - IoKind::BrokenPipe | IoKind::NotConnected | IoKind::Interrupted => ErrorKind::Unavailable, - IoKind::InvalidData | IoKind::UnexpectedEof => ErrorKind::CorruptData, - IoKind::Other if source.raw_os_error().is_none() => ErrorKind::Internal, + io::ErrorKind::WouldBlock if source.raw_os_error().is_some() => ErrorKind::Io, + io::ErrorKind::WouldBlock if has_bounded_admission(operation) => ErrorKind::Overloaded, + io::ErrorKind::WouldBlock | io::ErrorKind::AlreadyExists => ErrorKind::Internal, + io::ErrorKind::TimedOut if operation == ErrorOperation::Get => ErrorKind::Overloaded, + io::ErrorKind::TimedOut => ErrorKind::Io, + io::ErrorKind::OutOfMemory if operation == ErrorOperation::Get => ErrorKind::Overloaded, + io::ErrorKind::OutOfMemory => ErrorKind::ResourceExhausted, + io::ErrorKind::BrokenPipe | io::ErrorKind::NotConnected | io::ErrorKind::Interrupted => { + ErrorKind::Unavailable + } + io::ErrorKind::InvalidData | io::ErrorKind::UnexpectedEof => ErrorKind::CorruptData, + io::ErrorKind::Other if source.raw_os_error().is_none() => ErrorKind::Internal, _ => ErrorKind::Io, } } diff --git a/cache2/src/fixtures.rs b/cache2/src/fixtures.rs index ecafeb7..3839388 100644 --- a/cache2/src/fixtures.rs +++ b/cache2/src/fixtures.rs @@ -21,8 +21,6 @@ //! The sparse representation starts with the complete byte length. Each following line contains a //! hexadecimal offset and hexadecimal bytes; unspecified bytes are zero. -use std::str; - /// Checks every byte, including zero padding, and returns the committed bytes /// for decoder compatibility checks. #[track_caller] @@ -61,7 +59,7 @@ fn sparse_golden(input: &str) -> Vec { .as_chunks::<2>() .0 .iter() - .map(|pair| u8::from_str_radix(str::from_utf8(pair).unwrap(), 16).unwrap()) + .map(|pair| u8::from_str_radix(std::str::from_utf8(pair).unwrap(), 16).unwrap()) .collect::>(); let output = output.as_mut().expect("golden length must come first"); output[offset..offset + bytes.len()].copy_from_slice(&bytes); diff --git a/cache2/src/io/backend.rs b/cache2/src/io/backend.rs index 418d081..4fe391b 100644 --- a/cache2/src/io/backend.rs +++ b/cache2/src/io/backend.rs @@ -19,15 +19,9 @@ //! record, superblock, or barrier operation without changing the cache //! algorithm. -#[cfg(test)] -use std::env; -#[cfg(test)] -use std::fs; use std::fs::File; use std::fs::OpenOptions; use std::io; -#[cfg(test)] -use std::mem::MaybeUninit; #[cfg(unix)] use std::os::fd::AsRawFd; #[cfg(unix)] @@ -37,15 +31,11 @@ use std::os::unix::fs::MetadataExt; #[cfg(unix)] use std::os::unix::fs::OpenOptionsExt; use std::path::Path; -#[cfg(test)] -use std::process; use std::slice; use std::sync::Arc; use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; -#[cfg(test)] -use std::thread; use crate::config::IoMode; use crate::snapshot::CacheIoPathSnapshot; @@ -990,7 +980,10 @@ mod tests { impl TestFile { fn new(label: &str) -> Self { let nonce = NEXT_PATH.fetch_add(1, Ordering::Relaxed); - Self(env::temp_dir().join(format!("cache2-{label}-{}-{nonce}.cache", process::id()))) + Self(std::env::temp_dir().join(format!( + "cache2-{label}-{}-{nonce}.cache", + std::process::id() + ))) } fn open(&self) -> File { @@ -1006,7 +999,7 @@ mod tests { impl Drop for TestFile { fn drop(&mut self) { - let _ = fs::remove_file(&self.0); + let _ = std::fs::remove_file(&self.0); } } @@ -1068,7 +1061,7 @@ mod tests { MAX_INTERRUPTED_RETRIES + 1 ); - let mut uninitialized = MaybeUninit::::uninit(); + let mut uninitialized = std::mem::MaybeUninit::::uninit(); let backend = InterruptedBackend::default(); let (result, transferred) = read_exact_at_uninit_with_progress(&backend, uninitialized.as_mut_ptr(), 1, 0); @@ -1277,7 +1270,7 @@ mod tests { let alias = TestFile::new("control-alias"); let other = TestFile::new("control-other"); drop(primary.open()); - fs::hard_link(&primary.0, &alias.0).unwrap(); + std::fs::hard_link(&primary.0, &alias.0).unwrap(); let primary = FileBackend::open(&primary.0).unwrap(); let alias = FileBackend::open(&alias.0).unwrap(); @@ -1599,10 +1592,10 @@ pub mod testing { // run user code in the target process. if unsafe { kill(getpid(), SIGKILL) } == 0 { loop { - thread::park(); + std::thread::park(); } } - process::abort() + std::process::abort() } #[cfg(unix)] diff --git a/cache2/src/io/engine/mod.rs b/cache2/src/io/engine/mod.rs index a48161b..6bc5be5 100644 --- a/cache2/src/io/engine/mod.rs +++ b/cache2/src/io/engine/mod.rs @@ -18,7 +18,6 @@ //! buffer is returned only with the target operation's completion, which is //! the lifetime rule required by both positioned I/O workers and `io_uring`. -use std::error::Error as StdError; use std::fmt; use std::future::Future; use std::io; @@ -44,12 +43,7 @@ use std::time::Instant; use asyncband::semaphore::OwnedSemaphorePermit; use asyncband::semaphore::Semaphore; -use tokio::runtime::Handle as TokioHandle; -use tokio::time; -use tokio::time::Instant as TokioInstant; -#[cfg(unix)] -use crate::config::IoEngine as ConfiguredIoEngine; #[cfg(unix)] use crate::config::IoUringPoolConfig; use crate::io::backend::IoBackend; @@ -310,8 +304,8 @@ impl fmt::Display for IoBufferError { } } -impl StdError for IoBufferError { - fn source(&self) -> Option<&(dyn StdError + 'static)> { +impl std::error::Error for IoBufferError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.error) } } @@ -567,8 +561,8 @@ impl fmt::Display for SubmitError { } } -impl StdError for SubmitError { - fn source(&self) -> Option<&(dyn StdError + 'static)> { +impl std::error::Error for SubmitError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.error) } } @@ -805,13 +799,13 @@ impl BoundedIoRequest { pub async fn wait_async( self, engine: Arc, - tokio_handle: &TokioHandle, + tokio_handle: &tokio::runtime::Handle, ) -> Result { let mut request = AsyncRequestGuard::new(self.request, engine); - let deadline = TokioInstant::from_std(self.deadline); + let deadline = tokio::time::Instant::from_std(self.deadline); let completion = { let _entered = tokio_handle.enter(); - time::timeout_at(deadline, request.request_mut()) + tokio::time::timeout_at(deadline, request.request_mut()) } .await; if let Ok(completion) = completion { @@ -822,7 +816,7 @@ impl BoundedIoRequest { let cancel_error = request.cancel().err(); let completion = { let _entered = tokio_handle.enter(); - time::timeout(self.cancel_grace, request.request_mut()) + tokio::time::timeout(self.cancel_grace, request.request_mut()) } .await; match completion { @@ -1101,13 +1095,13 @@ impl ReadSlotAdmission { async fn acquire_until( &self, deadline: Instant, - tokio_handle: &TokioHandle, + tokio_handle: &tokio::runtime::Handle, ) -> io::Result { self.ensure_open()?; let acquire = Arc::clone(&self.slots).acquire_owned(1); { let _entered = tokio_handle.enter(); - time::timeout_at(TokioInstant::from_std(deadline), acquire) + tokio::time::timeout_at(tokio::time::Instant::from_std(deadline), acquire) } .await .map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "L2 read wait deadline expired")) @@ -1138,7 +1132,7 @@ impl ReadSlotWaiter { pub async fn reserve_until( self, deadline: Instant, - tokio_handle: &TokioHandle, + tokio_handle: &tokio::runtime::Handle, ) -> io::Result { let admission = self .shared @@ -1440,9 +1434,6 @@ impl RuntimeShared { ) ))] fn finish_quarantined(&self, task: Task, status: CompletionStatus, bytes_transferred: usize) { - #[cfg(not(test))] - use std::mem::forget; - let Task { request_id, operation, @@ -1457,7 +1448,7 @@ impl RuntimeShared { // intentional LeakSanitizer finding. lock_unpoisoned(&self.quarantined_buffers).push(buffer); #[cfg(not(test))] - forget(buffer); + std::mem::forget(buffer); } self.publish_completion( request_id, @@ -1970,13 +1961,13 @@ pub fn build_file_engine( files: RuntimeFileSet, max_in_flight: usize, posix_workers: usize, - kind: ConfiguredIoEngine, + kind: crate::config::IoEngine, io_uring_config: Option, statistics_enabled: bool, read_wait_enabled: bool, ) -> io::Result> { match kind { - ConfiguredIoEngine::Posix(_) => BackendIoEngine::new_with_files_and_workers( + crate::config::IoEngine::Posix(_) => BackendIoEngine::new_with_files_and_workers( files, max_in_flight, posix_workers, @@ -1984,7 +1975,7 @@ pub fn build_file_engine( read_wait_enabled, ) .map(|engine| Arc::new(engine) as Arc), - ConfiguredIoEngine::IoUring(_) => { + crate::config::IoEngine::IoUring(_) => { let _ = posix_workers; #[cfg(all( feature = "io-uring", diff --git a/cache2/src/io/engine/posix.rs b/cache2/src/io/engine/posix.rs index 201efbc..eec286e 100644 --- a/cache2/src/io/engine/posix.rs +++ b/cache2/src/io/engine/posix.rs @@ -24,7 +24,6 @@ use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; use std::sync::mpsc; use std::sync::mpsc::Receiver; -use std::thread; use std::time::Instant; use crate::io::backend::IoBackend; @@ -139,7 +138,7 @@ impl BackendIoEngine { let worker_backend = Arc::clone(&backend); let worker_shared = Arc::clone(&shared); let worker_receiver = Arc::clone(&receiver); - let spawn_result = thread::Builder::new() + let spawn_result = std::thread::Builder::new() .name(format!("cache2-sync-io-{worker_index}")) .stack_size(CACHE_THREAD_STACK_BYTES) .spawn(move || backend_driver(worker_backend, worker_shared, worker_receiver)); diff --git a/cache2/src/io/engine/tests.rs b/cache2/src/io/engine/tests.rs index f3804e2..1bf7091 100644 --- a/cache2/src/io/engine/tests.rs +++ b/cache2/src/io/engine/tests.rs @@ -12,22 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::env; use std::fs; use std::fs::File; use std::fs::OpenOptions; use std::path::PathBuf; -use std::process; use std::sync::atomic::AtomicU64; use std::sync::mpsc; -use std::thread; use std::time::Duration; -use tokio::runtime::Handle as TokioHandle; -use tokio::task; -use tokio::task::JoinHandle as TokioJoinHandle; -use tokio::time; - use super::*; use crate::config::PosixIoConfig; use crate::io::backend::FileBackend; @@ -50,7 +42,7 @@ async fn wait_for_registered_read_waiters(engine: &BackendIoEngine, expected: us if actual == expected { return; } - task::yield_now().await; + tokio::task::yield_now().await; } panic!("expected {expected} registered read waiters"); } @@ -59,18 +51,18 @@ async fn spawn_registered_read_slot_waiter( engine: &BackendIoEngine, timeout: Duration, expected_waiters: usize, -) -> TokioJoinHandle> { +) -> tokio::task::JoinHandle> { let slot_waiter = engine.read_slot_waiter(); let waiter = tokio::spawn(async move { slot_waiter - .reserve_until(Instant::now() + timeout, &TokioHandle::current()) + .reserve_until(Instant::now() + timeout, &tokio::runtime::Handle::current()) .await }); wait_for_registered_read_waiters(engine, expected_waiters).await; waiter } -async fn read_wait_error(waiter: TokioJoinHandle>) -> io::Error { +async fn read_wait_error(waiter: tokio::task::JoinHandle>) -> io::Error { match waiter.await.unwrap() { Ok(_) => panic!("read waiter unexpectedly reserved a slot"), Err(error) => error, @@ -84,7 +76,8 @@ struct TestFile { impl TestFile { fn new() -> Self { let id = FILE_ID.fetch_add(1, Ordering::Relaxed); - let path = env::temp_dir().join(format!("cache2-io-engine-{}-{id}.bin", process::id())); + let path = + std::env::temp_dir().join(format!("cache2-io-engine-{}-{id}.bin", std::process::id())); Self { path } } @@ -388,7 +381,7 @@ async fn async_request_is_woken_by_driver_completion() { .unwrap(); let completion = request - .wait_async(Arc::clone(&engine), &TokioHandle::current()) + .wait_async(Arc::clone(&engine), &tokio::runtime::Handle::current()) .await .unwrap(); @@ -410,10 +403,10 @@ async fn dropping_async_wait_requests_bounded_cancellation() { let waiter_engine = Arc::clone(&engine); let waiter = tokio::spawn(async move { request - .wait_async(waiter_engine, &TokioHandle::current()) + .wait_async(waiter_engine, &tokio::runtime::Handle::current()) .await }); - task::yield_now().await; + tokio::task::yield_now().await; assert!(backend.wait_for_entered(1)); waiter.abort(); @@ -439,10 +432,10 @@ async fn read_slot_waits_for_cancelled_request_to_release_physical_capacity() { let request_engine = Arc::clone(&engine); let request_waiter = tokio::spawn(async move { request - .wait_async(request_engine, &TokioHandle::current()) + .wait_async(request_engine, &tokio::runtime::Handle::current()) .await }); - task::yield_now().await; + tokio::task::yield_now().await; assert!(backend.wait_for_entered(1)); request_waiter.abort(); @@ -451,10 +444,10 @@ async fn read_slot_waits_for_cancelled_request_to_release_physical_capacity() { let slot_waiter = engine.read_slot_waiter(); let deadline = Instant::now() + Duration::from_secs(1); - let tokio_handle = TokioHandle::current(); + let tokio_handle = tokio::runtime::Handle::current(); let mut reservation = Box::pin(slot_waiter.reserve_until(deadline, &tokio_handle)); assert!( - time::timeout(Duration::from_millis(20), reservation.as_mut()) + tokio::time::timeout(Duration::from_millis(20), reservation.as_mut()) .await .is_err(), "caller cancellation must not publish physical capacity" @@ -520,7 +513,7 @@ async fn queued_read_reservations_are_fifo() { drop(held); let first_slot = first.await.unwrap().unwrap(); - task::yield_now().await; + tokio::task::yield_now().await; assert!( !second.is_finished(), "the second waiter bypassed the first" @@ -542,7 +535,7 @@ async fn queued_reads_use_every_released_engine_slot() { drop(held); let first_slot = first.await.unwrap().unwrap(); - let second_slot = time::timeout(Duration::from_millis(20), second) + let second_slot = tokio::time::timeout(Duration::from_millis(20), second) .await .expect("an idle second engine slot was blocked by the queue head") .unwrap() @@ -606,7 +599,7 @@ async fn async_read_deadline_keeps_other_slots_available() { assert!(backend.wait_for_entered(1)); let timeout = request - .wait_async(Arc::clone(&engine), &TokioHandle::current()) + .wait_async(Arc::clone(&engine), &tokio::runtime::Handle::current()) .await .unwrap_err(); let (error, buffer) = timeout.into_buffer(); @@ -770,7 +763,7 @@ fn configured_posix_engine_shares_its_worker_capacity() { files, 4, 4, - ConfiguredIoEngine::Posix(PosixIoConfig::new(4, 4, 1)), + crate::config::IoEngine::Posix(PosixIoConfig::new(4, 4, 1)), None, false, false, @@ -890,7 +883,7 @@ fn submit_wait_blocks_at_engine_capacity_and_resumes() { let (_, waiting_operation) = rejected.into_parts(); let (started_sender, started_receiver) = mpsc::sync_channel(1); let (sender, receiver) = mpsc::sync_channel(1); - let submitter = thread::spawn(move || { + let submitter = std::thread::spawn(move || { started_sender.send(()).unwrap(); sender .send(waiting_engine.submit_wait(waiting_operation)) @@ -934,7 +927,7 @@ fn controlled_slot_wait_observes_cancel_wake_and_absolute_deadline() { let waiting_operation = IoOperation::read(read_buffer(&resources, 1), 1); let (started_sender, started_receiver) = mpsc::sync_channel(1); let (result_sender, result_receiver) = mpsc::sync_channel(1); - let submitter = thread::spawn(move || { + let submitter = std::thread::spawn(move || { started_sender.send(()).unwrap(); result_sender .send(waiting_engine.submit_wait_controlled( diff --git a/cache2/src/io/engine/uring.rs b/cache2/src/io/engine/uring.rs index 12ab3db..055ab65 100644 --- a/cache2/src/io/engine/uring.rs +++ b/cache2/src/io/engine/uring.rs @@ -33,7 +33,6 @@ use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; use std::sync::mpsc; use std::sync::mpsc::Receiver; -use std::thread; use std::time::Instant; use hashcrew::xxhash::Xxh3_64Builder; @@ -213,7 +212,7 @@ impl UringIoEngine { let submit_state = Arc::new(RwLock::new(SubmitState { accepting: true })); let worker_shared = Arc::clone(&shared); let worker_submit_state = Arc::clone(&submit_state); - let worker = thread::Builder::new() + let worker = std::thread::Builder::new() .name("cache2-uring-io".into()) .stack_size(CACHE_THREAD_STACK_BYTES) .spawn(move || { @@ -994,7 +993,7 @@ impl UringDriver { if !self.has_active_target() { return; } - thread::yield_now(); + std::thread::yield_now(); } } diff --git a/cache2/src/memory/mod.rs b/cache2/src/memory/mod.rs index 56b9305..c7a7f46 100644 --- a/cache2/src/memory/mod.rs +++ b/cache2/src/memory/mod.rs @@ -31,8 +31,6 @@ use std::sync::TryLockError; use std::sync::atomic::AtomicU64; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; -#[cfg(test)] -use std::thread; use self::eviction::DetachedPolicy; use self::eviction::EvictionState; @@ -1451,7 +1449,7 @@ mod tests { assert!(!store.publish(22, b"b", &[2; 300], 2)); let barrier = Arc::new(Barrier::new(clones.len() + 1)); - thread::scope(|scope| { + std::thread::scope(|scope| { for value in clones { let barrier = Arc::clone(&barrier); scope.spawn(move || { diff --git a/cache2/src/region/appender.rs b/cache2/src/region/appender.rs index 5c45ef1..3c4fb34 100644 --- a/cache2/src/region/appender.rs +++ b/cache2/src/region/appender.rs @@ -18,7 +18,6 @@ //! disposable-cache protocol establishes durability once, when publishing a //! CLEAN image, and deliberately has no per-span sync. -use std::error::Error as StdError; use std::fmt; use std::io; @@ -58,8 +57,8 @@ impl fmt::Display for RegionSpanSubmitError { } } -impl StdError for RegionSpanSubmitError { - fn source(&self) -> Option<&(dyn StdError + 'static)> { +impl std::error::Error for RegionSpanSubmitError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.error) } } diff --git a/cache2/src/region/file_backend/mod.rs b/cache2/src/region/file_backend/mod.rs index 8e6ef9f..1e74ba2 100644 --- a/cache2/src/region/file_backend/mod.rs +++ b/cache2/src/region/file_backend/mod.rs @@ -26,9 +26,6 @@ use std::sync::Arc; use std::sync::Mutex; use std::sync::atomic::AtomicU64; -#[cfg(test)] -use tokio::runtime::Handle as TokioHandle; - use crate::config::CacheConfig; use crate::config::IoMode; #[cfg(test)] @@ -268,7 +265,7 @@ impl RegionStore> { async fn get_value_async( &self, key: &[u8], - tokio_handle: &TokioHandle, + tokio_handle: &tokio::runtime::Handle, ) -> io::Result> { self.runtime()? .data_plane()? diff --git a/cache2/src/region/file_backend/tests.rs b/cache2/src/region/file_backend/tests.rs index 8546e19..20d6284 100644 --- a/cache2/src/region/file_backend/tests.rs +++ b/cache2/src/region/file_backend/tests.rs @@ -12,14 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::env; use std::fs; use std::future::Future; use std::future::poll_fn; #[cfg(unix)] use std::os::unix::process::ExitStatusExt; use std::pin::Pin; -use std::process; #[cfg(unix)] use std::process::Command; #[cfg(unix)] @@ -30,14 +28,10 @@ use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; use std::sync::mpsc; use std::task::Poll; -use std::thread; use std::time::Duration; use std::time::Instant; -use tokio::runtime::Builder as TokioRuntimeBuilder; - use super::*; -use crate::config::IoEngine as ConfiguredIoEngine; use crate::config::MAX_WRITE_FLUSH_THRESHOLD_BYTES; use crate::config::PosixIoConfig; use crate::config::ReadAdmission; @@ -83,7 +77,7 @@ fn eventually_admitted(mut put: impl FnMut() -> io::Result) -> T { Instant::now() < deadline, "write buffer did not make progress" ); - thread::yield_now(); + std::thread::yield_now(); } Err(error) => panic!("cache write failed: {error}"), } @@ -106,7 +100,8 @@ struct TestDirectory { impl TestDirectory { fn new() -> Self { let ordinal = NEXT_TEST_DIRECTORY.fetch_add(1, Ordering::Relaxed); - let root = env::temp_dir().join(format!("cache2-region-{}-{ordinal}", process::id())); + let root = + std::env::temp_dir().join(format!("cache2-region-{}-{ordinal}", std::process::id())); let _ = fs::remove_dir_all(&root); fs::create_dir(&root).unwrap(); let files = RegionFiles::new( @@ -277,8 +272,8 @@ fn external_process_kill_recovery_contract() { const CHILD_CASE: &str = "CACHE2_CRASH_CHILD_CASE"; const CHILD_ROOT: &str = "CACHE2_CRASH_CHILD_ROOT"; - if let Ok(case) = env::var(CHILD_CASE) { - let root = PathBuf::from(env::var_os(CHILD_ROOT).expect("child root is set")); + if let Ok(case) = std::env::var(CHILD_CASE) { + let root = PathBuf::from(std::env::var_os(CHILD_ROOT).expect("child root is set")); let files = RegionFiles::new( root.join("data"), root.join("state"), @@ -306,7 +301,7 @@ fn external_process_kill_recovery_contract() { initial.drain().unwrap(); initial.close_warm().unwrap(); - let status = Command::new(env::current_exe().unwrap()) + let status = Command::new(std::env::current_exe().unwrap()) .arg("--exact") .arg("region::file_backend::tests::external_process_kill_recovery_contract") .arg("--ignored") @@ -413,7 +408,7 @@ fn configured_read_wait_is_bounded_and_cancel_safe() { let directory = TestDirectory::new(); let data = production_data_superblock(512 * 1024); let runtime_config = RuntimeOptions { - io_engine: ConfiguredIoEngine::Posix(PosixIoConfig::new(2, 4, 1)), + io_engine: crate::config::IoEngine::Posix(PosixIoConfig::new(2, 4, 1)), l1_capacity_bytes: 0, statistics: true, read_admission: ReadAdmission::Wait { @@ -432,7 +427,7 @@ fn configured_read_wait_is_bounded_and_cancel_safe() { ), ) .unwrap(); - let tokio_runtime = TokioRuntimeBuilder::new_multi_thread() + let tokio_runtime = tokio::runtime::Builder::new_multi_thread() .worker_threads(2) .enable_time() .build() @@ -497,7 +492,7 @@ fn queued_l2_read_does_not_pin_warm_close() { let directory = TestDirectory::new(); let data = production_data_superblock(512 * 1024); let runtime_config = RuntimeOptions { - io_engine: ConfiguredIoEngine::Posix(PosixIoConfig::new(1, 4, 1)), + io_engine: crate::config::IoEngine::Posix(PosixIoConfig::new(1, 4, 1)), l1_capacity_bytes: 0, read_admission: ReadAdmission::Wait { timeout: Duration::from_secs(1), @@ -515,7 +510,7 @@ fn queued_l2_read_does_not_pin_warm_close() { ), ) .unwrap(); - let tokio_runtime = TokioRuntimeBuilder::new_current_thread() + let tokio_runtime = tokio::runtime::Builder::new_current_thread() .enable_time() .build() .unwrap(); @@ -662,7 +657,7 @@ fn poisoned_runtime_gates_stop_workers_and_reject_warm_close() { let directory = TestDirectory::new(); let data = production_data_superblock(512 * 1024); let runtime_config = RuntimeOptions { - io_engine: ConfiguredIoEngine::Posix(PosixIoConfig::new(1, 1, 1)), + io_engine: crate::config::IoEngine::Posix(PosixIoConfig::new(1, 1, 1)), l1_capacity_bytes: 0, managed_memory_limit_bytes: 32 * 1024 * 1024, write_flush_threshold_bytes: 128 * 1024, @@ -805,7 +800,7 @@ fn foreground_stage_rejects_busy_shard_without_reserving_then_stages_once() { let record_bytes = required_record_bytes(b"key".len(), b"value".len()).unwrap(); let (sender, receiver) = mpsc::sync_channel(1); let core = Arc::clone(&runtime.core); - let writer = thread::spawn(move || { + let writer = std::thread::spawn(move || { let result = core.try_stage_value(&staging, 0, hash, record_bytes, b"key", b"value"); sender.send((result, staging)).unwrap(); }); @@ -854,7 +849,7 @@ fn completed_record_publication_does_not_enter_region_manager() { ); let (sender, receiver) = mpsc::sync_channel(1); let publisher_core = Arc::clone(&core); - let publisher = thread::spawn(move || { + let publisher = std::thread::spawn(move || { sender .send(publisher_core.publish_completed_records(&[record])) .unwrap(); diff --git a/cache2/src/region/index/packed.rs b/cache2/src/region/index/packed.rs index 1abf4f1..1e0fab6 100644 --- a/cache2/src/region/index/packed.rs +++ b/cache2/src/region/index/packed.rs @@ -14,7 +14,6 @@ //! Shared packed-location and index-entry primitives for the index. -use std::error::Error as StdError; use std::fmt; const REGION_BITS: u32 = 20; @@ -209,7 +208,7 @@ impl fmt::Display for PackedLocationError { } } -impl StdError for PackedLocationError {} +impl std::error::Error for PackedLocationError {} #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct IndexEntry { diff --git a/cache2/src/region/index/storage/mod.rs b/cache2/src/region/index/storage/mod.rs index 8ce4e7c..532f5c5 100644 --- a/cache2/src/region/index/storage/mod.rs +++ b/cache2/src/region/index/storage/mod.rs @@ -21,23 +21,12 @@ //! runtime mutations become private copy-on-write pages. use std::cell::UnsafeCell; -#[cfg(test)] -use std::env; -use std::error::Error as StdError; use std::fmt; -#[cfg(test)] -use std::fs; use std::fs::File; use std::io::Write; use std::io::{self}; #[cfg(any(target_os = "linux", target_os = "macos"))] use std::os::fd::AsRawFd; -#[cfg(test)] -use std::panic; -#[cfg(test)] -use std::panic::AssertUnwindSafe; -#[cfg(test)] -use std::process; use std::ptr; use std::slice; use std::sync::Arc; @@ -572,8 +561,8 @@ impl fmt::Display for IndexStorageError { } } -impl StdError for IndexStorageError { - fn source(&self) -> Option<&(dyn StdError + 'static)> { +impl std::error::Error for IndexStorageError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::Io(error) => Some(error), Self::InvalidArgument(_) @@ -1561,7 +1550,7 @@ impl PartitionedIndexStorage { #[cfg(test)] pub fn poison_hash_partition_for_test(&self, hash: u64) { let partition = index_partition_for(hash, self.partitions.len()); - let result = panic::catch_unwind(AssertUnwindSafe(|| { + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { let _guard = self.partitions[partition].write().unwrap(); panic!("poison index partition for test"); })); @@ -1871,8 +1860,10 @@ mod tests { impl TestFile { fn create() -> Self { let id = NEXT_TEST_FILE.fetch_add(1, Ordering::Relaxed); - let path = - env::temp_dir().join(format!("cache2-index-image-{}-{id}.tmp", process::id())); + let path = std::env::temp_dir().join(format!( + "cache2-index-image-{}-{id}.tmp", + std::process::id() + )); let file = OpenOptions::new() .create_new(true) .read(true) @@ -1885,7 +1876,7 @@ mod tests { impl Drop for TestFile { fn drop(&mut self) { - let _ = fs::remove_file(&self.path); + let _ = std::fs::remove_file(&self.path); } } diff --git a/cache2/src/region/reader.rs b/cache2/src/region/reader.rs index f0c0cf7..ddbd2e0 100644 --- a/cache2/src/region/reader.rs +++ b/cache2/src/region/reader.rs @@ -24,8 +24,6 @@ use std::io; use std::ops::Range; use std::sync::Arc; -use tokio::runtime::Handle as TokioHandle; - use crate::io::engine::BoundedIoRequest; use crate::io::engine::IoBuffer; use crate::io::engine::IoCompletion; @@ -103,7 +101,7 @@ impl PendingRead { pub async fn wait_async( self, engine: Arc, - tokio_handle: &TokioHandle, + tokio_handle: &tokio::runtime::Handle, ) -> ReadCompletion { let Self { plan, diff --git a/cache2/src/region/record/codec.rs b/cache2/src/region/record/codec.rs index a9f6538..6b48b6d 100644 --- a/cache2/src/region/record/codec.rs +++ b/cache2/src/region/record/codec.rs @@ -19,7 +19,6 @@ //! no allocation. Payload preparation computes the CRC before the append //! transaction copies the borrowed key and value into staging. -use std::error::Error as StdError; use std::fmt; use hashcrew::xxhash::xxh3_64_with_seed; @@ -77,8 +76,8 @@ impl fmt::Display for RecordEncodeError { } } -impl StdError for RecordEncodeError { - fn source(&self) -> Option<&(dyn StdError + 'static)> { +impl std::error::Error for RecordEncodeError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::InvalidLocation(error) => Some(error), _ => None, diff --git a/cache2/src/region/recovery/metadata.rs b/cache2/src/region/recovery/metadata.rs index 2f4de0f..cd68bcf 100644 --- a/cache2/src/region/recovery/metadata.rs +++ b/cache2/src/region/recovery/metadata.rs @@ -18,7 +18,6 @@ //! manager or index mapping becomes visible. Index slots remain independently //! lazy-validated; this section contains only O(regions + index partitions) state. -use std::error::Error as StdError; use std::fmt; use std::mem; use std::result; @@ -233,7 +232,7 @@ impl fmt::Display for RegionMetadataError { } } -impl StdError for RegionMetadataError {} +impl std::error::Error for RegionMetadataError {} type Result = result::Result; diff --git a/cache2/src/region/runtime/mod.rs b/cache2/src/region/runtime/mod.rs index e83d053..85046ea 100644 --- a/cache2/src/region/runtime/mod.rs +++ b/cache2/src/region/runtime/mod.rs @@ -20,16 +20,10 @@ //! device path. A fixed age deadline publishes partial batches without adding //! a durability sync; CLEAN remains the only steady-state durability boundary. -#[cfg(test)] -use std::env; -#[cfg(test)] -use std::fs; use std::io; use std::mem; use std::panic; use std::panic::AssertUnwindSafe; -#[cfg(test)] -use std::process; use std::sync::Arc; use std::sync::Condvar; use std::sync::Mutex; @@ -37,7 +31,6 @@ use std::sync::MutexGuard; use std::sync::atomic::AtomicU8; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; -use std::thread; use std::thread::JoinHandle; use std::time::Duration; use std::time::Instant; @@ -45,13 +38,9 @@ use std::time::Instant; use asyncband::semaphore::OwnedSemaphorePermit; use asyncband::semaphore::Semaphore; use asyncband::watch; -use tokio::runtime::Handle as TokioHandle; -#[cfg(test)] -use tokio::task; use self::metrics::RuntimeMetrics; use crate::config::CacheConfig; -use crate::config::IoEngine as ConfiguredIoEngine; use crate::config::IoMode; use crate::config::IoPoolTopology; #[cfg(test)] @@ -369,7 +358,7 @@ impl PendingGet { } } - async fn wait_async(self, tokio_handle: &TokioHandle) -> CompletedGet { + async fn wait_async(self, tokio_handle: &tokio::runtime::Handle) -> CompletedGet { let Self { engine, read, @@ -385,7 +374,7 @@ impl PendingGet { } impl WaitingGet { - async fn reserve_async(self, tokio_handle: &TokioHandle) -> io::Result { + async fn reserve_async(self, tokio_handle: &tokio::runtime::Handle) -> io::Result { let Self { engine, slot_waiter, @@ -927,7 +916,7 @@ impl RegionDataPlane { pub async fn get_async( &self, key: &[u8], - tokio_handle: &TokioHandle, + tokio_handle: &tokio::runtime::Handle, ) -> io::Result> { match self.prepare_get(key)? { PreparedGet::Complete(value) => Ok(value), @@ -1488,7 +1477,7 @@ fn start_running( })?; for shard_id in 0..shard_count { let worker_shared = Arc::clone(&shared); - match thread::Builder::new() + match std::thread::Builder::new() .name(format!("cache2-shard-{shard_id}")) .stack_size(CACHE_THREAD_STACK_BYTES) .spawn(move || shard_worker(worker_shared, shard_id)) @@ -1511,7 +1500,7 @@ fn start_running( } for (worker_id, buffer) in reclaim_buffers.into_iter().enumerate() { let reclaim_shared = Arc::clone(&shared); - match thread::Builder::new() + match std::thread::Builder::new() .name(format!("cache2-reclaim-{worker_id}")) .stack_size(CACHE_THREAD_STACK_BYTES) .spawn(move || reclaim_worker(reclaim_shared, buffer, worker_id, reclaim_worker_count)) @@ -1558,7 +1547,7 @@ fn build_engine_pool( engines .try_reserve_exact(engine_count) .map_err(|_| io::Error::new(io::ErrorKind::OutOfMemory, "cannot allocate I/O workers"))?; - let posix_workers = if matches!(config.io_engine, ConfiguredIoEngine::Posix(_)) { + let posix_workers = if matches!(config.io_engine, crate::config::IoEngine::Posix(_)) { topology.max_in_flight } else { 1 @@ -2110,7 +2099,7 @@ fn stop_running(mut owner: RunningOwner) -> io::Result { fn reap_engine_after_target_fence(engine: &Arc) { let reaper_engine = Arc::clone(engine); - let spawn = thread::Builder::new() + let spawn = std::thread::Builder::new() .name("cache2-io-reaper".to_owned()) .stack_size(CACHE_THREAD_STACK_BYTES) .spawn(move || { @@ -2197,7 +2186,10 @@ mod tests { #[test] fn read_lane_uses_one_bounded_alternate_on_primary_pressure() { let id = LANE_TEST_ID.fetch_add(1, Ordering::Relaxed); - let path = env::temp_dir().join(format!("cache2-read-lane-{}-{id}.cache", process::id())); + let path = std::env::temp_dir().join(format!( + "cache2-read-lane-{}-{id}.cache", + std::process::id() + )); let backend: Arc = Arc::new(FileBackend::open(&path).unwrap()); let engines: Box<[Arc]> = vec![ Arc::new(BackendIoEngine::new(Arc::clone(&backend), 1).unwrap()) as Arc, @@ -2230,15 +2222,15 @@ mod tests { } drop(engines); drop(backend); - fs::remove_file(path).unwrap(); + std::fs::remove_file(path).unwrap(); } #[test] fn hot_read_route_rotates_pressure_fallback_across_all_lanes() { let id = LANE_TEST_ID.fetch_add(1, Ordering::Relaxed); - let path = env::temp_dir().join(format!( + let path = std::env::temp_dir().join(format!( "cache2-read-lane-rotation-{}-{id}.cache", - process::id() + std::process::id() )); let backend: Arc = Arc::new(FileBackend::open(&path).unwrap()); let engines: Box<[Arc]> = (0..4) @@ -2264,7 +2256,7 @@ mod tests { } drop(engines); drop(backend); - fs::remove_file(path).unwrap(); + std::fs::remove_file(path).unwrap(); } #[test] @@ -2296,13 +2288,13 @@ mod tests { let mutation = gate.try_enter().unwrap(); let drain = gate.begin_drain().unwrap(); let closing_gate = Arc::clone(&gate); - let close = thread::spawn(move || { + let close = std::thread::spawn(move || { closing_gate.start_close(); closing_gate.wait_quiescent().unwrap(); }); while gate.state.load(Ordering::Acquire) & MUTATION_CLOSED == 0 { - thread::yield_now(); + std::thread::yield_now(); } drop(mutation); drain.wait().unwrap(); @@ -2338,7 +2330,7 @@ mod tests { let drain = drain_gate.begin_drain().unwrap(); drain.wait_async().await; }); - task::yield_now().await; + tokio::task::yield_now().await; assert!(gate.try_enter().is_none()); drop(mutation); @@ -2355,7 +2347,7 @@ mod tests { let drain = drain_gate.begin_drain().unwrap(); drain.wait_async().await; }); - task::yield_now().await; + tokio::task::yield_now().await; assert!(gate.try_enter().is_none()); drain.abort(); @@ -2439,7 +2431,7 @@ mod tests { for _ in 0..2 { let control = Arc::clone(&control); let ready = Arc::clone(&ready); - workers.push(thread::spawn(move || { + workers.push(std::thread::spawn(move || { let mut observed_generation = 0; ready.wait(); let notified = control.wait(&mut observed_generation).unwrap(); @@ -2481,8 +2473,10 @@ mod tests { use crate::region::store::RegionStore; let id = LANE_TEST_ID.fetch_add(1, Ordering::Relaxed); - let path = - env::temp_dir().join(format!("cache2-completion-timeout-{}-{id}", process::id())); + let path = std::env::temp_dir().join(format!( + "cache2-completion-timeout-{}-{id}", + std::process::id() + )); let files = RegionFiles::new( path.with_extension("cache"), path.with_extension("state"), @@ -2559,8 +2553,8 @@ mod tests { assert_eq!(snapshot.l2_read_overloads, 1); } } - fs::remove_file(files.data).unwrap(); - fs::remove_file(files.state).unwrap(); + std::fs::remove_file(files.data).unwrap(); + std::fs::remove_file(files.state).unwrap(); } #[test] diff --git a/cache2/src/region/runtime/shutdown_tests.rs b/cache2/src/region/runtime/shutdown_tests.rs index 3ec8eac..7d5eff9 100644 --- a/cache2/src/region/runtime/shutdown_tests.rs +++ b/cache2/src/region/runtime/shutdown_tests.rs @@ -208,11 +208,11 @@ fn assert_close_does_not_wait_for_read(submit_before_close: bool) { use crate::region::RegionFiles; use crate::region::recovery::PersistentId; use crate::region::store::RegionStore; - let root = env::temp_dir().join(format!( + let root = std::env::temp_dir().join(format!( "cache2-close-race-{}-{submit_before_close}", - process::id() + std::process::id() )); - fs::create_dir_all(&root).unwrap(); + std::fs::create_dir_all(&root).unwrap(); let files = RegionFiles::new(root.join("data"), root.join("state"), root.join("image")); let data = DataSuperblock { generation: 1, @@ -229,7 +229,7 @@ fn assert_close_does_not_wait_for_read(submit_before_close: bool) { let config = RuntimeOptions { append_shards: 1, l1_capacity_bytes: 0, - io_engine: ConfiguredIoEngine::Posix(PosixIoConfig::new(1, 1, 1)), + io_engine: crate::config::IoEngine::Posix(PosixIoConfig::new(1, 1, 1)), ..RuntimeOptions::default() }; let mut store = RegionStore::open( @@ -259,7 +259,7 @@ fn assert_close_does_not_wait_for_read(submit_before_close: bool) { shared.shards = Box::new([]); let shared = Arc::clone(&plane.shared); let (tx, rx) = mpsc::channel(); - let thread = thread::spawn(move || { + let thread = std::thread::spawn(move || { let result = stop_running(RunningOwner { shared, shard_workers: vec![], @@ -272,7 +272,7 @@ fn assert_close_does_not_wait_for_read(submit_before_close: bool) { thread.join().unwrap(); engine.shutdown().unwrap(); assert!(!engine.inject.load(Ordering::Acquire)); - fs::remove_dir_all(root).unwrap(); + std::fs::remove_dir_all(root).unwrap(); assert!( matches!(result, Ok(Ok(false))), "close synchronously joined a blocked read" diff --git a/cache2/src/region/staging.rs b/cache2/src/region/staging.rs index d343551..1722714 100644 --- a/cache2/src/region/staging.rs +++ b/cache2/src/region/staging.rs @@ -21,8 +21,6 @@ use std::mem; use std::mem::size_of; use std::sync::Mutex; use std::sync::MutexGuard; -#[cfg(test)] -use std::thread; use crate::io::backend::DIRECT_IO_ALIGNMENT; use crate::io::engine::IoBuffer; @@ -1111,7 +1109,7 @@ mod tests { let (entered_tx, entered_rx) = mpsc::sync_channel(0); let (release_tx, release_rx) = mpsc::sync_channel(0); let encoder_staging = Arc::clone(&staging); - let encoder = thread::spawn(move || { + let encoder = std::thread::spawn(move || { encoder_staging.encode_reserved(receipt, |target| { entered_tx.send(()).unwrap(); release_rx.recv().unwrap(); diff --git a/examples/src/logforth.rs b/examples/src/logforth.rs index 29bc62d..2aa421d 100644 --- a/examples/src/logforth.rs +++ b/examples/src/logforth.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::env; use std::io; use cache2::Cache; @@ -28,7 +27,7 @@ use logforth::layout::JsonLayout; async fn main() -> io::Result<()> { init_logforth(); - let path = env::args_os().nth(1).ok_or_else(|| { + let path = std::env::args_os().nth(1).ok_or_else(|| { io::Error::new( io::ErrorKind::InvalidInput, "usage: logforth ", diff --git a/tests-integration/tests/cache.rs b/tests-integration/tests/cache.rs index ab267ef..2e2dd40 100644 --- a/tests-integration/tests/cache.rs +++ b/tests-integration/tests/cache.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::env; use std::fs; use std::fs::OpenOptions; use std::io; @@ -22,14 +21,12 @@ use std::io::SeekFrom; use std::io::Write; use std::path::Path; use std::path::PathBuf; -use std::process; use std::sync::Arc; use std::sync::Barrier; use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicU64; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; -use std::thread; use std::time::Duration; use std::time::Instant; @@ -54,8 +51,6 @@ use cache2::RuntimeOptions; use cache2::StartupMode; use cache2::StorageLayout; use cache2::StorageOptions; -use tokio::runtime::Builder as TokioRuntimeBuilder; -use tokio::runtime::Handle as TokioHandle; static NEXT_FILE: AtomicU64 = AtomicU64::new(1); @@ -104,7 +99,8 @@ struct TestCache { impl TestCache { fn new(name: &str) -> Self { let id = NEXT_FILE.fetch_add(1, Ordering::Relaxed); - let data = env::temp_dir().join(format!("cache2-{name}-{}-{id}.cache", process::id())); + let data = + std::env::temp_dir().join(format!("cache2-{name}-{}-{id}.cache", std::process::id())); Self { data } } @@ -169,7 +165,7 @@ fn eventually_admitted(mut put: impl FnMut() -> Result) -> T { Instant::now() < deadline, "write buffer did not make progress" ); - thread::yield_now(); + std::thread::yield_now(); } Err(error) => panic!("cache write failed: {error}"), } @@ -205,19 +201,21 @@ async fn completed_reclaim_snapshot(cache: &Cache) -> DetailedCacheSnapshot { return detailed; } assert!(Instant::now() < deadline, "reclaim did not make progress"); - thread::yield_now(); + std::thread::yield_now(); } } #[test] fn explicit_tokio_handle_works_from_a_runtime_without_time_enabled() { let files = TestCache::new("explicit-tokio-handle"); - let cache_runtime = TokioRuntimeBuilder::new_multi_thread() + let cache_runtime = tokio::runtime::Builder::new_multi_thread() .worker_threads(2) .enable_time() .build() .unwrap(); - let caller_runtime = TokioRuntimeBuilder::new_current_thread().build().unwrap(); + let caller_runtime = tokio::runtime::Builder::new_current_thread() + .build() + .unwrap(); let config = test_config(1); let minimum_memory_bytes = config.minimum_memory_bytes(); let config = CacheConfig::new( @@ -322,7 +320,7 @@ async fn warm_close_fences_concurrent_arc_mutations() { let writer_cache = Arc::clone(&cache); let ready = Arc::new(Barrier::new(2)); let writer_ready = Arc::clone(&ready); - let writer = thread::spawn(move || { + let writer = std::thread::spawn(move || { writer_ready.wait(); let mut accepted = Vec::new(); for ordinal in 0_u64..256 { @@ -332,7 +330,7 @@ async fn warm_close_fences_concurrent_arc_mutations() { accepted.push(ordinal); break; } - Err(error) if error.kind() == ErrorKind::Overloaded => thread::yield_now(), + Err(error) if error.kind() == ErrorKind::Overloaded => std::thread::yield_now(), Err(error) if error.kind() == ErrorKind::Unavailable => return accepted, Err(error) => panic!("concurrent cache write failed: {error}"), } @@ -730,9 +728,9 @@ async fn concurrent_mixed_mutations_never_return_wrong_key_or_future_values() { let writers_left = AtomicUsize::new(WRITERS); let start = AtomicBool::new(false); let hits = AtomicU64::new(0); - let runtime = TokioHandle::current(); + let runtime = tokio::runtime::Handle::current(); - thread::scope(|scope| { + std::thread::scope(|scope| { for writer in 0..WRITERS { let cache = &cache; let keys = &keys; @@ -741,7 +739,7 @@ async fn concurrent_mixed_mutations_never_return_wrong_key_or_future_values() { let start = &start; scope.spawn(move || { while !start.load(Ordering::Acquire) { - thread::yield_now(); + std::thread::yield_now(); } let mut value = vec![0_u8; *VALUE_SIZES.iter().max().unwrap()]; for ordinal in 0..WRITES_PER_CLIENT { @@ -776,7 +774,7 @@ async fn concurrent_mixed_mutations_never_return_wrong_key_or_future_values() { let runtime = runtime.clone(); scope.spawn(move || { while !start.load(Ordering::Acquire) { - thread::yield_now(); + std::thread::yield_now(); } let mut ordinal = reader; while writers_left.load(Ordering::Acquire) != 0 { diff --git a/tests-integration/tests/error.rs b/tests-integration/tests/error.rs index 74a8005..14955d9 100644 --- a/tests-integration/tests/error.rs +++ b/tests-integration/tests/error.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::error::Error as _; use std::io; use cache2::Error; @@ -28,7 +27,7 @@ fn storage_construction_errors_expose_structured_context() { assert_eq!(error.operation(), ErrorOperation::BuildStorage); assert_eq!(error.io_kind(), io::ErrorKind::InvalidInput); assert!(error.raw_os_error().is_none()); - assert!(error.source().is_some()); + assert!(std::error::Error::source(&error).is_some()); assert!(error.to_string().contains("build_storage")); } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 7d9046f..68beaf3 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -12,13 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::env; use std::ffi::OsStr; use std::ffi::OsString; -use std::iter; use std::path::Path; -use std::process; -use std::process::Command as StdCommand; use cargo_metadata::Metadata; use cargo_metadata::MetadataCommand; @@ -89,7 +85,7 @@ impl CommandBench { run(self.command()); } - fn command(self) -> StdCommand { + fn command(self) -> std::process::Command { let mut command = cargo(); command.args(["bench", "--package", "benchmarks"]); command.args(self.cargo_args); @@ -184,7 +180,7 @@ impl CommandLint { command_run("taplo", ["format", "--check"]); command_run("hawkeye", ["check"]); } - command_run("typos", iter::empty::<&str>()); + command_run("typos", std::iter::empty::<&str>()); let mut docs = nightly_cargo(); docs.env("RUSTDOCFLAGS", "-D warnings -D missing_docs --cfg docsrs"); @@ -235,15 +231,15 @@ impl CommandTest { } } -fn cargo() -> StdCommand { - let executable = env::var_os("CARGO").unwrap_or_else(|| OsString::from("cargo")); - let mut command = StdCommand::new(executable); +fn cargo() -> std::process::Command { + let executable = std::env::var_os("CARGO").unwrap_or_else(|| OsString::from("cargo")); + let mut command = std::process::Command::new(executable); command.current_dir(Path::new(env!("CARGO_WORKSPACE_DIR"))); command } -fn nightly_cargo() -> StdCommand { - let mut command = StdCommand::new("rustup"); +fn nightly_cargo() -> std::process::Command { + let mut command = std::process::Command::new("rustup"); command .args(["run", "nightly", "cargo"]) .current_dir(Path::new(env!("CARGO_WORKSPACE_DIR"))); @@ -265,25 +261,25 @@ where I: IntoIterator, S: AsRef, { - let mut command = StdCommand::new(executable); + let mut command = std::process::Command::new(executable); command .current_dir(Path::new(env!("CARGO_WORKSPACE_DIR"))) .args(args); run(command); } -fn run(mut command: StdCommand) { +fn run(mut command: std::process::Command) { println!("{command:?}"); match command.status() { Ok(status) if status.success() => {} - Ok(status) => process::exit(status.code().unwrap_or(1)), + Ok(status) => std::process::exit(status.code().unwrap_or(1)), Err(error) => fail(&format!("failed to run {command:?}: {error}")), } } fn fail(message: &str) -> ! { eprintln!("{message}"); - process::exit(2) + std::process::exit(2) } #[cfg(test)] From 51f3b026ec749f276a7ab0eb882ec85ab53f1292 Mon Sep 17 00:00:00 2001 From: tison Date: Wed, 9 Sep 2026 19:38:25 +0800 Subject: [PATCH 05/14] refactor: spell out result error types Remove the public cache2::Result alias and the private recovery metadata Result alias. Name the error type at each return boundary, and update caller examples and migration notes while preserving the underlying error types. --- CHANGELOG.md | 3 +- README.md | 4 +- cache2/ERRORS.md | 6 +- cache2/src/cache.rs | 30 +++++----- cache2/src/config/runtime.rs | 7 ++- cache2/src/config/storage.rs | 4 +- cache2/src/error.rs | 4 -- cache2/src/lib.rs | 1 - cache2/src/region/recovery/metadata.rs | 77 ++++++++++++++------------ tests-integration/tests/cache.rs | 4 +- 10 files changed, 73 insertions(+), 67 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1aa5952..895b346 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ ### Breaking Changes -- Error types are exported only from the crate root. Replace imports from `cache2::error` with `cache2::{Error, ErrorKind, ErrorOperation, Result}`. +- Error types are exported only from the crate root. Replace imports from `cache2::error` with `cache2::{Error, ErrorKind, ErrorOperation}`. +- The `cache2::Result` alias is removed. Use the standard `Result` with `Error` imported from `cache2`; public operation error types are unchanged. - Configuration now separates editable `StorageOptions` / `RuntimeOptions` from immutable `StorageLayout` / `CacheConfig`. Build the layout, construct `CacheConfig::new(layout, options)`, and call `Cache::open(path, config)` or `Cache::open_with_handle(path, config, handle)`. `StaticConfig`, `RuntimeConfig`, `CacheBuilder`, and the standalone `validate` method are removed. - `ReadAdmission::Immediate` and `ReadAdmission::Wait { timeout, max_waiters }` replace the separate read-wait setters. Waiting requires a positive timeout; an omitted waiter bound follows the selected read execution capacity. - Configuration errors identify `ErrorOperation::BuildStorage` or `BuildConfig`. `StorageLayout::peak_disk_bytes()` is now an infallible query. diff --git a/README.md b/README.md index 2f4a7db..d75f23d 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,9 @@ C² (`cache2`) provides bounded, disposable acceleration for large file chunks. ## Quick start ```rust -use cache2::{Cache, CacheConfig, ErrorKind, Result, RuntimeOptions, StorageOptions}; +use cache2::{Cache, CacheConfig, Error, ErrorKind, RuntimeOptions, StorageOptions}; -async fn run() -> Result<()> { +async fn run() -> Result<(), Error> { let storage = StorageOptions::new(1024 * 1024 * 1024).build()?; let config = CacheConfig::new(storage, RuntimeOptions::default())?; let cache = Cache::open("/var/tmp/cache2.data", config).await?; diff --git a/cache2/ERRORS.md b/cache2/ERRORS.md index d05a166..c30512c 100644 --- a/cache2/ERRORS.md +++ b/cache2/ERRORS.md @@ -1,6 +1,6 @@ # Error handling -C² separates normal cache outcomes from failures. A miss, stale hit, L1 bypass, eviction, rejected recovery image, or transition to read miss-only mode is not an error. Public operations return `cache2::Result`, whose error type is `cache2::Error`. +C² separates normal cache outcomes from failures. A miss, stale hit, L1 bypass, eviction, rejected recovery image, or transition to read miss-only mode is not an error. Public operations return `Result`, where `Error` is the C² error type. An error has three independent pieces of information: @@ -15,9 +15,9 @@ Display text is intended for people and logs. Do not parse it or use it as a met `Overloaded` is an expected result of bounded admission. A lookaside-cache caller should normally continue through its authoritative data path or perform a bounded retry rather than fail the application request. ```rust -use cache2::{Cache, ErrorKind, Result}; +use cache2::{Cache, Error, ErrorKind}; -fn cache_value(cache: &Cache, key: &[u8], value: &[u8]) -> Result<()> { +fn cache_value(cache: &Cache, key: &[u8], value: &[u8]) -> Result<(), Error> { match cache.put(key, value) { Ok(_sequence) => Ok(()), Err(error) if error.kind() == ErrorKind::Overloaded => { diff --git a/cache2/src/cache.rs b/cache2/src/cache.rs index 1a47a9e..ebb24dd 100644 --- a/cache2/src/cache.rs +++ b/cache2/src/cache.rs @@ -39,8 +39,8 @@ use crate::config::CacheConfig; use crate::config::KEY_HASH_SEED; use crate::config::storage_fingerprint; use crate::config::storage_geometry; +use crate::error::Error; use crate::error::ErrorOperation; -use crate::error::Result; use crate::error::from_io; use crate::region::FileRegionBackend; use crate::region::HybridValueRead; @@ -143,7 +143,7 @@ impl Cache { /// Returns [`ErrorOperation::Open`] for file locking, recovery, allocation, /// device support, runtime binding, or worker startup failures. Configuration /// has already been checked by [`CacheConfig::new`]. - pub async fn open(path: impl AsRef, config: CacheConfig) -> Result { + pub async fn open(path: impl AsRef, config: CacheConfig) -> Result { let handle = tokio::runtime::Handle::try_current().map_err(|error| { from_io( ErrorOperation::Open, @@ -164,7 +164,7 @@ impl Cache { path: impl AsRef, config: CacheConfig, tokio_handle: tokio::runtime::Handle, - ) -> Result { + ) -> Result { let path = path.as_ref().to_path_buf(); let cache_handle = tokio_handle.clone(); let started = Instant::now(); @@ -276,7 +276,7 @@ impl Cache { /// admission is busy. Returns [`ErrorKind::Unavailable`](crate::ErrorKind::Unavailable) after /// close starts. Runtime and device failures use their corresponding structured /// classifications. - pub fn put(&self, key: impl AsRef<[u8]>, value: impl AsRef<[u8]>) -> Result { + pub fn put(&self, key: impl AsRef<[u8]>, value: impl AsRef<[u8]>) -> Result { self.ensure_open(ErrorOperation::Put)?; public_result( ErrorOperation::Put, @@ -296,7 +296,7 @@ impl Cache { /// Uses the same input, overload, runtime, and device classifications as /// [`Self::put`], including unavailable after close starts, with /// [`ErrorOperation::PutL2`](crate::ErrorOperation::PutL2) as its context. - pub fn put_l2(&self, key: impl AsRef<[u8]>, value: impl AsRef<[u8]>) -> Result { + pub fn put_l2(&self, key: impl AsRef<[u8]>, value: impl AsRef<[u8]>) -> Result { self.ensure_open(ErrorOperation::PutL2)?; public_result( ErrorOperation::PutL2, @@ -315,7 +315,7 @@ impl Cache { /// [`ErrorKind::Overloaded`](crate::ErrorKind::Overloaded) when bounded mutation admission is /// busy. Returns [`ErrorKind::Unavailable`](crate::ErrorKind::Unavailable) after close starts. /// Runtime and device failures remain explicit. - pub fn delete(&self, key: impl AsRef<[u8]>) -> Result { + pub fn delete(&self, key: impl AsRef<[u8]>) -> Result { self.ensure_open(ErrorOperation::Delete)?; public_result(ErrorOperation::Delete, self.data_plane.delete(key.as_ref())) } @@ -335,7 +335,7 @@ impl Cache { /// when waiting is enabled. Cache data and device failures that can safely fail /// open transition reads to misses instead of surfacing an application /// error. - pub async fn get(&self, key: impl AsRef<[u8]> + Send) -> Result> { + pub async fn get(&self, key: impl AsRef<[u8]> + Send) -> Result, Error> { if self.is_closed() { return Ok(None); } @@ -357,7 +357,7 @@ impl Cache { /// Returns [`ErrorKind::Overloaded`](crate::ErrorKind::Overloaded) if another drain is active, /// or [`ErrorKind::Unavailable`](crate::ErrorKind::Unavailable) after close starts. /// Accepted work that cannot complete returns a structured runtime/device failure. - pub async fn drain(&self) -> Result<()> { + pub async fn drain(&self) -> Result<(), Error> { self.ensure_open(ErrorOperation::Drain)?; public_result(ErrorOperation::Drain, self.data_plane.drain_async().await) } @@ -370,7 +370,7 @@ impl Cache { /// /// Returns [`ErrorKind::Unavailable`](crate::ErrorKind::Unavailable) after close starts, or a /// structured runtime failure if the snapshot cannot be read. - pub fn snapshot(&self) -> Result { + pub fn snapshot(&self) -> Result { self.ensure_open(ErrorOperation::Snapshot)?; let mut snapshot = public_result(ErrorOperation::Snapshot, self.data_plane.snapshot())?; snapshot.logical_disk_peak_bytes = self.logical_disk_peak_bytes; @@ -387,7 +387,7 @@ impl Cache { /// Returns [`ErrorKind::Unavailable`](crate::ErrorKind::Unavailable) after close starts, or a /// structured runtime failure if any diagnostic partition cannot be /// sampled. - pub fn detailed_snapshot(&self) -> Result { + pub fn detailed_snapshot(&self) -> Result { self.ensure_open(ErrorOperation::DetailedSnapshot)?; let mut snapshot = public_result( ErrorOperation::DetailedSnapshot, @@ -407,7 +407,7 @@ impl Cache { /// Returns [`ErrorKind::Unavailable`](crate::ErrorKind::Unavailable) if close already started, /// or a structured runtime, worker, or filesystem failure with /// [`ErrorOperation::CloseFast`](crate::ErrorOperation::CloseFast). - pub fn close_fast(&self) -> impl Future> + Send + 'static { + pub fn close_fast(&self) -> impl Future> + Send + 'static { self.close(false) } @@ -423,12 +423,12 @@ impl Cache { /// or a structured runtime, worker, filesystem, or device failure with /// [`ErrorOperation::CloseWarm`](crate::ErrorOperation::CloseWarm). A failed warm close does /// not publish a recoverable image. - pub fn close_warm(&self) -> impl Future> + Send + 'static { + pub fn close_warm(&self) -> impl Future> + Send + 'static { self.close(true) } #[inline(always)] - fn ensure_open(&self, operation: ErrorOperation) -> Result<()> { + fn ensure_open(&self, operation: ErrorOperation) -> Result<(), Error> { if self.is_closed() { return public_result(operation, Err(cache_closed_error())); } @@ -440,7 +440,7 @@ impl Cache { self.closed.load(Ordering::Acquire) } - fn close(&self, warm: bool) -> impl Future> + Send + 'static { + fn close(&self, warm: bool) -> impl Future> + Send + 'static { let (operation, mode) = if warm { (ErrorOperation::CloseWarm, "warm") } else { @@ -566,7 +566,7 @@ fn sidecar_path(path: &Path, suffix: &str) -> PathBuf { PathBuf::from(value) } -fn public_result(operation: ErrorOperation, result: io::Result) -> Result { +fn public_result(operation: ErrorOperation, result: io::Result) -> Result { result.map_err(|error| from_io(operation, error)) } diff --git a/cache2/src/config/runtime.rs b/cache2/src/config/runtime.rs index 7eb4a5a..8cf47b3 100644 --- a/cache2/src/config/runtime.rs +++ b/cache2/src/config/runtime.rs @@ -18,8 +18,8 @@ use std::time::Duration; use crate::config::CacheConfig; use crate::config::StorageLayout; +use crate::error::Error; use crate::error::ErrorOperation; -use crate::error::Result; use crate::error::from_io; use crate::io::engine::IO_QUEUE_ENTRY_RESERVATION_BYTES; use crate::io::engine::MAX_IO_REQUESTS_PER_ENGINE; @@ -482,7 +482,8 @@ impl CacheConfig { /// Checks the complete combination and resolves dependent runtime defaults. /// /// ```no_run - /// # async fn example() -> cache2::Result<()> { + /// # use cache2::Error; + /// # async fn example() -> Result<(), Error> { /// use cache2::Cache; /// use cache2::CacheConfig; /// use cache2::RuntimeOptions; @@ -502,7 +503,7 @@ impl CacheConfig { /// Returns [`ErrorOperation::BuildConfig`] for incompatible Region/shard /// counts, invalid runtime settings, unavailable build/platform features, /// or insufficient managed memory. Device capabilities are checked at open. - pub fn new(storage: StorageLayout, mut runtime: RuntimeOptions) -> Result { + pub fn new(storage: StorageLayout, mut runtime: RuntimeOptions) -> Result { let build = || -> io::Result { let geometry = storage.geometry; let index_slots = storage.index_slots; diff --git a/cache2/src/config/storage.rs b/cache2/src/config/storage.rs index 4fbf1e7..dce0354 100644 --- a/cache2/src/config/storage.rs +++ b/cache2/src/config/storage.rs @@ -21,8 +21,8 @@ use crate::config::CacheConfig; #[cfg(test)] use crate::config::RuntimeOptions; use crate::config::StorageLayout; +use crate::error::Error; use crate::error::ErrorOperation; -use crate::error::Result; use crate::error::from_io; use crate::region::index::MAX_PACKED_REGION_COUNT; use crate::region::index::MAX_PACKED_REGION_SIZE; @@ -77,7 +77,7 @@ impl StorageOptions { /// Returns [`ErrorOperation::BuildStorage`] if the geometry or index cannot /// be represented, disk accounting overflows, or bounded layout allocation /// fails. - pub fn build(self) -> Result { + pub fn build(self) -> Result { let build = || { let entries = match self.expected_entries { Some(entries) => entries, diff --git a/cache2/src/error.rs b/cache2/src/error.rs index e803bf7..f8a7641 100644 --- a/cache2/src/error.rs +++ b/cache2/src/error.rs @@ -14,10 +14,6 @@ use std::fmt; use std::io; -use std::result; - -/// A result returned by a public C² operation. -pub type Result = result::Result; /// Stable, actionable classification for a C² failure. /// diff --git a/cache2/src/lib.rs b/cache2/src/lib.rs index 8b19da3..2df51a5 100644 --- a/cache2/src/lib.rs +++ b/cache2/src/lib.rs @@ -26,7 +26,6 @@ mod error; pub use self::error::Error; pub use self::error::ErrorKind; pub use self::error::ErrorOperation; -pub use self::error::Result; mod cache; pub use self::cache::Cache; diff --git a/cache2/src/region/recovery/metadata.rs b/cache2/src/region/recovery/metadata.rs index cd68bcf..091a5cb 100644 --- a/cache2/src/region/recovery/metadata.rs +++ b/cache2/src/region/recovery/metadata.rs @@ -20,7 +20,6 @@ use std::fmt; use std::mem; -use std::result; use crate::checksum::Crc32c; use crate::region::index::MAX_INDEX_PARTITIONS; @@ -234,10 +233,8 @@ impl fmt::Display for RegionMetadataError { impl std::error::Error for RegionMetadataError {} -type Result = result::Result; - impl RegionMetadata { - pub fn encoded_len(&self) -> Result { + pub fn encoded_len(&self) -> Result { encoded_len_for_counts(self.root.region_count, self.root.partition_count) } @@ -264,7 +261,7 @@ impl RegionMetadata { && encoded_len == image.region_table_len } - pub fn encode(&self) -> Result> { + pub fn encode(&self) -> Result, RegionMetadataError> { self.validate()?; let layout = MetadataLayout::new(self.root.region_count, self.root.partition_count)?; let encoded_len = usize::try_from(layout.encoded_len) @@ -323,7 +320,7 @@ impl RegionMetadata { } #[cfg(test)] - pub fn decode(input: &[u8]) -> Result { + pub fn decode(input: &[u8]) -> Result { let metadata = Self::decode_pages(input)?; metadata.validate()?; Ok(metadata) @@ -331,14 +328,14 @@ impl RegionMetadata { /// Decodes an owned image and releases its encoded pages before allocating /// the queue-validation workspaces used by [`Self::validate`]. - pub fn decode_owned(input: Vec) -> Result { + pub fn decode_owned(input: Vec) -> Result { let metadata = Self::decode_pages(&input)?; drop(input); metadata.validate()?; Ok(metadata) } - fn decode_pages(input: &[u8]) -> Result { + fn decode_pages(input: &[u8]) -> Result { if input.len() < REGION_METADATA_PAGE_SIZE || !input.len().is_multiple_of(REGION_METADATA_PAGE_SIZE) { @@ -430,7 +427,7 @@ impl RegionMetadata { }) } - pub fn validate(&self) -> Result<()> { + pub fn validate(&self) -> Result<(), RegionMetadataError> { let layout = MetadataLayout::new(self.root.region_count, self.root.partition_count)?; validate_root_directory(self.root, layout)?; if self.regions.len() != self.root.region_count as usize { @@ -450,7 +447,7 @@ impl RegionMetadata { /// Shrinking seals excess Active Regions at the back of the sealed FIFO. /// Growing activates Regions from the back of the free FIFO so its existing /// rotation order remains stable. No index or Region data needs rewriting. - pub fn rebind_append_shards(&mut self, shard_count: u32) -> Result<()> { + pub fn rebind_append_shards(&mut self, shard_count: u32) -> Result<(), RegionMetadataError> { let old_shard_count = self.root.shard_count; if shard_count == old_shard_count { return Ok(()); @@ -531,7 +528,7 @@ struct MetadataLayout { } impl MetadataLayout { - fn new(region_count: u32, partition_count: u32) -> Result { + fn new(region_count: u32, partition_count: u32) -> Result { if region_count == 0 || partition_count == 0 { return Err(RegionMetadataError::InvalidField("record_count")); } @@ -563,11 +560,14 @@ impl MetadataLayout { } } -fn encoded_len_for_counts(region_count: u32, partition_count: u32) -> Result { +fn encoded_len_for_counts( + region_count: u32, + partition_count: u32, +) -> Result { Ok(MetadataLayout::new(region_count, partition_count)?.encoded_len) } -fn pages_for_records(records: u64, per_page: u64) -> Result { +fn pages_for_records(records: u64, per_page: u64) -> Result { let pages = records .checked_add(per_page - 1) .ok_or(RegionMetadataError::ArithmeticOverflow)? @@ -575,7 +575,10 @@ fn pages_for_records(records: u64, per_page: u64) -> Result { u32::try_from(pages).map_err(|_| RegionMetadataError::ArithmeticOverflow) } -fn validate_root_directory(root: RegionMetadataRoot, layout: MetadataLayout) -> Result<()> { +fn validate_root_directory( + root: RegionMetadataRoot, + layout: MetadataLayout, +) -> Result<(), RegionMetadataError> { if root.index_slots < 8 { return Err(RegionMetadataError::InvalidField("root")); } @@ -620,7 +623,10 @@ fn validate_root_directory(root: RegionMetadataRoot, layout: MetadataLayout) -> Ok(()) } -fn validate_encoded_root_directory(input: &[u8], layout: MetadataLayout) -> Result<()> { +fn validate_encoded_root_directory( + input: &[u8], + layout: MetadataLayout, +) -> Result<(), RegionMetadataError> { if get_u32(input, ROOT_REGION_FIRST_PAGE_OFFSET)? != layout.region_first_page || get_u32(input, ROOT_REGION_PAGE_COUNT_OFFSET)? != layout.region_page_count || get_u32(input, ROOT_PARTITION_FIRST_PAGE_OFFSET)? != layout.partition_first_page @@ -631,7 +637,10 @@ fn validate_encoded_root_directory(input: &[u8], layout: MetadataLayout) -> Resu Ok(()) } -fn validate_regions(root: RegionMetadataRoot, regions: &[RegionMetadataRecord]) -> Result<()> { +fn validate_regions( + root: RegionMetadataRoot, + regions: &[RegionMetadataRecord], +) -> Result<(), RegionMetadataError> { let mut free_seen = zeroed_bytes(root.free_region_count as usize)?; let mut active_seen = zeroed_bytes(root.active_region_count as usize)?; let mut sealed_seen = zeroed_bytes(root.sealed_region_count as usize)?; @@ -685,7 +694,7 @@ fn validate_regions(root: RegionMetadataRoot, regions: &[RegionMetadataRecord]) fn validate_partitions( root: RegionMetadataRoot, partitions: &[PartitionMetadataRecord], -) -> Result<()> { +) -> Result<(), RegionMetadataError> { let index_slots = usize::try_from(root.index_slots).map_err(|_| RegionMetadataError::ArithmeticOverflow)?; let canonical = @@ -735,14 +744,14 @@ fn index_layout_metadata_error(error: IndexStorageError) -> RegionMetadataError } } -fn minimum_bytes_fit(count: u64, bytes: u64) -> Result { +fn minimum_bytes_fit(count: u64, bytes: u64) -> Result { Ok(count .checked_mul(MIN_ENCODED_RECORD_SIZE) .ok_or(RegionMetadataError::ArithmeticOverflow)? <= bytes) } -fn zeroed_bytes(len: usize) -> Result> { +fn zeroed_bytes(len: usize) -> Result, RegionMetadataError> { let mut output = Vec::new(); output .try_reserve_exact(len) @@ -787,7 +796,7 @@ fn encode_page_envelope(page: &mut [u8], envelope: PageEnvelope) { put_u32(page, PAGE_RESERVED_OFFSET, 0); } -fn decode_page_envelope(page: &[u8]) -> Result { +fn decode_page_envelope(page: &[u8]) -> Result { if page.len() != REGION_METADATA_PAGE_SIZE { return Err(RegionMetadataError::InvalidLength); } @@ -833,7 +842,7 @@ fn validate_envelope_shape( page_index: u32, first_record: u32, record_count: u32, -) -> Result<()> { +) -> Result<(), RegionMetadataError> { if envelope.kind != kind || usize::from(envelope.record_size) != record_size || envelope.image_generation == 0 @@ -871,7 +880,7 @@ fn encode_record_pages( image_generation: u64, records: &[T], encode_record: fn(&T, &mut [u8]), -) -> Result<()> { +) -> Result<(), RegionMetadataError> { for (page_in_section, records) in records.chunks(records_per_page).enumerate() { let first_record = page_in_section .checked_mul(records_per_page) @@ -915,8 +924,8 @@ fn decode_record_pages( image_identity: PersistentId, image_generation: u64, record_count: usize, - decode_record: fn(&[u8]) -> Result, -) -> Result> { + decode_record: fn(&[u8]) -> Result, +) -> Result, RegionMetadataError> { let mut output = Vec::new(); output .try_reserve_exact(record_count) @@ -951,7 +960,7 @@ fn decode_record_pages( Ok(output) } -fn require_zero_padding(page: &[u8], payload_len: usize) -> Result<()> { +fn require_zero_padding(page: &[u8], payload_len: usize) -> Result<(), RegionMetadataError> { let end = REGION_METADATA_PAGE_HEADER_SIZE .checked_add(payload_len) .ok_or(RegionMetadataError::ArithmeticOverflow)?; @@ -1026,7 +1035,7 @@ fn encode_root(root: &RegionMetadataRoot, layout: MetadataLayout, output: &mut [ put_u32(output, ROOT_RESERVED_OFFSET, 0); } -fn decode_root(input: &[u8]) -> Result { +fn decode_root(input: &[u8]) -> Result { if input.len() != REGION_METADATA_ROOT_SIZE || get_u32(input, ROOT_RESERVED32_OFFSET)? != 0 || get_u64(input, ROOT_RESERVED_EPOCH_OFFSET)? != 0 @@ -1073,7 +1082,7 @@ fn encode_region(region: &RegionMetadataRecord, output: &mut [u8]) { output[REGION_STATE_OFFSET] = region.state as u8; } -fn decode_region(input: &[u8]) -> Result { +fn decode_region(input: &[u8]) -> Result { if input.len() != REGION_METADATA_REGION_SIZE { return Err(RegionMetadataError::InvalidField("region_encoding")); } @@ -1106,7 +1115,7 @@ fn encode_partition(partition: &PartitionMetadataRecord, output: &mut [u8]) { put_u32(output, PARTITION_RESERVED_OFFSET, 0); } -fn decode_partition(input: &[u8]) -> Result { +fn decode_partition(input: &[u8]) -> Result { if input.len() != REGION_METADATA_PARTITION_SIZE || get_u32(input, PARTITION_RESERVED_OFFSET)? != 0 { @@ -1119,7 +1128,7 @@ fn decode_partition(input: &[u8]) -> Result { }) } -fn page(input: &[u8], page_index: usize) -> Result<&[u8]> { +fn page(input: &[u8], page_index: usize) -> Result<&[u8], RegionMetadataError> { let start = page_index .checked_mul(REGION_METADATA_PAGE_SIZE) .ok_or(RegionMetadataError::ArithmeticOverflow)?; @@ -1131,7 +1140,7 @@ fn page(input: &[u8], page_index: usize) -> Result<&[u8]> { .ok_or(RegionMetadataError::InvalidLength) } -fn page_mut(input: &mut [u8], page_index: usize) -> Result<&mut [u8]> { +fn page_mut(input: &mut [u8], page_index: usize) -> Result<&mut [u8], RegionMetadataError> { let start = page_index .checked_mul(REGION_METADATA_PAGE_SIZE) .ok_or(RegionMetadataError::ArithmeticOverflow)?; @@ -1153,7 +1162,7 @@ fn page_payload_mut(page: &mut [u8], record: usize, record_size: usize) -> &mut &mut page[start..start + record_size] } -fn get_id(input: &[u8], offset: usize) -> Result { +fn get_id(input: &[u8], offset: usize) -> Result { let bytes: [u8; 16] = input .get(offset..offset + 16) .ok_or(RegionMetadataError::InvalidLength)? @@ -1166,7 +1175,7 @@ fn put_id(output: &mut [u8], offset: usize, id: PersistentId) { output[offset..offset + 16].copy_from_slice(&id.to_bytes()); } -fn get_u16(input: &[u8], offset: usize) -> Result { +fn get_u16(input: &[u8], offset: usize) -> Result { let bytes = input .get(offset..offset + 2) .ok_or(RegionMetadataError::InvalidLength)? @@ -1175,7 +1184,7 @@ fn get_u16(input: &[u8], offset: usize) -> Result { Ok(u16::from_le_bytes(bytes)) } -fn get_u32(input: &[u8], offset: usize) -> Result { +fn get_u32(input: &[u8], offset: usize) -> Result { let bytes = input .get(offset..offset + 4) .ok_or(RegionMetadataError::InvalidLength)? @@ -1184,7 +1193,7 @@ fn get_u32(input: &[u8], offset: usize) -> Result { Ok(u32::from_le_bytes(bytes)) } -fn get_u64(input: &[u8], offset: usize) -> Result { +fn get_u64(input: &[u8], offset: usize) -> Result { let bytes = input .get(offset..offset + 8) .ok_or(RegionMetadataError::InvalidLength)? diff --git a/tests-integration/tests/cache.rs b/tests-integration/tests/cache.rs index 2e2dd40..699989a 100644 --- a/tests-integration/tests/cache.rs +++ b/tests-integration/tests/cache.rs @@ -36,6 +36,7 @@ use cache2::CacheHealth; use cache2::CacheIoSnapshot; use cache2::CacheTier; use cache2::DetailedCacheSnapshot; +use cache2::Error; use cache2::ErrorKind; use cache2::ErrorOperation; use cache2::IoEngine; @@ -46,7 +47,6 @@ use cache2::IoUringConfig; use cache2::L1EvictionPolicy; use cache2::PosixIoConfig; use cache2::ReadAdmission; -use cache2::Result; use cache2::RuntimeOptions; use cache2::StartupMode; use cache2::StorageLayout; @@ -155,7 +155,7 @@ fn rewrite_page_version(path: &Path, offset: u64, version: u16) { file.sync_all().unwrap(); } -fn eventually_admitted(mut put: impl FnMut() -> Result) -> T { +fn eventually_admitted(mut put: impl FnMut() -> Result) -> T { let deadline = Instant::now() + Duration::from_secs(2); loop { match put() { From 742c43d65ea59e28d057aa4d23b5f49811f7cd6a Mon Sep 17 00:00:00 2001 From: tison Date: Wed, 9 Sep 2026 19:38:25 +0800 Subject: [PATCH 06/14] docs: trim contribution guide to essential workflows Keep the workspace entry points, tool setup, validation commands, and lasting contribution requirements. Refer to existing code and source configuration for details. --- CONTRIBUTING.md | 79 ++++++++----------------------------------------- 1 file changed, 13 insertions(+), 66 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0384934..7771c9b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,83 +1,30 @@ # Contributing to C² -C² requires Rust 1.98.0. Run development commands from the repository root so Cargo uses the complete workspace and the shared dependency and lint policy. +## Workspace -## Workspace layout +`cache2/` contains the published library and its private implementation tests. `tests-integration/` exercises the public API; `benchmarks/` and `examples/` contain workloads and runnable integrations. `xtask/` implements the `cargo x` development commands. -The repository separates published code from development-only consumers: +## Development -| Path | Purpose | -|----------------------|-----------------------------------------------------------------------------------------------| -| `cache2/` | The publishable `cache2` crate and private implementation tests. | -| `tests-integration/` | End-to-end tests that exercise only the public `cache2` API. | -| `benchmarks/` | Standalone benchmark targets and workload-specific harnesses. | -| `examples/` | Runnable programs that demonstrate complete integrations. | -| `xtask/` | The `cargo x` repository workflow entrypoint. | - -Keep unit tests beside the implementation when they need private access. Behavior visible to callers belongs in `tests-integration/tests`. - -Keep versioned format fixtures beside the module that owns their encoding and decoding. Share only the fixture parsing and assertion helpers. - -## Repository workflows - -The `.cargo/config.toml` alias maps `cargo x` to the `x` package in `xtask/`. Use these commands before opening a pull request: - -```sh -cargo x check -cargo x test -cargo x lint -``` - -`cargo x check` verifies the workspace and each optional `cache2` feature. `cargo x test` runs workspace tests with all features and the ignored extended library tests. These commands use the selected toolchain. `cargo x lint` explicitly uses nightly for Rust formatting, Clippy, and public documentation, and also checks TOML formatting, spelling, the publishable package, license headers, dependency licenses, advisories, and sources. - -The lint workflow uses nightly Rust and the latest releases of the lint tools so new diagnostics are caught early: +Run commands from the repository root. Use the Rust version declared in [Cargo.toml](Cargo.toml). Linting also requires nightly Rust and these tools: ```sh rustup toolchain install nightly --profile minimal --component rustfmt,clippy -cargo install cargo-deny --locked -cargo install hawkeye --locked -cargo install taplo-cli --locked -cargo install typos-cli --locked +cargo install --locked cargo-deny hawkeye taplo-cli typos-cli ``` -Run `cargo x lint --fix` to apply Clippy fixes, Rust and TOML formatting, and license headers. Review the changes, then run `cargo x lint` to verify the result. The Rust import and comment style follows `rustfmt.toml`; TOML formatting follows `taplo.toml`. - -CI runs nightly lint and feature checks in `check`, tests on Linux and macOS with Rust 1.98.0 and stable in `test`, and the pinned ASan/TSan suite in `safety`. The final `Required` job succeeds only when all three jobs succeed; failures, cancellations, and skipped dependencies fail the gate. Pull requests and pushes to `main`, including documentation changes, run the workflow. - -Use the underlying Cargo commands directly when isolating a failure. The release-mode test pass used by CI is: +Before submitting a pull request, run: ```sh -cargo test --workspace --release --all-features -``` - -## Rust Style - -Use `module/mod.rs` for modules with child files; keep leaf modules in a single `.rs` file. - -Declare restricted visibility at the module boundary and use `pub` for items in that module's API. - -Use imports for referenced symbols. Keep a short module qualifier or use an explicit alias when a bare name would obscure its origin or conflict with another symbol, such as `io::Error` or `ConfiguredIoEngine`. - -Start intra-crate imports at `crate`; reserve `use super::*` for test modules. - -## Documentation - -Keep each Markdown prose paragraph and list item on one source line. - -## Benchmarks and property tests - -Each benchmark is an explicit target in the `benchmarks` package. Run one target with: - -```sh -cargo x bench --bench cache +cargo x check +cargo x test +cargo x lint ``` -See `BENCHMARK.md` for workload controls and qualification requirements. The normal test workflow also runs 10,000 QuickCheck cases for each of four properties: persistent decoders, record round trips, the fixed-map state machine, and the Region-index state machine. Inputs are capped at 16 KiB. Run that group directly with: +`cargo x` is the source of truth for validation: `check` covers the workspace and optional features, `test` includes the extended library tests, and `lint` checks formatting, code, documentation, packaging, and dependencies. Use `cargo x lint --fix` to apply supported automatic fixes, then review the diff. -```sh -cargo test --package cache2 --lib property_tests:: -``` +See [BENCHMARK.md](BENCHMARK.md) for performance workloads and qualification. -## Changelog +## Changes -Update `CHANGELOG.md` for user-visible API, correctness, compatibility, performance, or operational changes. Internal refactors, tests, documentation, CI, tooling, and dependency maintenance do not need an entry unless they alter observable behavior. +Follow the surrounding code and the engineering constraints in [AGENTS.md](AGENTS.md). Cover behavior changes with tests, keep public documentation current, and record user-visible changes in [CHANGELOG.md](CHANGELOG.md). From 0fd35e68d9d7167d7d9df198cf9f47172817a16f Mon Sep 17 00:00:00 2001 From: tison Date: Wed, 9 Sep 2026 23:21:27 +0800 Subject: [PATCH 07/14] style: import the standard environment module --- benchmarks/cache/main.rs | 27 +++++++++++---------- benchmarks/cache_soak/main.rs | 27 +++++++++++---------- benchmarks/mixed_workloads/main.rs | 21 ++++++++-------- benchmarks/recovery_scale/main.rs | 9 ++++--- benchmarks/region_index_turnover/main.rs | 5 ++-- cache2/src/io/backend.rs | 3 ++- cache2/src/io/engine/tests.rs | 3 ++- cache2/src/region/file_backend/tests.rs | 10 ++++---- cache2/src/region/index/storage/mod.rs | 3 ++- cache2/src/region/runtime/mod.rs | 7 +++--- cache2/src/region/runtime/shutdown_tests.rs | 3 ++- examples/src/logforth.rs | 3 ++- tests-integration/tests/cache.rs | 4 +-- xtask/src/main.rs | 3 ++- 14 files changed, 70 insertions(+), 58 deletions(-) diff --git a/benchmarks/cache/main.rs b/benchmarks/cache/main.rs index 9697740..818a4bb 100644 --- a/benchmarks/cache/main.rs +++ b/benchmarks/cache/main.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::env; use std::fmt; use std::fs; use std::hint::black_box; @@ -108,7 +109,7 @@ impl BenchConfig { let reclaim_workers = env_usize("CACHE_BENCH_RECLAIM_WORKERS", 1)?; let clients = env_usize("CACHE_BENCH_CLIENTS", 8)?; let write_clients = env_usize("CACHE_BENCH_WRITE_CLIENTS", 4)?; - let io_engine = match std::env::var("CACHE_BENCH_IO_ENGINE") + let io_engine = match env::var("CACHE_BENCH_IO_ENGINE") .unwrap_or_else(|_| "posix".to_owned()) .as_str() { @@ -134,7 +135,7 @@ impl BenchConfig { )), value => return Err(invalid(format!("unsupported I/O engine: {value}"))), }; - let io_mode = match std::env::var("CACHE_BENCH_IO_MODE") + let io_mode = match env::var("CACHE_BENCH_IO_MODE") .unwrap_or_else(|_| "buffered".to_owned()) .as_str() { @@ -142,7 +143,7 @@ impl BenchConfig { "direct" => IoMode::Direct, value => return Err(invalid(format!("unsupported I/O mode: {value}"))), }; - let l1_eviction_policy = match std::env::var("CACHE_BENCH_L1_EVICTION") + let l1_eviction_policy = match env::var("CACHE_BENCH_L1_EVICTION") .unwrap_or_else(|_| "clock".to_owned()) .as_str() { @@ -153,9 +154,9 @@ impl BenchConfig { } }; let statistics_enabled = env_bool("CACHE_BENCH_STATS", false)?; - let directory = std::env::var_os("CACHE_BENCH_DIR") + let directory = env::var_os("CACHE_BENCH_DIR") .map(PathBuf::from) - .unwrap_or_else(std::env::temp_dir); + .unwrap_or_else(env::temp_dir); if entries == 0 || read_ops == 0 @@ -1165,7 +1166,7 @@ fn require_minimum_rate(name: &str, measurement: &Measurement) -> io::Result<()> } fn env_optional_f64(name: &str) -> io::Result> { - match std::env::var(name) { + match env::var(name) { Ok(value) => { let parsed = value .parse::() @@ -1177,37 +1178,37 @@ fn env_optional_f64(name: &str) -> io::Result> { } Ok(Some(parsed)) } - Err(std::env::VarError::NotPresent) => Ok(None), + Err(env::VarError::NotPresent) => Ok(None), Err(error) => Err(invalid(format!("cannot read {name}: {error}"))), } } fn env_usize(name: &str, default: usize) -> io::Result { - match std::env::var(name) { + match env::var(name) { Ok(value) => value .parse() .map_err(|_| invalid(format!("{name} must be an unsigned integer"))), - Err(std::env::VarError::NotPresent) => Ok(default), + Err(env::VarError::NotPresent) => Ok(default), Err(error) => Err(invalid(format!("cannot read {name}: {error}"))), } } fn env_u32(name: &str, default: u32) -> io::Result { - match std::env::var(name) { + match env::var(name) { Ok(value) => value .parse() .map_err(|_| invalid(format!("{name} must be an unsigned integer"))), - Err(std::env::VarError::NotPresent) => Ok(default), + Err(env::VarError::NotPresent) => Ok(default), Err(error) => Err(invalid(format!("cannot read {name}: {error}"))), } } fn env_bool(name: &str, default: bool) -> io::Result { - match std::env::var(name) { + match env::var(name) { Ok(value) if value == "true" || value == "1" => Ok(true), Ok(value) if value == "false" || value == "0" => Ok(false), Ok(_) => Err(invalid(format!("{name} must be true, false, 1, or 0"))), - Err(std::env::VarError::NotPresent) => Ok(default), + Err(env::VarError::NotPresent) => Ok(default), Err(error) => Err(invalid(format!("cannot read {name}: {error}"))), } } diff --git a/benchmarks/cache_soak/main.rs b/benchmarks/cache_soak/main.rs index 2700694..b70a5ba 100644 --- a/benchmarks/cache_soak/main.rs +++ b/benchmarks/cache_soak/main.rs @@ -13,6 +13,7 @@ // limitations under the License. use std::cmp::min; +use std::env; use std::fmt; use std::fs; use std::io; @@ -157,9 +158,9 @@ impl SoakConfig { )?; let io_mode = parse_io_mode("CACHE_SOAK_IO_MODE")?; let l1_eviction_policy = parse_l1_eviction_policy("CACHE_SOAK_L1_EVICTION")?; - let directory = std::env::var_os("CACHE_SOAK_DIR") + let directory = env::var_os("CACHE_SOAK_DIR") .map(PathBuf::from) - .unwrap_or_else(std::env::temp_dir); + .unwrap_or_else(env::temp_dir); if duration.is_zero() || sample_period.is_zero() || value_bytes.is_empty() @@ -1181,11 +1182,11 @@ fn peak_rss_bytes() -> u64 { } fn env_u64(name: &str, default: u64) -> io::Result { - match std::env::var(name) { + match env::var(name) { Ok(value) => value .parse() .map_err(|_| invalid(format!("{name} must be an unsigned integer"))), - Err(std::env::VarError::NotPresent) => Ok(default), + Err(env::VarError::NotPresent) => Ok(default), Err(error) => Err(invalid(format!("cannot read {name}: {error}"))), } } @@ -1197,7 +1198,7 @@ fn env_usize(name: &str, default: usize) -> io::Result { } fn env_usize_list(name: &str, default: &[usize]) -> io::Result> { - match std::env::var(name) { + match env::var(name) { Ok(value) => value .split(',') .map(|item| { @@ -1206,7 +1207,7 @@ fn env_usize_list(name: &str, default: &[usize]) -> io::Result> { }) .collect::>>() .map(Vec::into_boxed_slice), - Err(std::env::VarError::NotPresent) => Ok(default.to_vec().into_boxed_slice()), + Err(env::VarError::NotPresent) => Ok(default.to_vec().into_boxed_slice()), Err(error) => Err(invalid(format!("cannot read {name}: {error}"))), } } @@ -1217,22 +1218,22 @@ fn env_u32(name: &str, default: u32) -> io::Result { } fn env_optional_u32(name: &str) -> io::Result> { - match std::env::var(name) { + match env::var(name) { Ok(value) => value .parse::() .map(Some) .map_err(|_| invalid(format!("{name} must be an unsigned integer"))), - Err(std::env::VarError::NotPresent) => Ok(None), + Err(env::VarError::NotPresent) => Ok(None), Err(error) => Err(invalid(format!("cannot read {name}: {error}"))), } } fn env_bool(name: &str, default: bool) -> io::Result { - match std::env::var(name) { + match env::var(name) { Ok(value) if value == "true" || value == "1" => Ok(true), Ok(value) if value == "false" || value == "0" => Ok(false), Ok(_) => Err(invalid(format!("{name} must be true, false, 1, or 0"))), - Err(std::env::VarError::NotPresent) => Ok(default), + Err(env::VarError::NotPresent) => Ok(default), Err(error) => Err(invalid(format!("cannot read {name}: {error}"))), } } @@ -1243,7 +1244,7 @@ fn parse_io_engine( write_workers: usize, reclaim_workers: usize, ) -> io::Result { - match std::env::var(name) + match env::var(name) .unwrap_or_else(|_| "posix".to_owned()) .as_str() { @@ -1320,7 +1321,7 @@ fn io_uring_write_pool(write_workers: usize) -> io::Result { } fn parse_io_mode(name: &str) -> io::Result { - match std::env::var(name) + match env::var(name) .unwrap_or_else(|_| "buffered".to_owned()) .as_str() { @@ -1331,7 +1332,7 @@ fn parse_io_mode(name: &str) -> io::Result { } fn parse_l1_eviction_policy(name: &str) -> io::Result { - match std::env::var(name) + match env::var(name) .unwrap_or_else(|_| "clock".to_owned()) .as_str() { diff --git a/benchmarks/mixed_workloads/main.rs b/benchmarks/mixed_workloads/main.rs index 21b945c..1f9f18e 100644 --- a/benchmarks/mixed_workloads/main.rs +++ b/benchmarks/mixed_workloads/main.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::env; use std::f64::consts::TAU; use std::fmt; use std::fs; @@ -235,7 +236,7 @@ impl HarnessConfig { let reclaim_workers = env_usize("CACHE_WORKLOAD_RECLAIM_WORKERS", 1)?; let latency_sample_interval = env_usize("CACHE_WORKLOAD_LATENCY_SAMPLE_INTERVAL", 16)?; let seed = env_u64("CACHE_WORKLOAD_SEED", DEFAULT_SEED)?; - let io_engine = match std::env::var("CACHE_WORKLOAD_IO_ENGINE") + let io_engine = match env::var("CACHE_WORKLOAD_IO_ENGINE") .unwrap_or_else(|_| "posix".to_owned()) .as_str() { @@ -261,7 +262,7 @@ impl HarnessConfig { )), value => return Err(invalid(format!("unsupported I/O engine: {value}"))), }; - let io_mode = match std::env::var("CACHE_WORKLOAD_IO_MODE") + let io_mode = match env::var("CACHE_WORKLOAD_IO_MODE") .unwrap_or_else(|_| "buffered".to_owned()) .as_str() { @@ -269,7 +270,7 @@ impl HarnessConfig { "direct" => IoMode::Direct, value => return Err(invalid(format!("unsupported I/O mode: {value}"))), }; - let l1_eviction_policy = match std::env::var("CACHE_WORKLOAD_L1_EVICTION") + let l1_eviction_policy = match env::var("CACHE_WORKLOAD_L1_EVICTION") .unwrap_or_else(|_| "clock".to_owned()) .as_str() { @@ -277,9 +278,9 @@ impl HarnessConfig { "s3-fifo" => L1EvictionPolicy::S3Fifo, value => return Err(invalid(format!("unsupported L1 eviction policy: {value}"))), }; - let directory = std::env::var_os("CACHE_WORKLOAD_DIR") + let directory = env::var_os("CACHE_WORKLOAD_DIR") .map(PathBuf::from) - .unwrap_or_else(std::env::temp_dir); + .unwrap_or_else(env::temp_dir); if operations_per_thread == Some(0) || threads == Some(0) @@ -1170,7 +1171,7 @@ fn report_latency(scenario: Scenario, operation: &str, latency: &LatencyHistogra } fn parse_scenarios() -> io::Result> { - let value = std::env::var("CACHE_WORKLOAD_SCENARIO").unwrap_or_else(|_| "all".to_owned()); + let value = env::var("CACHE_WORKLOAD_SCENARIO").unwrap_or_else(|_| "all".to_owned()); if value == "all" { return Ok(Scenario::ALL.into()); } @@ -1235,22 +1236,22 @@ fn mixed(mut value: u64) -> u64 { } fn env_optional_usize(name: &str) -> io::Result> { - match std::env::var(name) { + match env::var(name) { Ok(value) => value .parse::() .map(Some) .map_err(|_| invalid(format!("{name} must be an unsigned integer"))), - Err(std::env::VarError::NotPresent) => Ok(None), + Err(env::VarError::NotPresent) => Ok(None), Err(error) => Err(invalid(format!("cannot read {name}: {error}"))), } } fn env_u64(name: &str, default: u64) -> io::Result { - match std::env::var(name) { + match env::var(name) { Ok(value) => value .parse() .map_err(|_| invalid(format!("{name} must be an unsigned integer"))), - Err(std::env::VarError::NotPresent) => Ok(default), + Err(env::VarError::NotPresent) => Ok(default), Err(error) => Err(invalid(format!("cannot read {name}: {error}"))), } } diff --git a/benchmarks/recovery_scale/main.rs b/benchmarks/recovery_scale/main.rs index ce4717b..680a9a7 100644 --- a/benchmarks/recovery_scale/main.rs +++ b/benchmarks/recovery_scale/main.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::env; use std::fmt; use std::fs; use std::io; @@ -62,9 +63,9 @@ impl ScaleConfig { .ok_or_else(|| invalid("recovery benchmark managed memory limit is too large"))?; let sentinel_count = env_usize("CACHE_RECOVERY_SENTINELS", 1_024)?; let value_bytes = env_usize("CACHE_RECOVERY_VALUE_BYTES", 1_024)?; - let directory = std::env::var_os("CACHE_RECOVERY_DIR") + let directory = env::var_os("CACHE_RECOVERY_DIR") .map(PathBuf::from) - .unwrap_or_else(std::env::temp_dir); + .unwrap_or_else(env::temp_dir); if expected_entries == 0 || sentinel_count == 0 || value_bytes < 8 || !directory.is_dir() { return Err(invalid( "expected entries and sentinels must be positive, values must be at least 8 bytes, and the benchmark directory must exist", @@ -418,11 +419,11 @@ fn sentinel_key(ordinal: usize) -> [u8; 16] { } fn env_u64(name: &str, default: u64) -> io::Result { - match std::env::var(name) { + match env::var(name) { Ok(value) => value .parse() .map_err(|_| invalid(format!("{name} must be an unsigned integer"))), - Err(std::env::VarError::NotPresent) => Ok(default), + Err(env::VarError::NotPresent) => Ok(default), Err(error) => Err(invalid(format!("cannot read {name}: {error}"))), } } diff --git a/benchmarks/region_index_turnover/main.rs b/benchmarks/region_index_turnover/main.rs index 93550ac..122abb3 100644 --- a/benchmarks/region_index_turnover/main.rs +++ b/benchmarks/region_index_turnover/main.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::env; use std::fmt; use std::io; @@ -134,11 +135,11 @@ fn report_phase(turn: usize, phase: &str, measurement: RegionIndexTurnoverPhase) } fn env_usize(name: &str, default: usize) -> io::Result { - match std::env::var(name) { + match env::var(name) { Ok(value) => value .parse() .map_err(|_| invalid(format!("{name} must be an unsigned integer"))), - Err(std::env::VarError::NotPresent) => Ok(default), + Err(env::VarError::NotPresent) => Ok(default), Err(error) => Err(invalid(format!("cannot read {name}: {error}"))), } } diff --git a/cache2/src/io/backend.rs b/cache2/src/io/backend.rs index 4fe391b..2c4def3 100644 --- a/cache2/src/io/backend.rs +++ b/cache2/src/io/backend.rs @@ -966,6 +966,7 @@ unsafe extern "C" { #[cfg(test)] mod tests { + use std::env; use std::path::PathBuf; use std::sync::atomic::AtomicU64; use std::sync::atomic::AtomicUsize; @@ -980,7 +981,7 @@ mod tests { impl TestFile { fn new(label: &str) -> Self { let nonce = NEXT_PATH.fetch_add(1, Ordering::Relaxed); - Self(std::env::temp_dir().join(format!( + Self(env::temp_dir().join(format!( "cache2-{label}-{}-{nonce}.cache", std::process::id() ))) diff --git a/cache2/src/io/engine/tests.rs b/cache2/src/io/engine/tests.rs index 1bf7091..22f4bde 100644 --- a/cache2/src/io/engine/tests.rs +++ b/cache2/src/io/engine/tests.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::env; use std::fs; use std::fs::File; use std::fs::OpenOptions; @@ -77,7 +78,7 @@ impl TestFile { fn new() -> Self { let id = FILE_ID.fetch_add(1, Ordering::Relaxed); let path = - std::env::temp_dir().join(format!("cache2-io-engine-{}-{id}.bin", std::process::id())); + env::temp_dir().join(format!("cache2-io-engine-{}-{id}.bin", std::process::id())); Self { path } } diff --git a/cache2/src/region/file_backend/tests.rs b/cache2/src/region/file_backend/tests.rs index 20d6284..555657f 100644 --- a/cache2/src/region/file_backend/tests.rs +++ b/cache2/src/region/file_backend/tests.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::env; use std::fs; use std::future::Future; use std::future::poll_fn; @@ -100,8 +101,7 @@ struct TestDirectory { impl TestDirectory { fn new() -> Self { let ordinal = NEXT_TEST_DIRECTORY.fetch_add(1, Ordering::Relaxed); - let root = - std::env::temp_dir().join(format!("cache2-region-{}-{ordinal}", std::process::id())); + let root = env::temp_dir().join(format!("cache2-region-{}-{ordinal}", std::process::id())); let _ = fs::remove_dir_all(&root); fs::create_dir(&root).unwrap(); let files = RegionFiles::new( @@ -272,8 +272,8 @@ fn external_process_kill_recovery_contract() { const CHILD_CASE: &str = "CACHE2_CRASH_CHILD_CASE"; const CHILD_ROOT: &str = "CACHE2_CRASH_CHILD_ROOT"; - if let Ok(case) = std::env::var(CHILD_CASE) { - let root = PathBuf::from(std::env::var_os(CHILD_ROOT).expect("child root is set")); + if let Ok(case) = env::var(CHILD_CASE) { + let root = PathBuf::from(env::var_os(CHILD_ROOT).expect("child root is set")); let files = RegionFiles::new( root.join("data"), root.join("state"), @@ -301,7 +301,7 @@ fn external_process_kill_recovery_contract() { initial.drain().unwrap(); initial.close_warm().unwrap(); - let status = Command::new(std::env::current_exe().unwrap()) + let status = Command::new(env::current_exe().unwrap()) .arg("--exact") .arg("region::file_backend::tests::external_process_kill_recovery_contract") .arg("--ignored") diff --git a/cache2/src/region/index/storage/mod.rs b/cache2/src/region/index/storage/mod.rs index 532f5c5..9ab20d1 100644 --- a/cache2/src/region/index/storage/mod.rs +++ b/cache2/src/region/index/storage/mod.rs @@ -1832,6 +1832,7 @@ unsafe impl Sync for Mapping {} #[cfg(test)] mod tests { + use std::env; use std::fs::OpenOptions; use std::io::Read; use std::io::Seek; @@ -1860,7 +1861,7 @@ mod tests { impl TestFile { fn create() -> Self { let id = NEXT_TEST_FILE.fetch_add(1, Ordering::Relaxed); - let path = std::env::temp_dir().join(format!( + let path = env::temp_dir().join(format!( "cache2-index-image-{}-{id}.tmp", std::process::id() )); diff --git a/cache2/src/region/runtime/mod.rs b/cache2/src/region/runtime/mod.rs index 85046ea..32310d6 100644 --- a/cache2/src/region/runtime/mod.rs +++ b/cache2/src/region/runtime/mod.rs @@ -2157,6 +2157,7 @@ fn invalid_runtime_config(message: &'static str) -> io::Error { #[cfg(test)] mod tests { + use std::env; use std::sync::Barrier; use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicU64; @@ -2186,7 +2187,7 @@ mod tests { #[test] fn read_lane_uses_one_bounded_alternate_on_primary_pressure() { let id = LANE_TEST_ID.fetch_add(1, Ordering::Relaxed); - let path = std::env::temp_dir().join(format!( + let path = env::temp_dir().join(format!( "cache2-read-lane-{}-{id}.cache", std::process::id() )); @@ -2228,7 +2229,7 @@ mod tests { #[test] fn hot_read_route_rotates_pressure_fallback_across_all_lanes() { let id = LANE_TEST_ID.fetch_add(1, Ordering::Relaxed); - let path = std::env::temp_dir().join(format!( + let path = env::temp_dir().join(format!( "cache2-read-lane-rotation-{}-{id}.cache", std::process::id() )); @@ -2473,7 +2474,7 @@ mod tests { use crate::region::store::RegionStore; let id = LANE_TEST_ID.fetch_add(1, Ordering::Relaxed); - let path = std::env::temp_dir().join(format!( + let path = env::temp_dir().join(format!( "cache2-completion-timeout-{}-{id}", std::process::id() )); diff --git a/cache2/src/region/runtime/shutdown_tests.rs b/cache2/src/region/runtime/shutdown_tests.rs index 7d5eff9..b97cd86 100644 --- a/cache2/src/region/runtime/shutdown_tests.rs +++ b/cache2/src/region/runtime/shutdown_tests.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::env; use std::sync::atomic::AtomicBool; use std::sync::mpsc; @@ -208,7 +209,7 @@ fn assert_close_does_not_wait_for_read(submit_before_close: bool) { use crate::region::RegionFiles; use crate::region::recovery::PersistentId; use crate::region::store::RegionStore; - let root = std::env::temp_dir().join(format!( + let root = env::temp_dir().join(format!( "cache2-close-race-{}-{submit_before_close}", std::process::id() )); diff --git a/examples/src/logforth.rs b/examples/src/logforth.rs index 2aa421d..29bc62d 100644 --- a/examples/src/logforth.rs +++ b/examples/src/logforth.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::env; use std::io; use cache2::Cache; @@ -27,7 +28,7 @@ use logforth::layout::JsonLayout; async fn main() -> io::Result<()> { init_logforth(); - let path = std::env::args_os().nth(1).ok_or_else(|| { + let path = env::args_os().nth(1).ok_or_else(|| { io::Error::new( io::ErrorKind::InvalidInput, "usage: logforth ", diff --git a/tests-integration/tests/cache.rs b/tests-integration/tests/cache.rs index 699989a..e427214 100644 --- a/tests-integration/tests/cache.rs +++ b/tests-integration/tests/cache.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::env; use std::fs; use std::fs::OpenOptions; use std::io; @@ -99,8 +100,7 @@ struct TestCache { impl TestCache { fn new(name: &str) -> Self { let id = NEXT_FILE.fetch_add(1, Ordering::Relaxed); - let data = - std::env::temp_dir().join(format!("cache2-{name}-{}-{id}.cache", std::process::id())); + let data = env::temp_dir().join(format!("cache2-{name}-{}-{id}.cache", std::process::id())); Self { data } } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 68beaf3..a19fcba 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::env; use std::ffi::OsStr; use std::ffi::OsString; use std::path::Path; @@ -232,7 +233,7 @@ impl CommandTest { } fn cargo() -> std::process::Command { - let executable = std::env::var_os("CARGO").unwrap_or_else(|| OsString::from("cargo")); + let executable = env::var_os("CARGO").unwrap_or_else(|| OsString::from("cargo")); let mut command = std::process::Command::new(executable); command.current_dir(Path::new(env!("CARGO_WORKSPACE_DIR"))); command From 453037eef8a8066dc764e8417230cbcafcc39d2e Mon Sep 17 00:00:00 2001 From: tison Date: Wed, 9 Sep 2026 23:26:14 +0800 Subject: [PATCH 08/14] fixup Signed-off-by: tison --- cache2/src/checksum.rs | 8 ++++---- cache2/src/config/runtime.rs | 6 ++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/cache2/src/checksum.rs b/cache2/src/checksum.rs index 8b0a15d..1bbb1d0 100644 --- a/cache2/src/checksum.rs +++ b/cache2/src/checksum.rs @@ -12,11 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! CRC32C (Castagnoli) used by the on-disk format. +//! CRC32C used by the on-disk format. //! //! The dependency selects hardware acceleration when the host supports it and //! retains a portable software fallback. This wrapper keeps the cache's codec -//! API and checksum values independent from that implementation detail. +//! API and checksum values independent of that implementation detail. use crc_fast::CrcAlgorithm; use crc_fast::Digest; @@ -27,8 +27,8 @@ pub fn crc32c(bytes: &[u8]) -> u32 { crc32_iscsi(bytes) } -/// Incremental CRC32C state, useful for checksumming a key and value without -/// first joining them in a temporary allocation. +/// Incremental CRC32C state, useful for checksum a key and value without first joining them in a +/// temporary allocation. pub struct Crc32c { digest: Digest, } diff --git a/cache2/src/config/runtime.rs b/cache2/src/config/runtime.rs index 8cf47b3..e218d5d 100644 --- a/cache2/src/config/runtime.rs +++ b/cache2/src/config/runtime.rs @@ -13,7 +13,6 @@ // limitations under the License. use std::io; -use std::mem::size_of; use std::time::Duration; use crate::config::CacheConfig; @@ -95,7 +94,7 @@ impl Default for PosixIoConfig { /// experimental io_uring engine. /// /// `max_in_flight` is distributed as evenly as possible across `rings`. This -/// keeps admission capacity independent from the number of driver threads. +/// keeps admission capacity independent of the number of driver threads. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct IoUringPoolConfig { rings: usize, @@ -482,8 +481,7 @@ impl CacheConfig { /// Checks the complete combination and resolves dependent runtime defaults. /// /// ```no_run - /// # use cache2::Error; - /// # async fn example() -> Result<(), Error> { + /// # async fn example() -> Result<(), cache2::Error> { /// use cache2::Cache; /// use cache2::CacheConfig; /// use cache2::RuntimeOptions; From f3e7b2a34cc4498b2b72dc49a088bc67ac1d795c Mon Sep 17 00:00:00 2001 From: tison Date: Wed, 9 Sep 2026 23:41:10 +0800 Subject: [PATCH 09/14] fixup Signed-off-by: tison --- CHANGELOG.md | 2 +- benchmarks/cache/main.rs | 10 ++-- benchmarks/cache_soak/main.rs | 10 ++-- benchmarks/mixed_workloads/main.rs | 10 ++-- benchmarks/recovery_scale/main.rs | 4 +- cache2/src/config/mod.rs | 2 +- cache2/src/config/runtime.rs | 46 +++++++++---------- cache2/src/hashing.rs | 1 - cache2/src/io/engine/mod.rs | 7 +-- cache2/src/io/engine/tests.rs | 2 +- cache2/src/lib.rs | 2 +- cache2/src/memory/eviction.rs | 1 - cache2/src/memory/mod.rs | 1 - cache2/src/region/file_backend/tests.rs | 7 +-- cache2/src/region/index/mod.rs | 1 - .../src/region/index/storage/page_format.rs | 1 - cache2/src/region/mod.rs | 1 - cache2/src/region/runtime/mod.rs | 7 +-- cache2/src/region/runtime/shutdown_tests.rs | 3 +- cache2/src/region/staging.rs | 1 - tests-integration/tests/cache.rs | 8 ++-- tests-integration/tests/config.rs | 20 ++++---- 22 files changed, 72 insertions(+), 75 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 895b346..2a6b798 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,7 @@ This release keeps the version 1 on-disk format and requires no disk migration. ### Breaking Changes -- `IoEngine` now carries backend-specific topology. Configure POSIX worker counts with `PosixIoConfig`; configure independent io_uring pools with `IoUringConfig` and `IoUringPoolConfig`. The backend-ambiguous `with_read_io_workers`, `with_write_io_workers`, and `with_reclaim_workers` methods were removed. +- `IoEngineConfig` now carries backend-specific topology. Configure POSIX worker counts with `PosixIoConfig`; configure independent io_uring pools with `IoUringConfig` and `IoUringPoolConfig`. The backend-ambiguous `with_read_io_workers`, `with_write_io_workers`, and `with_reclaim_workers` methods were removed. ### Improvements diff --git a/benchmarks/cache/main.rs b/benchmarks/cache/main.rs index 818a4bb..1328956 100644 --- a/benchmarks/cache/main.rs +++ b/benchmarks/cache/main.rs @@ -34,7 +34,7 @@ use benchmarks::report::emit_cache_report; use cache2::Cache; use cache2::CacheConfig; use cache2::CacheTier; -use cache2::IoEngine; +use cache2::IoEngineConfig; use cache2::IoMode; use cache2::IoUringConfig; use cache2::IoUringPoolConfig; @@ -78,7 +78,7 @@ struct BenchConfig { reclaim_workers: usize, write_clients: usize, clients: usize, - io_engine: IoEngine, + io_engine: IoEngineConfig, io_mode: IoMode, l1_eviction_policy: L1EvictionPolicy, statistics_enabled: bool, @@ -113,12 +113,12 @@ impl BenchConfig { .unwrap_or_else(|_| "posix".to_owned()) .as_str() { - "posix" => IoEngine::Posix(PosixIoConfig::new( + "posix" => IoEngineConfig::Posix(PosixIoConfig::new( read_io_workers, write_io_workers, reclaim_workers, )), - "io-uring" => IoEngine::IoUring(IoUringConfig::new( + "io-uring" => IoEngineConfig::IoUring(IoUringConfig::new( IoUringPoolConfig::new( read_io_workers, read_io_workers @@ -301,7 +301,7 @@ impl BenchConfig { match (self.io_engine, self.read_io_wait_timeout.is_zero()) { // Keep the benchmark at the POSIX engine's exact admission depth. // Saturation misses belong in the soak, not the device-rate phase. - (IoEngine::Posix(_), true) => self.clients.min(self.read_io_workers), + (IoEngineConfig::Posix(_), true) => self.clients.min(self.read_io_workers), _ => self.clients, } } diff --git a/benchmarks/cache_soak/main.rs b/benchmarks/cache_soak/main.rs index b70a5ba..fb82ffb 100644 --- a/benchmarks/cache_soak/main.rs +++ b/benchmarks/cache_soak/main.rs @@ -37,7 +37,7 @@ use cache2::Cache; use cache2::CacheConfig; use cache2::CacheHealth; use cache2::DetailedCacheSnapshot; -use cache2::IoEngine; +use cache2::IoEngineConfig; use cache2::IoMode; use cache2::IoUringConfig; use cache2::IoUringPoolConfig; @@ -85,7 +85,7 @@ struct SoakConfig { final_warm_verify: bool, require_path_coverage: bool, require_reinsert_coverage: bool, - io_engine: IoEngine, + io_engine: IoEngineConfig, io_mode: IoMode, l1_eviction_policy: L1EvictionPolicy, directory: PathBuf, @@ -1243,17 +1243,17 @@ fn parse_io_engine( read_workers: usize, write_workers: usize, reclaim_workers: usize, -) -> io::Result { +) -> io::Result { match env::var(name) .unwrap_or_else(|_| "posix".to_owned()) .as_str() { - "posix" => Ok(IoEngine::Posix(PosixIoConfig::new( + "posix" => Ok(IoEngineConfig::Posix(PosixIoConfig::new( read_workers, write_workers, reclaim_workers, ))), - "io-uring" => Ok(IoEngine::IoUring(IoUringConfig::new( + "io-uring" => Ok(IoEngineConfig::IoUring(IoUringConfig::new( io_uring_read_pool(read_workers)?, io_uring_write_pool(write_workers)?, IoUringPoolConfig::new(reclaim_workers, reclaim_workers), diff --git a/benchmarks/mixed_workloads/main.rs b/benchmarks/mixed_workloads/main.rs index 1f9f18e..01d705a 100644 --- a/benchmarks/mixed_workloads/main.rs +++ b/benchmarks/mixed_workloads/main.rs @@ -38,7 +38,7 @@ use cache2::CacheConfig; use cache2::CacheHealth; use cache2::CacheSnapshot; use cache2::DetailedCacheSnapshot; -use cache2::IoEngine; +use cache2::IoEngineConfig; use cache2::IoMode; use cache2::IoUringConfig; use cache2::IoUringPoolConfig; @@ -213,7 +213,7 @@ struct HarnessConfig { reclaim_workers: usize, latency_sample_interval: usize, seed: u64, - io_engine: IoEngine, + io_engine: IoEngineConfig, io_mode: IoMode, l1_eviction_policy: L1EvictionPolicy, directory: PathBuf, @@ -240,12 +240,12 @@ impl HarnessConfig { .unwrap_or_else(|_| "posix".to_owned()) .as_str() { - "posix" => IoEngine::Posix(PosixIoConfig::new( + "posix" => IoEngineConfig::Posix(PosixIoConfig::new( read_io_workers, write_io_workers, reclaim_workers, )), - "io-uring" => IoEngine::IoUring(IoUringConfig::new( + "io-uring" => IoEngineConfig::IoUring(IoUringConfig::new( IoUringPoolConfig::new( read_io_workers, read_io_workers @@ -414,7 +414,7 @@ struct EffectiveConfig { reclaim_workers: usize, latency_sample_interval: usize, seed: u64, - io_engine: IoEngine, + io_engine: IoEngineConfig, io_mode: IoMode, l1_eviction_policy: L1EvictionPolicy, directory: PathBuf, diff --git a/benchmarks/recovery_scale/main.rs b/benchmarks/recovery_scale/main.rs index 680a9a7..f5cc0a9 100644 --- a/benchmarks/recovery_scale/main.rs +++ b/benchmarks/recovery_scale/main.rs @@ -28,7 +28,7 @@ use benchmarks::report::JobReport; use benchmarks::report::RunReporter; use cache2::Cache; use cache2::CacheConfig; -use cache2::IoEngine; +use cache2::IoEngineConfig; use cache2::IoMode; use cache2::PosixIoConfig; use cache2::RuntimeOptions; @@ -92,7 +92,7 @@ impl ScaleConfig { fn runtime_options(&self) -> RuntimeOptions { RuntimeOptions { - io_engine: IoEngine::Posix(PosixIoConfig::new(1, 1, 1)), + io_engine: IoEngineConfig::Posix(PosixIoConfig::new(1, 1, 1)), io_mode: IoMode::Buffered, append_shards: 4, l1_capacity_bytes: self.memory_bytes, diff --git a/cache2/src/config/mod.rs b/cache2/src/config/mod.rs index 58773d0..f5511c0 100644 --- a/cache2/src/config/mod.rs +++ b/cache2/src/config/mod.rs @@ -17,7 +17,7 @@ use crate::region::recovery::DataGeometry; mod runtime; -pub use self::runtime::IoEngine; +pub use self::runtime::IoEngineConfig; pub use self::runtime::IoMode; pub use self::runtime::IoPoolTopology; pub use self::runtime::IoUringConfig; diff --git a/cache2/src/config/runtime.rs b/cache2/src/config/runtime.rs index e218d5d..1a6e4e8 100644 --- a/cache2/src/config/runtime.rs +++ b/cache2/src/config/runtime.rs @@ -246,7 +246,7 @@ impl Default for IoUringConfig { /// I/O pools. #[non_exhaustive] #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum IoEngine { +pub enum IoEngineConfig { /// Worker-backed POSIX positioned I/O with explicit thread counts. Posix(PosixIoConfig), /// Experimental Linux io_uring engine with independent ring and in-flight @@ -260,13 +260,13 @@ pub enum IoEngine { IoUring(IoUringConfig), } -impl Default for IoEngine { +impl Default for IoEngineConfig { fn default() -> Self { Self::Posix(PosixIoConfig::default()) } } -impl IoEngine { +impl IoEngineConfig { const fn is_available(self) -> bool { match self { Self::Posix(_) => true, @@ -294,24 +294,24 @@ pub struct IoPoolTopology { } impl IoPoolTopology { - pub const fn read(engine: IoEngine) -> Self { + pub const fn read(engine: IoEngineConfig) -> Self { match engine { - IoEngine::Posix(config) => Self::posix(config.read_workers), - IoEngine::IoUring(config) => Self::io_uring(config.read), + IoEngineConfig::Posix(config) => Self::posix(config.read_workers), + IoEngineConfig::IoUring(config) => Self::io_uring(config.read), } } - pub const fn write(engine: IoEngine) -> Self { + pub const fn write(engine: IoEngineConfig) -> Self { match engine { - IoEngine::Posix(config) => Self::posix(config.write_workers), - IoEngine::IoUring(config) => Self::io_uring(config.write), + IoEngineConfig::Posix(config) => Self::posix(config.write_workers), + IoEngineConfig::IoUring(config) => Self::io_uring(config.write), } } - pub const fn reclaim(engine: IoEngine) -> Self { + pub const fn reclaim(engine: IoEngineConfig) -> Self { match engine { - IoEngine::Posix(config) => Self::posix(config.reclaim_workers), - IoEngine::IoUring(config) => Self::io_uring(config.reclaim), + IoEngineConfig::Posix(config) => Self::posix(config.reclaim_workers), + IoEngineConfig::IoUring(config) => Self::io_uring(config.reclaim), } } @@ -404,7 +404,7 @@ pub enum ReadAdmission { pub struct RuntimeOptions { /// Independent read, write, and reclaim pools. Defaults to POSIX with 4, 4, /// and 1 workers. - pub io_engine: IoEngine, + pub io_engine: IoEngineConfig, /// Record I/O mode. Defaults to buffered; direct I/O requires supported Linux storage. pub io_mode: IoMode, /// Admission policy after an L2 candidate has been selected. @@ -438,7 +438,7 @@ pub struct RuntimeOptions { impl Default for RuntimeOptions { fn default() -> Self { Self { - io_engine: IoEngine::default(), + io_engine: IoEngineConfig::default(), io_mode: IoMode::Buffered, read_admission: ReadAdmission::Immediate, append_shards: DEFAULT_APPEND_SHARDS, @@ -572,12 +572,12 @@ impl RuntimeOptions { let write_topology = IoPoolTopology::write(self.io_engine); let reclaim_topology = IoPoolTopology::reclaim(self.io_engine); match self.io_engine { - IoEngine::Posix(_) => { + IoEngineConfig::Posix(_) => { validate_posix_pool("read", read_topology)?; validate_posix_pool("write", write_topology)?; validate_posix_pool("reclaim", reclaim_topology)?; } - IoEngine::IoUring(config) => { + IoEngineConfig::IoUring(config) => { validate_io_uring_pool("read", config.read())?; validate_io_uring_pool("write", config.write())?; validate_io_uring_pool("reclaim", config.reclaim())?; @@ -806,7 +806,7 @@ mod tests { #[test] fn optional_read_wait_queue_is_memory_accounted() { let base = RuntimeOptions { - io_engine: IoEngine::Posix(PosixIoConfig::new(7, 4, 1)), + io_engine: IoEngineConfig::Posix(PosixIoConfig::new(7, 4, 1)), ..RuntimeOptions::default() }; let no_wait = runtime_topology_memory_bytes(&base).unwrap(); @@ -836,7 +836,7 @@ mod tests { }; let (_, base_minimum) = base.memory_requirements(geometry, 0).unwrap(); let (_, parallel_minimum) = RuntimeOptions { - io_engine: IoEngine::Posix(PosixIoConfig::new(4, 4, 2)), + io_engine: IoEngineConfig::Posix(PosixIoConfig::new(4, 4, 2)), ..base } .memory_requirements(geometry, 0) @@ -853,7 +853,7 @@ mod tests { #[test] fn io_engine_topology_matches_backend_shape() { - let posix = IoEngine::Posix(PosixIoConfig::new(7, 5, 2)); + let posix = IoEngineConfig::Posix(PosixIoConfig::new(7, 5, 2)); assert_eq!( IoPoolTopology::read(posix), IoPoolTopology { @@ -864,7 +864,7 @@ mod tests { } ); - let io_uring = IoEngine::IoUring(IoUringConfig::new( + let io_uring = IoEngineConfig::IoUring(IoUringConfig::new( IoUringPoolConfig::new(3, 8), IoUringPoolConfig::new(2, 5), IoUringPoolConfig::new(1, 2), @@ -882,11 +882,11 @@ mod tests { fn io_uring_depth_reserves_more_than_common_request_bookkeeping() { let pool = IoUringPoolConfig::new(1, 1); let shallow = RuntimeOptions { - io_engine: IoEngine::IoUring(IoUringConfig::new(pool, pool, pool)), + io_engine: IoEngineConfig::IoUring(IoUringConfig::new(pool, pool, pool)), ..RuntimeOptions::default() }; let deep = RuntimeOptions { - io_engine: IoEngine::IoUring(IoUringConfig::new( + io_engine: IoEngineConfig::IoUring(IoUringConfig::new( IoUringPoolConfig::new(1, MAX_IO_REQUESTS_PER_ENGINE), pool, pool, @@ -930,7 +930,7 @@ mod tests { fn io_poll_requires_direct_mode() { let pool = IoUringPoolConfig::default().with_io_poll(true); let mut config = RuntimeOptions { - io_engine: IoEngine::IoUring(IoUringConfig::new( + io_engine: IoEngineConfig::IoUring(IoUringConfig::new( pool, IoUringPoolConfig::default(), IoUringPoolConfig::new(1, 1), diff --git a/cache2/src/hashing.rs b/cache2/src/hashing.rs index 1a87d49..d75b1df 100644 --- a/cache2/src/hashing.rs +++ b/cache2/src/hashing.rs @@ -13,7 +13,6 @@ // limitations under the License. use std::io; -use std::mem::size_of; const EMPTY_VALUE: u32 = u32::MAX; const DELETED_VALUE: u32 = u32::MAX - 1; diff --git a/cache2/src/io/engine/mod.rs b/cache2/src/io/engine/mod.rs index 6bc5be5..0175611 100644 --- a/cache2/src/io/engine/mod.rs +++ b/cache2/src/io/engine/mod.rs @@ -44,6 +44,7 @@ use std::time::Instant; use asyncband::semaphore::OwnedSemaphorePermit; use asyncband::semaphore::Semaphore; +use crate::IoEngineConfig; #[cfg(unix)] use crate::config::IoUringPoolConfig; use crate::io::backend::IoBackend; @@ -1961,13 +1962,13 @@ pub fn build_file_engine( files: RuntimeFileSet, max_in_flight: usize, posix_workers: usize, - kind: crate::config::IoEngine, + kind: IoEngineConfig, io_uring_config: Option, statistics_enabled: bool, read_wait_enabled: bool, ) -> io::Result> { match kind { - crate::config::IoEngine::Posix(_) => BackendIoEngine::new_with_files_and_workers( + IoEngineConfig::Posix(_) => BackendIoEngine::new_with_files_and_workers( files, max_in_flight, posix_workers, @@ -1975,7 +1976,7 @@ pub fn build_file_engine( read_wait_enabled, ) .map(|engine| Arc::new(engine) as Arc), - crate::config::IoEngine::IoUring(_) => { + IoEngineConfig::IoUring(_) => { let _ = posix_workers; #[cfg(all( feature = "io-uring", diff --git a/cache2/src/io/engine/tests.rs b/cache2/src/io/engine/tests.rs index 22f4bde..c12c54b 100644 --- a/cache2/src/io/engine/tests.rs +++ b/cache2/src/io/engine/tests.rs @@ -764,7 +764,7 @@ fn configured_posix_engine_shares_its_worker_capacity() { files, 4, 4, - crate::config::IoEngine::Posix(PosixIoConfig::new(4, 4, 1)), + IoEngineConfig::Posix(PosixIoConfig::new(4, 4, 1)), None, false, false, diff --git a/cache2/src/lib.rs b/cache2/src/lib.rs index 2df51a5..a3208d0 100644 --- a/cache2/src/lib.rs +++ b/cache2/src/lib.rs @@ -34,7 +34,7 @@ pub use self::cache::Value; mod config; pub use self::config::CacheConfig; -pub use self::config::IoEngine; +pub use self::config::IoEngineConfig; pub use self::config::IoMode; pub use self::config::IoUringConfig; pub use self::config::IoUringPoolConfig; diff --git a/cache2/src/memory/eviction.rs b/cache2/src/memory/eviction.rs index a4ce814..db4dd02 100644 --- a/cache2/src/memory/eviction.rs +++ b/cache2/src/memory/eviction.rs @@ -18,7 +18,6 @@ //! the optional CLOCK or S3-FIFO metadata and chooses bounded victims. use std::io; -use std::mem::size_of; use crate::config::L1EvictionPolicy; use crate::hashing::FixedPrehashedMap; diff --git a/cache2/src/memory/mod.rs b/cache2/src/memory/mod.rs index c7a7f46..f8f8302 100644 --- a/cache2/src/memory/mod.rs +++ b/cache2/src/memory/mod.rs @@ -20,7 +20,6 @@ use std::hint::spin_loop; use std::io; -use std::mem::size_of; use std::ops::Deref; use std::sync::Arc; #[cfg(test)] diff --git a/cache2/src/region/file_backend/tests.rs b/cache2/src/region/file_backend/tests.rs index 555657f..53c84b1 100644 --- a/cache2/src/region/file_backend/tests.rs +++ b/cache2/src/region/file_backend/tests.rs @@ -33,6 +33,7 @@ use std::time::Duration; use std::time::Instant; use super::*; +use crate::IoEngineConfig; use crate::config::MAX_WRITE_FLUSH_THRESHOLD_BYTES; use crate::config::PosixIoConfig; use crate::config::ReadAdmission; @@ -408,7 +409,7 @@ fn configured_read_wait_is_bounded_and_cancel_safe() { let directory = TestDirectory::new(); let data = production_data_superblock(512 * 1024); let runtime_config = RuntimeOptions { - io_engine: crate::config::IoEngine::Posix(PosixIoConfig::new(2, 4, 1)), + io_engine: IoEngineConfig::Posix(PosixIoConfig::new(2, 4, 1)), l1_capacity_bytes: 0, statistics: true, read_admission: ReadAdmission::Wait { @@ -492,7 +493,7 @@ fn queued_l2_read_does_not_pin_warm_close() { let directory = TestDirectory::new(); let data = production_data_superblock(512 * 1024); let runtime_config = RuntimeOptions { - io_engine: crate::config::IoEngine::Posix(PosixIoConfig::new(1, 4, 1)), + io_engine: IoEngineConfig::Posix(PosixIoConfig::new(1, 4, 1)), l1_capacity_bytes: 0, read_admission: ReadAdmission::Wait { timeout: Duration::from_secs(1), @@ -657,7 +658,7 @@ fn poisoned_runtime_gates_stop_workers_and_reject_warm_close() { let directory = TestDirectory::new(); let data = production_data_superblock(512 * 1024); let runtime_config = RuntimeOptions { - io_engine: crate::config::IoEngine::Posix(PosixIoConfig::new(1, 1, 1)), + io_engine: IoEngineConfig::Posix(PosixIoConfig::new(1, 1, 1)), l1_capacity_bytes: 0, managed_memory_limit_bytes: 32 * 1024 * 1024, write_flush_threshold_bytes: 128 * 1024, diff --git a/cache2/src/region/index/mod.rs b/cache2/src/region/index/mod.rs index 84f0846..41720ab 100644 --- a/cache2/src/region/index/mod.rs +++ b/cache2/src/region/index/mod.rs @@ -25,7 +25,6 @@ use std::array; #[cfg(feature = "benchmarking")] use std::cell::Cell; use std::io; -use std::mem::size_of; use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; diff --git a/cache2/src/region/index/storage/page_format.rs b/cache2/src/region/index/storage/page_format.rs index 54cc1ae..3027f81 100644 --- a/cache2/src/region/index/storage/page_format.rs +++ b/cache2/src/region/index/storage/page_format.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::mem::size_of; use crate::checksum::Crc32c; use crate::region::index::storage::CorruptPageReason; diff --git a/cache2/src/region/mod.rs b/cache2/src/region/mod.rs index 142a782..e1cfb41 100644 --- a/cache2/src/region/mod.rs +++ b/cache2/src/region/mod.rs @@ -16,7 +16,6 @@ use std::fmt; use std::io; -use std::mem::size_of; use std::ops::Range; use std::sync::Arc; use std::sync::Mutex; diff --git a/cache2/src/region/runtime/mod.rs b/cache2/src/region/runtime/mod.rs index 32310d6..1efc534 100644 --- a/cache2/src/region/runtime/mod.rs +++ b/cache2/src/region/runtime/mod.rs @@ -40,6 +40,7 @@ use asyncband::semaphore::Semaphore; use asyncband::watch; use self::metrics::RuntimeMetrics; +use crate::IoEngineConfig; use crate::config::CacheConfig; use crate::config::IoMode; use crate::config::IoPoolTopology; @@ -1547,7 +1548,7 @@ fn build_engine_pool( engines .try_reserve_exact(engine_count) .map_err(|_| io::Error::new(io::ErrorKind::OutOfMemory, "cannot allocate I/O workers"))?; - let posix_workers = if matches!(config.io_engine, crate::config::IoEngine::Posix(_)) { + let posix_workers = if matches!(config.io_engine, IoEngineConfig::Posix(_)) { topology.max_in_flight } else { 1 @@ -2463,7 +2464,7 @@ mod tests { #[test] fn completion_timeouts_follow_read_wait_mode() { - use crate::config::IoEngine; + use crate::config::IoEngineConfig; use crate::config::PosixIoConfig; use crate::region::FileRegionBackend; use crate::region::RegionFiles; @@ -2497,7 +2498,7 @@ mod tests { }; for wait in [Duration::ZERO, Duration::from_millis(1)] { let config = RuntimeOptions { - io_engine: IoEngine::Posix(PosixIoConfig::new(1, 1, 1)), + io_engine: IoEngineConfig::Posix(PosixIoConfig::new(1, 1, 1)), append_shards: 1, l1_capacity_bytes: 0, statistics: true, diff --git a/cache2/src/region/runtime/shutdown_tests.rs b/cache2/src/region/runtime/shutdown_tests.rs index b97cd86..14d94af 100644 --- a/cache2/src/region/runtime/shutdown_tests.rs +++ b/cache2/src/region/runtime/shutdown_tests.rs @@ -28,6 +28,7 @@ use crate::io::engine::IoRequest; use crate::io::engine::ReadSlotWaiter; use crate::io::engine::RequestId; use crate::io::engine::SubmitError; +use crate::IoEngineConfig; #[derive(Default)] struct BlockedReadState { @@ -230,7 +231,7 @@ fn assert_close_does_not_wait_for_read(submit_before_close: bool) { let config = RuntimeOptions { append_shards: 1, l1_capacity_bytes: 0, - io_engine: crate::config::IoEngine::Posix(PosixIoConfig::new(1, 1, 1)), + io_engine: IoEngineConfig::Posix(PosixIoConfig::new(1, 1, 1)), ..RuntimeOptions::default() }; let mut store = RegionStore::open( diff --git a/cache2/src/region/staging.rs b/cache2/src/region/staging.rs index 1722714..6ca5488 100644 --- a/cache2/src/region/staging.rs +++ b/cache2/src/region/staging.rs @@ -18,7 +18,6 @@ use std::fmt; use std::mem; -use std::mem::size_of; use std::sync::Mutex; use std::sync::MutexGuard; diff --git a/tests-integration/tests/cache.rs b/tests-integration/tests/cache.rs index e427214..039f99f 100644 --- a/tests-integration/tests/cache.rs +++ b/tests-integration/tests/cache.rs @@ -40,7 +40,7 @@ use cache2::DetailedCacheSnapshot; use cache2::Error; use cache2::ErrorKind; use cache2::ErrorOperation; -use cache2::IoEngine; +use cache2::IoEngineConfig; #[cfg(not(target_os = "linux"))] use cache2::IoMode; #[cfg(not(target_os = "linux"))] @@ -67,7 +67,7 @@ fn test_storage() -> StorageLayout { fn test_runtime_options(workers: usize, append_shards: u32) -> RuntimeOptions { RuntimeOptions { - io_engine: IoEngine::Posix(PosixIoConfig::new(workers, workers, 1)), + io_engine: IoEngineConfig::Posix(PosixIoConfig::new(workers, workers, 1)), append_shards, l1_capacity_bytes: 4 * 1024 * 1024, managed_memory_limit_bytes: 32 * 1024 * 1024, @@ -512,7 +512,7 @@ async fn l1_bypass_may_remain_stale_after_region_completion() { fn unavailable_io_engine_is_rejected_before_file_creation() { let files = TestCache::new("unavailable-io-engine"); let runtime = RuntimeOptions { - io_engine: IoEngine::IoUring(IoUringConfig::default()), + io_engine: IoEngineConfig::IoUring(IoUringConfig::default()), write_flush_threshold_bytes: 128 * 1024, statistics: false, ..test_runtime_options(1, 2) @@ -569,7 +569,7 @@ async fn runtime_options_can_change_across_a_warm_reopen() { cache.close_warm().await.unwrap(); let retuned = RuntimeOptions { - io_engine: IoEngine::Posix(PosixIoConfig::new(7, 2, 2)), + io_engine: IoEngineConfig::Posix(PosixIoConfig::new(7, 2, 2)), l1_capacity_bytes: 2 * 1024 * 1024, l1_eviction_policy: L1EvictionPolicy::S3Fifo, l1_shards: 7, diff --git a/tests-integration/tests/config.rs b/tests-integration/tests/config.rs index 65b45b2..3f38db1 100644 --- a/tests-integration/tests/config.rs +++ b/tests-integration/tests/config.rs @@ -17,7 +17,7 @@ use std::time::Duration; use cache2::CacheConfig; use cache2::ErrorKind; use cache2::ErrorOperation; -use cache2::IoEngine; +use cache2::IoEngineConfig; use cache2::L1EvictionPolicy; use cache2::PosixIoConfig; use cache2::ReadAdmission; @@ -100,7 +100,7 @@ fn large_layout_memory_floor_includes_each_l1_policy() { let runtime = RuntimeOptions { l1_capacity_bytes: 10 * GIB, managed_memory_limit_bytes: 15 * GIB, - io_engine: IoEngine::Posix(PosixIoConfig::new(4, 4, 2)), + io_engine: IoEngineConfig::Posix(PosixIoConfig::new(4, 4, 2)), l1_shards: 64, l1_eviction_policy: policy, ..RuntimeOptions::default() @@ -144,7 +144,7 @@ fn automatic_wait_capacity_uses_the_selected_engine() { let config = CacheConfig::new( storage.clone(), RuntimeOptions { - io_engine: IoEngine::Posix(PosixIoConfig::new(workers, 1, 1)), + io_engine: IoEngineConfig::Posix(PosixIoConfig::new(workers, 1, 1)), ..options.clone() }, ) @@ -204,23 +204,23 @@ fn invalid_runtime_options_are_rejected_when_building_configuration() { ..config }), ("zero-reclaim-workers", |config| RuntimeOptions { - io_engine: IoEngine::Posix(PosixIoConfig::new(1, 1, 0)), + io_engine: IoEngineConfig::Posix(PosixIoConfig::new(1, 1, 0)), ..config }), ("too-many-reclaim-workers", |config| RuntimeOptions { - io_engine: IoEngine::Posix(PosixIoConfig::new(1, 1, 3)), + io_engine: IoEngineConfig::Posix(PosixIoConfig::new(1, 1, 3)), ..config }), ("zero-read-workers", |config| RuntimeOptions { - io_engine: IoEngine::Posix(PosixIoConfig::new(0, 1, 1)), + io_engine: IoEngineConfig::Posix(PosixIoConfig::new(0, 1, 1)), ..config }), ("zero-write-workers", |config| RuntimeOptions { - io_engine: IoEngine::Posix(PosixIoConfig::new(1, 0, 1)), + io_engine: IoEngineConfig::Posix(PosixIoConfig::new(1, 0, 1)), ..config }), ("too-many-read-workers", |config| RuntimeOptions { - io_engine: IoEngine::Posix(PosixIoConfig::new(4097, 1, 1)), + io_engine: IoEngineConfig::Posix(PosixIoConfig::new(4097, 1, 1)), ..config }), ("zero-read-wait-capacity", |config| RuntimeOptions { @@ -238,7 +238,7 @@ fn invalid_runtime_options_are_rejected_when_building_configuration() { ..config }), ("too-many-write-workers", |config| RuntimeOptions { - io_engine: IoEngine::Posix(PosixIoConfig::new(1, 4097, 1)), + io_engine: IoEngineConfig::Posix(PosixIoConfig::new(1, 4097, 1)), ..config }), ("excessive-read-wait", |config| RuntimeOptions { @@ -254,7 +254,7 @@ fn invalid_runtime_options_are_rejected_when_building_configuration() { ..config }), ("fixed-footprint-exceeds-budget", |config| RuntimeOptions { - io_engine: IoEngine::Posix(PosixIoConfig::new(2, 2, 1)), + io_engine: IoEngineConfig::Posix(PosixIoConfig::new(2, 2, 1)), l1_capacity_bytes: 0, managed_memory_limit_bytes: 2 * 1024 * 1024, write_flush_threshold_bytes: 128 * 1024, From a57d56e84356182fcf4f1a6bf6cc734652e77360 Mon Sep 17 00:00:00 2001 From: tison Date: Wed, 9 Sep 2026 23:55:48 +0800 Subject: [PATCH 10/14] docs: align configuration examples with IoEngineConfig --- CHANGELOG.md | 1 + CONFIGURATION.md | 4 ++-- README.md | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a6b798..9f36f58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Breaking Changes +- The public I/O configuration enum is now named `IoEngineConfig`; replace `IoEngine` imports and variant paths with `IoEngineConfig`. - Error types are exported only from the crate root. Replace imports from `cache2::error` with `cache2::{Error, ErrorKind, ErrorOperation}`. - The `cache2::Result` alias is removed. Use the standard `Result` with `Error` imported from `cache2`; public operation error types are unchanged. - Configuration now separates editable `StorageOptions` / `RuntimeOptions` from immutable `StorageLayout` / `CacheConfig`. Build the layout, construct `CacheConfig::new(layout, options)`, and call `Cache::open(path, config)` or `Cache::open_with_handle(path, config, handle)`. `StaticConfig`, `RuntimeConfig`, `CacheBuilder`, and the standalone `validate` method are removed. diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 6767efa..7deb233 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -270,14 +270,14 @@ io_uring is feature-gated and experimental. Its three pools configure physical r ```rust use cache2::{ - IoEngine, IoMode, IoUringConfig, IoUringPoolConfig, + IoEngineConfig, IoMode, IoUringConfig, IoUringPoolConfig, IoUringSqPollConfig, RuntimeOptions, }; let read = IoUringPoolConfig::new(1, 128) .with_sq_poll(IoUringSqPollConfig::new(2_000).with_cpu(4)); let runtime = RuntimeOptions { - io_engine: IoEngine::IoUring(IoUringConfig::new( + io_engine: IoEngineConfig::IoUring(IoUringConfig::new( read, IoUringPoolConfig::new(1, 64), IoUringPoolConfig::new(1, 1), diff --git a/README.md b/README.md index d75f23d..2acf0fa 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ See the [configuration guide](CONFIGURATION.md#configuration-lifecycle) for exam | Area | Controls | Default and behavior | |-----------|----------------------------------------------------------------------------|---------------------------------------------------------------------------------------------| | L1 | `l1_capacity_bytes`, `l1_shards`, `l1_eviction_policy` | 256 MiB, 32 shards, CLOCK. Zero capacity disables L1; entries charged above 256 KiB use L2. | -| I/O pools | `io_engine: IoEngine::Posix(...)` or `IoEngine::IoUring(...)` | Four POSIX read workers, four write workers, and one reclaimer; io_uring is experimental. | +| I/O pools | `io_engine: IoEngineConfig::Posix(...)` or `IoEngineConfig::IoUring(...)` | Four POSIX read workers, four write workers, and one reclaimer; io_uring is experimental. | | Read wait | `read_admission: ReadAdmission::Immediate` or `ReadAdmission::Wait { .. }` | Immediate admission; wait capacity defaults to aggregate read capacity. | | Writes | `append_shards`, `write_flush_threshold_bytes` | Four append shards and a 4 MiB flush threshold. | | Memory | `managed_memory_limit_bytes` | 1 GiB across cache-managed allocations. | From edd39c59b2e79d2cee6c258c320438c2dfe4807e Mon Sep 17 00:00:00 2001 From: tison Date: Wed, 9 Sep 2026 23:55:48 +0800 Subject: [PATCH 11/14] refactor: use owning modules for internal references Keep selective public exports at the crate root. Expose internal child modules through private parents and reference their definitions directly, removing redundant re-exports. Keep implementations used only by their parent module private. --- cache2/src/benchmarking.rs | 12 ++--- cache2/src/cache.rs | 14 +++--- cache2/src/config/mod.rs | 28 ++--------- cache2/src/config/runtime.rs | 4 +- cache2/src/config/storage.rs | 14 +++--- cache2/src/io/backend.rs | 2 +- cache2/src/io/engine/mod.rs | 16 +----- cache2/src/io/engine/tests.rs | 2 +- cache2/src/io/engine/uring.rs | 2 +- cache2/src/lib.rs | 20 ++++---- cache2/src/memory/eviction.rs | 2 +- cache2/src/memory/mod.rs | 2 +- cache2/src/property_tests.rs | 18 +++---- cache2/src/region/file_backend/mod.rs | 26 +++++----- cache2/src/region/file_backend/tests.rs | 16 +++--- cache2/src/region/index/mod.rs | 22 ++------- cache2/src/region/index/storage/mod.rs | 24 ++++----- .../src/region/index/storage/page_format.rs | 1 - cache2/src/region/manager.rs | 14 +++--- cache2/src/region/mod.rs | 49 +++++++------------ cache2/src/region/reader.rs | 4 +- cache2/src/region/record/codec.rs | 8 +-- cache2/src/region/record/mod.rs | 8 +-- cache2/src/region/recovery/metadata.rs | 10 ++-- cache2/src/region/recovery/mod.rs | 19 ++----- cache2/src/region/runtime/mod.rs | 41 ++++++++-------- cache2/src/region/runtime/shutdown_tests.rs | 8 +-- cache2/src/region/staging.rs | 8 +-- 28 files changed, 162 insertions(+), 232 deletions(-) diff --git a/cache2/src/benchmarking.rs b/cache2/src/benchmarking.rs index 750ecdf..79399fd 100644 --- a/cache2/src/benchmarking.rs +++ b/cache2/src/benchmarking.rs @@ -21,17 +21,17 @@ use std::time::Duration; use std::time::Instant; use crate::region::index::BenchmarkProbeStats; -use crate::region::index::IndexEntry; -use crate::region::index::MAX_INDEX_PROBES; -use crate::region::index::MAX_PACKED_REGION_COUNT; -use crate::region::index::MAX_REGION_OFFSET; -use crate::region::index::PackedLocation; use crate::region::index::RegionIndex; +use crate::region::index::packed::IndexEntry; +use crate::region::index::packed::MAX_INDEX_PROBES; +use crate::region::index::packed::MAX_PACKED_REGION_COUNT; +use crate::region::index::packed::MAX_REGION_OFFSET; +use crate::region::index::packed::PackedLocation; use crate::region::index::reset_benchmark_probe_stats; use crate::region::index::storage::PartitionedIndexStorage; use crate::region::index::storage::validated_index_partition_ranges; use crate::region::index::take_benchmark_probe_stats; -use crate::region::record::hash_key; +use crate::region::record::codec::hash_key; use crate::snapshot::CacheIndexSnapshot; const BENCHMARK_HASH_SEED: u64 = 0x6a09_e667_f3bc_c909; diff --git a/cache2/src/cache.rs b/cache2/src/cache.rs index ebb24dd..9baffaf 100644 --- a/cache2/src/cache.rs +++ b/cache2/src/cache.rs @@ -36,22 +36,22 @@ use std::time::UNIX_EPOCH; use tokio::task::JoinError; use crate::config::CacheConfig; -use crate::config::KEY_HASH_SEED; +use crate::config::storage::KEY_HASH_SEED; use crate::config::storage_fingerprint; use crate::config::storage_geometry; use crate::error::Error; use crate::error::ErrorOperation; use crate::error::from_io; -use crate::region::FileRegionBackend; -use crate::region::HybridValueRead; -use crate::region::RegionDataPlane; -use crate::region::RegionFiles; -use crate::region::RegionStore; -use crate::region::SystemRegionFileSystem; +use crate::region::file_backend::FileRegionBackend; +use crate::region::file_backend::RegionFiles; +use crate::region::file_backend::SystemRegionFileSystem; use crate::region::recovery::DataSuperblock; use crate::region::recovery::PersistentId; use crate::region::recovery::RECOVERY_IMAGE_INDEX_OFFSET; use crate::region::recovery::recovery_image_index_len; +use crate::region::runtime::HybridValueRead; +use crate::region::runtime::RegionDataPlane; +use crate::region::store::RegionStore; use crate::snapshot::CacheSnapshot; use crate::snapshot::DetailedCacheSnapshot; use crate::snapshot::StartupMode; diff --git a/cache2/src/config/mod.rs b/cache2/src/config/mod.rs index f5511c0..6d3cd3a 100644 --- a/cache2/src/config/mod.rs +++ b/cache2/src/config/mod.rs @@ -14,29 +14,11 @@ //! Configuration construction, independent of file paths and runtime handles. +use crate::config::runtime::RuntimeOptions; use crate::region::recovery::DataGeometry; -mod runtime; -pub use self::runtime::IoEngineConfig; -pub use self::runtime::IoMode; -pub use self::runtime::IoPoolTopology; -pub use self::runtime::IoUringConfig; -pub use self::runtime::IoUringPoolConfig; -pub use self::runtime::IoUringSqPollConfig; -pub use self::runtime::L1EvictionPolicy; -#[cfg(test)] -pub use self::runtime::MAX_WRITE_FLUSH_THRESHOLD_BYTES; -pub use self::runtime::PosixIoConfig; -pub use self::runtime::ReadAdmission; -pub use self::runtime::RuntimeOptions; -pub use self::runtime::read_io_wait_capacity; -pub use self::runtime::read_io_wait_timeout; - -mod storage; -pub use self::storage::KEY_HASH_SEED; -pub use self::storage::StorageOptions; -#[cfg(test)] -pub use self::storage::cache_config; +pub mod runtime; +pub mod storage; /// Complete, immutable configuration for opening a [`Cache`](crate::Cache). /// @@ -77,8 +59,8 @@ impl CacheConfig { /// Immutable persistent geometry with a checked logical disk bound. /// -/// Created by [`StorageOptions::build`]. Changing the geometry or index size -/// changes the disk identity, so an incompatible recovery image opens empty. +/// Created by [`StorageOptions::build`](crate::StorageOptions::build). Changing the geometry or +/// index size changes the disk identity, so an incompatible recovery image opens empty. /// Layout construction neither reserves disk space nor requires a Tokio runtime. #[derive(Clone, Debug, Eq, PartialEq)] pub struct StorageLayout { diff --git a/cache2/src/config/runtime.rs b/cache2/src/config/runtime.rs index 1a6e4e8..ce309af 100644 --- a/cache2/src/config/runtime.rs +++ b/cache2/src/config/runtime.rs @@ -24,10 +24,10 @@ use crate::io::engine::IO_QUEUE_ENTRY_RESERVATION_BYTES; use crate::io::engine::MAX_IO_REQUESTS_PER_ENGINE; use crate::io::engine::io_uring_extra_memory_bytes; use crate::memory::MemoryStore; -use crate::region::ActivityMetrics; -use crate::region::RegionStaging; use crate::region::recovery::DataGeometry; +use crate::region::runtime::metrics::ActivityMetrics; use crate::region::runtime_fixed_memory_bytes; +use crate::region::staging::RegionStaging; use crate::resources::BUFFER_ALIGNMENT; use crate::resources::CACHE_THREAD_STACK_BYTES; use crate::resources::MAX_CONFIG_COUNT; diff --git a/cache2/src/config/storage.rs b/cache2/src/config/storage.rs index dce0354..804b640 100644 --- a/cache2/src/config/storage.rs +++ b/cache2/src/config/storage.rs @@ -18,23 +18,23 @@ use std::io; #[cfg(test)] use crate::config::CacheConfig; -#[cfg(test)] -use crate::config::RuntimeOptions; use crate::config::StorageLayout; +#[cfg(test)] +use crate::config::runtime::RuntimeOptions; use crate::error::Error; use crate::error::ErrorOperation; use crate::error::from_io; -use crate::region::index::MAX_PACKED_REGION_COUNT; -use crate::region::index::MAX_PACKED_REGION_SIZE; +use crate::region::index::packed::MAX_PACKED_REGION_COUNT; +use crate::region::index::packed::MAX_PACKED_REGION_SIZE; use crate::region::index::storage::IndexStorageError; use crate::region::index::storage::validated_index_partition_ranges; use crate::region::recovery::DataGeometry; use crate::region::recovery::KEY_HASH_ALGORITHM_XXH3_64; use crate::region::recovery::RECOVERY_IMAGE_INDEX_OFFSET; -use crate::region::recovery::REGION_METADATA_PAGE_SIZE; -use crate::region::recovery::REGION_METADATA_PARTITIONS_PER_PAGE; -use crate::region::recovery::REGION_METADATA_REGIONS_PER_PAGE; use crate::region::recovery::STATE_FILE_SIZE; +use crate::region::recovery::metadata::REGION_METADATA_PAGE_SIZE; +use crate::region::recovery::metadata::REGION_METADATA_PARTITIONS_PER_PAGE; +use crate::region::recovery::metadata::REGION_METADATA_REGIONS_PER_PAGE; use crate::region::recovery::recovery_image_index_len; const DEFAULT_REGION_SIZE: u64 = 32 * 1024 * 1024; diff --git a/cache2/src/io/backend.rs b/cache2/src/io/backend.rs index 2c4def3..c8eae6a 100644 --- a/cache2/src/io/backend.rs +++ b/cache2/src/io/backend.rs @@ -37,7 +37,7 @@ use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; -use crate::config::IoMode; +use crate::config::runtime::IoMode; use crate::snapshot::CacheIoPathSnapshot; pub const DIRECT_IO_ALIGNMENT: usize = 4096; diff --git a/cache2/src/io/engine/mod.rs b/cache2/src/io/engine/mod.rs index 0175611..07ad2d8 100644 --- a/cache2/src/io/engine/mod.rs +++ b/cache2/src/io/engine/mod.rs @@ -46,7 +46,7 @@ use asyncband::semaphore::Semaphore; use crate::IoEngineConfig; #[cfg(unix)] -use crate::config::IoUringPoolConfig; +use crate::config::runtime::IoUringPoolConfig; use crate::io::backend::IoBackend; #[cfg(unix)] use crate::io::backend::RuntimeFileSet; @@ -93,18 +93,6 @@ mod posix; ) ))] mod uring; -#[cfg(all( - feature = "io-uring", - target_os = "linux", - any( - target_arch = "x86_64", - target_arch = "aarch64", - target_arch = "riscv64", - target_arch = "loongarch64", - target_arch = "powerpc64" - ) -))] -pub use self::uring::UringIoEngine; /// Reference engine: a small fixed worker pool executes exact operations /// through the existing fault-injectable positioned-I/O backend. @@ -1996,7 +1984,7 @@ pub fn build_file_engine( "io_uring pool configuration is missing", ) })?; - UringIoEngine::new_with_files( + uring::UringIoEngine::new_with_files( files, max_in_flight, io_uring_config, diff --git a/cache2/src/io/engine/tests.rs b/cache2/src/io/engine/tests.rs index c12c54b..a0fa67c 100644 --- a/cache2/src/io/engine/tests.rs +++ b/cache2/src/io/engine/tests.rs @@ -22,7 +22,7 @@ use std::sync::mpsc; use std::time::Duration; use super::*; -use crate::config::PosixIoConfig; +use crate::config::runtime::PosixIoConfig; use crate::io::backend::FileBackend; use crate::io::backend::SyncMode; use crate::io::backend::SyncPoint; diff --git a/cache2/src/io/engine/uring.rs b/cache2/src/io/engine/uring.rs index 055ab65..02e5426 100644 --- a/cache2/src/io/engine/uring.rs +++ b/cache2/src/io/engine/uring.rs @@ -42,7 +42,7 @@ use io_uring::opcode; use io_uring::squeue; use io_uring::types; -use crate::config::IoUringPoolConfig; +use crate::config::runtime::IoUringPoolConfig; use crate::io::backend::RuntimeFileSet; use crate::io::backend::RuntimeIoPath; use crate::io::backend::RuntimeIoStatsHandle; diff --git a/cache2/src/lib.rs b/cache2/src/lib.rs index a3208d0..da6a711 100644 --- a/cache2/src/lib.rs +++ b/cache2/src/lib.rs @@ -34,17 +34,17 @@ pub use self::cache::Value; mod config; pub use self::config::CacheConfig; -pub use self::config::IoEngineConfig; -pub use self::config::IoMode; -pub use self::config::IoUringConfig; -pub use self::config::IoUringPoolConfig; -pub use self::config::IoUringSqPollConfig; -pub use self::config::L1EvictionPolicy; -pub use self::config::PosixIoConfig; -pub use self::config::ReadAdmission; -pub use self::config::RuntimeOptions; pub use self::config::StorageLayout; -pub use self::config::StorageOptions; +pub use self::config::runtime::IoEngineConfig; +pub use self::config::runtime::IoMode; +pub use self::config::runtime::IoUringConfig; +pub use self::config::runtime::IoUringPoolConfig; +pub use self::config::runtime::IoUringSqPollConfig; +pub use self::config::runtime::L1EvictionPolicy; +pub use self::config::runtime::PosixIoConfig; +pub use self::config::runtime::ReadAdmission; +pub use self::config::runtime::RuntimeOptions; +pub use self::config::storage::StorageOptions; mod snapshot; pub use self::snapshot::CacheHealth; diff --git a/cache2/src/memory/eviction.rs b/cache2/src/memory/eviction.rs index db4dd02..05d587e 100644 --- a/cache2/src/memory/eviction.rs +++ b/cache2/src/memory/eviction.rs @@ -19,7 +19,7 @@ use std::io; -use crate::config::L1EvictionPolicy; +use crate::config::runtime::L1EvictionPolicy; use crate::hashing::FixedPrehashedMap; /// Maximum policy metadata inspected by one complete foreground admission. diff --git a/cache2/src/memory/mod.rs b/cache2/src/memory/mod.rs index f8f8302..1d7d3ae 100644 --- a/cache2/src/memory/mod.rs +++ b/cache2/src/memory/mod.rs @@ -36,7 +36,7 @@ use self::eviction::EvictionState; use self::eviction::MAX_POLICY_SCAN_STEPS; use self::eviction::MAX_POLICY_SLOT_INDEX; use self::eviction::PolicySlot; -use crate::config::L1EvictionPolicy; +use crate::config::runtime::L1EvictionPolicy; use crate::hashing::FixedPrehashedMap; use crate::hashing::route_hash; use crate::snapshot::CacheL1Snapshot; diff --git a/cache2/src/property_tests.rs b/cache2/src/property_tests.rs index 2122c37..0b16ec1 100644 --- a/cache2/src/property_tests.rs +++ b/cache2/src/property_tests.rs @@ -23,28 +23,28 @@ use quickcheck::QuickCheck; use crate::checksum::Crc32c; use crate::checksum::crc32c; use crate::hashing::FixedPrehashedMap; -use crate::region::index::IndexEntry; -use crate::region::index::PackedLocation; use crate::region::index::ReclaimIndexAction; use crate::region::index::RegionIndex; -use crate::region::index::record_size_class_upper_bound; -use crate::region::index::storage::INDEX_IMAGE_SLOT_SIZE; +use crate::region::index::packed::IndexEntry; +use crate::region::index::packed::PackedLocation; +use crate::region::index::packed::record_size_class_upper_bound; use crate::region::index::storage::IndexSlot; use crate::region::index::storage::PartitionedIndexStorage; +use crate::region::index::storage::page_format::INDEX_IMAGE_SLOT_SIZE; use crate::region::manager::RegionAppendReservation; use crate::region::record::MAX_KEY_SIZE; use crate::region::record::RECORD_ALIGNMENT; use crate::region::record::RECORD_HEADER_SIZE; use crate::region::record::RecordHeader; -use crate::region::record::RecordPayload; -use crate::region::record::encode_reinsert_into_hashed; -use crate::region::record::encode_value_into_hashed; -use crate::region::record::required_record_bytes; +use crate::region::record::codec::RecordPayload; +use crate::region::record::codec::encode_reinsert_into_hashed; +use crate::region::record::codec::encode_value_into_hashed; +use crate::region::record::codec::required_record_bytes; use crate::region::recovery::DataSuperblock; use crate::region::recovery::RECOVERY_PAGE_SIZE; use crate::region::recovery::RecoveryImageHeader; -use crate::region::recovery::RegionMetadata; use crate::region::recovery::StateRecord; +use crate::region::recovery::metadata::RegionMetadata; const MAX_PROPERTY_INPUT_BYTES: usize = 16 * 1024; const MAX_PROPERTY_MAP_ENTRIES: usize = 64; diff --git a/cache2/src/region/file_backend/mod.rs b/cache2/src/region/file_backend/mod.rs index 1e74ba2..93eed41 100644 --- a/cache2/src/region/file_backend/mod.rs +++ b/cache2/src/region/file_backend/mod.rs @@ -27,11 +27,11 @@ use std::sync::Mutex; use std::sync::atomic::AtomicU64; use crate::config::CacheConfig; -use crate::config::IoMode; +use crate::config::runtime::IoMode; #[cfg(test)] -use crate::config::RuntimeOptions; +use crate::config::runtime::RuntimeOptions; #[cfg(test)] -use crate::config::cache_config; +use crate::config::storage::cache_config; use crate::io::backend::ControlIoBackend; use crate::io::backend::FileBackend; use crate::io::backend::IoBackend; @@ -48,8 +48,8 @@ use crate::region::RegionHealthLatch; use crate::region::RegionManagerAuthority; use crate::region::RegionShard; use crate::region::guarded_index_result; -use crate::region::index::MAX_INDEX_PARTITIONS; use crate::region::index::RegionIndex; +use crate::region::index::packed::MAX_INDEX_PARTITIONS; use crate::region::index::storage::IndexImageBinding; use crate::region::index::storage::IndexPartitionRange; use crate::region::index::storage::IndexPhysicalStats; @@ -59,21 +59,12 @@ use crate::region::index_storage_io_error; use crate::region::manager::RegionManager; use crate::region::recovery::DataSuperblock; use crate::region::recovery::DataSuperblockProbe; -use crate::region::recovery::PartitionMetadataRecord; use crate::region::recovery::PersistentId; use crate::region::recovery::RECOVERY_IMAGE_INDEX_OFFSET; use crate::region::recovery::RECOVERY_PAGE_SIZE; -use crate::region::recovery::REGION_METADATA_PAGE_SIZE; -use crate::region::recovery::REGION_METADATA_PARTITIONS_PER_PAGE; -use crate::region::recovery::REGION_METADATA_REGIONS_PER_PAGE; use crate::region::recovery::RecoveryImageHeader; use crate::region::recovery::RecoveryImageHeaderProbe; use crate::region::recovery::RecoveryState; -use crate::region::recovery::RegionMetadata; -use crate::region::recovery::RegionMetadataError; -use crate::region::recovery::RegionMetadataRecord; -use crate::region::recovery::RegionMetadataRoot; -use crate::region::recovery::RegionMetadataState; use crate::region::recovery::STATE_FILE_SIZE; use crate::region::recovery::STATE_SLOT_COUNT; use crate::region::recovery::SelectedState; @@ -83,6 +74,15 @@ use crate::region::recovery::StateRecord; use crate::region::recovery::StateSelectionError; use crate::region::recovery::clean_image_matches; use crate::region::recovery::latest_state; +use crate::region::recovery::metadata::PartitionMetadataRecord; +use crate::region::recovery::metadata::REGION_METADATA_PAGE_SIZE; +use crate::region::recovery::metadata::REGION_METADATA_PARTITIONS_PER_PAGE; +use crate::region::recovery::metadata::REGION_METADATA_REGIONS_PER_PAGE; +use crate::region::recovery::metadata::RegionMetadata; +use crate::region::recovery::metadata::RegionMetadataError; +use crate::region::recovery::metadata::RegionMetadataRecord; +use crate::region::recovery::metadata::RegionMetadataRoot; +use crate::region::recovery::metadata::RegionMetadataState; use crate::region::recovery::prepare_next_state; use crate::region::recovery::prepare_running_barrier; use crate::region::recovery::recovery_image_index_len; diff --git a/cache2/src/region/file_backend/tests.rs b/cache2/src/region/file_backend/tests.rs index 53c84b1..d14e2ee 100644 --- a/cache2/src/region/file_backend/tests.rs +++ b/cache2/src/region/file_backend/tests.rs @@ -34,9 +34,9 @@ use std::time::Instant; use super::*; use crate::IoEngineConfig; -use crate::config::MAX_WRITE_FLUSH_THRESHOLD_BYTES; -use crate::config::PosixIoConfig; -use crate::config::ReadAdmission; +use crate::config::runtime::MAX_WRITE_FLUSH_THRESHOLD_BYTES; +use crate::config::runtime::PosixIoConfig; +use crate::config::runtime::ReadAdmission; use crate::io::backend::MAX_INTERRUPTED_RETRIES; use crate::io::backend::testing::FaultAction; use crate::io::backend::testing::FaultBackend; @@ -46,18 +46,18 @@ use crate::io::backend::testing::kill_process; use crate::io::engine::BackendIoEngine; use crate::io::engine::IoEngine; use crate::region::RegionStageValue; -use crate::region::index::IndexEntry; -use crate::region::index::PackedLocation; -use crate::region::index::storage::INDEX_IMAGE_SLOTS_PER_PAGE; +use crate::region::index::packed::IndexEntry; +use crate::region::index::packed::PackedLocation; use crate::region::index::storage::IndexSlot; use crate::region::index::storage::IndexSlotState; +use crate::region::index::storage::page_format::INDEX_IMAGE_SLOTS_PER_PAGE; use crate::region::reader::ReadCandidate; use crate::region::reader::ReadCompletion; use crate::region::reader::ReadPlan; use crate::region::reader::plan_read; use crate::region::record::RECORD_ALIGNMENT; -use crate::region::record::hash_key; -use crate::region::record::required_record_bytes; +use crate::region::record::codec::hash_key; +use crate::region::record::codec::required_record_bytes; use crate::region::recovery::DATA_REGION_AREA_OFFSET; use crate::region::recovery::DataGeometry; use crate::region::recovery::PersistentId; diff --git a/cache2/src/region/index/mod.rs b/cache2/src/region/index/mod.rs index 41720ab..c04afa1 100644 --- a/cache2/src/region/index/mod.rs +++ b/cache2/src/region/index/mod.rs @@ -34,24 +34,12 @@ use self::storage::IndexSlotState; use self::storage::IndexStorageError; use self::storage::PartitionedIndexStorage; use crate::hashing::route_hash; +use crate::region::index::packed::INDEX_CANDIDATES; +use crate::region::index::packed::IndexEntry; +use crate::region::index::packed::PackedLocation; use crate::snapshot::CacheIndexSnapshot; -mod packed; -pub use self::packed::INDEX_CANDIDATES; -pub use self::packed::IndexEntry; -pub use self::packed::MAX_INDEX_PARTITIONS; -#[cfg(feature = "benchmarking")] -pub use self::packed::MAX_INDEX_PROBES; -pub use self::packed::MAX_PACKED_REGION_COUNT; -pub use self::packed::MAX_PACKED_REGION_SIZE; -pub use self::packed::MAX_RECORD_LEN; -#[cfg(feature = "benchmarking")] -pub use self::packed::MAX_REGION_OFFSET; -pub use self::packed::PackedLocation; -pub use self::packed::PackedLocationError; -pub use self::packed::index_partition_for; -pub use self::packed::record_size_class_upper_bound; - +pub mod packed; pub mod storage; const CANDIDATE_OFFSETS: [usize; INDEX_CANDIDATES] = [0, 23, 61, 97]; @@ -709,7 +697,7 @@ fn candidate_offset(displacement: usize, slot_count: usize) -> usize { mod tests { use super::*; use crate::region::index::storage::IndexPhysicalStats; - use crate::region::record::hash_key; + use crate::region::record::codec::hash_key; fn entry(region_id: u32, offset: u32) -> IndexEntry { IndexEntry { diff --git a/cache2/src/region/index/storage/mod.rs b/cache2/src/region/index/storage/mod.rs index 9ab20d1..9b8f328 100644 --- a/cache2/src/region/index/storage/mod.rs +++ b/cache2/src/region/index/storage/mod.rs @@ -45,20 +45,20 @@ use self::page_format::put_u32; use self::page_format::put_u64; use self::page_format::read_u64; use self::page_format::validate_page_header; -use crate::region::index::INDEX_CANDIDATES; -use crate::region::index::IndexEntry; -use crate::region::index::MAX_INDEX_PARTITIONS; -use crate::region::index::PackedLocation; -use crate::region::index::PackedLocationError; -use crate::region::index::index_partition_for; -use crate::region::index::record_size_class_upper_bound; +use crate::region::index::packed::INDEX_CANDIDATES; +use crate::region::index::packed::IndexEntry; +use crate::region::index::packed::MAX_INDEX_PARTITIONS; +use crate::region::index::packed::PackedLocation; +use crate::region::index::packed::PackedLocationError; +use crate::region::index::packed::index_partition_for; +use crate::region::index::packed::record_size_class_upper_bound; +use crate::region::index::storage::page_format::INDEX_IMAGE_PAGE_HEADER_SIZE; +use crate::region::index::storage::page_format::INDEX_IMAGE_PAGE_SIZE; +use crate::region::index::storage::page_format::INDEX_IMAGE_SLOT_SIZE; +use crate::region::index::storage::page_format::INDEX_IMAGE_SLOTS_PER_PAGE; use crate::region::record::RECORD_ALIGNMENT; -mod page_format; -pub use self::page_format::INDEX_IMAGE_PAGE_HEADER_SIZE; -pub use self::page_format::INDEX_IMAGE_PAGE_SIZE; -pub use self::page_format::INDEX_IMAGE_SLOT_SIZE; -pub use self::page_format::INDEX_IMAGE_SLOTS_PER_PAGE; +pub mod page_format; /// Upper bound for one underlying warm-image write. /// diff --git a/cache2/src/region/index/storage/page_format.rs b/cache2/src/region/index/storage/page_format.rs index 3027f81..e6571a3 100644 --- a/cache2/src/region/index/storage/page_format.rs +++ b/cache2/src/region/index/storage/page_format.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. - use crate::checksum::Crc32c; use crate::region::index::storage::CorruptPageReason; use crate::region::index::storage::IndexImageBinding; diff --git a/cache2/src/region/manager.rs b/cache2/src/region/manager.rs index da5f187..1e45c90 100644 --- a/cache2/src/region/manager.rs +++ b/cache2/src/region/manager.rs @@ -23,13 +23,13 @@ use std::collections::VecDeque; use crate::io::backend::DIRECT_IO_ALIGNMENT; use crate::region::record::RECORD_ALIGNMENT; -use crate::region::recovery::PartitionMetadataRecord; use crate::region::recovery::PersistentId; -use crate::region::recovery::RegionMetadata; -use crate::region::recovery::RegionMetadataError; -use crate::region::recovery::RegionMetadataRecord; -use crate::region::recovery::RegionMetadataRoot; -use crate::region::recovery::RegionMetadataState; +use crate::region::recovery::metadata::PartitionMetadataRecord; +use crate::region::recovery::metadata::RegionMetadata; +use crate::region::recovery::metadata::RegionMetadataError; +use crate::region::recovery::metadata::RegionMetadataRecord; +use crate::region::recovery::metadata::RegionMetadataRoot; +use crate::region::recovery::metadata::RegionMetadataState; use crate::snapshot::RegionSnapshot; const UNASSIGNED_REGION: u32 = u32::MAX; @@ -1280,8 +1280,8 @@ fn try_unassigned_queue( #[cfg(test)] mod tests { use super::*; - use crate::region::index::storage::INDEX_IMAGE_SLOTS_PER_PAGE; use crate::region::index::storage::canonical_index_partition_ranges; + use crate::region::index::storage::page_format::INDEX_IMAGE_SLOTS_PER_PAGE; fn id(byte: u8) -> PersistentId { PersistentId::from_bytes([byte; 16]).unwrap() diff --git a/cache2/src/region/mod.rs b/cache2/src/region/mod.rs index e1cfb41..b295580 100644 --- a/cache2/src/region/mod.rs +++ b/cache2/src/region/mod.rs @@ -26,13 +26,9 @@ use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; use self::appender::submit_span; -#[cfg(test)] -use self::index::IndexEntry; -use self::index::PackedLocation; use self::index::ReclaimIndexAction; use self::index::RegionIndex; use self::index::heat_memory_bytes; -use self::index::storage::INDEX_IMAGE_PAGE_SIZE; use self::index::storage::IndexStorageError; use self::index::storage::WARM_IMAGE_WRITE_BATCH_BYTES; use self::index::storage::canonical_index_partition_ranges; @@ -48,18 +44,8 @@ use self::reader::plan_read; use self::reader::submit_read; use self::record::RECORD_ALIGNMENT; use self::record::RECORD_HEADER_SIZE; -use self::record::RecordEncodeError; use self::record::RecordHeader; -use self::record::RecordPayload; -use self::record::encode_reinsert_into_hashed; -use self::record::encode_value_into_hashed; -#[cfg(test)] -use self::record::hash_key; use self::recovery::DATA_REGION_AREA_OFFSET; -use self::recovery::REGION_METADATA_PAGE_SIZE; -use self::recovery::REGION_METADATA_PARTITIONS_PER_PAGE; -use self::recovery::REGION_METADATA_REGIONS_PER_PAGE; -use self::recovery::RegionMetadataError; use self::recovery::recovery_image_index_len; use self::staging::StageAppend; use self::staging::StagedRecord; @@ -73,32 +59,35 @@ use crate::io::engine::IoBuffer; use crate::io::engine::IoEngine; use crate::io::engine::ReadSlot; use crate::region::appender::RegionSpanCompletion; +#[cfg(test)] +use crate::region::index::packed::IndexEntry; +use crate::region::index::packed::PackedLocation; +use crate::region::index::storage::page_format::INDEX_IMAGE_PAGE_SIZE; use crate::region::manager::RegionWriteSpan; +use crate::region::record::codec::RecordEncodeError; +use crate::region::record::codec::RecordPayload; +use crate::region::record::codec::encode_reinsert_into_hashed; +use crate::region::record::codec::encode_value_into_hashed; +#[cfg(test)] +use crate::region::record::codec::hash_key; use crate::region::recovery::DataGeometry; +use crate::region::recovery::metadata::REGION_METADATA_PAGE_SIZE; +use crate::region::recovery::metadata::REGION_METADATA_PARTITIONS_PER_PAGE; +use crate::region::recovery::metadata::REGION_METADATA_REGIONS_PER_PAGE; +use crate::region::recovery::metadata::RegionMetadataError; +use crate::region::staging::RegionStaging; use crate::resources::BufferLease; use crate::snapshot::CacheIndexSnapshot; use crate::snapshot::RegionSnapshot; -mod file_backend; -pub use self::file_backend::FileRegionBackend; -pub use self::file_backend::RegionFiles; -pub use self::file_backend::SystemRegionFileSystem; - +pub mod file_backend; pub mod index; pub mod manager; pub mod record; pub mod recovery; - -mod runtime; -pub use self::runtime::ActivityMetrics; -pub use self::runtime::HybridValueRead; -pub use self::runtime::RegionDataPlane; - -mod staging; -pub use self::staging::RegionStaging; - -mod store; -pub use self::store::RegionStore; +pub mod runtime; +pub mod staging; +pub mod store; mod appender; mod reader; diff --git a/cache2/src/region/reader.rs b/cache2/src/region/reader.rs index ddbd2e0..792aa77 100644 --- a/cache2/src/region/reader.rs +++ b/cache2/src/region/reader.rs @@ -34,7 +34,7 @@ use crate::io::engine::OperationKind; use crate::io::engine::ReadSlot; use crate::io::engine::RequestId; use crate::io::engine::submit_cache_read; -use crate::region::index::IndexEntry; +use crate::region::index::packed::IndexEntry; use crate::region::record::RECORD_ALIGNMENT; use crate::region::recovery::DATA_REGION_AREA_OFFSET; use crate::region::recovery::DataGeometry; @@ -325,7 +325,7 @@ mod tests { use crate::io::backend::SyncPoint; use crate::io::backend::WritePoint; use crate::io::engine::BackendIoEngine; - use crate::region::index::PackedLocation; + use crate::region::index::packed::PackedLocation; use crate::resources::ResourceController; use crate::resources::ResourceLimits; diff --git a/cache2/src/region/record/codec.rs b/cache2/src/region/record/codec.rs index 6b48b6d..2183cc4 100644 --- a/cache2/src/region/record/codec.rs +++ b/cache2/src/region/record/codec.rs @@ -26,10 +26,10 @@ use hashcrew::xxhash::xxh3_64_with_seed; use crate::checksum::Crc32c; #[cfg(test)] use crate::io::backend::DIRECT_IO_ALIGNMENT; -use crate::region::index::IndexEntry; -use crate::region::index::MAX_RECORD_LEN; -use crate::region::index::PackedLocation; -use crate::region::index::PackedLocationError; +use crate::region::index::packed::IndexEntry; +use crate::region::index::packed::MAX_RECORD_LEN; +use crate::region::index::packed::PackedLocation; +use crate::region::index::packed::PackedLocationError; use crate::region::manager::RegionAppendReservation; use crate::region::record::MAX_KEY_SIZE; use crate::region::record::RECORD_ALIGNMENT; diff --git a/cache2/src/region/record/mod.rs b/cache2/src/region/record/mod.rs index ee9798c..e238eea 100644 --- a/cache2/src/region/record/mod.rs +++ b/cache2/src/region/record/mod.rs @@ -20,13 +20,7 @@ use crate::checksum::Crc32c; use crate::checksum::crc32c; -mod codec; -pub use self::codec::RecordEncodeError; -pub use self::codec::RecordPayload; -pub use self::codec::encode_reinsert_into_hashed; -pub use self::codec::encode_value_into_hashed; -pub use self::codec::hash_key; -pub use self::codec::required_record_bytes; +pub mod codec; pub const RECORD_FORMAT_VERSION: u16 = 1; diff --git a/cache2/src/region/recovery/metadata.rs b/cache2/src/region/recovery/metadata.rs index 091a5cb..01a80f9 100644 --- a/cache2/src/region/recovery/metadata.rs +++ b/cache2/src/region/recovery/metadata.rs @@ -22,13 +22,13 @@ use std::fmt; use std::mem; use crate::checksum::Crc32c; -use crate::region::index::MAX_INDEX_PARTITIONS; -use crate::region::index::MAX_PACKED_REGION_COUNT; -use crate::region::index::MAX_PACKED_REGION_SIZE; -use crate::region::index::storage::INDEX_IMAGE_PAGE_SIZE; -use crate::region::index::storage::INDEX_IMAGE_SLOTS_PER_PAGE; +use crate::region::index::packed::MAX_INDEX_PARTITIONS; +use crate::region::index::packed::MAX_PACKED_REGION_COUNT; +use crate::region::index::packed::MAX_PACKED_REGION_SIZE; use crate::region::index::storage::IndexStorageError; use crate::region::index::storage::canonical_index_partition_ranges; +use crate::region::index::storage::page_format::INDEX_IMAGE_PAGE_SIZE; +use crate::region::index::storage::page_format::INDEX_IMAGE_SLOTS_PER_PAGE; use crate::region::index::storage::validated_index_partition_ranges; use crate::region::recovery::DataSuperblock; use crate::region::recovery::PersistentId; diff --git a/cache2/src/region/recovery/mod.rs b/cache2/src/region/recovery/mod.rs index c10040d..ac40b42 100644 --- a/cache2/src/region/recovery/mod.rs +++ b/cache2/src/region/recovery/mod.rs @@ -22,23 +22,14 @@ use crate::checksum::Crc32c; use crate::checksum::crc32c; -use crate::region::index::MAX_PACKED_REGION_COUNT; -use crate::region::index::MAX_PACKED_REGION_SIZE; -use crate::region::index::storage::INDEX_IMAGE_PAGE_SIZE; -use crate::region::index::storage::INDEX_IMAGE_SLOTS_PER_PAGE; +use crate::region::index::packed::MAX_PACKED_REGION_COUNT; +use crate::region::index::packed::MAX_PACKED_REGION_SIZE; +use crate::region::index::storage::page_format::INDEX_IMAGE_PAGE_SIZE; +use crate::region::index::storage::page_format::INDEX_IMAGE_SLOTS_PER_PAGE; use crate::region::record::RECORD_ALIGNMENT; use crate::region::record::RECORD_FORMAT_VERSION; -mod metadata; -pub use self::metadata::PartitionMetadataRecord; -pub use self::metadata::REGION_METADATA_PAGE_SIZE; -pub use self::metadata::REGION_METADATA_PARTITIONS_PER_PAGE; -pub use self::metadata::REGION_METADATA_REGIONS_PER_PAGE; -pub use self::metadata::RegionMetadata; -pub use self::metadata::RegionMetadataError; -pub use self::metadata::RegionMetadataRecord; -pub use self::metadata::RegionMetadataRoot; -pub use self::metadata::RegionMetadataState; +pub mod metadata; const RECOVERY_FORMAT_VERSION: u16 = 1; pub const RECOVERY_PAGE_SIZE: usize = 4 * 1024; diff --git a/cache2/src/region/runtime/mod.rs b/cache2/src/region/runtime/mod.rs index 1efc534..182155c 100644 --- a/cache2/src/region/runtime/mod.rs +++ b/cache2/src/region/runtime/mod.rs @@ -42,15 +42,15 @@ use asyncband::watch; use self::metrics::RuntimeMetrics; use crate::IoEngineConfig; use crate::config::CacheConfig; -use crate::config::IoMode; -use crate::config::IoPoolTopology; -#[cfg(test)] -use crate::config::ReadAdmission; -use crate::config::RuntimeOptions; use crate::config::l1_entry_capacity; -use crate::config::read_io_wait_capacity; -use crate::config::read_io_wait_timeout; use crate::config::reserved_memory_bytes; +use crate::config::runtime::IoMode; +use crate::config::runtime::IoPoolTopology; +#[cfg(test)] +use crate::config::runtime::ReadAdmission; +use crate::config::runtime::RuntimeOptions; +use crate::config::runtime::read_io_wait_capacity; +use crate::config::runtime::read_io_wait_timeout; use crate::config::storage_geometry; use crate::hashing::route_hash; use crate::io::backend::RuntimeFileSet; @@ -71,13 +71,13 @@ use crate::region::FileRegionCore; use crate::region::RegionStageValue; use crate::region::RegionValueRead; #[cfg(test)] -use crate::region::index::IndexEntry; +use crate::region::index::packed::IndexEntry; #[cfg(test)] -use crate::region::index::PackedLocation; +use crate::region::index::packed::PackedLocation; #[cfg(test)] -use crate::region::index::storage::INDEX_IMAGE_PAGE_SIZE; +use crate::region::index::storage::page_format::INDEX_IMAGE_PAGE_SIZE; #[cfg(test)] -use crate::region::index::storage::INDEX_IMAGE_SLOTS_PER_PAGE; +use crate::region::index::storage::page_format::INDEX_IMAGE_SLOTS_PER_PAGE; use crate::region::reader::PendingRead; #[cfg(test)] use crate::region::reader::ReadCandidate; @@ -87,8 +87,8 @@ use crate::region::reader::plan_read; use crate::region::record::MAX_KEY_SIZE; #[cfg(test)] use crate::region::record::RECORD_HEADER_SIZE; -use crate::region::record::hash_key; -use crate::region::record::required_record_bytes; +use crate::region::record::codec::hash_key; +use crate::region::record::codec::required_record_bytes; #[cfg(test)] use crate::region::recovery::DataGeometry; use crate::region::recovery::DataSuperblock; @@ -108,8 +108,7 @@ use crate::snapshot::CacheIoSnapshot; use crate::snapshot::CacheSnapshot; use crate::snapshot::DetailedCacheSnapshot; -mod metrics; -pub use self::metrics::ActivityMetrics; +pub mod metrics; const WRITE_FLUSH_DELAY: Duration = Duration::from_millis(1); const _RETRY_AGE: Duration = Duration::from_micros(50); @@ -2464,12 +2463,12 @@ mod tests { #[test] fn completion_timeouts_follow_read_wait_mode() { - use crate::config::IoEngineConfig; - use crate::config::PosixIoConfig; - use crate::region::FileRegionBackend; - use crate::region::RegionFiles; - use crate::region::index::IndexEntry; - use crate::region::index::PackedLocation; + use crate::config::runtime::IoEngineConfig; + use crate::config::runtime::PosixIoConfig; + use crate::region::file_backend::FileRegionBackend; + use crate::region::file_backend::RegionFiles; + use crate::region::index::packed::IndexEntry; + use crate::region::index::packed::PackedLocation; use crate::region::recovery::DATA_REGION_AREA_OFFSET; use crate::region::recovery::PersistentId; use crate::region::store::RegionStore; diff --git a/cache2/src/region/runtime/shutdown_tests.rs b/cache2/src/region/runtime/shutdown_tests.rs index 14d94af..c8c0a50 100644 --- a/cache2/src/region/runtime/shutdown_tests.rs +++ b/cache2/src/region/runtime/shutdown_tests.rs @@ -17,6 +17,7 @@ use std::sync::atomic::AtomicBool; use std::sync::mpsc; use super::*; +use crate::IoEngineConfig; use crate::io::backend::IoBackend; use crate::io::backend::SyncMode; use crate::io::backend::SyncPoint; @@ -28,7 +29,6 @@ use crate::io::engine::IoRequest; use crate::io::engine::ReadSlotWaiter; use crate::io::engine::RequestId; use crate::io::engine::SubmitError; -use crate::IoEngineConfig; #[derive(Default)] struct BlockedReadState { @@ -205,9 +205,9 @@ fn submitted_read_must_not_pin_close() { } fn assert_close_does_not_wait_for_read(submit_before_close: bool) { - use crate::config::PosixIoConfig; - use crate::region::FileRegionBackend; - use crate::region::RegionFiles; + use crate::config::runtime::PosixIoConfig; + use crate::region::file_backend::FileRegionBackend; + use crate::region::file_backend::RegionFiles; use crate::region::recovery::PersistentId; use crate::region::store::RegionStore; let root = env::temp_dir().join(format!( diff --git a/cache2/src/region/staging.rs b/cache2/src/region/staging.rs index 6ca5488..1563a3b 100644 --- a/cache2/src/region/staging.rs +++ b/cache2/src/region/staging.rs @@ -23,9 +23,9 @@ use std::sync::MutexGuard; use crate::io::backend::DIRECT_IO_ALIGNMENT; use crate::io::engine::IoBuffer; -use crate::region::index::IndexEntry; -use crate::region::index::MAX_RECORD_LEN; -use crate::region::index::PackedLocation; +use crate::region::index::packed::IndexEntry; +use crate::region::index::packed::MAX_RECORD_LEN; +use crate::region::index::packed::PackedLocation; use crate::region::manager::RegionAppendReservation; use crate::region::manager::RegionPaddingReceipt; use crate::region::manager::RegionWriteSpan; @@ -905,7 +905,7 @@ mod tests { use std::time::Duration; use super::*; - use crate::region::index::PackedLocation; + use crate::region::index::packed::PackedLocation; use crate::resources::ResourceLimits; fn resources(memory_limit_bytes: usize) -> ResourceController { From d9195adb5b5d015409731cbb5cbf4a7776634df3 Mon Sep 17 00:00:00 2001 From: tison Date: Thu, 10 Sep 2026 11:06:26 +0800 Subject: [PATCH 12/14] docs: consolidate repository contribution guidelines --- AGENTS.md | 58 +++---------------------------------------------- CHANGELOG.md | 13 ++++------- CONTRIBUTING.md | 29 ++++++++++++++++++++----- 3 files changed, 31 insertions(+), 69 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 4ff74c6..331db40 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,57 +1,5 @@ -# C² Engineering Guidelines +# Repository Instructions for Agents -## Repository Workflow +Before planning or modifying this repository, read [CONTRIBUTING.md](CONTRIBUTING.md) in full and treat its development, design, Rust style, documentation, changelog, and pull request guidance as repository requirements. -Before planning or modifying this repository, read `CONTRIBUTING.md` and treat its workspace layout and validation guidance as repository requirements. Use `cargo x` as the source of truth for routine check, test, lint, and benchmark workflows. - -## Priorities - -Use this order when correctness and performance goals compete: - -1. Keep the request path simple and fast. -2. Preserve or improve hit rate. -3. Prefer newer values when sequence information makes that cheap. - -C² uses best-effort consistency and may return stale hits. Sequence numbers provide advisory ordering. Each L2 attempt uses one index lookup and one local validation pass. - -## Global bounds - -- Bound every cache-owned allocation, queue, buffer, probe, scan, retry, and eviction decision. -- Resolve saturation through L1 bypass, a cache miss, mutation throttling, or explicit overload. -- Keep critical sections short and shard-local. Release locks before device I/O. -- Keep read, write, and reclaim I/O pools bounded and independent. -- Compute the full-key hash and record size once at the public boundary. -- Give compare-exchange bookkeeping a small retry budget, then bypass or miss. -- Keep statistics optional and implement enabled counters with low-cost atomics. - -## Read path - -- Probe L1 through shard routing and one short critical section. Bound same-hash chains and victim work; pressure bypasses insertion and promotion. -- Consult L2 after every L1 miss. Index misses and index or warm-page contention return a miss before read-buffer allocation. -- Admit one Region- and size-class-bounded read. Reserve one read slot, charge one managed buffer, and validate the record locally. -- Expand direct-I/O reads to 4 KiB boundaries within the selected Region. -- Use the full read-pool depth for immediate admission. -- Optional waiting begins after candidate selection and is limited by a short deadline and an explicit waiter bound. Wait capacity is independent of execution capacity and defaults to the aggregate read-pool depth. A queued request retains its plan and allocates its buffer after admission. -- Queue, memory, and timeout pressure return overload in wait mode. Memory and engine pressure return a miss in immediate mode. -- Validate address, generation, size class, hash, full key, lengths, checksums, and sequence structure in one pass. -- Successful promotion returns an L1-backed value, preserves Region as the hit source, and releases the transient read buffer. Bypassed promotion may return a zero-copy Region value that owns the aligned allocation until drop. -- Heat updates are single, bounded, lossy relaxed-atomic operations independent of the read result. - -## Mutation path - -- Finish foreground mutations after bounded in-memory work. Accepted writes publish asynchronously. -- Preflight shard capacity before allocating an append receipt. Reserve the Region tail and open span under one manager try-lock; encode under the shard mutation gate after releasing the manager guard. -- Batch Region writes. Background shard workers own write-slot waits; explicit completion barriers may wait for those workers. -- Publish an L2 index entry after its data write completes. -- Make L1 admission immediately readable and evictable. L1 bypass continues through Region. -- Route `put_l2` payloads through Region, apply best-effort L1 cleanup, and make them visible after L2 publication. -- Preserve logical sequence numbers across reinsertion and use them to cheaply prefer newer L1 values. L2 stores compact location metadata. -- Accept older valid values from contention, eviction, delayed publication, and promotion. Validate the complete key after every hash match. -- Treat `drain` as the completion barrier for accepted writes. - -## Recovery and failure - -- Treat eviction, bypass, rejection, throttling, misses, stale reads, overload, and cache loss as valid cache outcomes. -- Publish a clean recovery image during warm close. Fast close and unclean exit reopen empty. -- Move unsafe I/O, index, or metadata failures to miss-only. Reads then return misses, while mutations report errors. -- Return values that pass bounds, key, and checksum validation. Failed validation returns a miss. +Keep the documented workflow synchronized with structural changes and run the applicable validation commands before handing work back. `CONTRIBUTING.md` is the single source of truth for contribution rules. diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f36f58..fe9a6cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,15 +5,10 @@ ### Breaking Changes - The public I/O configuration enum is now named `IoEngineConfig`; replace `IoEngine` imports and variant paths with `IoEngineConfig`. -- Error types are exported only from the crate root. Replace imports from `cache2::error` with `cache2::{Error, ErrorKind, ErrorOperation}`. -- The `cache2::Result` alias is removed. Use the standard `Result` with `Error` imported from `cache2`; public operation error types are unchanged. -- Configuration now separates editable `StorageOptions` / `RuntimeOptions` from immutable `StorageLayout` / `CacheConfig`. Build the layout, construct `CacheConfig::new(layout, options)`, and call `Cache::open(path, config)` or `Cache::open_with_handle(path, config, handle)`. `StaticConfig`, `RuntimeConfig`, `CacheBuilder`, and the standalone `validate` method are removed. +- Error types are exported only from the crate root, and the `cache2::Result` alias is removed. Replace imports from `cache2::error` with `cache2::{Error, ErrorKind, ErrorOperation}` and use the standard `Result`; public operation error types are unchanged. +- Configuration now separates editable `StorageOptions` / `RuntimeOptions` from immutable `StorageLayout` / `CacheConfig`. Replace `StaticConfig`, `RuntimeConfig`, and `CacheBuilder` by building a layout, constructing `CacheConfig::new(layout, options)`, and calling `Cache::open(path, config)` or `Cache::open_with_handle(path, config, handle)`. Construction validates and retains geometry and memory requirements; the standalone `validate` method is removed. Configurations can be inspected and reused without file access or an active Tokio runtime, with paths and runtime handles supplied separately at open. - `ReadAdmission::Immediate` and `ReadAdmission::Wait { timeout, max_waiters }` replace the separate read-wait setters. Waiting requires a positive timeout; an omitted waiter bound follows the selected read execution capacity. -- Configuration errors identify `ErrorOperation::BuildStorage` or `BuildConfig`. `StorageLayout::peak_disk_bytes()` is now an infallible query. - -### Improvements - -- Geometry and memory requirements are retained through startup. Configurations can be inspected and reused without file access or an active Tokio runtime; paths and runtime handles are supplied separately when opening each cache. +- Configuration errors identify `ErrorOperation::BuildStorage` or `BuildConfig` instead of `ValidateConfig` or `PeakDiskBytes`. Replace fallible `StaticConfig::peak_disk_bytes()` calls with the infallible `StorageLayout::peak_disk_bytes()` query. ## v0.3.0 (2026-09-04) @@ -29,7 +24,7 @@ This release keeps the version 1 on-disk format and requires no disk migration. ### Breaking Changes -- `IoEngineConfig` now carries backend-specific topology. Configure POSIX worker counts with `PosixIoConfig`; configure independent io_uring pools with `IoUringConfig` and `IoUringPoolConfig`. The backend-ambiguous `with_read_io_workers`, `with_write_io_workers`, and `with_reclaim_workers` methods were removed. +- `IoEngine` now carries backend-specific topology. Configure POSIX worker counts with `PosixIoConfig`; configure independent io_uring pools with `IoUringConfig` and `IoUringPoolConfig`. The backend-ambiguous `with_read_io_workers`, `with_write_io_workers`, and `with_reclaim_workers` methods were removed. ### Improvements diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7771c9b..8b234a8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,9 @@ ## Development -Run commands from the repository root. Use the Rust version declared in [Cargo.toml](Cargo.toml). Linting also requires nightly Rust and these tools: +Run commands from the repository root. Use `cargo x` as the source of truth for repository workflows. Read `cargo x --help` and the relevant subcommand's `--help` before running build, test, lint, or formatting commands. + +Use a Rust toolchain at or above the `rust-version` declared in [Cargo.toml](Cargo.toml). Linting also requires nightly Rust and these tools: ```sh rustup toolchain install nightly --profile minimal --component rustfmt,clippy @@ -21,10 +23,27 @@ cargo x test cargo x lint ``` -`cargo x` is the source of truth for validation: `check` covers the workspace and optional features, `test` includes the extended library tests, and `lint` checks formatting, code, documentation, packaging, and dependencies. Use `cargo x lint --fix` to apply supported automatic fixes, then review the diff. +`check` covers the workspace and optional features, `test` includes the extended library tests, and `lint` checks formatting, code, documentation, packaging, and dependencies. Use `cargo x lint --fix` to apply supported automatic fixes, then review the diff. + +Cover observable behavior changes with tests. See [BENCHMARK.md](BENCHMARK.md) for performance workloads and qualification. + +## Design and Rust Style + +Follow the surrounding code and the design constraints in [ARCHITECTURE.md](ARCHITECTURE.md), including bounded resource use, best-effort consistency, request-path priorities, and recovery guarantees. + +Declare restricted visibility at module boundaries and use `pub` for items in those modules' APIs. Keep items private when only their defining module and its descendants need them. For items reachable through public modules or re-exported public types, reserve `pub` for intentional public API and use narrower visibility for internal callers. + +## Documentation + +Keep public documentation current and describe observable contracts. Keep each Markdown prose paragraph and list item on one source line. + +## Changelog -See [BENCHMARK.md](BENCHMARK.md) for performance workloads and qualification. +- Update [CHANGELOG.md](CHANGELOG.md) for significant user-visible changes by comparing the final behavior with the latest release tag, not the sequence of commits in the current development cycle. Add entries under `Unreleased`, using only categories that contain entries. +- Before adding a bug-fix entry, verify from the latest release tag that the faulty behavior was shipped. If the affected API or behavior is unreleased, describe only its final contract in the relevant feature entry and omit the development-only correction. +- Include public API migrations, new capabilities, correctness or compatibility changes, and meaningful performance improvements. Exclude tests, internal refactors, documentation, CI, tooling, dependency maintenance, discarded intermediate APIs, and implementation history unless they change supported or observable behavior relative to the latest release. +- Write each entry from the user's perspective as one coherent observable change, including required migration guidance for breaking changes. Scope performance claims to the workloads supported by evidence. -## Changes +## Pull Requests -Follow the surrounding code and the engineering constraints in [AGENTS.md](AGENTS.md). Cover behavior changes with tests, keep public documentation current, and record user-visible changes in [CHANGELOG.md](CHANGELOG.md). +Format pull request titles according to [.github/semantic.yml](.github/semantic.yml) and keep the description concise. Use a `Summary` section for routine changes and add `Design Notes` only when the design needs explanation. From e3f94e976e66c588a49ea4149091e2c713dd49b7 Mon Sep 17 00:00:00 2001 From: tison Date: Thu, 10 Sep 2026 11:13:05 +0800 Subject: [PATCH 13/14] fixup Signed-off-by: tison --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2acf0fa..aa78223 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ See the [configuration guide](CONFIGURATION.md#configuration-lifecycle) for exam | Area | Controls | Default and behavior | |-----------|----------------------------------------------------------------------------|---------------------------------------------------------------------------------------------| | L1 | `l1_capacity_bytes`, `l1_shards`, `l1_eviction_policy` | 256 MiB, 32 shards, CLOCK. Zero capacity disables L1; entries charged above 256 KiB use L2. | -| I/O pools | `io_engine: IoEngineConfig::Posix(...)` or `IoEngineConfig::IoUring(...)` | Four POSIX read workers, four write workers, and one reclaimer; io_uring is experimental. | +| I/O pools | `io_engine: IoEngineConfig::Posix(...)` or `IoEngineConfig::IoUring(...)` | Four POSIX read workers, four write workers, and one reclaimer; io_uring is experimental. | | Read wait | `read_admission: ReadAdmission::Immediate` or `ReadAdmission::Wait { .. }` | Immediate admission; wait capacity defaults to aggregate read capacity. | | Writes | `append_shards`, `write_flush_threshold_bytes` | Four append shards and a 4 MiB flush threshold. | | Memory | `managed_memory_limit_bytes` | 1 GiB across cache-managed allocations. | From 3df232d8349fc629c4cacf0a600070ebf6b003d6 Mon Sep 17 00:00:00 2001 From: tison Date: Thu, 10 Sep 2026 11:19:45 +0800 Subject: [PATCH 14/14] fixup Signed-off-by: tison --- AGENTS.md | 2 +- CONTRIBUTING.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 331db40..27cd76e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,4 +1,4 @@ -# Repository Instructions for Agents +# C² Engineering Guidelines Before planning or modifying this repository, read [CONTRIBUTING.md](CONTRIBUTING.md) in full and treat its development, design, Rust style, documentation, changelog, and pull request guidance as repository requirements. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8b234a8..e0f92bc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,7 +35,7 @@ Declare restricted visibility at module boundaries and use `pub` for items in th ## Documentation -Keep public documentation current and describe observable contracts. Keep each Markdown prose paragraph and list item on one source line. +Keep public documentation current and describe observable contracts. Keep each Markdown prose paragraph and list item on one source line. Format Markdown tables so their columns and separators align in the source. ## Changelog