1use std::fmt;
2use std::sync::Arc;
3use std::time::Duration;
4
5use rivet_envoy_client::config::HttpRequest;
6
7use crate::inspector::InspectorTabEntry;
8
9const DEFAULT_STATE_SAVE_INTERVAL: Duration = Duration::from_secs(1);
10const DEFAULT_CREATE_VARS_TIMEOUT: Duration = Duration::from_secs(5);
11const DEFAULT_CREATE_CONN_STATE_TIMEOUT: Duration = Duration::from_secs(5);
12const DEFAULT_ON_BEFORE_CONNECT_TIMEOUT: Duration = Duration::from_secs(5);
13const DEFAULT_ON_CONNECT_TIMEOUT: Duration = Duration::from_secs(5);
14const DEFAULT_ON_MIGRATE_TIMEOUT: Duration = Duration::from_secs(30);
15const DEFAULT_ACTION_TIMEOUT: Duration = Duration::from_secs(60);
16const DEFAULT_SLEEP_TIMEOUT: Duration = Duration::from_secs(30);
17const DEFAULT_SLEEP_GRACE_PERIOD: Duration = Duration::from_secs(15);
18const DEFAULT_CONNECTION_LIVENESS_TIMEOUT: Duration = Duration::from_millis(2500);
19const DEFAULT_CONNECTION_LIVENESS_INTERVAL: Duration = Duration::from_secs(5);
20const DEFAULT_MAX_QUEUE_SIZE: u32 = 1000;
21pub const DEFAULT_MAX_SCHEDULES: u32 = 1_000;
22const DEFAULT_MAX_QUEUE_MESSAGE_SIZE: u32 = 65_536;
23const DEFAULT_MAX_INCOMING_MESSAGE_SIZE: u32 = 65_536;
24const DEFAULT_MAX_OUTGOING_MESSAGE_SIZE: u32 = 1_048_576;
25pub(crate) const MAX_SQLITE_TRANSACTION_TRACE_STATEMENTS: usize = 32;
26
27#[derive(Clone)]
28pub enum CanHibernateWebSocket {
29 Bool(bool),
30 Callback(Arc<dyn Fn(&HttpRequest) -> bool + Send + Sync>),
31}
32
33impl fmt::Debug for CanHibernateWebSocket {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 match self {
36 Self::Bool(value) => f.debug_tuple("Bool").field(value).finish(),
37 Self::Callback(_) => f.write_str("Callback(..)"),
38 }
39 }
40}
41
42impl Default for CanHibernateWebSocket {
43 fn default() -> Self {
44 Self::Bool(false)
45 }
46}
47
48#[derive(Clone, Debug, Default)]
49pub struct ActorConfigOverrides {
50 pub sleep_grace_period: Option<Duration>,
51}
52
53#[derive(Clone, Debug)]
54pub struct ActionDefinition {
55 pub name: String,
56}
57
58#[derive(Clone, Debug)]
63pub struct SqliteProfilingConfig {
64 pub enabled: bool,
65 pub max_tracked_statement_fingerprints: usize,
66 pub max_tracked_transaction_fingerprints: usize,
67 pub max_prometheus_series: usize,
68 pub max_statements_per_transaction_trace: usize,
69 pub max_get_pages_requests_per_trace: usize,
70 pub max_transaction_name_bytes: usize,
71 pub slow_operation_threshold_ms: u64,
72 pub baseline_sample_rate: f64,
73 pub max_diagnostic_events_per_minute: usize,
74 pub diagnostic_event_queue_capacity: usize,
75}
76
77impl Default for SqliteProfilingConfig {
78 fn default() -> Self {
79 Self {
80 enabled: true,
81 max_tracked_statement_fingerprints: 128,
82 max_tracked_transaction_fingerprints: 8,
83 max_prometheus_series: 25_000,
84 max_statements_per_transaction_trace: 32,
85 max_get_pages_requests_per_trace: 16,
86 max_transaction_name_bytes: 128,
87 slow_operation_threshold_ms: 10,
88 baseline_sample_rate: 0.001,
89 max_diagnostic_events_per_minute: 120,
90 diagnostic_event_queue_capacity: 256,
91 }
92 }
93}
94
95#[derive(Clone, Debug, Default)]
101pub struct SqliteProfilingConfigInput {
102 pub enabled: Option<bool>,
103 pub max_tracked_statement_fingerprints: Option<u32>,
104 pub max_tracked_transaction_fingerprints: Option<u32>,
105 pub max_prometheus_series: Option<u32>,
106 pub max_statements_per_transaction_trace: Option<u32>,
107 pub max_get_pages_requests_per_trace: Option<u32>,
108 pub max_transaction_name_bytes: Option<u32>,
109 pub slow_operation_threshold_ms: Option<u32>,
110 pub baseline_sample_rate: Option<f64>,
111 pub max_diagnostic_events_per_minute: Option<u32>,
112 pub diagnostic_event_queue_capacity: Option<u32>,
113}
114
115impl SqliteProfilingConfig {
116 fn from_input(input: SqliteProfilingConfigInput) -> Self {
117 let mut config = Self::default();
118 macro_rules! set_usize {
119 ($field:ident) => {
120 if let Some(value) = input.$field {
121 config.$field = value as usize;
122 }
123 };
124 }
125 if let Some(value) = input.enabled {
126 config.enabled = value;
127 }
128 set_usize!(max_tracked_statement_fingerprints);
129 set_usize!(max_tracked_transaction_fingerprints);
130 set_usize!(max_prometheus_series);
131 set_usize!(max_statements_per_transaction_trace);
132 set_usize!(max_get_pages_requests_per_trace);
133 set_usize!(max_transaction_name_bytes);
134 if let Some(value) = input.slow_operation_threshold_ms {
135 config.slow_operation_threshold_ms = u64::from(value);
136 }
137 if let Some(value) = input.baseline_sample_rate {
138 config.baseline_sample_rate = value;
139 }
140 set_usize!(max_diagnostic_events_per_minute);
141 set_usize!(diagnostic_event_queue_capacity);
142 config
143 }
144}
145
146#[derive(Clone, Debug)]
147pub struct ActorConfig {
148 pub name: Option<String>,
149 pub icon: Option<String>,
150 pub has_database: bool,
153 pub remote_sqlite: bool,
154 pub sqlite_profiling: SqliteProfilingConfig,
155 pub enable_actor_runtime_socket: bool,
157 pub has_state: bool,
160 pub can_hibernate_websocket: CanHibernateWebSocket,
161 pub state_save_interval: Duration,
162 pub create_vars_timeout: Duration,
163 pub create_conn_state_timeout: Duration,
164 pub on_before_connect_timeout: Duration,
165 pub on_connect_timeout: Duration,
166 pub on_migrate_timeout: Duration,
167 pub action_timeout: Duration,
168 pub sleep_timeout: Duration,
169 pub no_sleep: bool,
170 pub sleep_grace_period: Duration,
171 pub sleep_grace_period_overridden: bool,
172 pub connection_liveness_timeout: Duration,
173 pub connection_liveness_interval: Duration,
174 pub max_queue_size: u32,
175 pub max_schedules: u32,
176 pub max_queue_message_size: u32,
177 pub max_incoming_message_size: u32,
178 pub max_outgoing_message_size: u32,
179 pub overrides: Option<ActorConfigOverrides>,
180 pub actions: Vec<ActionDefinition>,
181 pub inspector_tabs: Vec<InspectorTabEntry>,
184}
185
186#[derive(Clone, Debug, Default)]
188pub struct ActorConfigInput {
189 pub name: Option<String>,
190 pub icon: Option<String>,
191 pub has_database: Option<bool>,
192 pub remote_sqlite: Option<bool>,
193 pub sqlite_profiling: Option<SqliteProfilingConfigInput>,
194 pub enable_actor_runtime_socket: Option<bool>,
195 pub has_state: Option<bool>,
196 pub can_hibernate_websocket: Option<bool>,
197 pub state_save_interval_ms: Option<u32>,
198 pub create_vars_timeout_ms: Option<u32>,
199 pub create_conn_state_timeout_ms: Option<u32>,
200 pub on_before_connect_timeout_ms: Option<u32>,
201 pub on_connect_timeout_ms: Option<u32>,
202 pub on_migrate_timeout_ms: Option<u32>,
203 pub action_timeout_ms: Option<u32>,
204 pub sleep_timeout_ms: Option<u32>,
205 pub no_sleep: Option<bool>,
206 pub sleep_grace_period_ms: Option<u32>,
207 pub connection_liveness_timeout_ms: Option<u32>,
208 pub connection_liveness_interval_ms: Option<u32>,
209 pub max_queue_size: Option<u32>,
210 pub max_schedules: Option<u32>,
211 pub max_queue_message_size: Option<u32>,
212 pub max_incoming_message_size: Option<u32>,
213 pub max_outgoing_message_size: Option<u32>,
214 pub actions: Option<Vec<ActionDefinition>>,
215 pub inspector_tabs: Option<Vec<InspectorTabEntry>>,
216}
217
218impl ActorConfig {
219 pub fn from_input(config: ActorConfigInput) -> Self {
220 let mut actor_config = Self {
221 name: config.name,
222 icon: config.icon,
223 has_database: config.has_database.unwrap_or(false),
224 remote_sqlite: config.remote_sqlite.unwrap_or(false),
225 sqlite_profiling: config
226 .sqlite_profiling
227 .map(SqliteProfilingConfig::from_input)
228 .unwrap_or_default(),
229 enable_actor_runtime_socket: config.enable_actor_runtime_socket.unwrap_or(false),
230 has_state: config.has_state.unwrap_or(false),
231 ..Self::default()
232 };
233 if let Some(can_hibernate_websocket) = config.can_hibernate_websocket {
234 actor_config.can_hibernate_websocket =
235 CanHibernateWebSocket::Bool(can_hibernate_websocket);
236 }
237 if let Some(value) = config.state_save_interval_ms {
238 actor_config.state_save_interval = duration_ms(value);
239 }
240 if let Some(value) = config.create_vars_timeout_ms {
241 actor_config.create_vars_timeout = duration_ms(value);
242 }
243 if let Some(value) = config.create_conn_state_timeout_ms {
244 actor_config.create_conn_state_timeout = duration_ms(value);
245 }
246 if let Some(value) = config.on_before_connect_timeout_ms {
247 actor_config.on_before_connect_timeout = duration_ms(value);
248 }
249 if let Some(value) = config.on_connect_timeout_ms {
250 actor_config.on_connect_timeout = duration_ms(value);
251 }
252 if let Some(value) = config.on_migrate_timeout_ms {
253 actor_config.on_migrate_timeout = duration_ms(value);
254 }
255 if let Some(value) = config.action_timeout_ms {
256 actor_config.action_timeout = duration_ms(value);
257 }
258 if let Some(value) = config.sleep_timeout_ms {
259 actor_config.sleep_timeout = duration_ms(value);
260 }
261 if let Some(value) = config.no_sleep {
262 actor_config.no_sleep = value;
263 }
264 if let Some(value) = config.sleep_grace_period_ms {
265 actor_config.sleep_grace_period = duration_ms(value);
266 actor_config.sleep_grace_period_overridden = true;
267 }
268 if let Some(value) = config.connection_liveness_timeout_ms {
269 actor_config.connection_liveness_timeout = duration_ms(value);
270 }
271 if let Some(value) = config.connection_liveness_interval_ms {
272 actor_config.connection_liveness_interval = duration_ms(value);
273 }
274 if let Some(value) = config.max_queue_size {
275 actor_config.max_queue_size = value;
276 }
277 if let Some(value) = config.max_schedules {
278 actor_config.max_schedules = value;
279 }
280 if let Some(value) = config.max_queue_message_size {
281 actor_config.max_queue_message_size = value;
282 }
283 if let Some(value) = config.max_incoming_message_size {
284 actor_config.max_incoming_message_size = value;
285 }
286 if let Some(value) = config.max_outgoing_message_size {
287 actor_config.max_outgoing_message_size = value;
288 }
289 if let Some(actions) = config.actions {
290 actor_config.actions = actions;
291 }
292 if let Some(tabs) = config.inspector_tabs {
293 actor_config.inspector_tabs = tabs;
294 }
295
296 actor_config
297 }
298
299 pub fn effective_sleep_grace_period(&self) -> Duration {
300 cap_duration(
301 self.sleep_grace_period,
302 self.overrides
303 .as_ref()
304 .and_then(|overrides| overrides.sleep_grace_period),
305 )
306 }
307
308 pub fn validate(&self) -> anyhow::Result<()> {
313 crate::inspector::validate_inspector_tabs(&self.inspector_tabs)?;
314 anyhow::ensure!(
315 self.sqlite_profiling.baseline_sample_rate.is_finite()
316 && (0.0..=1.0).contains(&self.sqlite_profiling.baseline_sample_rate),
317 "SQLite profiling baselineSampleRate must be between 0 and 1"
318 );
319 anyhow::ensure!(
320 self.sqlite_profiling.max_get_pages_requests_per_trace <= 16,
321 "SQLite profiling maxGetPagesRequestsPerTrace must be at most 16"
322 );
323 anyhow::ensure!(
324 self.sqlite_profiling.max_statements_per_transaction_trace
325 <= MAX_SQLITE_TRANSACTION_TRACE_STATEMENTS,
326 "SQLite profiling maxStatementsPerTransactionTrace must be at most 32"
327 );
328 anyhow::ensure!(
329 self.sqlite_profiling.max_transaction_name_bytes > 0,
330 "SQLite profiling maxTransactionNameBytes must be greater than zero"
331 );
332 Ok(())
333 }
334}
335
336impl Default for ActorConfig {
337 fn default() -> Self {
338 Self {
339 name: None,
340 icon: None,
341 has_database: false,
342 remote_sqlite: false,
343 sqlite_profiling: SqliteProfilingConfig::default(),
344 enable_actor_runtime_socket: false,
345 has_state: false,
346 can_hibernate_websocket: CanHibernateWebSocket::default(),
347 state_save_interval: DEFAULT_STATE_SAVE_INTERVAL,
348 create_vars_timeout: DEFAULT_CREATE_VARS_TIMEOUT,
349 create_conn_state_timeout: DEFAULT_CREATE_CONN_STATE_TIMEOUT,
350 on_before_connect_timeout: DEFAULT_ON_BEFORE_CONNECT_TIMEOUT,
351 on_connect_timeout: DEFAULT_ON_CONNECT_TIMEOUT,
352 on_migrate_timeout: DEFAULT_ON_MIGRATE_TIMEOUT,
353 action_timeout: DEFAULT_ACTION_TIMEOUT,
354 sleep_timeout: DEFAULT_SLEEP_TIMEOUT,
355 no_sleep: false,
356 sleep_grace_period: DEFAULT_SLEEP_GRACE_PERIOD,
357 sleep_grace_period_overridden: false,
358 connection_liveness_timeout: DEFAULT_CONNECTION_LIVENESS_TIMEOUT,
359 connection_liveness_interval: DEFAULT_CONNECTION_LIVENESS_INTERVAL,
360 max_queue_size: DEFAULT_MAX_QUEUE_SIZE,
361 max_schedules: DEFAULT_MAX_SCHEDULES,
362 max_queue_message_size: DEFAULT_MAX_QUEUE_MESSAGE_SIZE,
363 max_incoming_message_size: DEFAULT_MAX_INCOMING_MESSAGE_SIZE,
364 max_outgoing_message_size: DEFAULT_MAX_OUTGOING_MESSAGE_SIZE,
365 overrides: None,
366 actions: Vec::new(),
367 inspector_tabs: Vec::new(),
368 }
369 }
370}
371
372fn cap_duration(duration: Duration, override_duration: Option<Duration>) -> Duration {
373 if let Some(override_duration) = override_duration {
374 duration.min(override_duration)
375 } else {
376 duration
377 }
378}
379
380fn duration_ms(value: u32) -> Duration {
381 Duration::from_millis(u64::from(value))
382}
383
384#[cfg(test)]
386#[path = "../../tests/config.rs"]
387mod tests;