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.
229/// A stop error or panic permanently marks the instance orphaned; future
230/// starts are rejected because resource teardown was not proven complete.
231/// Concurrent or reentrant lifecycle calls fail immediately without invoking
232/// another callback; health probes remain independent of this admission gate.
233pub struct CallbackManagedService {
234    descriptor: ServiceDescriptor,
235    start_action: Arc<StartAction>,
236    stop_action: Arc<StopAction>,
237    health_probe: Arc<HealthProbe>,
238    state: Mutex<CallbackState>,
239    lifecycle: Mutex<()>,
240}
241
242struct CallbackState {
243    health: ServiceHealth,
244    runtime_state: ServiceRuntimeState,
245}
246
247impl CallbackManagedService {
248    /// Creates a restartable embedded service.
249    pub fn new<S, T, H>(
250        descriptor: ServiceDescriptor,
251        start_action: S,
252        stop_action: T,
253        health_probe: H,
254    ) -> Self
255    where
256        S: Fn() -> Result<(), String> + Send + Sync + 'static,
257        T: Fn(Duration) -> Result<(), String> + Send + Sync + 'static,
258        H: Fn() -> ServiceHealth + Send + Sync + 'static,
259    {
260        Self {
261            descriptor,
262            start_action: Arc::new(start_action),
263            stop_action: Arc::new(stop_action),
264            health_probe: Arc::new(health_probe),
265            lifecycle: Mutex::new(()),
266            state: Mutex::new(CallbackState {
267                health: ServiceHealth::Unknown,
268                runtime_state: ServiceRuntimeState::Stopped,
269            }),
270        }
271    }
272
273    fn set_state(
274        &self,
275        health: ServiceHealth,
276        runtime_state: ServiceRuntimeState,
277    ) -> SupervisorResult<()> {
278        let mut state = self
279            .state
280            .lock()
281            .map_err(|_| SupervisorError::StatePoisoned)?;
282        if state.runtime_state == ServiceRuntimeState::Orphaned {
283            return Err(SupervisorError::ServiceOrphaned(
284                self.descriptor.name().to_string(),
285            ));
286        }
287        state.health = health;
288        state.runtime_state = runtime_state;
289        Ok(())
290    }
291
292    fn acquire_lifecycle(&self) -> SupervisorResult<std::sync::MutexGuard<'_, ()>> {
293        // Never wait on an arbitrary callback or allow overlapping resource ownership.
294        self.lifecycle
295            .try_lock()
296            .map_err(|_| SupervisorError::ServiceFailure {
297                service: self.descriptor.name().to_string(),
298                reason: "lifecycle operation unavailable".to_string(),
299            })
300    }
301}
302
303impl ManagedService for CallbackManagedService {
304    fn descriptor(&self) -> &ServiceDescriptor {
305        &self.descriptor
306    }
307
308    fn start(&self) -> SupervisorResult<()> {
309        let _operation = self.acquire_lifecycle()?;
310        if self.descriptor.activation() != ServiceActivationState::Enabled {
311            return self.set_state(ServiceHealth::Unknown, ServiceRuntimeState::Stopped);
312        }
313        self.set_state(ServiceHealth::Starting, ServiceRuntimeState::Starting)?;
314        catch_unwind(AssertUnwindSafe(|| (self.start_action)()))
315            .map_err(|_| "managed start callback panicked".to_string())
316            .and_then(|result| result)
317            .map_err(|reason| {
318                let _ = self.set_state(ServiceHealth::Failed, ServiceRuntimeState::Failed);
319                SupervisorError::ServiceFailure {
320                    service: self.descriptor.name().to_string(),
321                    reason,
322                }
323            })?;
324        self.set_state(ServiceHealth::Ready, ServiceRuntimeState::Running)
325    }
326
327    fn stop(&self, timeout: Duration) -> SupervisorResult<()> {
328        let _operation = self.acquire_lifecycle()?;
329        self.set_state(ServiceHealth::Stopping, ServiceRuntimeState::Stopping)?;
330        catch_unwind(AssertUnwindSafe(|| (self.stop_action)(timeout)))
331            .map_err(|_| "managed stop callback panicked".to_string())
332            .and_then(|result| result)
333            .map_err(|reason| {
334                let _ = self.set_state(ServiceHealth::Failed, ServiceRuntimeState::Orphaned);
335                SupervisorError::ServiceFailure {
336                    service: self.descriptor.name().to_string(),
337                    reason,
338                }
339            })?;
340        self.set_state(ServiceHealth::Unknown, ServiceRuntimeState::Stopped)
341    }
342
343    fn health(&self) -> ServiceHealth {
344        let state = self
345            .state
346            .lock()
347            .map(|state| state.health)
348            .unwrap_or(ServiceHealth::Failed);
349        if matches!(
350            state,
351            ServiceHealth::Ready | ServiceHealth::Healthy | ServiceHealth::Degraded
352        ) {
353            return catch_unwind(AssertUnwindSafe(|| (self.health_probe)()))
354                .unwrap_or(ServiceHealth::Failed);
355        }
356        state
357    }
358
359    fn runtime_state(&self) -> ServiceRuntimeState {
360        self.state
361            .lock()
362            .map(|state| state.runtime_state)
363            .unwrap_or(ServiceRuntimeState::Failed)
364    }
365}
366
367/// Managed adapter for a passive Runtime resource with no owned worker.
368pub struct PassiveManagedService {
369    descriptor: ServiceDescriptor,
370    state: Mutex<CallbackState>,
371}
372
373impl PassiveManagedService {
374    /// Creates a passive service whose start transition produces `Ready`.
375    pub fn new(descriptor: ServiceDescriptor) -> Self {
376        Self {
377            descriptor,
378            state: Mutex::new(CallbackState {
379                health: ServiceHealth::Unknown,
380                runtime_state: ServiceRuntimeState::Stopped,
381            }),
382        }
383    }
384}
385
386impl ManagedService for PassiveManagedService {
387    fn descriptor(&self) -> &ServiceDescriptor {
388        &self.descriptor
389    }
390
391    fn start(&self) -> SupervisorResult<()> {
392        let mut state = self
393            .state
394            .lock()
395            .map_err(|_| SupervisorError::StatePoisoned)?;
396        if self.descriptor.activation() == ServiceActivationState::Enabled {
397            state.health = ServiceHealth::Healthy;
398            state.runtime_state = ServiceRuntimeState::Running;
399        } else {
400            state.health = ServiceHealth::Unknown;
401            state.runtime_state = ServiceRuntimeState::Stopped;
402        }
403        Ok(())
404    }
405
406    fn stop(&self, _timeout: Duration) -> SupervisorResult<()> {
407        let mut state = self
408            .state
409            .lock()
410            .map_err(|_| SupervisorError::StatePoisoned)?;
411        state.health = ServiceHealth::Unknown;
412        state.runtime_state = ServiceRuntimeState::Stopped;
413        Ok(())
414    }
415
416    fn health(&self) -> ServiceHealth {
417        self.state
418            .lock()
419            .map(|state| state.health)
420            .unwrap_or(ServiceHealth::Failed)
421    }
422
423    fn runtime_state(&self) -> ServiceRuntimeState {
424        self.state
425            .lock()
426            .map(|state| state.runtime_state)
427            .unwrap_or(ServiceRuntimeState::Failed)
428    }
429}