cloudiful-scheduler 0.4.1

Single-job async scheduling library for background work with optional Valkey-backed state.
Documentation
use crate::model::OverlapPolicy;
use crate::scheduler::trigger::PendingTrigger;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum OverlapAction {
    Spawn(PendingTrigger),
    QueueUpdated,
    Dropped,
}

pub(crate) fn take_queued_if_idle(
    active_count: usize,
    queued_trigger: &mut Option<PendingTrigger>,
) -> Option<PendingTrigger> {
    if active_count == 0 {
        queued_trigger.take()
    } else {
        None
    }
}

pub(crate) fn dispatch_trigger(
    policy: OverlapPolicy,
    active_count: usize,
    queued_trigger: &mut Option<PendingTrigger>,
    trigger: PendingTrigger,
) -> OverlapAction {
    match policy {
        OverlapPolicy::AllowParallel => OverlapAction::Spawn(trigger),
        OverlapPolicy::Forbid => {
            if active_count == 0 {
                OverlapAction::Spawn(trigger)
            } else {
                OverlapAction::Dropped
            }
        }
        OverlapPolicy::QueueOne => {
            if active_count == 0 {
                OverlapAction::Spawn(trigger)
            } else {
                if queued_trigger.is_none() {
                    *queued_trigger = Some(trigger);
                }
                OverlapAction::QueueUpdated
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{OverlapAction, dispatch_trigger, take_queued_if_idle};
    use crate::OverlapPolicy;
    use crate::scheduler::trigger::PendingTrigger;
    use chrono::Utc;

    fn trigger() -> PendingTrigger {
        PendingTrigger {
            scheduled_at: Utc::now(),
            catch_up: false,
            trigger_count: 1,
        }
    }

    #[test]
    fn queue_one_keeps_existing_queued_trigger() {
        let existing = trigger();
        let mut queued_trigger = Some(existing);

        let action = dispatch_trigger(OverlapPolicy::QueueOne, 1, &mut queued_trigger, trigger());

        assert_eq!(action, OverlapAction::QueueUpdated);
        assert_eq!(queued_trigger, Some(existing));
    }

    #[test]
    fn forbid_drops_trigger_while_active() {
        let mut queued_trigger = None;

        let action = dispatch_trigger(OverlapPolicy::Forbid, 1, &mut queued_trigger, trigger());

        assert_eq!(action, OverlapAction::Dropped);
        assert!(queued_trigger.is_none());
    }

    #[test]
    fn queued_trigger_is_taken_only_when_idle() {
        let queued = trigger();
        let mut queued_trigger = Some(queued);

        assert_eq!(take_queued_if_idle(1, &mut queued_trigger), None);
        assert_eq!(take_queued_if_idle(0, &mut queued_trigger), Some(queued));
        assert!(queued_trigger.is_none());
    }
}