1use std::io::Cursor;
14use std::io::Read;
15use std::path::PathBuf;
16use std::time::Duration;
17
18use hyperactor_config::AttrValue;
19use hyperactor_config::CONFIG;
20use hyperactor_config::ConfigAttr;
21use hyperactor_config::NonZeroUsize;
22use hyperactor_config::attrs::declare_attrs;
23use serde::Deserialize;
24use serde::Serialize;
25use typeuri::Named;
26
27#[derive(Clone, Debug, Serialize, Named)]
29#[named("hyperactor::config::Pem")]
30pub enum Pem {
31 Value(Vec<u8>),
33 File(PathBuf),
35 StaticPath(&'static str),
37}
38
39impl<'de> Deserialize<'de> for Pem {
41 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
42 where
43 D: serde::Deserializer<'de>,
44 {
45 #[derive(Deserialize)]
46 enum PemDeserialize {
47 Value(Vec<u8>),
48 File(PathBuf),
49 StaticPath(String),
51 }
52
53 match PemDeserialize::deserialize(deserializer)? {
54 PemDeserialize::Value(v) => Ok(Pem::Value(v)),
55 PemDeserialize::File(p) => Ok(Pem::File(p)),
56 PemDeserialize::StaticPath(s) => Ok(Pem::File(PathBuf::from(s))),
58 }
59 }
60}
61
62impl AttrValue for Pem {
63 fn display(&self) -> String {
64 match self {
65 Pem::Value(data) => String::from_utf8_lossy(data).to_string(),
66 Pem::File(path) => path.display().to_string(),
67 Pem::StaticPath(path) => path.to_string(),
68 }
69 }
70
71 fn parse(value: &str) -> Result<Self, anyhow::Error> {
72 if value.trim_start().starts_with("-----BEGIN") {
74 Ok(Pem::Value(value.as_bytes().to_vec()))
75 } else {
76 Ok(Pem::File(PathBuf::from(value)))
77 }
78 }
79}
80
81impl Pem {
82 pub fn reader(&self) -> std::io::Result<Box<dyn Read + '_>> {
84 match self {
85 Pem::Value(data) => Ok(Box::new(Cursor::new(data))),
86 Pem::File(path) => Ok(Box::new(std::fs::File::open(path)?)),
87 Pem::StaticPath(path) => Ok(Box::new(std::fs::File::open(path)?)),
88 }
89 }
90}
91
92#[derive(Clone, Debug)]
94pub struct PemBundle {
95 pub ca: Pem,
97 pub cert: Pem,
99 pub key: Pem,
101}
102
103declare_attrs! {
105 @meta(CONFIG = ConfigAttr::new(
107 Some("HYPERACTOR_CODEC_MAX_FRAME_LENGTH".to_string()),
108 Some("codec_max_frame_length".to_string()),
109 ))
110 pub attr CODEC_MAX_FRAME_LENGTH: usize = 10 * 1024 * 1024 * 1024; @meta(CONFIG = ConfigAttr::new(
114 Some("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT".to_string()),
115 Some("message_delivery_timeout".to_string()),
116 ))
117 pub attr MESSAGE_DELIVERY_TIMEOUT: Duration = Duration::from_secs(30);
118
119 @meta(CONFIG = ConfigAttr::new(
123 Some("HYPERACTOR_PENDING_ACTOR_DELIVERY_TIMEOUT".to_string()),
124 Some("pending_actor_delivery_timeout".to_string()),
125 ))
126 pub attr PENDING_ACTOR_DELIVERY_TIMEOUT: Duration = Duration::from_secs(30);
127
128 @meta(CONFIG = ConfigAttr::new(
132 Some("HYPERACTOR_TERMINATED_SNAPSHOT_RETENTION".to_string()),
133 Some("terminated_snapshot_retention".to_string()),
134 ))
135 pub attr TERMINATED_SNAPSHOT_RETENTION: usize = 100;
136
137 @meta(CONFIG = ConfigAttr::new(
139 Some("HYPERACTOR_PROCESS_EXIT_TIMEOUT".to_string()),
140 Some("process_exit_timeout".to_string()),
141 ))
142 pub attr PROCESS_EXIT_TIMEOUT: Duration = Duration::from_secs(10);
143
144 @meta(CONFIG = ConfigAttr::new(
146 Some("HYPERACTOR_MESSAGE_ACK_TIME_INTERVAL".to_string()),
147 Some("message_ack_time_interval".to_string()),
148 ))
149 pub attr MESSAGE_ACK_TIME_INTERVAL: Duration = Duration::from_millis(500);
150
151 @meta(CONFIG = ConfigAttr::new(
153 Some("HYPERACTOR_MESSAGE_ACK_EVERY_N_MESSAGES".to_string()),
154 Some("message_ack_every_n_messages".to_string()),
155 ))
156 pub attr MESSAGE_ACK_EVERY_N_MESSAGES: u64 = 1000;
157
158 @meta(CONFIG = ConfigAttr::new(
160 Some("HYPERACTOR_MESSAGE_TTL_DEFAULT".to_string()),
161 Some("message_ttl_default".to_string()),
162 ))
163 pub attr MESSAGE_TTL_DEFAULT : u8 = 64;
164
165 @meta(CONFIG = ConfigAttr::new(
167 Some("HYPERACTOR_SPLIT_MAX_BUFFER_SIZE".to_string()),
168 Some("split_max_buffer_size".to_string()),
169 ))
170 pub attr SPLIT_MAX_BUFFER_SIZE: usize = 5;
171
172 @meta(CONFIG = ConfigAttr::new(
174 Some("HYPERACTOR_SPLIT_MAX_BUFFER_AGE".to_string()),
175 Some("split_max_buffer_age".to_string()),
176 ))
177 pub attr SPLIT_MAX_BUFFER_AGE: Duration = Duration::from_millis(50);
178
179 @meta(CONFIG = ConfigAttr::new(
181 Some("HYPERACTOR_STOP_ACTOR_TIMEOUT".to_string()),
182 Some("stop_actor_timeout".to_string()),
183 ))
184 pub attr STOP_ACTOR_TIMEOUT: Duration = Duration::from_secs(10);
185
186 @meta(CONFIG = ConfigAttr::new(
189 Some("HYPERACTOR_CLEANUP_TIMEOUT".to_string()),
190 Some("cleanup_timeout".to_string()),
191 ))
192 pub attr CLEANUP_TIMEOUT: Duration = Duration::from_secs(3);
193
194 @meta(CONFIG = ConfigAttr::new(
196 Some("HYPERACTOR_CHANNEL_NET_RX_BUFFER_FULL_CHECK_INTERVAL".to_string()),
197 Some("channel_net_rx_buffer_full_check_interval".to_string()),
198 ))
199 pub attr CHANNEL_NET_RX_BUFFER_FULL_CHECK_INTERVAL: Duration = Duration::from_secs(5);
200
201 @meta(CONFIG = ConfigAttr::new(
211 Some("HYPERACTOR_CHANNEL_TCP_KEEPALIVE_IDLE".to_string()),
212 Some("channel_tcp_keepalive_idle".to_string()),
213 ))
214 pub attr CHANNEL_TCP_KEEPALIVE_IDLE: Duration = Duration::from_secs(60);
215
216 @meta(CONFIG = ConfigAttr::new(
243 Some("HYPERACTOR_CHANNEL_TCP_CONGESTION".to_string()),
244 Some("channel_tcp_congestion".to_string()),
245 ).process_local())
246 pub attr CHANNEL_TCP_CONGESTION: String = String::new();
247
248 @meta(CONFIG = ConfigAttr::new(
254 Some("HYPERACTOR_CHANNEL_RECONNECT_TIMEOUT".to_string()),
255 Some("channel_reconnect_timeout".to_string()),
256 ))
257 pub attr CHANNEL_RECONNECT_TIMEOUT: Duration = Duration::from_secs(60);
258
259 @meta(CONFIG = ConfigAttr::new(
262 Some("HYPERACTOR_MESSAGE_LATENCY_SAMPLING_RATE".to_string()),
263 Some("message_latency_sampling_rate".to_string()),
264 ))
265 pub attr MESSAGE_LATENCY_SAMPLING_RATE: f32 = 0.01;
266
267 @meta(CONFIG = ConfigAttr::new(
269 Some("HYPERACTOR_ENABLE_DEST_ACTOR_REORDERING_BUFFER".to_string()),
270 Some("enable_dest_actor_reordering_buffer".to_string()),
271 ))
272 pub attr ENABLE_DEST_ACTOR_REORDERING_BUFFER: bool = true;
273
274 @meta(CONFIG = ConfigAttr::new(
277 Some("HYPERACTOR_SEQUENCER_MAX_LOCKED_KEYS".to_string()),
278 Some("sequencer_max_locked_keys".to_string()),
279 ))
280 pub attr SEQUENCER_MAX_LOCKED_KEYS: NonZeroUsize =
281 NonZeroUsize::new(128).expect("128 is non-zero");
282
283 @meta(CONFIG = ConfigAttr::new(
288 Some("HYPERACTOR_HOST_SPAWN_READY_TIMEOUT".to_string()),
289 Some("host_spawn_ready_timeout".to_string()),
290 ))
291 pub attr HOST_SPAWN_READY_TIMEOUT: Duration = Duration::from_secs(30);
292
293 @meta(CONFIG = ConfigAttr::new(
296 Some("HYPERACTOR_SERVER_HEARTBEAT_INTERVAL".to_string()),
297 Some("server_heartbeat_interval".to_string()),
298 ))
299 pub attr SERVER_HEARTBEAT_INTERVAL: Duration = Duration::from_secs(1);
300
301 @meta(CONFIG = ConfigAttr::new(
306 Some("HYPERACTOR_FORWARDER_FLUSH_TIMEOUT".to_string()),
307 Some("forwarder_flush_timeout".to_string()),
308 ))
309 pub attr FORWARDER_FLUSH_TIMEOUT: Duration = Duration::from_secs(5);
310
311 @meta(CONFIG = ConfigAttr::new(
313 Some("HYPERACTOR_TLS_CERT".to_string()),
314 Some("hyperactor_tls_cert".to_string()),
315 ).process_local())
316 pub attr TLS_CERT: Pem = Pem::StaticPath("/etc/hyperactor/tls/tls.crt");
317
318 @meta(CONFIG = ConfigAttr::new(
320 Some("HYPERACTOR_TLS_KEY".to_string()),
321 Some("hyperactor_tls_key".to_string()),
322 ).process_local())
323 pub attr TLS_KEY: Pem = Pem::StaticPath("/etc/hyperactor/tls/tls.key");
324
325 @meta(CONFIG = ConfigAttr::new(
327 Some("HYPERACTOR_TLS_CA".to_string()),
328 Some("hyperactor_tls_ca".to_string()),
329 ).process_local())
330 pub attr TLS_CA: Pem = Pem::StaticPath("/etc/hyperactor/tls/ca.crt");
331}
332
333#[cfg(test)]
334mod tests {
335 #[test]
336 fn channel_tcp_congestion_is_process_local() {
337 let meta = CHANNEL_TCP_CONGESTION
338 .attrs()
339 .get(CONFIG)
340 .expect("CHANNEL_TCP_CONGESTION should carry CONFIG meta");
341 assert!(
342 !meta.propagate,
343 "CHANNEL_TCP_CONGESTION must not propagate to child procs; \
344 see the attr docs for the measurement"
345 );
346 }
347
348 use std::collections::HashSet;
349
350 use hyperactor_config::Attrs;
351 use hyperactor_config::from_env;
352 use indoc::indoc;
353
354 use super::*;
355
356 fn logs_assert_unscoped(f: impl Fn(&[&str]) -> Result<(), String>) {
360 let buf = tracing_test::internal::global_buf().lock().unwrap();
361 let logs_str = std::str::from_utf8(&buf).expect("Logs contain invalid UTF8");
362 let lines: Vec<&str> = logs_str.lines().collect();
363 match f(&lines) {
364 Ok(()) => {}
365 Err(msg) => panic!("{}", msg),
366 }
367 }
368
369 const CODEC_MAX_FRAME_LENGTH_DEFAULT: usize = 10 * 1024 * 1024 * 1024;
370
371 #[test]
372 fn test_default_config() {
373 let config = Attrs::new();
374 assert_eq!(
375 config[CODEC_MAX_FRAME_LENGTH],
376 CODEC_MAX_FRAME_LENGTH_DEFAULT
377 );
378 assert_eq!(config[MESSAGE_DELIVERY_TIMEOUT], Duration::from_secs(30));
379 assert_eq!(
380 config[MESSAGE_ACK_TIME_INTERVAL],
381 Duration::from_millis(500)
382 );
383 assert_eq!(config[MESSAGE_ACK_EVERY_N_MESSAGES], 1000);
384 assert_eq!(config[SPLIT_MAX_BUFFER_SIZE], 5);
385 assert_eq!(config[SEQUENCER_MAX_LOCKED_KEYS].get(), 128);
386 }
387
388 #[tracing_test::traced_test]
389 #[test]
390 #[cfg_attr(not(fbcode_build), ignore)]
392 fn test_from_env() {
393 unsafe { std::env::set_var("HYPERACTOR_CODEC_MAX_FRAME_LENGTH", "1024") };
396 unsafe { std::env::set_var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT", "60s") };
398
399 let config = from_env();
400
401 assert_eq!(config[CODEC_MAX_FRAME_LENGTH], 1024);
402 assert_eq!(config[MESSAGE_DELIVERY_TIMEOUT], Duration::from_mins(1));
403 assert_eq!(
404 config[MESSAGE_ACK_TIME_INTERVAL],
405 Duration::from_millis(500)
406 ); let expected_lines: HashSet<&str> = indoc! {"
409 # export HYPERACTOR_MESSAGE_LATENCY_SAMPLING_RATE=0.01
410 # export HYPERACTOR_CHANNEL_NET_RX_BUFFER_FULL_CHECK_INTERVAL=5s
411 # export HYPERACTOR_STOP_ACTOR_TIMEOUT=10s
412 # export HYPERACTOR_SPLIT_MAX_BUFFER_SIZE=5
413 # export HYPERACTOR_MESSAGE_TTL_DEFAULT=64
414 # export HYPERACTOR_MESSAGE_ACK_EVERY_N_MESSAGES=1000
415 # export HYPERACTOR_MESSAGE_ACK_TIME_INTERVAL=500ms
416 # export HYPERACTOR_PROCESS_EXIT_TIMEOUT=10s
417 # export HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT=30s
418 export HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT=1m
419 # export HYPERACTOR_CODEC_MAX_FRAME_LENGTH=10737418240
420 export HYPERACTOR_CODEC_MAX_FRAME_LENGTH=1024
421 # export HYPERACTOR_CLEANUP_TIMEOUT=3s
422 # export HYPERACTOR_SPLIT_MAX_BUFFER_AGE=50ms
423 # export HYPERACTOR_DEFAULT_ENCODING=serde_multipart
424 # export HYPERACTOR_HOST_SPAWN_READY_TIMEOUT=30s
425 "}
426 .trim_end()
427 .lines()
428 .collect();
429
430 logs_assert_unscoped(|logged_lines: &[&str]| {
434 let mut expected_lines = expected_lines.clone(); for logged in logged_lines {
436 expected_lines.remove(logged);
437 }
438
439 if expected_lines.is_empty() {
440 Ok(())
441 } else {
442 Err(format!("missing log lines: {:?}", expected_lines))
443 }
444 });
445
446 unsafe { std::env::remove_var("HYPERACTOR_CODEC_MAX_FRAME_LENGTH") };
449 unsafe { std::env::remove_var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT_SECS") };
451 }
452
453 #[test]
454 fn test_defaults() {
455 let config = Attrs::new();
457
458 assert!(config.is_empty());
460
461 assert_eq!(
463 config[CODEC_MAX_FRAME_LENGTH],
464 CODEC_MAX_FRAME_LENGTH_DEFAULT
465 );
466 assert_eq!(config[MESSAGE_DELIVERY_TIMEOUT], Duration::from_secs(30));
467 assert_eq!(
468 config[MESSAGE_ACK_TIME_INTERVAL],
469 Duration::from_millis(500)
470 );
471 assert_eq!(config[MESSAGE_ACK_EVERY_N_MESSAGES], 1000);
472 assert_eq!(config[SPLIT_MAX_BUFFER_SIZE], 5);
473 assert_eq!(config[SEQUENCER_MAX_LOCKED_KEYS].get(), 128);
474
475 assert!(CODEC_MAX_FRAME_LENGTH.has_default());
477 assert!(MESSAGE_DELIVERY_TIMEOUT.has_default());
478 assert!(MESSAGE_ACK_TIME_INTERVAL.has_default());
479 assert!(MESSAGE_ACK_EVERY_N_MESSAGES.has_default());
480 assert!(SPLIT_MAX_BUFFER_SIZE.has_default());
481 assert!(SEQUENCER_MAX_LOCKED_KEYS.has_default());
482
483 assert_eq!(
485 CODEC_MAX_FRAME_LENGTH.default(),
486 Some(&(CODEC_MAX_FRAME_LENGTH_DEFAULT))
487 );
488 assert_eq!(
489 MESSAGE_DELIVERY_TIMEOUT.default(),
490 Some(&Duration::from_secs(30))
491 );
492 assert_eq!(
493 MESSAGE_ACK_TIME_INTERVAL.default(),
494 Some(&Duration::from_millis(500))
495 );
496 assert_eq!(MESSAGE_ACK_EVERY_N_MESSAGES.default(), Some(&1000));
497 assert_eq!(SPLIT_MAX_BUFFER_SIZE.default(), Some(&5));
498 assert_eq!(
499 SEQUENCER_MAX_LOCKED_KEYS.default().map(|v| v.get()),
500 Some(128)
501 );
502 }
503
504 #[test]
505 fn test_serialization_only_includes_set_values() {
506 let mut config = Attrs::new();
507
508 let serialized = serde_json::to_string(&config).unwrap();
510 assert_eq!(serialized, "{}");
511
512 config[CODEC_MAX_FRAME_LENGTH] = 1024;
513
514 let serialized = serde_json::to_string(&config).unwrap();
515 assert!(serialized.contains("codec_max_frame_length"));
516 assert!(!serialized.contains("message_delivery_timeout")); let restored_config: Attrs = serde_json::from_str(&serialized).unwrap();
520
521 assert_eq!(restored_config[CODEC_MAX_FRAME_LENGTH], 1024);
523
524 assert_eq!(
526 restored_config[MESSAGE_DELIVERY_TIMEOUT],
527 Duration::from_secs(30)
528 );
529 }
530}