← Back to Blog

Periodic Scheduler: Dynamic Adjustment Strategies

In systems requiring periodic refresh of configurations, statistics, or caches, how do we design a scheduler that can dynamically adjust update intervals while gracefully handling errors? Today we analyze an industrial-grade Periodically Updated scheduler implementation.

The Core Problem

Traditional timed refresh mechanisms typically have fixed intervals, but in production environments:

  • Normal operation requires longer intervals (reduce overhead)
  • Errors require shorter intervals (fast recovery)
  • Need to support runtime dynamic adjustment of refresh intervals

Design: Dual Duration + Error Recovery

class TPeriodicallyUpdated {
protected:
    virtual bool UpdateImpl() = 0;  // Subclass implements update logic
    
    void SetReloadDuration(TDuration reloadDuration);  // Dynamic adjustment
    
private:
    TDuration mReloadDuration;        // Normal refresh interval
    TDuration mErrorReloadDuration;   // Error refresh interval
    TDuration mCheckPeriod;           // Check interval
};

Core Mechanisms

  1. Dual Duration Strategy:

    • Normal case: uses longer interval mReloadDuration
    • Error case: uses shorter interval mErrorReloadDuration
  2. First Update Wait:

    bool WaitForFirstUpdate(unsigned timeoutSeconds);

    Wait for first update to complete, ensuring system initialization

  3. Error Recovery:

    catch (yexception &ex) {
        mNextReloadTime = TInstant::Now() + mErrorReloadDuration;
    }

Trade-off Analysis

Advantages

  • Dynamic Adjustment: Runtime modifiable refresh intervals
  • Error Awareness: Auto-switch to fast recovery mode
  • Thread Safe: Uses mutex + condition variable

Costs

  • Thread Overhead: Independent background thread
  • Complexity: Need to handle first update wait
  • Resource Management: Requires explicit Stop() call

Clean Room Reimplementation: Rust Implementation

use std::sync::{Arc, Mutex, Condvar};
use std::time::{Duration, Instant};
use std::thread;
use std::sync::atomic::{AtomicBool, Ordering};

pub struct PeriodicallyUpdated<F>
where
    F: FnMut() -> bool + Send + 'static,
{
    reload_duration: Duration,
    error_reload_duration: Duration,
    check_period: Duration,
    
    stopping: Arc<AtomicBool>,
    updated: Arc<AtomicBool>,
    updated_cond: Condvar,
    
    next_reload_time: Mutex<Instant>,
    
    updater: Option<thread::JoinHandle<()>>,
    update_fn: Arc<Mutex<F>>,
}

impl<F> PeriodicallyUpdated<F>
where
    F: FnMut() -> bool + Send + 'static,
{
    pub fn new(
        reload_duration: Duration,
        error_reload_duration: Duration,
        update_fn: F,
    ) -> Self {
        // ...
    }

    pub fn start(&mut self) {
        // Start background thread for periodic updates
    }

    pub fn wait_for_first_update(&self, timeout: Duration) -> bool {
        // Wait for first update to complete
    }

    pub fn stop(&mut self) {
        // Signal stop and join thread
    }
}

Summary

The dynamic adjustment strategy of periodic update schedulers embodies the adaptive system design philosophy:

  1. Dual Duration Mode: Different intervals for normal and error states
  2. First Update Wait: Ensures initialization completes
  3. Error Recovery: Auto-switch to fast mode after exceptions

This design is particularly effective in configuration hot-reload, cache refresh, and similar scenarios.