hyperactor/config/global.rs
1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9//! Global layered configuration for Hyperactor.
10//!
11//! This module provides the process-wide configuration store and APIs
12//! to access it. Configuration values are resolved via a **layered
13//! model**: `TestOverride → Runtime → Env → File → Default`.
14//!
15//! - Reads (`get`, `get_cloned`) consult layers in that order, falling
16//! back to defaults if no explicit value is set.
17//! - `attrs()` returns a complete snapshot of all CONFIG-marked keys at
18//! call time: it materializes defaults for keys not set in any layer.
19//! Keys without @meta(CONFIG = …) are excluded.
20//! - In tests, `lock()` and `override_key` allow temporary overrides
21//! that are removed automatically when the guard drops.
22//! - In normal operation, a parent process can capture its effective
23//! config via `attrs()` and pass that snapshot to a child during
24//! bootstrap. The child installs it as a `Runtime` layer so the
25//! parent's values take precedence over Env/File/Defaults.
26//!
27//! This design provides flexibility (easy test overrides, runtime
28//! updates, YAML/Env baselines) while ensuring type safety and
29//! predictable resolution order.
30//!
31//!
32//! # Testing
33//!
34//! Tests can override global configuration using [`lock`]. This
35//! ensures such tests are serialized (and cannot clobber each other's
36//! overrides).
37//!
38//! ```ignore
39//! #[test]
40//! fn test_my_feature() {
41//! let config = hyperactor::config::global::lock();
42//! let _guard = config.override_key(SOME_CONFIG_KEY, test_value);
43//! // ... test logic here ...
44//! }
45//! ```
46use std::collections::HashMap;
47use std::marker::PhantomData;
48use std::sync::atomic::AtomicU64;
49use std::sync::atomic::Ordering;
50
51use super::*;
52use crate::attrs::AttrValue;
53use crate::attrs::Key;
54use crate::config::CONFIG;
55
56/// Configuration source layers in priority order.
57///
58/// Resolution order is always: **TestOverride -> Runtime -> Env
59/// -> File -> ClientOverride -> Default**.
60///
61/// Smaller `priority()` number = higher precedence.
62#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
63pub enum Source {
64 /// Values set by the config snapshot sent from the client
65 /// during proc bootstrap.
66 ClientOverride,
67 /// Values loaded from configuration files (e.g., YAML).
68 File,
69 /// Values read from environment variables at process startup.
70 /// Higher priority than File and ClientOverride, but lower than
71 /// Runtime/TestOverride.
72 Env,
73 /// Values set programmatically at runtime. Highest stable
74 /// priority layer; only overridden by TestOverride.
75 Runtime,
76 /// Ephemeral values inserted by tests via
77 /// `ConfigLock::override_key`. Always wins over all other
78 /// sources; removed when the guard drops.
79 TestOverride,
80}
81
82/// Return the numeric priority for a source.
83///
84/// Smaller number = higher precedence. Matches the documented
85/// order: TestOverride (0) -> Runtime (1) -> Env (2) -> File (3) -> ClientOverride (4).
86fn priority(s: Source) -> u8 {
87 match s {
88 Source::TestOverride => 0,
89 Source::Runtime => 1,
90 Source::Env => 2,
91 Source::File => 3,
92 Source::ClientOverride => 4,
93 }
94}
95
96/// The full set of configuration layers in priority order.
97///
98/// `Layers` wraps a vector of [`Layer`]s, always kept sorted by
99/// [`priority`] (lowest number = highest precedence).
100///
101/// Resolution (`get`, `get_cloned`, `attrs`) consults `ordered`
102/// from front to back, returning the first value found for each
103/// key and falling back to defaults if none are set in any layer.
104struct Layers {
105 /// Kept sorted by `priority` (lowest number first = highest
106 /// priority).
107 ordered: Vec<Layer>,
108}
109
110/// A single configuration layer in the global configuration model.
111///
112/// Layers are consulted in priority order (`TestOverride → Runtime →
113/// Env → File → Default`) when resolving configuration values. Each
114/// variant holds an [`Attrs`] map of key/value pairs.
115///
116/// The `TestOverride` variant additionally maintains per-key override
117/// stacks to support nested and out-of-order test overrides. These
118/// stacks are currently placeholders for a future refactor; for now,
119/// only the `attrs` field is used.
120///
121/// Variants:
122/// - [`Layer::ClientOverride`] - Values set by the config snapshot sent from the client
123/// during proc bootstrap.
124/// - [`Layer::File`] — Values loaded from configuration files.
125/// - [`Layer::Env`] — Values sourced from process environment
126/// variables.
127/// - [`Layer::Runtime`] — Programmatically set runtime overrides.
128/// - [`Layer::TestOverride`] — Temporary in-test overrides applied
129/// under [`ConfigLock`].
130///
131/// Layers are stored in [`Layers::ordered`], kept sorted by their
132/// effective [`Source`] priority (`TestOverride` first, `Default` last).
133enum Layer {
134 /// Values set by the config snapshot sent from the client
135 /// during proc bootstrap.
136 ClientOverride(Attrs),
137
138 /// Values loaded from configuration files.
139 File(Attrs),
140
141 /// Values read from process environment variables. Typically
142 /// installed at startup via [`init_from_env`].
143 Env(Attrs),
144
145 /// Values set programmatically at runtime. Stable high-priority
146 /// layer used by parent/child bootstrap and dynamic updates.
147 Runtime(Attrs),
148
149 /// Ephemeral values inserted during tests via
150 /// [`ConfigLock::override_key`]. Always takes precedence over all
151 /// other layers. Currently holds both the active `attrs` map and
152 /// a per-key `stacks` table (used to support nested or
153 /// out-of-order test overrides in future refactors).
154 TestOverride {
155 attrs: Attrs,
156 stacks: HashMap<&'static str, OverrideStack>,
157 },
158}
159
160/// A per-key stack of test overrides used by the
161/// [`Layer::TestOverride`] layer.
162///
163/// Each stack tracks the sequence of active overrides applied to a
164/// single configuration key. The topmost frame represents the
165/// currently effective override; earlier frames represent older
166/// (still live) guards that may drop out of order.
167///
168/// Fields:
169/// - `env_var`: The associated process environment variable name, if
170/// any.
171/// - `saved_env`: The original environment variable value before the
172/// first override was applied (or `None` if it did not exist).
173/// - `frames`: The stack of active override frames, with the top
174/// being the last element in the vector.
175///
176/// The full stack mechanism is not yet active; it is introduced
177/// incrementally to prepare for robust out-of-order test override
178/// restoration.
179struct OverrideStack {
180 /// The name of the process environment variable associated with
181 /// this configuration key, if any.
182 ///
183 /// Used to mirror changes to the environment when overrides are
184 /// applied or removed. `None` if the key has no
185 /// `CONFIG.env_name`.
186 env_var: Option<String>,
187
188 /// The original value of the environment variable before the
189 /// first override was applied.
190 ///
191 /// Stored so it can be restored once the last frame is dropped.
192 /// `None` means the variable did not exist prior to overriding.
193 saved_env: Option<String>,
194
195 /// The sequence of active override frames for this key.
196 ///
197 /// Each frame represents one active test override; the last
198 /// element (`frames.last()`) is the current top-of-stack and
199 /// defines the effective value seen in the configuration and
200 /// environment.
201 frames: Vec<OverrideFrame>,
202}
203
204/// A single entry in a per-key override stack.
205///
206/// Each `OverrideFrame` represents one active test override applied
207/// via [`ConfigLock::override_key`]. Frames are uniquely identified
208/// by a monotonically increasing token and record both the value
209/// being overridden and its string form for environment mirroring.
210///
211/// When a guard drops, its frame is removed from the stack; if it was
212/// the top, the next frame (if any) becomes active, or the original
213/// environment is restored when the stack becomes empty.
214struct OverrideFrame {
215 /// A unique, monotonically increasing identifier for this
216 /// override frame.
217 ///
218 /// Used to associate a dropping [`ConfigValueGuard`] with its
219 /// corresponding entry in the stack, even if drops occur out of
220 /// order.
221 token: u64,
222
223 /// The serialized configuration value active while this frame is
224 /// on top of its stack.
225 ///
226 /// Stored as a boxed [`SerializableValue`] to match how values
227 /// are kept within [`Attrs`].
228 value: Box<dyn crate::attrs::SerializableValue>,
229
230 /// Pre-rendered string form of the value, used for environment
231 /// variable updates when this frame becomes active.
232 ///
233 /// Avoids recomputing `value.display()` on every push or pop.
234 env_str: String,
235}
236
237/// Return the [`Source`] corresponding to a given [`Layer`].
238///
239/// This provides a uniform way to retrieve a layer's logical source
240/// (File, Env, Runtime, or TestOverride) regardless of its internal
241/// representation. Used for sorting layers by priority and for
242/// source-based lookups or removals.
243fn layer_source(l: &Layer) -> Source {
244 match l {
245 Layer::File(_) => Source::File,
246 Layer::Env(_) => Source::Env,
247 Layer::Runtime(_) => Source::Runtime,
248 Layer::TestOverride { .. } => Source::TestOverride,
249 Layer::ClientOverride(_) => Source::ClientOverride,
250 }
251}
252
253/// Return an immutable reference to the [`Attrs`] contained in a
254/// [`Layer`].
255///
256/// This abstracts over the specific layer variant so callers can read
257/// configuration values uniformly without needing to pattern-match on
258/// the layer type. For `TestOverride`, this returns the current
259/// top-level attributes reflecting the active overrides.
260fn layer_attrs(l: &Layer) -> &Attrs {
261 match l {
262 Layer::File(a) | Layer::Env(a) | Layer::Runtime(a) | Layer::ClientOverride(a) => a,
263 Layer::TestOverride { attrs, .. } => attrs,
264 }
265}
266
267/// Return a mutable reference to the [`Attrs`] contained in a
268/// [`Layer`].
269///
270/// This allows callers to modify configuration values within any
271/// layer without needing to pattern-match on its variant. For
272/// `TestOverride`, the returned [`Attrs`] always reflect the current
273/// top-of-stack overrides for each key.
274fn layer_attrs_mut(l: &mut Layer) -> &mut Attrs {
275 match l {
276 Layer::File(a) | Layer::Env(a) | Layer::Runtime(a) | Layer::ClientOverride(a) => a,
277 Layer::TestOverride { attrs, .. } => attrs,
278 }
279}
280
281/// Return the index of the [`Layer::TestOverride`] within the
282/// [`Layers`] vector.
283///
284/// If a TestOverride layer is present, its position in the ordered
285/// list is returned; otherwise, `None` is returned. This is used to
286/// locate the active test override layer for inserting or restoring
287/// temporary configuration values.
288fn test_override_index(layers: &Layers) -> Option<usize> {
289 layers
290 .ordered
291 .iter()
292 .position(|l| matches!(l, Layer::TestOverride { .. }))
293}
294
295/// Global layered configuration store.
296///
297/// This is the single authoritative store for configuration in
298/// the process. It is always present, protected by an `RwLock`,
299/// and holds a [`Layers`] struct containing all active sources.
300///
301/// On startup it is seeded with a single [`Source::Env`] layer
302/// (values loaded from process environment variables). Additional
303/// layers can be installed later via [`set`] or cleared with
304/// [`clear`]. Reads (`get`, `get_cloned`, `attrs`) consult the
305/// layers in priority order.
306///
307/// In tests, a [`Source::TestOverride`] layer is pushed on demand
308/// by [`ConfigLock::override_key`]. This layer always takes
309/// precedence and is automatically removed when the guard drops.
310///
311/// In normal operation, a parent process may capture its config
312/// with [`attrs`] and pass it to a child during bootstrap. The
313/// child installs this snapshot as its [`Source::Runtime`] layer,
314/// ensuring the parent's values override Env/File/Defaults.
315static LAYERS: LazyLock<Arc<RwLock<Layers>>> = LazyLock::new(|| {
316 let env = super::from_env();
317 let layers = Layers {
318 ordered: vec![Layer::Env(env)],
319 };
320 Arc::new(RwLock::new(layers))
321});
322
323/// Monotonically increasing sequence used to assign unique tokens to
324/// each test override frame.
325///
326/// Tokens identify individual [`ConfigValueGuard`] instances within a
327/// key's override stack, allowing frames to be removed safely even
328/// when guards are dropped out of order. The counter starts at 1 and
329/// uses relaxed atomic ordering since exact sequencing across threads
330/// is not required—only uniqueness.
331static OVERRIDE_TOKEN_SEQ: AtomicU64 = AtomicU64::new(1);
332
333/// Acquire the global configuration lock.
334///
335/// This lock serializes all mutations of the global
336/// configuration, ensuring they cannot clobber each other. It
337/// returns a [`ConfigLock`] guard, which must be held for the
338/// duration of any mutation (e.g. inserting or overriding
339/// values).
340///
341/// Most commonly used in tests, where it provides exclusive
342/// access to push a [`Source::TestOverride`] layer via
343/// [`ConfigLock::override_key`]. The override layer is
344/// automatically removed when the guard drops, restoring the
345/// original state.
346///
347/// # Example
348/// ```rust,ignore
349/// let lock = hyperactor::config::global::lock();
350/// let _guard = lock.override_key(CONFIG_KEY, "test_value");
351/// // Code under test sees the overridden config.
352/// // On drop, the key is restored.
353/// ```
354pub fn lock() -> ConfigLock {
355 static MUTEX: LazyLock<std::sync::Mutex<()>> = LazyLock::new(|| std::sync::Mutex::new(()));
356 ConfigLock {
357 _guard: MUTEX.lock().unwrap(),
358 }
359}
360
361/// Initialize the global configuration from environment variables.
362///
363/// Reads values from process environment variables, using each key's
364/// `CONFIG.env_name` (from `@meta(CONFIG = ConfigAttr { … })`) to
365/// determine its mapping. The resulting values are installed as the
366/// [`Source::Env`] layer. Keys without a corresponding environment
367/// variable fall back to defaults or higher-priority sources.
368///
369/// Typically invoked once at process startup to overlay config values
370/// from the environment. Repeated calls replace the existing Env
371/// layer.
372pub fn init_from_env() {
373 set(Source::Env, super::from_env());
374}
375
376/// Initialize the global configuration from a YAML file.
377///
378/// Loads values from the specified YAML file and installs them as
379/// the [`Source::File`] layer. This is the lowest-priority
380/// explicit source: values from Env, Runtime, or TestOverride
381/// layers always take precedence. Keys not present in the file
382/// fall back to their defaults or higher-priority sources.
383///
384/// Typically invoked once at process startup to provide a
385/// baseline configuration. Repeated calls replace the existing
386/// File layer.
387pub fn init_from_yaml<P: AsRef<Path>>(path: P) -> Result<(), anyhow::Error> {
388 let file = super::from_yaml(path)?;
389 set(Source::File, file);
390 Ok(())
391}
392
393/// Get a key from the global configuration (Copy types).
394///
395/// Resolution order: TestOverride -> Runtime -> Env -> File ->
396/// Default. Panics if the key has no default and is not set in
397/// any layer.
398pub fn get<T: AttrValue + Copy>(key: Key<T>) -> T {
399 let layers = LAYERS.read().unwrap();
400 for layer in &layers.ordered {
401 let a = layer_attrs(layer);
402 if let Some(value) = a.get_value_by_name(key.name()) {
403 let t = value.as_any().downcast_ref::<T>().unwrap_or_else(|| {
404 panic!(
405 "cannot cast to type {} for key {}",
406 std::any::type_name::<T>(),
407 key.name()
408 )
409 });
410 return *t;
411 }
412 }
413 *key.default().expect("key must have a default")
414}
415
416/// Return the override value for `key` if it is explicitly present in
417/// `overrides`, otherwise fall back to the global value for that key.
418pub fn override_or_global<T: AttrValue + Copy>(overrides: &Attrs, key: Key<T>) -> T {
419 if overrides.contains_key(key) {
420 *overrides.get(key).unwrap()
421 } else {
422 get(key)
423 }
424}
425
426/// Get a key by cloning the value.
427///
428/// Resolution order: TestOverride -> Runtime -> Env -> File ->
429/// Default. Panics if the key has no default and is not set in
430/// any layer.
431pub fn get_cloned<T: AttrValue>(key: Key<T>) -> T {
432 try_get_cloned(key)
433 .expect("key must have a default")
434 .clone()
435}
436
437/// Try to get a key by cloning the value.
438///
439/// Resolution order: TestOverride -> Runtime -> Env -> File ->
440/// Default. Returns None if the key has no default and is not set in
441/// any layer.
442pub fn try_get_cloned<T: AttrValue>(key: Key<T>) -> Option<T> {
443 let layers = LAYERS.read().unwrap();
444 for layer in &layers.ordered {
445 let a = layer_attrs(layer);
446 if a.contains_key(key) {
447 return a.get(key).cloned();
448 }
449 }
450 key.default().cloned()
451}
452
453/// Construct a [`Layer`] for the given [`Source`] using the provided
454/// `attrs`.
455///
456/// Used by [`set`] and [`create_or_merge`] when installing a new
457/// configuration layer.
458fn make_layer(source: Source, attrs: Attrs) -> Layer {
459 match source {
460 Source::File => Layer::File(attrs),
461 Source::Env => Layer::Env(attrs),
462 Source::Runtime => Layer::Runtime(attrs),
463 Source::TestOverride => Layer::TestOverride {
464 attrs,
465 stacks: HashMap::new(),
466 },
467 Source::ClientOverride => Layer::ClientOverride(attrs),
468 }
469}
470
471/// Insert or replace a configuration layer for the given source.
472///
473/// If a layer with the same [`Source`] already exists, its
474/// contents are replaced with the provided `attrs`. Otherwise a
475/// new layer is added. After insertion, layers are re-sorted so
476/// that higher-priority sources (e.g. [`Source::TestOverride`],
477/// [`Source::Runtime`]) appear before lower-priority ones
478/// ([`Source::Env`], [`Source::File`]).
479///
480/// This function is used by initialization routines (e.g.
481/// `init_from_env`, `init_from_yaml`) and by tests when
482/// overriding configuration values.
483pub fn set(source: Source, attrs: Attrs) {
484 let mut g = LAYERS.write().unwrap();
485 if let Some(l) = g.ordered.iter_mut().find(|l| layer_source(l) == source) {
486 *layer_attrs_mut(l) = attrs;
487 } else {
488 g.ordered.push(make_layer(source, attrs));
489 }
490 g.ordered.sort_by_key(|l| priority(layer_source(l))); // TestOverride < Runtime < Env < File < ClientOverride
491}
492
493/// Insert or update a configuration layer for the given [`Source`].
494///
495/// If a layer with the same [`Source`] already exists, its attributes
496/// are **updated in place**: all keys present in `attrs` are absorbed
497/// into the existing layer, overwriting any previous values for those
498/// keys while leaving all other keys in that layer unchanged.
499///
500/// If no layer for `source` exists yet, this behaves like [`set`]: a
501/// new layer is created with the provided `attrs`.
502///
503/// This is useful for incremental / additive updates (for example,
504/// runtime configuration driven by a Python API), where callers want
505/// to change a subset of keys without discarding previously installed
506/// values in the same layer.
507///
508/// By contrast, [`set`] replaces the entire layer for `source` with
509/// `attrs`, discarding any existing values in that layer.
510pub fn create_or_merge(source: Source, attrs: Attrs) {
511 let mut g = LAYERS.write().unwrap();
512 if let Some(layer) = g.ordered.iter_mut().find(|l| layer_source(l) == source) {
513 layer_attrs_mut(layer).merge(attrs);
514 } else {
515 g.ordered.push(make_layer(source, attrs));
516 }
517 g.ordered.sort_by_key(|l| priority(layer_source(l))); // TestOverride < Runtime < Env < File < ClientOverride
518}
519
520/// Remove the configuration layer for the given [`Source`], if
521/// present.
522///
523/// After this call, values from that source will no longer
524/// contribute to resolution in [`get`], [`get_cloned`], or
525/// [`attrs`]. Defaults and any remaining layers continue to apply
526/// in their normal priority order.
527#[allow(dead_code)]
528pub(crate) fn clear(source: Source) {
529 let mut g = LAYERS.write().unwrap();
530 g.ordered.retain(|l| layer_source(l) != source);
531}
532
533/// Return a complete, merged snapshot of the effective configuration
534/// **(only keys marked with `@meta(CONFIG = ...)`)**.
535///
536/// Resolution per key:
537/// 1) First explicit value found in layers (TestOverride →
538/// Runtime → Env → File).
539/// 2) Otherwise, the key's default (if any).
540///
541/// Notes:
542/// - This materializes defaults into the returned Attrs for all
543/// CONFIG-marked keys, so it's self-contained.
544/// - Keys without `CONFIG` meta are excluded.
545pub fn attrs() -> Attrs {
546 let layers = LAYERS.read().unwrap();
547 let mut merged = Attrs::new();
548
549 // Iterate all declared keys (registered via `declare_attrs!`
550 // + inventory).
551 for info in inventory::iter::<AttrKeyInfo>() {
552 // Skip keys not marked as `CONFIG`.
553 if info.meta.get(CONFIG).is_none() {
554 continue;
555 }
556
557 let name = info.name;
558
559 // Try to resolve from highest -> lowest priority layer.
560 let mut chosen: Option<Box<dyn crate::attrs::SerializableValue>> = None;
561 for layer in &layers.ordered {
562 if let Some(v) = layer_attrs(layer).get_value_by_name(name) {
563 chosen = Some(v.cloned());
564 break;
565 }
566 }
567
568 // If no explicit value, materialize the default if there
569 // is one.
570 let boxed = match chosen {
571 Some(b) => b,
572 None => {
573 if let Some(default) = info.default {
574 default.cloned()
575 } else {
576 // No explicit value and no default — skip
577 // this key.
578 continue;
579 }
580 }
581 };
582
583 merged.insert_value_by_name_unchecked(name, boxed);
584 }
585
586 merged
587}
588
589/// Reset the global configuration to only Defaults (for testing).
590///
591/// This clears all explicit layers (`File`, `Env`, `Runtime`, and
592/// `TestOverride`). Subsequent lookups will resolve keys entirely
593/// from their declared defaults.
594///
595/// Note: Should be called while holding [`global::lock`] in
596/// tests, to ensure no concurrent modifications happen.
597pub fn reset_to_defaults() {
598 let mut g = LAYERS.write().unwrap();
599 g.ordered.clear();
600}
601
602/// A guard that holds the global configuration lock and provides
603/// override functionality.
604///
605/// This struct acts as both a lock guard (preventing other tests from
606/// modifying global config) and as the only way to create
607/// configuration overrides. Override guards cannot outlive this
608/// ConfigLock, ensuring proper synchronization.
609pub struct ConfigLock {
610 _guard: std::sync::MutexGuard<'static, ()>,
611}
612
613impl ConfigLock {
614 /// Create a configuration override that is active until the
615 /// returned guard is dropped.
616 ///
617 /// Each call pushes a new frame onto a per-key override stack
618 /// within the [`Source::TestOverride`] layer. The topmost frame
619 /// defines the effective value seen by `get()` and in the
620 /// mirrored environment variable (if any). When a guard is
621 /// dropped, its frame is removed: if it was the top, the previous
622 /// frame (if any) becomes active or the key and env var are
623 /// restored to their prior state.
624 ///
625 /// The returned guard must not outlive this [`ConfigLock`].
626 pub fn override_key<'a, T: AttrValue>(
627 &'a self,
628 key: crate::attrs::Key<T>,
629 value: T,
630 ) -> ConfigValueGuard<'a, T> {
631 let token = OVERRIDE_TOKEN_SEQ.fetch_add(1, Ordering::Relaxed);
632
633 let mut g = LAYERS.write().unwrap();
634
635 // Ensure TestOverride layer exists.
636 let idx = if let Some(i) = test_override_index(&g) {
637 i
638 } else {
639 g.ordered.push(Layer::TestOverride {
640 attrs: Attrs::new(),
641 stacks: HashMap::new(),
642 });
643 g.ordered.sort_by_key(|l| priority(layer_source(l)));
644 test_override_index(&g).expect("just inserted TestOverride layer")
645 };
646
647 // Mutably access TestOverride's attrs + stacks.
648 let (attrs, stacks) = match &mut g.ordered[idx] {
649 Layer::TestOverride { attrs, stacks } => (attrs, stacks),
650 _ => unreachable!(),
651 };
652
653 // Compute env var (if any) for this key once.
654 let (env_var, env_str) = if let Some(cfg) = key.attrs().get(crate::config::CONFIG) {
655 if let Some(name) = &cfg.env_name {
656 (Some(name.clone()), value.display())
657 } else {
658 (None, String::new())
659 }
660 } else {
661 (None, String::new())
662 };
663
664 // Get per-key stack (by declared name).
665 let key_name = key.name();
666 let stack = stacks.entry(key_name).or_insert_with(|| OverrideStack {
667 env_var: env_var.clone(),
668 saved_env: env_var.as_ref().and_then(|n| std::env::var(n).ok()),
669 frames: Vec::new(),
670 });
671
672 // Push the new frame.
673 let boxed: Box<dyn crate::attrs::SerializableValue> = Box::new(value.clone());
674 stack.frames.push(OverrideFrame {
675 token,
676 value: boxed,
677 env_str,
678 });
679
680 // Make this frame the active value in TestOverride attrs.
681 attrs.set(key, value.clone());
682
683 // Update process env to reflect new top-of-stack.
684 if let (Some(var), Some(top)) = (stack.env_var.as_ref(), stack.frames.last()) {
685 // SAFETY: Under global ConfigLock during tests.
686 unsafe { std::env::set_var(var, &top.env_str) }
687 }
688
689 ConfigValueGuard {
690 key,
691 token,
692 _phantom: PhantomData,
693 }
694 }
695}
696
697/// When a [`ConfigLock`] is dropped, the special
698/// [`Source::TestOverride`] layer (if present) is removed
699/// entirely. This discards all temporary overrides created under
700/// the lock, ensuring they cannot leak into subsequent tests or
701/// callers. Other layers (`Runtime`, `Env`, `File`, and defaults)
702/// are left untouched.
703///
704/// Note: individual values within the TestOverride layer may
705/// already have been restored by [`ConfigValueGuard`]s as they
706/// drop. This final removal guarantees no residual layer remains
707/// once the lock itself is released.
708impl Drop for ConfigLock {
709 fn drop(&mut self) {
710 let mut guard = LAYERS.write().unwrap();
711 if let Some(pos) = test_override_index(&guard) {
712 guard.ordered.remove(pos);
713 }
714 }
715}
716
717/// A guard that restores a single configuration value when dropped
718pub struct ConfigValueGuard<'a, T: 'static> {
719 key: crate::attrs::Key<T>,
720 token: u64,
721 // This is here so we can hold onto a 'a lifetime.
722 _phantom: PhantomData<&'a ()>,
723}
724
725/// When a [`ConfigValueGuard`] is dropped, it restores configuration
726/// state for the key it was guarding.
727///
728/// Behavior:
729/// - Each key maintains a stack of override frames. The most recent
730/// frame (top of stack) defines the effective value in
731/// [`Source::TestOverride`].
732/// - Dropping a guard removes its frame. If it was the top frame, the
733/// next frame (if any) becomes active and both the config and
734/// mirrored env var are updated accordingly.
735/// - If the dropped frame was not on top, no changes occur until the
736/// active frame is dropped.
737/// - When the last frame for a key is removed, the key is deleted
738/// from the TestOverride layer and its associated environment
739/// variable (if any) is restored to its original value or removed
740/// if it did not exist.
741///
742/// This guarantees that nested or out-of-order test overrides are
743/// restored deterministically and without leaking state into
744/// subsequent tests.
745impl<T: 'static> Drop for ConfigValueGuard<'_, T> {
746 fn drop(&mut self) {
747 let mut g = LAYERS.write().unwrap();
748 let i = if let Some(i) = test_override_index(&g) {
749 i
750 } else {
751 return;
752 };
753
754 // Access TestOverride internals
755 let (attrs, stacks) = match &mut g.ordered[i] {
756 Layer::TestOverride { attrs, stacks } => (attrs, stacks),
757 _ => unreachable!("TestOverride index points to non-TestOverride layer"),
758 };
759
760 let key_name = self.key.name();
761
762 // We need a tiny scope for the &mut borrow of the stack so we
763 // can call `stacks.remove(key_name)` afterward if it becomes
764 // empty.
765 let mut remove_empty_stack = false;
766 let mut restore_env_var: Option<String> = None;
767 let mut restore_env_to: Option<String> = None;
768
769 if let Some(stack) = stacks.get_mut(key_name) {
770 // Find this guard's frame by token.
771 if let Some(pos) = stack.frames.iter().position(|f| f.token == self.token) {
772 let is_top = pos + 1 == stack.frames.len();
773
774 if is_top {
775 // Pop the active frame
776 stack.frames.pop();
777
778 if let Some(new_top) = stack.frames.last() {
779 // New top becomes active: update attrs and env.
780 attrs.insert_value(self.key, (*new_top.value).cloned());
781 if let Some(var) = stack.env_var.as_ref() {
782 // SAFETY: Under global ConfigLock during tests.
783 unsafe { std::env::set_var(var, &new_top.env_str) }
784 }
785 } else {
786 // Stack empty: remove the key now, then after
787 // releasing the &mut borrow of the stack,
788 // restore the env var and remove the stack
789 // entry.
790 let _ = attrs.remove_value(self.key);
791
792 // Capture restoration details while we still
793 // have access to the stack.
794 if let Some(var) = stack.env_var.as_ref() {
795 restore_env_var = Some(var.clone());
796 restore_env_to = stack.saved_env.clone(); // None => unset
797 }
798 remove_empty_stack = true
799 }
800 } else {
801 // Out-of-order drop: remove only that frame:
802 // active top stays
803 stack.frames.remove(pos);
804 // No changes to attrs or env here.
805 }
806 } // else: token already handled; nothing to do
807 } // &must stack borrow ends here
808
809 // If we emptied the stack for this key, restore env and drop
810 // the stack entry.
811 if remove_empty_stack {
812 if let Some(var) = restore_env_var.as_ref() {
813 // SAFETY: Under global ConfigLock during tests.
814 unsafe {
815 if let Some(val) = restore_env_to.as_ref() {
816 std::env::set_var(var, val);
817 } else {
818 std::env::remove_var(var);
819 }
820 }
821 }
822 // Now it's safe to remove the stack from the map.
823 let _ = stacks.remove(key_name);
824 }
825 }
826}
827
828#[cfg(test)]
829mod tests {
830
831 use super::*;
832
833 #[test]
834 fn test_global_config() {
835 let config = lock();
836
837 // Reset global config to defaults to avoid interference from other tests
838 reset_to_defaults();
839
840 assert_eq!(get(CODEC_MAX_FRAME_LENGTH), CODEC_MAX_FRAME_LENGTH_DEFAULT);
841 {
842 let _guard = config.override_key(CODEC_MAX_FRAME_LENGTH, 1024);
843 assert_eq!(get(CODEC_MAX_FRAME_LENGTH), 1024);
844 // The configuration will be automatically restored when _guard goes out of scope
845 }
846
847 assert_eq!(get(CODEC_MAX_FRAME_LENGTH), CODEC_MAX_FRAME_LENGTH_DEFAULT);
848 }
849
850 #[test]
851 fn test_overrides() {
852 let config = lock();
853
854 // Reset global config to defaults to avoid interference from other tests
855 reset_to_defaults();
856
857 // Test the new lock/override API for individual config values
858 assert_eq!(get(CODEC_MAX_FRAME_LENGTH), CODEC_MAX_FRAME_LENGTH_DEFAULT);
859 assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(30));
860
861 // Test single value override
862 {
863 let _guard = config.override_key(CODEC_MAX_FRAME_LENGTH, 2048);
864 assert_eq!(get(CODEC_MAX_FRAME_LENGTH), 2048);
865 assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(30)); // Unchanged
866 }
867
868 // Values should be restored after guard is dropped
869 assert_eq!(get(CODEC_MAX_FRAME_LENGTH), CODEC_MAX_FRAME_LENGTH_DEFAULT);
870
871 // Test multiple overrides
872 let orig_value = std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").ok();
873 {
874 let _guard1 = config.override_key(CODEC_MAX_FRAME_LENGTH, 4096);
875 let _guard2 = config.override_key(MESSAGE_DELIVERY_TIMEOUT, Duration::from_secs(60));
876
877 assert_eq!(get(CODEC_MAX_FRAME_LENGTH), 4096);
878 assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(60));
879 // This was overridden:
880 assert_eq!(
881 std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").unwrap(),
882 "1m"
883 );
884 }
885 assert_eq!(
886 std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").ok(),
887 orig_value
888 );
889
890 // All values should be restored
891 assert_eq!(get(CODEC_MAX_FRAME_LENGTH), CODEC_MAX_FRAME_LENGTH_DEFAULT);
892 assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(30));
893 }
894
895 #[test]
896 fn test_layer_precedence_env_over_file_and_replacement() {
897 let _lock = lock();
898 reset_to_defaults();
899
900 // File sets a value.
901 let mut file = Attrs::new();
902 file[CODEC_MAX_FRAME_LENGTH] = 1111;
903 set(Source::File, file);
904
905 // Env sets a different value.
906 let mut env = Attrs::new();
907 env[CODEC_MAX_FRAME_LENGTH] = 2222;
908 set(Source::Env, env);
909
910 // Env should win over File.
911 assert_eq!(get(CODEC_MAX_FRAME_LENGTH), 2222);
912
913 // Replace Env layer with a new value.
914 let mut env2 = Attrs::new();
915 env2[CODEC_MAX_FRAME_LENGTH] = 3333;
916 set(Source::Env, env2);
917
918 assert_eq!(get(CODEC_MAX_FRAME_LENGTH), 3333);
919 }
920
921 #[test]
922 fn test_layer_precedence_read_file_if_not_found_in_env() {
923 let _lock = lock();
924 reset_to_defaults();
925
926 // Read the default value because no layers have been set.
927 assert_eq!(get(CODEC_MAX_FRAME_LENGTH), 10737418240);
928
929 // File sets a value.
930 let mut file = Attrs::new();
931 file[CODEC_MAX_FRAME_LENGTH] = 1111;
932 set(Source::File, file);
933
934 // Env does not have any attribute.
935 let env = Attrs::new();
936 set(Source::Env, env);
937
938 // Should read from File.
939 assert_eq!(get(CODEC_MAX_FRAME_LENGTH), 1111);
940
941 // Replace Env layer with a new value.
942 let mut env2 = Attrs::new();
943 env2[CODEC_MAX_FRAME_LENGTH] = 2222;
944 set(Source::Env, env2);
945
946 // Env should win over File.
947 assert_eq!(get(CODEC_MAX_FRAME_LENGTH), 2222);
948 }
949
950 #[test]
951 fn test_runtime_overrides_and_clear_restores_lower_layers() {
952 let _lock = lock();
953 reset_to_defaults();
954
955 // File baseline.
956 let mut file = Attrs::new();
957 file[MESSAGE_DELIVERY_TIMEOUT] = Duration::from_secs(30);
958 set(Source::File, file);
959
960 // Env override.
961 let mut env = Attrs::new();
962 env[MESSAGE_DELIVERY_TIMEOUT] = Duration::from_secs(40);
963 set(Source::Env, env);
964
965 // Runtime beats both.
966 let mut rt = Attrs::new();
967 rt[MESSAGE_DELIVERY_TIMEOUT] = Duration::from_secs(50);
968 set(Source::Runtime, rt);
969
970 assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(50));
971
972 // Clearing Runtime should reveal Env again.
973 clear(Source::Runtime);
974
975 // With the Runtime layer gone, Env still wins over File.
976 assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(40));
977 }
978
979 #[test]
980 fn test_attrs_snapshot_materializes_defaults_and_omits_meta() {
981 let _lock = lock();
982 reset_to_defaults();
983
984 // No explicit layers: values should come from Defaults.
985 let snap = attrs();
986
987 // A few representative defaults are materialized:
988 assert_eq!(snap[CODEC_MAX_FRAME_LENGTH], 10 * 1024 * 1024 * 1024);
989 assert_eq!(snap[MESSAGE_DELIVERY_TIMEOUT], Duration::from_secs(30));
990
991 // CONFIG has no default and wasn't explicitly set: should be
992 // omitted.
993 let json = serde_json::to_string(&snap).unwrap();
994 assert!(
995 !json.contains("hyperactor::config::config"),
996 "CONFIG must not appear in snapshot unless explicitly set"
997 );
998 }
999
1000 #[test]
1001 fn test_parent_child_snapshot_as_runtime_layer() {
1002 let _lock = lock();
1003 reset_to_defaults();
1004
1005 // Parent effective config (pretend it's a parent process).
1006 let mut parent_env = Attrs::new();
1007 parent_env[MESSAGE_ACK_EVERY_N_MESSAGES] = 12345;
1008 set(Source::Env, parent_env);
1009
1010 let parent_snap = attrs();
1011
1012 // "Child" process: start clean, install parent snapshot as
1013 // Runtime.
1014 reset_to_defaults();
1015 set(Source::Runtime, parent_snap);
1016
1017 // Child should observe parent's effective value (as highest
1018 // stable layer).
1019 assert_eq!(get(MESSAGE_ACK_EVERY_N_MESSAGES), 12345);
1020 }
1021
1022 #[test]
1023 fn test_testoverride_layer_override_and_env_restore() {
1024 let lock = lock();
1025 reset_to_defaults();
1026
1027 assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(30));
1028
1029 // SAFETY: single-threaded test.
1030 unsafe {
1031 std::env::remove_var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT");
1032 }
1033
1034 {
1035 let _guard = lock.override_key(MESSAGE_DELIVERY_TIMEOUT, Duration::from_secs(99));
1036 // Override wins:
1037 assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(99));
1038
1039 // Env should be mirrored to the same duration (string may
1040 // be "1m 39s")
1041 let s = std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").unwrap();
1042 let parsed = humantime::parse_duration(&s).unwrap();
1043 assert_eq!(parsed, Duration::from_secs(99));
1044 }
1045
1046 // After drop, value and env restored:
1047 assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(30));
1048 assert!(std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").is_err());
1049 }
1050
1051 #[test]
1052 fn test_reset_to_defaults_clears_all_layers() {
1053 let _lock = lock();
1054 reset_to_defaults();
1055
1056 // Seed multiple layers.
1057 let mut file = Attrs::new();
1058 file[SPLIT_MAX_BUFFER_SIZE] = 7;
1059 set(Source::File, file);
1060
1061 let mut env = Attrs::new();
1062 env[SPLIT_MAX_BUFFER_SIZE] = 8;
1063 set(Source::Env, env);
1064
1065 let mut rt = Attrs::new();
1066 rt[SPLIT_MAX_BUFFER_SIZE] = 9;
1067 set(Source::Runtime, rt);
1068
1069 // Sanity: highest wins.
1070 assert_eq!(get(SPLIT_MAX_BUFFER_SIZE), 9);
1071
1072 // Reset clears all explicit layers; defaults apply.
1073 reset_to_defaults();
1074 assert_eq!(get(SPLIT_MAX_BUFFER_SIZE), 5); // default
1075 }
1076
1077 #[test]
1078 fn test_get_cloned_resolution_matches_get() {
1079 let _lock = lock();
1080 reset_to_defaults();
1081
1082 let mut env = Attrs::new();
1083 env[CHANNEL_MULTIPART] = false;
1084 set(Source::Env, env);
1085
1086 assert!(!get(CHANNEL_MULTIPART));
1087 let v = get_cloned(CHANNEL_MULTIPART);
1088 assert!(!v);
1089 }
1090
1091 #[test]
1092 fn test_attrs_snapshot_respects_layer_precedence_per_key() {
1093 let _lock = lock();
1094 reset_to_defaults();
1095
1096 let mut file = Attrs::new();
1097 file[MESSAGE_TTL_DEFAULT] = 10;
1098 set(Source::File, file);
1099
1100 let mut env = Attrs::new();
1101 env[MESSAGE_TTL_DEFAULT] = 20;
1102 set(Source::Env, env);
1103
1104 let snap = attrs();
1105 assert_eq!(snap[MESSAGE_TTL_DEFAULT], 20); // Env beats File
1106 }
1107
1108 declare_attrs! {
1109 @meta(CONFIG = ConfigAttr {
1110 env_name: None,
1111 py_name: None,
1112 })
1113 pub attr CONFIG_KEY: bool = true;
1114
1115 pub attr NON_CONFIG_KEY: bool = true;
1116 }
1117
1118 #[test]
1119 fn test_attrs_excludes_non_config_keys() {
1120 let _lock = lock();
1121 reset_to_defaults();
1122
1123 let snap = attrs();
1124 let json = serde_json::to_string(&snap).unwrap();
1125
1126 // Expect our CONFIG_KEY to be present.
1127 assert!(
1128 json.contains("hyperactor::config::global::tests::config_key"),
1129 "attrs() should include keys with @meta(CONFIG = ...)"
1130 );
1131 // Expect our NON_CONFIG_KEY to be omitted.
1132 assert!(
1133 !json.contains("hyperactor::config::global::tests::non_config_key"),
1134 "attrs() should exclude keys without @meta(CONFIG = ...)"
1135 );
1136 }
1137
1138 #[test]
1139 fn test_testoverride_multiple_stacked_overrides_lifo() {
1140 let lock = lock();
1141 reset_to_defaults();
1142
1143 // Baseline sanity.
1144 assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(30));
1145
1146 // Start from a clean env so we can assert restoration to "unset".
1147 // SAFETY: single-threaded tests.
1148 unsafe {
1149 std::env::remove_var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT");
1150 }
1151 assert!(std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").is_err());
1152
1153 // Stack A: 40s (becomes top)
1154 let guard_a = lock.override_key(MESSAGE_DELIVERY_TIMEOUT, Duration::from_secs(40));
1155 assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(40));
1156 {
1157 let s = std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").unwrap();
1158 assert_eq!(
1159 humantime::parse_duration(&s).unwrap(),
1160 Duration::from_secs(40)
1161 );
1162 }
1163
1164 // Stack B: 50s (new top)
1165 let guard_b = lock.override_key(MESSAGE_DELIVERY_TIMEOUT, Duration::from_secs(50));
1166 assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(50));
1167 {
1168 let s = std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").unwrap();
1169 assert_eq!(
1170 humantime::parse_duration(&s).unwrap(),
1171 Duration::from_secs(50)
1172 );
1173 }
1174
1175 // Drop B first → should reveal A (LIFO)
1176 std::mem::drop(guard_b);
1177 assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(40));
1178 {
1179 let s = std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").unwrap();
1180 assert_eq!(
1181 humantime::parse_duration(&s).unwrap(),
1182 Duration::from_secs(40)
1183 );
1184 }
1185
1186 // Drop A → should restore default and unset env.
1187 std::mem::drop(guard_a);
1188 assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(30));
1189 assert!(std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").is_err());
1190 }
1191
1192 #[test]
1193 fn test_testoverride_out_of_order_drop_keeps_top_stable() {
1194 let lock = lock();
1195 reset_to_defaults();
1196
1197 // Clean env baseline.
1198 // SAFETY: single-threaded tests.
1199 unsafe {
1200 std::env::remove_var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT");
1201 }
1202 assert!(std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").is_err());
1203
1204 // Push three frames in order: A=40s, B=50s, C=70s (C is top).
1205 let guard_a = lock.override_key(MESSAGE_DELIVERY_TIMEOUT, Duration::from_secs(40));
1206 let guard_b = lock.override_key(MESSAGE_DELIVERY_TIMEOUT, Duration::from_secs(50));
1207 let guard_c = lock.override_key(MESSAGE_DELIVERY_TIMEOUT, Duration::from_secs(70));
1208
1209 // Top is C.
1210 assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(70));
1211 {
1212 let s = std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").unwrap();
1213 assert_eq!(
1214 humantime::parse_duration(&s).unwrap(),
1215 Duration::from_secs(70)
1216 );
1217 }
1218
1219 // Drop the *middle* frame (B) first → top must remain C, env unchanged.
1220 std::mem::drop(guard_b);
1221 assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(70));
1222 {
1223 let s = std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").unwrap();
1224 assert_eq!(
1225 humantime::parse_duration(&s).unwrap(),
1226 Duration::from_secs(70)
1227 );
1228 }
1229
1230 // Now drop C → A becomes top, env follows A.
1231 std::mem::drop(guard_c);
1232 assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(40));
1233 {
1234 let s = std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").unwrap();
1235 assert_eq!(
1236 humantime::parse_duration(&s).unwrap(),
1237 Duration::from_secs(40)
1238 );
1239 }
1240
1241 // Drop A → restore default and clear env.
1242 std::mem::drop(guard_a);
1243 assert_eq!(get(MESSAGE_DELIVERY_TIMEOUT), Duration::from_secs(30));
1244 assert!(std::env::var("HYPERACTOR_MESSAGE_DELIVERY_TIMEOUT").is_err());
1245 }
1246
1247 #[test]
1248 fn test_priority_order() {
1249 use Source::*;
1250 assert!(priority(TestOverride) < priority(Runtime));
1251 assert!(priority(Runtime) < priority(Env));
1252 assert!(priority(Env) < priority(File));
1253 assert!(priority(File) < priority(ClientOverride));
1254 }
1255
1256 #[test]
1257 fn test_create_or_merge_runtime_merges_keys() {
1258 let _lock = lock();
1259 reset_to_defaults();
1260
1261 // Seed Runtime with one key.
1262 let mut rt = Attrs::new();
1263 rt[MESSAGE_TTL_DEFAULT] = 10;
1264 set(Source::Runtime, rt);
1265
1266 // Now update Runtime with a different key via
1267 // `create_or_merge`.
1268 let mut update = Attrs::new();
1269 update[MESSAGE_ACK_EVERY_N_MESSAGES] = 123;
1270 create_or_merge(Source::Runtime, update);
1271
1272 // Both keys should now be visible from Runtime.
1273 assert_eq!(get(MESSAGE_TTL_DEFAULT), 10);
1274 assert_eq!(get(MESSAGE_ACK_EVERY_N_MESSAGES), 123);
1275 }
1276
1277 #[test]
1278 fn test_create_or_merge_runtime_creates_layer_if_missing() {
1279 let _lock = lock();
1280 reset_to_defaults();
1281
1282 let mut rt = Attrs::new();
1283 rt[MESSAGE_TTL_DEFAULT] = 42;
1284 create_or_merge(Source::Runtime, rt);
1285
1286 assert_eq!(get(MESSAGE_TTL_DEFAULT), 42);
1287 }
1288}