Skip to main content

appcore_supervisor/
adapters.rs

1// =============================================================================
2//        #######
3//     ###       ###     F: adapters.rs
4//    ##   ## ##   ##    P: AppCore-Runtime
5//         ## ##
6//                       C: 2026/07/24 11:51:10 by dnettoRaw
7//    ##   ## ##   ##    U: 2026/08/02 13:24:05 by dnettoRaw
8//      ###########      S: 1.0.1-rc.8
9// =============================================================================
10
11//! Reusable managed-service adapters for threads and embedded resources.
12
13use crate::{
14    ManagedService, ServiceActivationState, ServiceDescriptor, ServiceHealth, ServiceRuntimeState,
15    SupervisorError, SupervisorResult,
16};
17use std::panic::{catch_unwind, AssertUnwindSafe};
18use std::sync::atomic::{AtomicBool, Ordering};
19use std::sync::{Arc, Mutex};
20use std::thread::JoinHandle;
21use std::time::{Duration, Instant};
22
23type ThreadFactory =
24    dyn Fn(Arc<AtomicBool>) -> Result<JoinHandle<Result<(), String>>, String> + Send + Sync;
25type StartAction = dyn Fn() -> Result<(), String> + Send + Sync;
26type StopAction = dyn Fn(Duration) -> Result<(), String> + Send + Sync;
27type HealthProbe = dyn Fn() -> ServiceHealth + Send + Sync;
28
29struct ThreadRuntime {
30    shutdown: Arc<AtomicBool>,
31    handle: JoinHandle<Result<(), String>>,
32}
33
34struct ThreadState {
35    health: ServiceHealth,
36    runtime_state: ServiceRuntimeState,
37    runtime: Option<ThreadRuntime>,
38}
39
40/// Managed adapter for one cooperatively cancellable thread.
41pub struct ManagedThreadService {
42    descriptor: ServiceDescriptor,
43    factory: Arc<ThreadFactory>,
44    health_probe: Option<Arc<HealthProbe>>,
45    state: Mutex<ThreadState>,
46}
47
48impl ManagedThreadService {
49    /// Creates a managed thread from a restartable factory.
50    pub fn new<F>(descriptor: ServiceDescriptor, factory: F) -> Self
51    where
52        F: Fn(Arc<AtomicBool>) -> Result<JoinHandle<Result<(), String>>, String>
53            + Send
54            + Sync
55            + 'static,
56    {
57        Self {
58            descriptor,
59            factory: Arc::new(factory),
60            health_probe: None,
61            state: Mutex::new(ThreadState {
62                health: ServiceHealth::Unknown,
63                runtime_state: ServiceRuntimeState::Stopped,
64                runtime: None,
65            }),
66        }
67    }
68
69    /// Adds a live health probe evaluated while the thread is running.
70    pub fn with_health_probe<H>(mut self, health_probe: H) -> Self
71    where
72        H: Fn() -> ServiceHealth + Send + Sync + 'static,
73    {
74        self.health_probe = Some(Arc::new(health_probe));
75        self
76    }
77
78    fn refresh(state: &mut ThreadState) {
79        let finished = state
80            .runtime
81            .as_ref()
82            .is_some_and(|runtime| runtime.handle.is_finished());
83        if !finished {
84            return;
85        }
86        let Some(runtime) = state.runtime.take() else {
87            return;
88        };
89        (state.health, state.runtime_state) = if runtime.shutdown.load(Ordering::Acquire) {
90            (ServiceHealth::Unknown, ServiceRuntimeState::Stopped)
91        } else {
92            (ServiceHealth::Failed, ServiceRuntimeState::Failed)
93        };
94        let _ = runtime.handle.join();
95    }
96}
97
98impl ManagedService for ManagedThreadService {
99    fn descriptor(&self) -> &ServiceDescriptor {
100        &self.descriptor
101    }
102
103    fn start(&self) -> SupervisorResult<()> {
104        let mut state = self
105            .state
106            .lock()
107            .map_err(|_| SupervisorError::StatePoisoned)?;
108        Self::refresh(&mut state);
109        if state.runtime_state == ServiceRuntimeState::Orphaned {
110            return Err(SupervisorError::ServiceOrphaned(
111                self.descriptor.name().to_string(),
112            ));
113        }
114        if state.runtime.is_some() {
115            return Ok(());
116        }
117        if self.descriptor.activation() != ServiceActivationState::Enabled {
118            state.health = ServiceHealth::Unknown;
119            state.runtime_state = ServiceRuntimeState::Stopped;
120            return Ok(());
121        }
122        state.health = ServiceHealth::Starting;
123        state.runtime_state = ServiceRuntimeState::Starting;
124        let shutdown = Arc::new(AtomicBool::new(false));
125        let handle = catch_unwind(AssertUnwindSafe(|| (self.factory)(Arc::clone(&shutdown))))
126            .map_err(|_| "managed thread factory panicked".to_string())
127            .and_then(|result| result)
128            .map_err(|reason| {
129                state.health = ServiceHealth::Failed;
130                state.runtime_state = ServiceRuntimeState::Failed;
131                SupervisorError::ServiceFailure {
132                    service: self.descriptor.name().to_string(),
133                    reason,
134                }
135            })?;
136        state.runtime = Some(ThreadRuntime { shutdown, handle });
137        state.health = ServiceHealth::Ready;
138        state.runtime_state = ServiceRuntimeState::Running;
139        Ok(())
140    }
141
142    fn stop(&self, timeout: Duration) -> SupervisorResult<()> {
143        let runtime = {
144            let mut state = self
145                .state
146                .lock()
147                .map_err(|_| SupervisorError::StatePoisoned)?;
148            Self::refresh(&mut state);
149            if state.runtime_state == ServiceRuntimeState::Orphaned {
150                return Err(SupervisorError::ServiceOrphaned(
151                    self.descriptor.name().to_string(),
152                ));
153            }
154            state.health = ServiceHealth::Stopping;
155            state.runtime_state = ServiceRuntimeState::StopRequested;
156            let Some(runtime) = state.runtime.take() else {
157                state.health = ServiceHealth::Unknown;
158                state.runtime_state = ServiceRuntimeState::Stopped;
159                return Ok(());
160            };
161            runtime.shutdown.store(true, Ordering::Release);
162            state.runtime_state = ServiceRuntimeState::Stopping;
163            runtime
164        };
165        let deadline = Instant::now().checked_add(timeout);
166        while !runtime.handle.is_finished()
167            && deadline.is_none_or(|deadline| Instant::now() < deadline)
168        {
169            std::thread::sleep(Duration::from_millis(10));
170        }
171        if !runtime.handle.is_finished() {
172            let mut state = self
173                .state
174                .lock()
175                .map_err(|_| SupervisorError::StatePoisoned)?;
176            state.runtime = Some(runtime);
177            state.health = ServiceHealth::Failed;
178            state.runtime_state = ServiceRuntimeState::Orphaned;
179            return Err(SupervisorError::ShutdownTimeout(
180                self.descriptor.name().to_string(),
181            ));
182        }
183        let result = runtime
184            .handle
185            .join()
186            .map_err(|_| SupervisorError::ServiceFailure {
187                service: self.descriptor.name().to_string(),
188                reason: "managed thread panicked".to_string(),
189            })?;
190        let mut state = self
191            .state
192            .lock()
193            .map_err(|_| SupervisorError::StatePoisoned)?;
194        state.health = ServiceHealth::Unknown;
195        state.runtime_state = ServiceRuntimeState::Stopped;
196        result.map_err(|reason| SupervisorError::ServiceFailure {
197            service: self.descriptor.name().to_string(),
198            reason,
199        })
200    }
201
202    fn health(&self) -> ServiceHealth {
203        let Ok(mut state) = self.state.lock() else {
204            return ServiceHealth::Failed;
205        };
206        Self::refresh(&mut state);
207        if matches!(state.health, ServiceHealth::Ready | ServiceHealth::Healthy) {
208            return self
209                .health_probe
210                .as_ref()
211                .map(|probe| {
212                    catch_unwind(AssertUnwindSafe(|| probe())).unwrap_or(ServiceHealth::Failed)
213                })
214                .unwrap_or(state.health);
215        }
216        state.health
217    }
218
219    fn runtime_state(&self) -> ServiceRuntimeState {
220        let Ok(mut state) = self.state.lock() else {
221            return ServiceRuntimeState::Failed;
222        };
223        Self::refresh(&mut state);
224        state.runtime_state
225    }
226}
227
228/// Managed adapter for an embedded resource controlled by callbacks.
229pub struct CallbackManagedService {
230    descriptor: ServiceDescriptor,
231    start_action: Arc<StartAction>,
232    stop_action: Arc<StopAction>,
233    health_probe: Arc<HealthProbe>,
234    state: Mutex<CallbackState>,
235}
236
237struct CallbackState {
238    health: ServiceHealth,
239    runtime_state: ServiceRuntimeState,
240}
241
242impl CallbackManagedService {
243    /// Creates a restartable embedded service.
244    pub fn new<S, T, H>(
245        descriptor: ServiceDescriptor,
246        start_action: S,
247        stop_action: T,
248        health_probe: H,
249    ) -> Self
250    where
251        S: Fn() -> Result<(), String> + Send + Sync + 'static,
252        T: Fn(Duration) -> Result<(), String> + Send + Sync + 'static,
253        H: Fn() -> ServiceHealth + Send + Sync + 'static,
254    {
255        Self {
256            descriptor,
257            start_action: Arc::new(start_action),
258            stop_action: Arc::new(stop_action),
259            health_probe: Arc::new(health_probe),
260            state: Mutex::new(CallbackState {
261                health: ServiceHealth::Unknown,
262                runtime_state: ServiceRuntimeState::Stopped,
263            }),
264        }
265    }
266
267    fn set_state(
268        &self,
269        health: ServiceHealth,
270        runtime_state: ServiceRuntimeState,
271    ) -> SupervisorResult<()> {
272        let mut state = self
273            .state
274            .lock()
275            .map_err(|_| SupervisorError::StatePoisoned)?;
276        state.health = health;
277        state.runtime_state = runtime_state;
278        Ok(())
279    }
280}
281
282impl ManagedService for CallbackManagedService {
283    fn descriptor(&self) -> &ServiceDescriptor {
284        &self.descriptor
285    }
286
287    fn start(&self) -> SupervisorResult<()> {
288        if self.descriptor.activation() != ServiceActivationState::Enabled {
289            return self.set_state(ServiceHealth::Unknown, ServiceRuntimeState::Stopped);
290        }
291        self.set_state(ServiceHealth::Starting, ServiceRuntimeState::Starting)?;
292        catch_unwind(AssertUnwindSafe(|| (self.start_action)()))
293            .map_err(|_| "managed start callback panicked".to_string())
294            .and_then(|result| result)
295            .map_err(|reason| {
296                let _ = self.set_state(ServiceHealth::Failed, ServiceRuntimeState::Failed);
297                SupervisorError::ServiceFailure {
298                    service: self.descriptor.name().to_string(),
299                    reason,
300                }
301            })?;
302        self.set_state(ServiceHealth::Ready, ServiceRuntimeState::Running)
303    }
304
305    fn stop(&self, timeout: Duration) -> SupervisorResult<()> {
306        self.set_state(ServiceHealth::Stopping, ServiceRuntimeState::Stopping)?;
307        catch_unwind(AssertUnwindSafe(|| (self.stop_action)(timeout)))
308            .map_err(|_| "managed stop callback panicked".to_string())
309            .and_then(|result| result)
310            .map_err(|reason| {
311                let _ = self.set_state(ServiceHealth::Failed, ServiceRuntimeState::Failed);
312                SupervisorError::ServiceFailure {
313                    service: self.descriptor.name().to_string(),
314                    reason,
315                }
316            })?;
317        self.set_state(ServiceHealth::Unknown, ServiceRuntimeState::Stopped)
318    }
319
320    fn health(&self) -> ServiceHealth {
321        let state = self
322            .state
323            .lock()
324            .map(|state| state.health)
325            .unwrap_or(ServiceHealth::Failed);
326        if matches!(
327            state,
328            ServiceHealth::Ready | ServiceHealth::Healthy | ServiceHealth::Degraded
329        ) {
330            return catch_unwind(AssertUnwindSafe(|| (self.health_probe)()))
331                .unwrap_or(ServiceHealth::Failed);
332        }
333        state
334    }
335
336    fn runtime_state(&self) -> ServiceRuntimeState {
337        self.state
338            .lock()
339            .map(|state| state.runtime_state)
340            .unwrap_or(ServiceRuntimeState::Failed)
341    }
342}
343
344/// Managed adapter for a passive Runtime resource with no owned worker.
345pub struct PassiveManagedService {
346    descriptor: ServiceDescriptor,
347    state: Mutex<CallbackState>,
348}
349
350impl PassiveManagedService {
351    /// Creates a passive service whose start transition produces `Ready`.
352    pub fn new(descriptor: ServiceDescriptor) -> Self {
353        Self {
354            descriptor,
355            state: Mutex::new(CallbackState {
356                health: ServiceHealth::Unknown,
357                runtime_state: ServiceRuntimeState::Stopped,
358            }),
359        }
360    }
361}
362
363impl ManagedService for PassiveManagedService {
364    fn descriptor(&self) -> &ServiceDescriptor {
365        &self.descriptor
366    }
367
368    fn start(&self) -> SupervisorResult<()> {
369        let mut state = self
370            .state
371            .lock()
372            .map_err(|_| SupervisorError::StatePoisoned)?;
373        if self.descriptor.activation() == ServiceActivationState::Enabled {
374            state.health = ServiceHealth::Healthy;
375            state.runtime_state = ServiceRuntimeState::Running;
376        } else {
377            state.health = ServiceHealth::Unknown;
378            state.runtime_state = ServiceRuntimeState::Stopped;
379        }
380        Ok(())
381    }
382
383    fn stop(&self, _timeout: Duration) -> SupervisorResult<()> {
384        let mut state = self
385            .state
386            .lock()
387            .map_err(|_| SupervisorError::StatePoisoned)?;
388        state.health = ServiceHealth::Unknown;
389        state.runtime_state = ServiceRuntimeState::Stopped;
390        Ok(())
391    }
392
393    fn health(&self) -> ServiceHealth {
394        self.state
395            .lock()
396            .map(|state| state.health)
397            .unwrap_or(ServiceHealth::Failed)
398    }
399
400    fn runtime_state(&self) -> ServiceRuntimeState {
401        self.state
402            .lock()
403            .map(|state| state.runtime_state)
404            .unwrap_or(ServiceRuntimeState::Failed)
405    }
406}