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::atomic::{AtomicU8, Ordering};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub(crate) enum EntryState {
    Scheduled = 0,
    Cancelled = 1,
    Dispatching = 2,
    Expired = 3,
    Rejected = 4,
}

#[derive(Debug)]
pub(crate) struct TimeoutState {
    state: AtomicU8,
}

impl TimeoutState {
    pub(crate) fn scheduled() -> Self {
        Self {
            state: AtomicU8::new(EntryState::Scheduled as u8),
        }
    }

    pub(crate) fn load(&self) -> EntryState {
        match self.state.load(Ordering::Acquire) {
            0 => EntryState::Scheduled,
            1 => EntryState::Cancelled,
            2 => EntryState::Dispatching,
            3 => EntryState::Expired,
            _ => EntryState::Rejected,
        }
    }

    pub(crate) fn cancel(&self) -> bool {
        self.state
            .compare_exchange(
                EntryState::Scheduled as u8,
                EntryState::Cancelled as u8,
                Ordering::AcqRel,
                Ordering::Acquire,
            )
            .is_ok()
    }

    pub(crate) fn dispatch(&self) -> bool {
        self.state
            .compare_exchange(
                EntryState::Scheduled as u8,
                EntryState::Dispatching as u8,
                Ordering::AcqRel,
                Ordering::Acquire,
            )
            .is_ok()
    }

    pub(crate) fn reschedule(&self) -> bool {
        self.state
            .compare_exchange(
                EntryState::Dispatching as u8,
                EntryState::Scheduled as u8,
                Ordering::AcqRel,
                Ordering::Acquire,
            )
            .is_ok()
    }

    pub(crate) fn expire(&self) -> bool {
        self.state
            .compare_exchange(
                EntryState::Dispatching as u8,
                EntryState::Expired as u8,
                Ordering::AcqRel,
                Ordering::Acquire,
            )
            .is_ok()
    }

    pub(crate) fn reject(&self) -> bool {
        self.state
            .compare_exchange(
                EntryState::Dispatching as u8,
                EntryState::Rejected as u8,
                Ordering::AcqRel,
                Ordering::Acquire,
            )
            .is_ok()
    }
}

#[cfg(test)]
mod tests {
    use super::{EntryState, TimeoutState};

    #[test]
    fn state_moves_from_scheduled_to_cancelled_once() {
        let state = TimeoutState::scheduled();

        assert!(state.cancel());
        assert!(!state.cancel());
        assert_eq!(state.load(), EntryState::Cancelled);
    }

    #[test]
    fn dispatch_can_expire_or_reschedule() {
        let state = TimeoutState::scheduled();

        assert!(state.dispatch());
        assert!(state.reschedule());
        assert!(state.dispatch());
        assert!(state.expire());
        assert_eq!(state.load(), EntryState::Expired);
    }

    #[test]
    fn dispatch_can_reject_once() {
        let state = TimeoutState::scheduled();

        assert!(state.dispatch());
        assert!(state.reject());
        assert!(!state.reject());
        assert_eq!(state.load(), EntryState::Rejected);
    }
}