Skip to main content

kestrel_timer/
config.rs

1//! Timer Configuration Module
2//!
3//! Provides hierarchical configuration structure and Builder pattern for configuring timing wheel, service, and batch processing behavior.
4//!
5//! 定时器配置模块,提供分层的配置结构和 Builder 模式,用于配置时间轮、服务和批处理行为。
6use crate::error::TimerError;
7use std::num::NonZeroUsize;
8use std::time::Duration;
9
10/// Timing Wheel Configuration
11///
12/// Used to configure parameters for hierarchical timing wheel. The system only supports hierarchical mode.
13///
14/// # 时间轮配置
15///
16/// 用于配置分层时间轮的参数。系统只支持分层模式。
17///
18/// # Examples (示例)
19/// ```no_run
20/// use kestrel_timer::config::WheelConfig;
21/// use std::time::Duration;
22///
23/// // Use default configuration (使用默认配置,分层模式)
24/// let config = WheelConfig::default();
25///
26/// // Use Builder to customize configuration (使用 Builder 自定义配置)
27/// let config = WheelConfig::builder()
28///     .l0_tick_duration(Duration::from_millis(20))
29///     .l0_slot_count(1024)
30///     .l1_tick_duration(Duration::from_secs(2))
31///     .l1_slot_count(128)
32///     .build()
33///     .unwrap();
34/// ```
35#[derive(Debug, Clone)]
36pub struct WheelConfig {
37    /// Duration of each tick in L0 layer, bottom layer
38    ///
39    /// L0 层每个 tick 的持续时间
40    l0_tick_duration: Duration,
41
42    /// Number of slots in L0 layer, must be power of 2
43    ///
44    /// L0 层槽位数,必须是 2 的幂
45    l0_slot_count: usize,
46
47    /// Duration of each tick in L1 layer, upper layer
48    ///
49    /// L1 层每个 tick 的持续时间
50    l1_tick_duration: Duration,
51
52    /// Number of slots in L1 layer, must be power of 2
53    ///
54    /// L1 层槽位数,必须是 2 的幂
55    l1_slot_count: usize,
56}
57
58impl Default for WheelConfig {
59    fn default() -> Self {
60        Self {
61            l0_tick_duration: Duration::from_millis(10),
62            l0_slot_count: 512,
63            l1_tick_duration: Duration::from_secs(1),
64            l1_slot_count: 64,
65        }
66    }
67}
68
69impl WheelConfig {
70    /// Create configuration builder (创建配置构建器)
71    pub fn builder() -> WheelConfigBuilder {
72        WheelConfigBuilder::default()
73    }
74
75    /// Get the L0 tick duration.
76    pub fn l0_tick_duration(&self) -> Duration {
77        self.l0_tick_duration
78    }
79
80    /// Get the L0 slot count.
81    pub fn l0_slot_count(&self) -> usize {
82        self.l0_slot_count
83    }
84
85    /// Get the L1 tick duration.
86    pub fn l1_tick_duration(&self) -> Duration {
87        self.l1_tick_duration
88    }
89
90    /// Get the L1 slot count.
91    pub fn l1_slot_count(&self) -> usize {
92        self.l1_slot_count
93    }
94
95    pub(crate) fn validate(self) -> Result<Self, TimerError> {
96        WheelConfigBuilder {
97            l0_tick_duration: self.l0_tick_duration,
98            l0_slot_count: self.l0_slot_count,
99            l1_tick_duration: self.l1_tick_duration,
100            l1_slot_count: self.l1_slot_count,
101        }
102        .build()
103    }
104}
105
106/// Timing Wheel Configuration Builder
107#[derive(Debug, Clone)]
108pub struct WheelConfigBuilder {
109    l0_tick_duration: Duration,
110    l0_slot_count: usize,
111    l1_tick_duration: Duration,
112    l1_slot_count: usize,
113}
114
115impl Default for WheelConfigBuilder {
116    fn default() -> Self {
117        Self {
118            l0_tick_duration: Duration::from_millis(10),
119            l0_slot_count: 512,
120            l1_tick_duration: Duration::from_secs(1),
121            l1_slot_count: 64,
122        }
123    }
124}
125
126impl WheelConfigBuilder {
127    /// Set L0 layer tick duration
128    pub fn l0_tick_duration(mut self, duration: Duration) -> Self {
129        self.l0_tick_duration = duration;
130        self
131    }
132
133    /// Set L0 layer slot count
134    pub fn l0_slot_count(mut self, count: usize) -> Self {
135        self.l0_slot_count = count;
136        self
137    }
138
139    /// Set L1 layer tick duration
140    pub fn l1_tick_duration(mut self, duration: Duration) -> Self {
141        self.l1_tick_duration = duration;
142        self
143    }
144
145    /// Set L1 layer slot count
146    pub fn l1_slot_count(mut self, count: usize) -> Self {
147        self.l1_slot_count = count;
148        self
149    }
150
151    /// Build and validate configuration
152    ///
153    /// # Returns
154    /// - `Ok(WheelConfig)`: Configuration is valid
155    /// - `Err(TimerError)`: Configuration validation failed
156    ///
157    /// # Validation Rules
158    /// - L0 tick duration must be greater than 0
159    /// - L1 tick duration must be greater than 0
160    /// - L0 slot count must be greater than 0 and power of 2
161    /// - L1 slot count must be greater than 0 and power of 2
162    /// - L1 tick must be an integer multiple of L0 tick
163    pub fn build(self) -> Result<WheelConfig, TimerError> {
164        // Validate L0 layer configuration
165        if self.l0_tick_duration.is_zero() {
166            return Err(TimerError::InvalidConfiguration {
167                field: "l0_tick_duration".to_string(),
168                reason: "L0 layer tick duration must be greater than 0".to_string(),
169            });
170        }
171
172        if self.l0_slot_count == 0 {
173            return Err(TimerError::InvalidSlotCount {
174                slot_count: self.l0_slot_count,
175                reason: "L0 layer slot count must be greater than 0",
176            });
177        }
178
179        if !self.l0_slot_count.is_power_of_two() {
180            return Err(TimerError::InvalidSlotCount {
181                slot_count: self.l0_slot_count,
182                reason: "L0 layer slot count must be power of 2",
183            });
184        }
185
186        // Validate L1 layer configuration
187        if self.l1_tick_duration.is_zero() {
188            return Err(TimerError::InvalidConfiguration {
189                field: "l1_tick_duration".to_string(),
190                reason: "L1 layer tick duration must be greater than 0".to_string(),
191            });
192        }
193
194        if self.l1_slot_count == 0 {
195            return Err(TimerError::InvalidSlotCount {
196                slot_count: self.l1_slot_count,
197                reason: "L1 layer slot count must be greater than 0",
198            });
199        }
200
201        if !self.l1_slot_count.is_power_of_two() {
202            return Err(TimerError::InvalidSlotCount {
203                slot_count: self.l1_slot_count,
204                reason: "L1 layer slot count must be power of 2",
205            });
206        }
207
208        // Validate L1 tick is an integer multiple of L0 tick without losing
209        // sub-millisecond precision.
210        let l0_nanos = self.l0_tick_duration.as_nanos();
211        let l1_nanos = self.l1_tick_duration.as_nanos();
212        if !l1_nanos.is_multiple_of(l0_nanos) {
213            return Err(TimerError::InvalidConfiguration {
214                field: "l1_tick_duration".to_string(),
215                reason: format!(
216                    "L1 tick duration ({:?}) must be an integer multiple of L0 tick duration ({:?})",
217                    self.l1_tick_duration, self.l0_tick_duration
218                ),
219            });
220        }
221
222        if u64::try_from(l1_nanos / l0_nanos).is_err() {
223            return Err(TimerError::InvalidConfiguration {
224                field: "l1_tick_duration".to_string(),
225                reason: "L1/L0 tick ratio must fit in u64".to_string(),
226            });
227        }
228
229        Ok(WheelConfig {
230            l0_tick_duration: self.l0_tick_duration,
231            l0_slot_count: self.l0_slot_count,
232            l1_tick_duration: self.l1_tick_duration,
233            l1_slot_count: self.l1_slot_count,
234        })
235    }
236}
237
238/// Service Configuration
239///
240/// Used to configure channel capacities for TimerService.
241///
242/// # 服务配置
243///
244/// 用于配置 TimerService 的通道容量。
245///
246/// # Examples (示例)
247/// ```no_run
248/// use kestrel_timer::config::ServiceConfig;
249/// use std::num::NonZeroUsize;
250///
251/// // Use default configuration (使用默认配置)
252/// let config = ServiceConfig::default();
253///
254/// // Use Builder to customize configuration (使用 Builder 自定义配置)
255/// let config = ServiceConfig::builder()
256///     .command_channel_capacity(NonZeroUsize::new(1024).unwrap())
257///     .timeout_channel_capacity(NonZeroUsize::new(2000).unwrap())
258///     .build();
259/// ```
260#[derive(Debug, Clone)]
261pub struct ServiceConfig {
262    /// Command channel capacity
263    ///
264    /// 命令通道容量
265    pub command_channel_capacity: NonZeroUsize,
266
267    /// Timeout channel capacity
268    ///
269    /// 超时通道容量
270    pub timeout_channel_capacity: NonZeroUsize,
271}
272
273impl Default for ServiceConfig {
274    fn default() -> Self {
275        Self {
276            command_channel_capacity: NonZeroUsize::new(512).unwrap(),
277            timeout_channel_capacity: NonZeroUsize::new(1000).unwrap(),
278        }
279    }
280}
281
282impl ServiceConfig {
283    /// Create configuration builder (创建配置构建器)
284    pub fn builder() -> ServiceConfigBuilder {
285        ServiceConfigBuilder::default()
286    }
287}
288
289/// Service Configuration Builder
290///
291/// 用于构建 ServiceConfig 的构建器。
292#[derive(Debug, Clone)]
293pub struct ServiceConfigBuilder {
294    /// Command channel capacity
295    ///
296    /// 命令通道容量
297    pub command_channel_capacity: NonZeroUsize,
298
299    /// Timeout channel capacity
300    ///
301    /// 超时通道容量
302    pub timeout_channel_capacity: NonZeroUsize,
303}
304
305impl Default for ServiceConfigBuilder {
306    fn default() -> Self {
307        let config = ServiceConfig::default();
308        Self {
309            command_channel_capacity: config.command_channel_capacity,
310            timeout_channel_capacity: config.timeout_channel_capacity,
311        }
312    }
313}
314
315impl ServiceConfigBuilder {
316    /// Set command channel capacity (设置命令通道容量)
317    pub fn command_channel_capacity(mut self, capacity: NonZeroUsize) -> Self {
318        self.command_channel_capacity = capacity;
319        self
320    }
321
322    /// Set timeout channel capacity (设置超时通道容量)
323    pub fn timeout_channel_capacity(mut self, capacity: NonZeroUsize) -> Self {
324        self.timeout_channel_capacity = capacity;
325        self
326    }
327
328    /// Build and validate configuration
329    ///
330    /// # Returns
331    /// - `Ok(ServiceConfig)`: Configuration is valid
332    /// - `Err(TimerError)`: Configuration validation failed
333    ///
334    /// # Validation Rules
335    /// - All channel capacities must be greater than 0
336    ///
337    /// 构建并验证配置
338    ///
339    /// # 返回值
340    /// - `Ok(ServiceConfig)`: 配置有效
341    /// - `Err(TimerError)`: 配置验证失败
342    ///
343    /// # 验证规则
344    /// - 所有通道容量必须大于 0
345    ///
346    pub fn build(self) -> ServiceConfig {
347        ServiceConfig {
348            command_channel_capacity: self.command_channel_capacity,
349            timeout_channel_capacity: self.timeout_channel_capacity,
350        }
351    }
352}
353
354/// Batch Processing Configuration
355///
356/// Used to configure optimization parameters for batch operations.
357///
358/// 用于配置批处理操作的优化参数。
359///
360/// # 批处理配置
361///
362/// 用于配置批处理操作的优化参数。
363///
364/// # Examples (示例)
365/// ```no_run
366/// use kestrel_timer::config::BatchConfig;
367///
368/// // Use default configuration (使用默认配置)
369/// let config = BatchConfig::default();
370///
371/// // Custom configuration (使用自定义配置)
372/// let config = BatchConfig {
373///     small_batch_threshold: 20,
374/// };
375/// ```
376#[derive(Debug, Clone)]
377pub struct BatchConfig {
378    /// Small batch threshold, used for batch cancellation optimization
379    /// When the number of tasks to be cancelled is less than or equal to this value, cancel individually without grouping and sorting
380    ///
381    /// 小批量阈值,用于批量取消操作的优化
382    ///
383    /// 当需要取消的任务数量小于或等于此值时,取消操作将单独进行,无需分组和排序
384    pub small_batch_threshold: usize,
385}
386
387impl Default for BatchConfig {
388    fn default() -> Self {
389        Self {
390            small_batch_threshold: 10,
391        }
392    }
393}
394
395/// Top-level Timer Configuration
396///
397/// Combines all sub-configurations to provide complete timer system configuration.
398///
399/// # 定时器配置
400///
401/// 用于组合所有子配置,提供完整的定时器系统配置。
402///
403/// # Examples (示例)
404/// ```no_run
405/// use kestrel_timer::config::TimerConfig;
406/// use std::num::NonZeroUsize;
407///
408/// // Use default configuration (使用默认配置)
409/// let config = TimerConfig::default();
410///
411/// // Use Builder to customize configuration, service parameters only
412/// // (使用 Builder 自定义配置,仅配置服务参数)
413/// let config = TimerConfig::builder()
414///     .command_channel_capacity(NonZeroUsize::new(1024).unwrap())
415///     .timeout_channel_capacity(NonZeroUsize::new(2000).unwrap())
416///     .build();
417/// ```
418#[derive(Debug, Clone, Default)]
419pub struct TimerConfig {
420    /// Timing wheel configuration
421    pub wheel: WheelConfig,
422    /// Service configuration
423    pub service: ServiceConfig,
424    /// Batch processing configuration
425    pub batch: BatchConfig,
426}
427
428impl TimerConfig {
429    /// Create configuration builder (创建配置构建器)
430    pub fn builder() -> TimerConfigBuilder {
431        TimerConfigBuilder::default()
432    }
433}
434
435/// Top-level Timer Configuration Builder (顶级定时器配置构建器)
436///
437/// 用于构建 TimerConfig 的构建器。
438#[derive(Debug, Default)]
439pub struct TimerConfigBuilder {
440    wheel_builder: WheelConfigBuilder,
441    service_builder: ServiceConfigBuilder,
442    batch_config: BatchConfig,
443}
444
445impl TimerConfigBuilder {
446    /// Set command channel capacity (设置命令通道容量)
447    pub fn command_channel_capacity(mut self, capacity: NonZeroUsize) -> Self {
448        self.service_builder = self.service_builder.command_channel_capacity(capacity);
449        self
450    }
451
452    /// Set timeout channel capacity (设置超时通道容量)
453    pub fn timeout_channel_capacity(mut self, capacity: NonZeroUsize) -> Self {
454        self.service_builder = self.service_builder.timeout_channel_capacity(capacity);
455        self
456    }
457
458    /// Set small batch threshold (设置小批量阈值)
459    pub fn small_batch_threshold(mut self, threshold: usize) -> Self {
460        self.batch_config.small_batch_threshold = threshold;
461        self
462    }
463
464    /// Build and validate configuration
465    ///
466    /// # Returns
467    /// - `Ok(TimerConfig)`: Configuration is valid
468    /// - `Err(TimerError)`: Configuration validation failed
469    ///
470    /// # 构建并验证配置
471    ///
472    /// # 返回值
473    /// - `Ok(TimerConfig)`: 配置有效
474    /// - `Err(TimerError)`: 配置验证失败
475    ///
476    pub fn build(self) -> Result<TimerConfig, TimerError> {
477        Ok(TimerConfig {
478            wheel: self.wheel_builder.build()?,
479            service: self.service_builder.build(),
480            batch: self.batch_config,
481        })
482    }
483}
484
485#[cfg(test)]
486mod tests {
487    use super::*;
488
489    #[test]
490    fn test_wheel_config_default() {
491        let config = WheelConfig::default();
492        assert_eq!(config.l0_tick_duration(), Duration::from_millis(10));
493        assert_eq!(config.l0_slot_count(), 512);
494        assert_eq!(config.l1_tick_duration(), Duration::from_secs(1));
495        assert_eq!(config.l1_slot_count(), 64);
496    }
497
498    #[test]
499    fn test_wheel_config_builder() {
500        let config = WheelConfig::builder()
501            .l0_tick_duration(Duration::from_millis(20))
502            .l0_slot_count(1024)
503            .l1_tick_duration(Duration::from_secs(2))
504            .l1_slot_count(128)
505            .build()
506            .unwrap();
507
508        assert_eq!(config.l0_tick_duration(), Duration::from_millis(20));
509        assert_eq!(config.l0_slot_count(), 1024);
510        assert_eq!(config.l1_tick_duration(), Duration::from_secs(2));
511        assert_eq!(config.l1_slot_count(), 128);
512    }
513
514    #[test]
515    fn test_wheel_config_validation_zero_tick() {
516        let result = WheelConfig::builder()
517            .l0_tick_duration(Duration::ZERO)
518            .build();
519
520        assert!(result.is_err());
521    }
522
523    #[test]
524    fn test_wheel_config_accepts_sub_millisecond_ticks() {
525        let result = WheelConfig::builder()
526            .l0_tick_duration(Duration::from_micros(500))
527            .l1_tick_duration(Duration::from_millis(1))
528            .build();
529
530        assert!(result.is_ok());
531    }
532
533    #[test]
534    fn test_wheel_config_rejects_tick_ratio_overflow() {
535        let result = WheelConfig::builder()
536            .l0_tick_duration(Duration::from_nanos(1))
537            .l1_tick_duration(Duration::MAX)
538            .build();
539
540        assert!(matches!(
541            result,
542            Err(TimerError::InvalidConfiguration { field, .. })
543                if field == "l1_tick_duration"
544        ));
545    }
546
547    #[test]
548    fn test_wheel_config_validation_invalid_slot_count() {
549        let result = WheelConfig::builder().l0_slot_count(100).build();
550
551        assert!(result.is_err());
552    }
553
554    #[test]
555    fn test_service_config_builder() {
556        let config = ServiceConfig::builder()
557            .command_channel_capacity(NonZeroUsize::new(1024).unwrap())
558            .timeout_channel_capacity(NonZeroUsize::new(2000).unwrap())
559            .build();
560
561        assert_eq!(
562            config.command_channel_capacity,
563            NonZeroUsize::new(1024).unwrap()
564        );
565        assert_eq!(
566            config.timeout_channel_capacity,
567            NonZeroUsize::new(2000).unwrap()
568        );
569    }
570
571    #[test]
572    fn test_batch_config_default() {
573        let config = BatchConfig::default();
574        assert_eq!(config.small_batch_threshold, 10);
575    }
576
577    #[test]
578    fn test_timer_config_default() {
579        let config = TimerConfig::default();
580        assert_eq!(config.wheel.l0_slot_count(), 512);
581        assert_eq!(
582            config.service.command_channel_capacity,
583            NonZeroUsize::new(512).unwrap()
584        );
585        assert_eq!(config.batch.small_batch_threshold, 10);
586    }
587
588    #[test]
589    fn test_timer_config_builder() {
590        let config = TimerConfig::builder()
591            .command_channel_capacity(NonZeroUsize::new(1024).unwrap())
592            .timeout_channel_capacity(NonZeroUsize::new(2000).unwrap())
593            .small_batch_threshold(20)
594            .build()
595            .unwrap();
596
597        assert_eq!(
598            config.service.command_channel_capacity,
599            NonZeroUsize::new(1024).unwrap()
600        );
601        assert_eq!(
602            config.service.timeout_channel_capacity,
603            NonZeroUsize::new(2000).unwrap()
604        );
605        assert_eq!(config.batch.small_batch_threshold, 20);
606    }
607}