From d79ab6ebbe79908a1560af4d2ae93f5d578c854b Mon Sep 17 00:00:00 2001 From: Lars Kroll Date: Sat, 11 Apr 2026 15:11:30 +0200 Subject: [PATCH 1/3] Fix benchmark RNG after dependency update --- Cargo.toml | 9 ++++----- benches/wheel_benchmark.rs | 10 +++++----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 505d3da..a3aac99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,8 +26,8 @@ fnv-hash = ["fnv"] fx-hash = ["rustc-hash"] [dependencies] -uuid = { version = "1.3", features = ["v4"], optional = true } -fnv = { version = "1.0", optional = true } +uuid = { version = "1", features = ["v4"], optional = true } +fnv = { version = "1", optional = true } rustc-hash = { version = "2", optional = true } crossbeam-channel = { version = "0.5", optional = true } @@ -40,9 +40,8 @@ maintenance = { status = "passively-maintained" } github-actions = { repository = "Bathtor/rust-hash-wheel-timer", workflow = "ci.yml" } [dev-dependencies] -criterion = "0.5" -rand = "0.8" -rand_xoshiro = "0.6" +criterion = "0.8" +rand = "0.10" [[bench]] name = "wheel_benchmark" diff --git a/benches/wheel_benchmark.rs b/benches/wheel_benchmark.rs index 0a7b64b..4c4bcc6 100644 --- a/benches/wheel_benchmark.rs +++ b/benches/wheel_benchmark.rs @@ -1,6 +1,6 @@ use criterion::{criterion_group, criterion_main, BatchSize, Bencher, Criterion, Throughput}; use hierarchical_hash_wheel_timer::{wheels::cancellable::*, UuidOnlyTimerEntry}; -use rand::prelude::*; +use rand::{rngs::Xoshiro256PlusPlus, RngExt, SeedableRng}; use std::{rc::Rc, time::Duration}; use uuid::Uuid; @@ -53,10 +53,10 @@ fn write_only_uniform_bench(bencher: &mut Bencher) { || { let timer: QuadWheelWithOverflow = QuadWheelWithOverflow::new(); let mut entries = Vec::with_capacity(NUM_ELEMENTS); - let mut rng = rand_xoshiro::Xoshiro256PlusPlus::seed_from_u64(42); + let mut rng = Xoshiro256PlusPlus::seed_from_u64(42); for _i in 1..=NUM_ELEMENTS { let id = Uuid::new_v4(); - let mut delay: u32 = rng.gen(); + let mut delay: u32 = rng.random(); if delay == 0 { // make sure the entry is actually inserted and not just returned immediately delay = 1; @@ -85,10 +85,10 @@ fn write_only_uniform_with_overflow_bench(bencher: &mut Bencher) { || { let timer: QuadWheelWithOverflow = QuadWheelWithOverflow::new(); let mut entries = Vec::with_capacity(NUM_ELEMENTS); - let mut rng = rand_xoshiro::Xoshiro256PlusPlus::seed_from_u64(42); + let mut rng = Xoshiro256PlusPlus::seed_from_u64(42); for _i in 1..=NUM_ELEMENTS { let id = Uuid::new_v4(); - let mut delay: u64 = rng.gen(); + let mut delay: u64 = rng.random(); if delay == 0 { // make sure the entry is actually inserted and not just returned immediately delay = 1; From 92078fa6496e648813ca8f0ca86cf5accedd0535 Mon Sep 17 00:00:00 2001 From: Lars Kroll Date: Sat, 11 Apr 2026 15:12:57 +0200 Subject: [PATCH 2/3] Simplify rustfmt --- rustfmt.toml | 57 +++++++--------------------------------------------- 1 file changed, 7 insertions(+), 50 deletions(-) diff --git a/rustfmt.toml b/rustfmt.toml index 2d6a4d0..e810fe3 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,56 +1,13 @@ max_width = 100 -hard_tabs = false -tab_spaces = 4 -newline_style = "Unix" -use_small_heuristics = "Default" -indent_style = "Block" -wrap_comments = false -comment_width = 180 -normalize_comments = false -#license_template_path = "" -format_strings = false -empty_item_single_line = true -struct_lit_single_line = true -fn_single_line = false -where_single_line = false +edition = "2021" + imports_granularity = "Crate" -imports_indent = "Block" imports_layout = "HorizontalVertical" -reorder_imports = true -reorder_modules = true -reorder_impl_items = true -type_punctuation_density = "Wide" -space_before_colon = false -space_after_colon = true -spaces_around_ranges = false -binop_separator = "Front" -remove_nested_parens = true -combine_control_expr = true -struct_field_align_threshold = 0 -match_arm_blocks = true -force_multiline_blocks = false + fn_params_layout = "Tall" -brace_style = "SameLineWhere" -control_brace_style = "AlwaysSameLine" -trailing_semicolon = true -trailing_comma = "Vertical" -match_block_trailing_comma = false -blank_lines_upper_bound = 1 -blank_lines_lower_bound = 0 -merge_derives = true -use_try_shorthand = false +binop_separator = "Front" + use_field_init_shorthand = false force_explicit_abi = true -condense_wildcard_suffixes = false -color = "Auto" -required_version = "1.9.0" -unstable_features = false -disable_all_formatting = false -skip_children = false -show_parse_errors = true -error_on_line_overflow = false -error_on_unformatted = false -ignore = [] -emit_mode = "Files" -make_backup = false -edition = "2018" +reorder_impl_items = false +wrap_comments = false From f121cee179da27c3a0a27d8650efd6509f61bea3 Mon Sep 17 00:00:00 2001 From: Lars Kroll Date: Sat, 11 Apr 2026 15:50:01 +0200 Subject: [PATCH 3/3] Prepare release metadata and MSRV checks --- .github/workflows/ci.yml | 27 +++++++++++++++++++++ Cargo.toml | 5 ++-- README.md | 8 ++++--- src/lib.rs | 27 +++++++++++---------- src/simulation.rs | 2 +- src/thread_timer.rs | 2 +- src/uuid_extras.rs | 2 +- src/wheels/cancellable.rs | 50 ++++++++++++++++++++++++--------------- src/wheels/quad_wheel.rs | 2 +- 9 files changed, 84 insertions(+), 41 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b3728a3..13e2be0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,6 +32,33 @@ jobs: command: test args: --verbose + msrv: + name: MSRV + runs-on: ubuntu-latest + steps: + - name: Setup Rust 1.94.1 + uses: actions-rs/toolchain@v1 + with: + toolchain: 1.94.1 + override: true + - name: Checkout sources + uses: actions/checkout@v2 + - name: Run tests (default features) + uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose + - name: Run tests (fnv) + uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose --no-default-features --features fnv-hash,uuid-extras + - name: Run tests (sip) + uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose --no-default-features --features sip-hash,uuid-extras + no-default-feature-tests: runs-on: ${{ matrix.os }} strategy: diff --git a/Cargo.toml b/Cargo.toml index a3aac99..ef296c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,8 @@ name = "hierarchical_hash_wheel_timer" version = "1.3.0" authors = ["Lars Kroll "] edition = "2021" -description = "A low-level timer implementantion using a hierarchical four-level hash wheel with overflow." +rust-version = "1.94.1" +description = "A low-level timer implementation using a hierarchical four-level hash wheel with overflow." documentation = "https://docs.rs/hierarchical_hash_wheel_timer" homepage = "https://github.com/Bathtor/rust-hash-wheel-timer" repository = "https://github.com/Bathtor/rust-hash-wheel-timer" @@ -16,7 +17,7 @@ license = "MIT" default = ["uuid-extras", "fx-hash", "thread-timer"] uuid-extras = ["uuid"] thread-timer = ["crossbeam-channel"] -# The following three features are mutually exclusive and you have to pick exactly one of them! +# The following three features are mutually exclusive and you have to pick exactly one of them. # Use Rust's default Sip Hasher for cancellable timers sip-hash = [] diff --git a/README.md b/README.md index 707fbd9..50a4965 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ This crate provides a low-level event timer implementation based on hierarchical The APIs in the crate are offered at three different levels of abstraction, listed below from lowest to highest. ### 1 – Single Wheel -The fundamental abstraction of this crate a single hash wheel with 256 slots addressed with a single byte. Each slot stores a list of a generic event type. +The fundamental abstraction of this crate is a single hash wheel with 256 slots addressed with a single byte. Each slot stores a list of a generic event type. The whole wheel can be "ticked" causing entries in the slots that are being moved over to expire. With every tick, all expired event entries are returned for handling. ### 2 – Hierarchical Wheel @@ -26,6 +26,8 @@ This crate provides two variant implementations of this four level wheel structu - The `wheels::quad_wheel::QuadWheelWithOverflow` corresponds directly to the implementation described above. - The `wheels::cancellable::QuadWheelWithOverflow` additionally supports the cancellation of outstanding timers before they expire. In order to do so, however, it requires the generic timer entry type to provide a unique identifier field. It also uses `std::rc::Rc` internally to avoid double storing the actual entry, which makes it (potentially) unsuitable for situations where the timer must be able to move between threads. +For the cancellable wheel, exactly one of `fx-hash`, `fnv-hash`, or `sip-hash` must be enabled. + ### 3 – High Level APIs This crate also provides three high-level APIs that can either be used directly or can be seen as examples of how to use the lower level APIs in an application. @@ -44,7 +46,7 @@ The `manual_timer` module provides the same queue-based scheduling API as the th ## Documentation -For reference and examples check the [API Docs](https://docs.rs/hierarchical_hash_wheel_timer). +For reference and examples, check the [API docs](https://docs.rs/hierarchical_hash_wheel_timer). ## Performance @@ -54,6 +56,6 @@ You can repeat these experiments on your own hardware by checking out the source ## License -Licensed under the terms of MIT license. +Licensed under the terms of the MIT licence. See [LICENSE](LICENSE) for details. diff --git a/src/lib.rs b/src/lib.rs index ff10117..0b96628 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,14 +5,14 @@ //! listed below from lowest to highest. //! //! # 1 – Single Wheel -//! The fundamental abstraction of this crate a single hash wheel with 256 slots +//! The fundamental abstraction of this crate is a single hash wheel with 256 slots //! addressed with a single byte. Each slot stores a list of a generic event type. //! The whole wheel can be "ticked" causing entries in the slots that are being moved over //! to expire. With every tick, all expired event entries are returned for handling. //! For more details see the [byte_wheel](wheels::byte_wheel) module. //! -//! # 2 – Hierachical Wheel -//! Combining four byte wheels we get a hierachical timer that can represent timeouts +//! # 2 – Hierarchical Wheel +//! Combining four byte wheels we get a hierarchical timer that can represent timeouts //! up to [`u32::MAX`](std::u32::MAX) time units into the future. //! In order to support timeouts of up to [`u64::MAX`](std::u64::MAX) time units, //! our implementations also come with an overflow list, which stores all timers that didn't fit @@ -28,8 +28,9 @@ //! - The [quad_wheel::QuadWheelWithOverflow](wheels::quad_wheel::QuadWheelWithOverflow) corresponds directly to the implementation described above. //! - The [cancellable::QuadWheelWithOverflow](wheels::cancellable::QuadWheelWithOverflow) additionally supports the cancellation of outstanding timers //! before they expire. In order to do so, however, it requires the generic timer entry type to provide a unique identifier field. It also uses -//! [Rc](std::rc::Rc) internally to avoid double storing the actual entry, which makes it (potentialy) unsuitable for situations where the timer must -//! be able to move threads (since Rc](std::rc::Rc) is not `Send`). +//! [Rc](std::rc::Rc) internally to avoid double storing the actual entry, which makes it potentially unsuitable for situations where the timer must +//! be able to move between threads (since [Rc](std::rc::Rc) is not `Send`). +//! - Exactly one of `fx-hash`, `fnv-hash`, or `sip-hash` must be enabled for this variant. //! //! # 3 – High Level APIs //! This crate also provides three high-level APIs that can either be used @@ -41,16 +42,16 @@ //! and discarded once expired. //! //! ## Simulation Timer -//! The [simulation](simulation) module provides an implementation for an event timer used to drive a discrete event simulation. -//! Its particular feature is that it can skip quickly through periods where no events are schedulled as it doesn't track real time, +//! The [simulation] module provides an implementation for an event timer used to drive a discrete event simulation. +//! Its particular feature is that it can skip quickly through periods where no events are scheduled as it doesn't track real time, //! but rather provides the rate at which the simulation proceeds. //! //! ## Thread Timer -//! The [thread_timer](thread_timer) module provides a timer for real-time event schedulling with millisecond accuracy. +//! The [thread_timer] module provides a timer for real-time event scheduling with millisecond accuracy. //! It runs on its own dedicated thread and uses a shareable handle called a `TimerRef` for communication with other threads. //! //! ## Manual Timer -//! The [manual_timer](manual_timer) module provides the same queue-based +//! The [manual_timer] module provides the same queue-based //! scheduling API as the thread timer, but advances only when the caller //! explicitly steps time forward. @@ -77,12 +78,12 @@ mod uuid_extras; #[cfg(feature = "uuid-extras")] pub use self::uuid_extras::*; -/// Errors encounted by a timer implementation +/// Errors encountered by a timer implementation #[derive(Debug)] pub enum TimerError { /// The timeout with the given id was not found NotFound, - /// The timout has already expired + /// The timeout has already expired Expired(EntryType), } @@ -91,7 +92,7 @@ pub enum TimerError { pub struct IdOnlyTimerEntry { /// The unique identifier part of the entry pub id: I, - /// The delay that this entry is to be schedulled with (i.e., expire after) + /// The delay that this entry is to be scheduled with (i.e., expire after) pub delay: Duration, } impl IdOnlyTimerEntry { @@ -120,7 +121,7 @@ where } } -/// A module with some convenince functions for writing timer tests +/// A module with some convenience functions for writing timer tests #[cfg(test)] pub mod test_helpers { use std::time::Duration; diff --git a/src/simulation.rs b/src/simulation.rs index ad2f510..df5399d 100644 --- a/src/simulation.rs +++ b/src/simulation.rs @@ -4,7 +4,7 @@ //! but rather provides the rate at which the simulation proceeds. //! //! Progress in the simulation is driven by repeatedly calling the [next](SimulationTimer::next) function -//! until it returns [SimulationStep::Finished](SimulationStep::Finished) indicating that the timer is empty +//! until it returns [SimulationStep::Finished] indicating that the timer is empty //! and thus the simulation has run to completion. //! //! # Example diff --git a/src/thread_timer.rs b/src/thread_timer.rs index b0d402d..320fd5e 100644 --- a/src/thread_timer.rs +++ b/src/thread_timer.rs @@ -1,7 +1,7 @@ //! This module provides a timer for real-time event schedulling with millisecond accuracy. //! //! It runs on its own dedicated thread and uses a shareable handle called a `TimerRef` for communication with other threads. -//! This inter-thread communication is based on [crossbeam_channel](crossbeam_channel). +//! This inter-thread communication is based on [crossbeam_channel]. //! //! ## Note //! Sine this timer runs on its own thread, instance creation will fail if the generic id or state types used are not `Send`. diff --git a/src/uuid_extras.rs b/src/uuid_extras.rs index 48b0ec8..48d80f6 100644 --- a/src/uuid_extras.rs +++ b/src/uuid_extras.rs @@ -6,7 +6,7 @@ use uuid::Uuid; pub type UuidOnlyTimerEntry = IdOnlyTimerEntry; impl UuidOnlyTimerEntry { - /// Produce an entry with a random [Uuid](uuid::Uuid) and the given `delay` + /// Produce an entry with a random [Uuid] and the given `delay` /// /// Uses `Uuid::new_v4()` internally. pub fn with_random_id(delay: Duration) -> Self { diff --git a/src/wheels/cancellable.rs b/src/wheels/cancellable.rs index b61eca9..8016602 100644 --- a/src/wheels/cancellable.rs +++ b/src/wheels/cancellable.rs @@ -1,20 +1,21 @@ //! An implementation of a four-level hierarchical hash wheel with overflow //! that allows entries to be cancelled before they expire. //! -//! The design reuses the normal [quad_wheel](crate::wheels::quad_wheel), but +//! The design reuses the normal [quad_wheel], but //! adds an internal hash map to keep track of which timeouts are still valid. //! This allows for constant time cancellation of timer entries, //! but with lazy deallocation (garbage collection) as the wheel advances. //! //! Depending on your concrete application and identifier type different hashers may -//! give you the best performance. By the default this crate will use the `FxHasher` +//! give you the best performance. By default this crate will use the `FxHasher` //! from the [rustc-hash](https://crates.io/crates/rustc-hash) crate, which provides very //! fast performance for small ids such as `u64` or `Uuid`. -//! If you require a different set of performance characteristics you can switch the implemantion +//! If you require a different set of performance characteristics you can switch the implementation //! using the `fnv-hash` or `sip-hash` features when compiling this crate. +//! Exactly one of `fx-hash`, `fnv-hash`, or `sip-hash` must be enabled. //! //! # Examples -//! A very simple example of schedulling and then cancelling a single entry can be seen below. +//! A very simple example of scheduling and then cancelling a single entry can be seen below. //! ``` //! # use std::time::Duration; //! use hierarchical_hash_wheel_timer::*; @@ -36,6 +37,20 @@ //! More advanced examples can be found in the sources for the [SimulationTimer](crate::simulation::SimulationTimer) //! and the [TimerWithThread](crate::thread_timer::TimerWithThread). +#[cfg(not(any(feature = "fnv-hash", feature = "fx-hash", feature = "sip-hash")))] +compile_error!( + "The cancellable wheel requires exactly one of the `fx-hash`, `fnv-hash`, or `sip-hash` features." +); + +#[cfg(any( + all(feature = "fnv-hash", feature = "fx-hash"), + all(feature = "fnv-hash", feature = "sip-hash"), + all(feature = "fx-hash", feature = "sip-hash"), +))] +compile_error!( + "The cancellable wheel requires exactly one of the `fx-hash`, `fnv-hash`, or `sip-hash` features." +); + use super::*; use crate::wheels::quad_wheel::{ PruneDecision, @@ -43,11 +58,18 @@ use crate::wheels::quad_wheel::{ }; #[cfg(feature = "fnv-hash")] use fnv::FnvHashMap; -#[cfg(feature = "fx-hash")] +#[cfg(all(not(feature = "fnv-hash"), feature = "fx-hash"))] use rustc_hash::FxHashMap; -#[cfg(feature = "sip-hash")] +#[cfg(all(not(feature = "fnv-hash"), not(feature = "fx-hash")))] use std::collections::HashMap; +#[cfg(feature = "fnv-hash")] +type TimerMap = FnvHashMap; +#[cfg(all(not(feature = "fnv-hash"), feature = "fx-hash"))] +type TimerMap = FxHashMap; +#[cfg(all(not(feature = "fnv-hash"), not(feature = "fx-hash")))] +type TimerMap = HashMap; + /// A trait for timer entries that can be uniquely identified, so they can be cancelled pub trait CancellableTimerEntry: Debug { /// The type of the unique id of the outstanding timeout @@ -57,7 +79,7 @@ pub trait CancellableTimerEntry: Debug { fn id(&self) -> &Self::Id; } -/// A pruner implementation for [Weak](std::rc::Weak) references +/// A pruner implementation for [Weak] references /// /// Keeps values that can still be upgraded. pub fn rc_prune(e: &Weak) -> PruneDecision { @@ -80,12 +102,7 @@ where EntryType: CancellableTimerEntry, { wheel: BasicQuadWheelWithOverflow>, - #[cfg(feature = "fnv-hash")] - timers: FnvHashMap>, - #[cfg(feature = "sip-hash")] - timers: HashMap>, - #[cfg(feature = "fx-hash")] - timers: FxHashMap>, + timers: TimerMap>, } impl QuadWheelWithOverflow @@ -118,12 +135,7 @@ where pub fn new() -> Self { QuadWheelWithOverflow { wheel: BasicQuadWheelWithOverflow::new(rc_prune::), - #[cfg(feature = "fnv-hash")] - timers: FnvHashMap::default(), - #[cfg(feature = "sip-hash")] - timers: HashMap::new(), - #[cfg(feature = "fx-hash")] - timers: FxHashMap::default(), + timers: TimerMap::default(), } } diff --git a/src/wheels/quad_wheel.rs b/src/wheels/quad_wheel.rs index a34a5e5..b3797c4 100644 --- a/src/wheels/quad_wheel.rs +++ b/src/wheels/quad_wheel.rs @@ -87,7 +87,7 @@ impl PruneDecision { /// A simple pruner implementation that never drops any value /// -/// This is the default pruner for the [QuadWheelWithOverflow](QuadWheelWithOverflow) +/// This is the default pruner for the [QuadWheelWithOverflow] pub fn no_prune(_e: &E) -> PruneDecision { PruneDecision::Keep }