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;
use std::time::Duration;

use crate::error::{Error, Result};
use crate::executor;
use crate::policy::{BackpressurePolicy, ExpiredTaskPolicy};
use crate::timer::metric_sink::{MetricSink, NoopMetricSink};
use crate::timer::scheduler::Timer;

/// Builder for a hierarchical timer-wheel scheduler.
#[derive(Clone)]
pub struct TimerBuilder {
    pub(crate) tick: Duration,
    pub(crate) bucket_count: usize,
    pub(crate) command_capacity: usize,
    pub(crate) max_pending: usize,
    pub(crate) backpressure_policy: BackpressurePolicy,
    pub(crate) expired_task_policy: ExpiredTaskPolicy,
    pub(crate) expired_task_retry: Duration,
    pub(crate) executor: Option<Arc<dyn executor::Executor>>,
    pub(crate) metric_sink: Arc<dyn MetricSink>,
    pub(crate) owns_executor: bool,
}

impl Default for TimerBuilder {
    fn default() -> Self {
        let tick = Duration::from_millis(1);
        Self {
            tick,
            bucket_count: 512,
            command_capacity: 65_536,
            max_pending: 1_000_000,
            backpressure_policy: BackpressurePolicy::Reject,
            expired_task_policy: ExpiredTaskPolicy::Reject,
            expired_task_retry: tick,
            executor: None,
            metric_sink: Arc::new(NoopMetricSink),
            owns_executor: true,
        }
    }
}

impl TimerBuilder {
    /// Sets the timer-wheel tick resolution.
    pub fn tick(mut self, tick: Duration) -> Self {
        self.tick = tick;
        self
    }

    /// Sets the number of buckets in each timer-wheel level.
    pub fn bucket_count(mut self, bucket_count: usize) -> Self {
        self.bucket_count = bucket_count;
        self
    }

    /// Sets the bounded scheduler command queue capacity.
    pub fn command_capacity(mut self, command_capacity: usize) -> Self {
        self.command_capacity = command_capacity;
        self
    }

    /// Sets the maximum number of pending timeouts accepted by the timer.
    pub fn max_pending(mut self, max_pending: usize) -> Self {
        self.max_pending = max_pending;
        self
    }

    /// Sets the policy used when the scheduler command queue is full.
    pub fn backpressure_policy(mut self, policy: BackpressurePolicy) -> Self {
        self.backpressure_policy = policy;
        self
    }

    /// Sets the policy used when an executor rejects an expired task.
    pub fn expired_task_policy(mut self, policy: ExpiredTaskPolicy) -> Self {
        self.expired_task_policy = policy;
        self
    }

    /// Sets the retry delay for expired tasks rejected by a saturated executor.
    pub fn expired_task_retry(mut self, retry: Duration) -> Self {
        self.expired_task_retry = retry;
        self
    }

    /// Sets the executor used for expired task execution.
    pub fn executor(mut self, executor: Arc<dyn executor::Executor>) -> Self {
        self.executor = Some(executor);
        self.owns_executor = false;
        self
    }

    /// Sets the metrics observer called after timer metric changes.
    pub fn metric_sink<S>(mut self, metric_sink: S) -> Self
    where
        S: MetricSink,
    {
        self.metric_sink = Arc::new(metric_sink);
        self
    }

    /// Builds the timer and starts its scheduler thread.
    pub fn build(self) -> Result<Timer> {
        if self.tick.is_zero() {
            return Err(Error::InvalidConfig("tick must be greater than zero"));
        }
        if self.bucket_count == 0 {
            return Err(Error::InvalidConfig(
                "bucket_count must be greater than zero",
            ));
        }
        if self.command_capacity == 0 {
            return Err(Error::InvalidConfig(
                "command_capacity must be greater than zero",
            ));
        }
        if self.max_pending == 0 {
            return Err(Error::InvalidConfig(
                "max_pending must be greater than zero",
            ));
        }
        if self.expired_task_retry.is_zero() {
            return Err(Error::InvalidConfig(
                "expired_task_retry must be greater than zero",
            ));
        }

        Timer::new(self)
    }
}