Skip to main content

ash_flare/
types.rs

1//! Common types used throughout the supervision tree
2
3use crate::restart::RestartPolicy;
4use serde::{Deserialize, Serialize};
5
6use dashmap::DashMap;
7use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
8use std::sync::Arc;
9
10/// Result of a worker's execution
11#[derive(
12    Debug,
13    Clone,
14    Copy,
15    PartialEq,
16    Eq,
17    Serialize,
18    Deserialize,
19    Archive,
20    RkyvSerialize,
21    RkyvDeserialize,
22)]
23#[rkyv(derive(Debug))]
24pub enum ChildExitReason {
25    /// Normal termination
26    Normal,
27    /// Abnormal termination (error or panic)
28    Abnormal,
29    /// Shutdown requested
30    Shutdown,
31}
32
33/// Child identifier type
34pub type ChildId = String;
35
36/// Information about a child process
37#[derive(Debug, Clone)]
38pub struct ChildInfo {
39    /// Unique identifier for the child
40    pub id: ChildId,
41    /// Type of child (Worker or Supervisor)
42    pub child_type: ChildType,
43    /// Restart policy for the child (None for supervisors)
44    pub restart_policy: Option<RestartPolicy>,
45}
46
47/// Type of child in supervision tree
48#[derive(
49    Debug,
50    Clone,
51    Copy,
52    PartialEq,
53    Eq,
54    Serialize,
55    Deserialize,
56    Archive,
57    RkyvSerialize,
58    RkyvDeserialize,
59)]
60#[rkyv(derive(Debug))]
61pub enum ChildType {
62    /// A worker process
63    Worker,
64    /// A nested supervisor
65    Supervisor,
66}
67
68/// Shared context for stateful workers with in-memory key-value store.
69///
70/// Provides a process-local, concurrency-safe storage for workers to share state.
71/// The store is backed by `DashMap` for lock-free concurrent access.
72///
73/// # Performance
74/// - Uses `DashMap` for lock-free concurrent operations
75/// - Cloning is cheap (only clones the `Arc`, not the data)
76#[derive(Clone, Debug)]
77pub struct WorkerContext {
78    store: Arc<DashMap<String, serde_json::Value>>,
79}
80
81impl WorkerContext {
82    /// Creates a new empty WorkerContext.
83    pub fn new() -> Self {
84        Self {
85            store: Arc::new(DashMap::new()),
86        }
87    }
88
89    /// Gets a value from the store by key.
90    ///
91    /// Returns `None` if the key doesn't exist.
92    ///
93    /// Note: This method clones the value. For read-heavy workloads,
94    /// consider caching values or using primitives that are cheap to clone.
95    #[inline]
96    pub fn get(&self, key: &str) -> Option<serde_json::Value> {
97        self.store.get(key).map(|entry| entry.value().clone())
98    }
99
100    /// Gets a value and applies a function to it without cloning.
101    ///
102    /// This is more efficient than `get()` when you only need to inspect the value.
103    ///
104    /// # Examples
105    /// ```
106    /// # use ash_flare::WorkerContext;
107    /// let ctx = WorkerContext::new();
108    /// ctx.set("count", serde_json::json!(42));
109    /// let is_positive = ctx.with_value("count", |v| {
110    ///     v.and_then(|val| val.as_i64()).map(|n| n > 0).unwrap_or(false)
111    /// });
112    /// assert!(is_positive);
113    /// ```
114    #[inline]
115    pub fn with_value<F, R>(&self, key: &str, f: F) -> R
116    where
117        F: FnOnce(Option<&serde_json::Value>) -> R,
118    {
119        match self.store.get(key) {
120            Some(entry) => f(Some(entry.value())),
121            None => f(None),
122        }
123    }
124
125    /// Sets a value in the store.
126    #[inline]
127    pub fn set(&self, key: impl Into<String>, value: serde_json::Value) {
128        self.store.insert(key.into(), value);
129    }
130
131    /// Deletes a key from the store.
132    ///
133    /// Returns the previous value if it existed.
134    #[inline]
135    pub fn delete(&self, key: &str) -> Option<serde_json::Value> {
136        self.store.remove(key).map(|(_, v)| v)
137    }
138
139    /// Returns the number of key-value pairs in the store.
140    #[inline]
141    pub fn len(&self) -> usize {
142        self.store.len()
143    }
144
145    /// Returns `true` if the store contains no key-value pairs.
146    #[inline]
147    pub fn is_empty(&self) -> bool {
148        self.store.is_empty()
149    }
150
151    /// Checks if a key exists in the store without retrieving the value.
152    #[inline]
153    pub fn contains_key(&self, key: &str) -> bool {
154        self.store.contains_key(key)
155    }
156
157    /// Updates a value in the store using a function.
158    ///
159    /// The function receives the current value (or `None` if the key doesn't exist)
160    /// and returns an `Option` to control the update:
161    /// - `Some(value)` - Insert or update the key with the new value
162    /// - `None` - **Remove the key from the store** (if it existed)
163    ///
164    /// # Warning
165    /// Returning `None` from the update function will **delete the key** from the store.
166    /// This is useful for conditional removal but can lead to unexpected data loss if not intended.
167    ///
168    /// # Examples
169    /// ```
170    /// # use ash_flare::WorkerContext;
171    /// let ctx = WorkerContext::new();
172    ///
173    /// // Increment a counter, initializing to 1 if it doesn't exist
174    /// ctx.update("counter", |v| {
175    ///     let count = v.and_then(|v| v.as_u64()).unwrap_or(0);
176    ///     Some(serde_json::json!(count + 1))
177    /// });
178    /// assert_eq!(ctx.get("counter").and_then(|v| v.as_u64()), Some(1));
179    ///
180    /// // Conditionally remove a key by returning None
181    /// ctx.set("temp", serde_json::json!("remove me"));
182    /// ctx.update("temp", |_| None); // Key is now deleted
183    /// assert!(!ctx.contains_key("temp"));
184    /// ```
185    pub fn update<F>(&self, key: &str, f: F)
186    where
187        F: FnOnce(Option<serde_json::Value>) -> Option<serde_json::Value>,
188    {
189        match self.store.entry(key.to_owned()) {
190            dashmap::mapref::entry::Entry::Occupied(mut entry) => {
191                let old_value = entry.get().clone();
192                match f(Some(old_value)) {
193                    Some(new_value) => {
194                        entry.insert(new_value);
195                    }
196                    None => {
197                        entry.remove();
198                    }
199                }
200            }
201            dashmap::mapref::entry::Entry::Vacant(entry) => {
202                if let Some(new_value) = f(None) {
203                    entry.insert(new_value);
204                }
205            }
206        }
207    }
208}
209
210impl Default for WorkerContext {
211    fn default() -> Self {
212        Self::new()
213    }
214}
215
216// ============================================================================
217// Beam-ready Foundation Types
218// ============================================================================
219
220/// Process identifier (Beam-style) - foundation for future linking/monitoring
221#[allow(dead_code)]
222#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
223pub struct Pid(pub u64);
224
225/// Extended exit reason for future Beam-like semantics
226#[allow(dead_code)]
227#[derive(Debug, Clone)]
228pub enum ExitReason {
229    /// Normal termination
230    Normal,
231    /// Killed/aborted
232    Killed,
233    /// Terminated with error
234    Error(String),
235}