rusmes_config/performance.rs
1//! Performance tuning configuration for RusMES.
2//!
3//! The `[performance]` TOML section controls thread counts and per-connection
4//! buffer sizes. All fields are optional — omitting the entire section (or
5//! individual fields) uses the `Default` values documented below.
6//!
7//! ## Example
8//!
9//! ```toml
10//! [performance]
11//! worker_threads = 4
12//! imap_pool_size = 128
13//! smtp_pool_size = 128
14//! read_buffer_kb = 32
15//! write_buffer_kb = 32
16//! ```
17
18use serde::{Deserialize, Serialize};
19
20/// Runtime performance tuning parameters.
21///
22/// Exposed via the `[performance]` TOML section. Defaults are conservative
23/// values that work well on typical single-node deployments. Increase pool
24/// sizes and buffer sizes on high-traffic installations with ample RAM.
25#[derive(Debug, Clone, Deserialize, Serialize)]
26pub struct PerformanceConfig {
27 /// Default: `None` (use Tokio's default, typically the number of logical
28 /// CPUs). Set to override the number of Tokio worker threads at runtime
29 /// startup. Values of `0` are rejected by validation.
30 #[serde(default)]
31 pub worker_threads: Option<usize>,
32
33 /// Default: `64`. Maximum number of concurrent IMAP connections the server
34 /// will accept per listener socket before new connections are queued.
35 #[serde(default = "default_pool_size")]
36 pub imap_pool_size: usize,
37
38 /// Default: `64`. Maximum number of concurrent SMTP connections the server
39 /// will accept per listener socket before new connections are queued.
40 #[serde(default = "default_pool_size")]
41 pub smtp_pool_size: usize,
42
43 /// Default: `64`. Read buffer size in kibibytes allocated per connection.
44 /// Larger values reduce system-call overhead on bulk transfers at the cost
45 /// of memory.
46 #[serde(default = "default_buffer_kb")]
47 pub read_buffer_kb: usize,
48
49 /// Default: `64`. Write buffer size in kibibytes allocated per connection.
50 /// Larger values reduce system-call overhead on bulk transfers at the cost
51 /// of memory.
52 #[serde(default = "default_buffer_kb")]
53 pub write_buffer_kb: usize,
54}
55
56fn default_pool_size() -> usize {
57 64
58}
59
60fn default_buffer_kb() -> usize {
61 64
62}
63
64impl Default for PerformanceConfig {
65 fn default() -> Self {
66 Self {
67 worker_threads: None,
68 imap_pool_size: default_pool_size(),
69 smtp_pool_size: default_pool_size(),
70 read_buffer_kb: default_buffer_kb(),
71 write_buffer_kb: default_buffer_kb(),
72 }
73 }
74}
75
76impl PerformanceConfig {
77 /// Validate performance configuration values.
78 ///
79 /// Returns an error if any pool size or buffer size is zero, or if
80 /// `worker_threads` is explicitly set to zero.
81 pub fn validate(&self) -> anyhow::Result<()> {
82 if let Some(threads) = self.worker_threads {
83 if threads == 0 {
84 anyhow::bail!("performance.worker_threads must be greater than 0");
85 }
86 }
87 if self.imap_pool_size == 0 {
88 anyhow::bail!("performance.imap_pool_size must be greater than 0");
89 }
90 if self.smtp_pool_size == 0 {
91 anyhow::bail!("performance.smtp_pool_size must be greater than 0");
92 }
93 if self.read_buffer_kb == 0 {
94 anyhow::bail!("performance.read_buffer_kb must be greater than 0");
95 }
96 if self.write_buffer_kb == 0 {
97 anyhow::bail!("performance.write_buffer_kb must be greater than 0");
98 }
99 Ok(())
100 }
101
102 /// Return `worker_threads` or a sensible fallback based on the number of
103 /// logical CPUs (minimum 1).
104 pub fn effective_worker_threads(&self) -> usize {
105 self.worker_threads.unwrap_or_else(|| {
106 std::thread::available_parallelism()
107 .map(|n| n.get())
108 .unwrap_or(1)
109 })
110 }
111}