timerwheel 0.1.0

Hierarchical timer wheel for delayed task scheduling with pluggable executors.
Documentation
// Copyright © 2026-present The Timerwheel Authors. All rights reserved.
//
// 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.

use std::sync::{
    Arc,
    atomic::{AtomicUsize, Ordering},
    mpsc,
};
use std::time::{Duration, Instant};

use timerwheel::prelude::*;

struct CountingTimerSink {
    observations: Arc<AtomicUsize>,
}

impl TimerMetricSink for CountingTimerSink {
    fn observe_metrics(&self, _metrics: TimerMetrics) {
        self.observations.fetch_add(1, Ordering::Relaxed);
    }
}

struct CountingExecutorSink {
    observations: Arc<AtomicUsize>,
}

impl ExecutorMetricSink for CountingExecutorSink {
    fn observe_metrics(&self, _metrics: ExecutorMetrics) {
        self.observations.fetch_add(1, Ordering::Relaxed);
    }
}

#[test]
fn prelude_imports_core_timer_executor_and_metric_sink_aliases() {
    let timer_observations = Arc::new(AtomicUsize::new(0));
    let timer: Timer = Timer::builder()
        .tick(Duration::from_millis(1))
        .bucket_count(64)
        .metric_sink(CountingTimerSink {
            observations: Arc::clone(&timer_observations),
        })
        .build()
        .expect("timer builds");
    let (timer_tx, timer_rx) = mpsc::channel();

    let timeout: Timeout = timer
        .schedule(Duration::from_millis(5), move || {
            timer_tx.send(()).expect("send succeeds");
        })
        .expect("schedule succeeds");

    timer_rx
        .recv_timeout(Duration::from_secs(1))
        .expect("timeout should fire");
    let deadline = Instant::now() + Duration::from_secs(1);
    while !timeout.is_expired() && Instant::now() < deadline {
        std::thread::sleep(Duration::from_millis(1));
    }
    assert!(timeout.is_expired());
    assert_eq!(BackpressurePolicy::Reject, BackpressurePolicy::Reject);
    assert_eq!(ExpiredTaskPolicy::Retry, ExpiredTaskPolicy::Retry);
    assert!(timer.metrics().scheduled_timeouts >= 1);
    assert!(timer_observations.load(Ordering::Relaxed) > 0);
    timer.shutdown().expect("timer shuts down");

    let executor_observations = Arc::new(AtomicUsize::new(0));
    let pool: Pool = Pool::builder()
        .workers(1)
        .queue_capacity(8)
        .reject_policy(RejectPolicy::Reject)
        .metric_sink(CountingExecutorSink {
            observations: Arc::clone(&executor_observations),
        })
        .build()
        .expect("pool builds");
    let (pool_tx, pool_rx) = mpsc::channel();

    pool.execute(move || {
        pool_tx.send(()).expect("send succeeds");
    })
    .expect("pool accepts task");

    pool_rx
        .recv_timeout(Duration::from_secs(1))
        .expect("pool task should run");
    assert!(pool.metrics().submitted_tasks >= 1);
    assert!(executor_observations.load(Ordering::Relaxed) > 0);
    pool.shutdown().expect("pool shuts down");
}

#[cfg(feature = "tokio")]
#[test]
fn prelude_imports_tokio_aliases_when_feature_is_enabled() {
    fn assert_tokio_timer_builder(_: TokioTimerBuilder) {}

    let runtime = tokio::runtime::Runtime::new().expect("runtime builds");
    runtime.block_on(async {
        let executor = TokioExecutor::current().expect("tokio executor builds");
        executor.shutdown().expect("tokio executor shuts down");

        assert_tokio_timer_builder(TokioTimer::builder());
    });
}