1mod restart;
20mod runner;
21
22#[cfg(feature = "plugin-sidecar")]
23mod sidecar;
24
25pub(crate) use runner::{Once, Typed};
28
29#[cfg(feature = "plugin-sidecar")]
30pub(crate) use sidecar::Endpoint as NativeEndpoint;
31#[cfg(feature = "plugin-sidecar")]
32pub(crate) use sidecar::RuntimeDriver;
33
34#[cfg(feature = "plugin-sidecar")]
35pub(crate) use sidecar::HostPublish;
36#[cfg(feature = "plugin-sidecar")]
37pub use sidecar::{DemandLease, DemandSource, Managed, ManagedError, Notice, Publish, SidecarError, Supervised};
38
39use std::fmt;
43use std::future::Future;
44use std::sync::Arc;
45use std::task::Poll;
46use std::time::Duration;
47use tokio::sync::watch;
49use crate::operations::{
51 Backoff, CallContext, ContextError, ContextValue, Jitter, OperationDeclaration, OperationInstance, OperationManager, OperationPolicy,
52 Response, RestartPolicy, RetryPolicy,
53};
54use crate::supervision::executor;
55use crate::{Application, Error, Result, evidence::Evidence, state::StateManager, streams::StreamManager};
56
57pub const POLICY: OperationPolicy = OperationPolicy {
62 timeout: Duration::from_secs(5),
63 attempt_timeout: Duration::from_secs(5),
64 retries: RetryPolicy::Never,
65 restart: RestartPolicy::OnTransient {
66 credit: 3,
67 sustained_health: Duration::from_millis(250),
68 backoff: Backoff::Exponential {
69 initial: Duration::from_millis(10),
70 maximum: Duration::from_secs(5),
71 jitter: Jitter::Deterministic,
72 },
73 },
74};
75
76#[derive(Clone, Debug)]
83pub struct ServiceDefinition {
84 pub id: String,
85 pub policy: OperationPolicy,
86}
87
88pub(crate) struct Handles<A: Application> {
90 pub(crate) storage: Arc<std::sync::OnceLock<crate::storage::StorageManager<crate::storage::ErasedCatalog>>>,
91 pub(crate) state: StateManager<A>,
92 pub(crate) records: crate::records::RecordManager<A>,
93 pub(crate) streams: StreamManager<A>,
94 pub(crate) evidence: Evidence<A>,
95}
96
97pub struct Service<A: Application> {
98 pub(crate) state: StateManager<A>,
99 pub(crate) records: crate::records::RecordManager<A>,
100 pub(crate) operations: OperationManager,
101 pub(crate) streams: StreamManager<A>,
102 pub(crate) evidence: Evidence<A>,
103 pub(crate) stopping: watch::Receiver<bool>,
104 pub(crate) ready: watch::Sender<bool>,
105
106 #[cfg(feature = "plugin-sidecar")]
107 pub(crate) provider: Arc<crate::supervision::instance::Instance>,
108}
109
110#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
113pub enum Fault<E> {
114 #[error("environmental fault: {0}")]
115 Environmental(E),
116
117 #[error("application fault: {0}")]
118 Application(E),
119}
120
121#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
122pub enum ServiceExit {
123 #[error("application: {0}")]
124 Application(String),
125
126 #[error("resident panicked")]
127 Panicked,
128
129 #[error("restart credit exhausted")]
130 CreditExhausted,
131
132 #[error("resident stop deadline exceeded")]
133 StopTimedOut,
134}
135
136#[derive(Clone, Debug, PartialEq, Eq)]
137pub enum ServiceStatus {
138 Starting,
139 Running { credit: usize },
140 Stopping,
141 BackingOff,
142 Stopped,
143 Inactive { cause: ServiceExit, history: Vec<String> },
144}
145
146pub trait Resident<A: Application>: Send + 'static {
151 type Error: fmt::Debug + fmt::Display + Send + Sync + 'static;
152
153 fn run(&mut self, context: Service<A>) -> impl Future<Output = std::result::Result<(), Fault<Self::Error>>> + Send;
154
155 fn stop(&mut self) -> impl Future<Output = std::result::Result<(), Self::Error>> + Send {
156 async { Ok(()) }
157 }
158
159 fn cleanup(&mut self) -> impl Future<Output = std::result::Result<(), Self::Error>> + Send;
160}
161
162impl ServiceDefinition {
165 pub fn new(id: impl Into<String>) -> Self {
166 Self { id: id.into(), policy: POLICY }
167 }
168}
169
170impl<A: Application> Handles<A> {
171 pub(crate) fn context(
172 &self, operations: OperationManager, stopping: watch::Receiver<bool>, ready: watch::Sender<bool>,
173 #[cfg(feature = "plugin-sidecar")] provider: Arc<crate::supervision::instance::Instance>,
174 ) -> Service<A> {
175 Service {
176 state: self.state.clone(),
177 records: self.records.clone(),
178 operations,
179 streams: self.streams.clone(),
180 evidence: self.evidence.clone(),
181 stopping,
182 ready,
183 #[cfg(feature = "plugin-sidecar")]
184 provider,
185 }
186 }
187}
188
189impl<A: Application> Clone for Handles<A> {
190 fn clone(&self) -> Self {
191 Self {
192 storage: self.storage.clone(),
193 state: self.state.clone(),
194 records: self.records.clone(),
195 streams: self.streams.clone(),
196 evidence: self.evidence.clone(),
197 }
198 }
199}
200
201impl<A: Application> Service<A> {
202 #[cfg(feature = "plugin-sidecar")]
203 pub(crate) fn shared_attempt(&self) -> Self {
204 Self {
205 state: self.state.clone(),
206 records: self.records.clone(),
207 operations: self.operations.clone(),
208 streams: self.streams.clone(),
209 evidence: self.evidence.clone(),
210 stopping: self.stopping.clone(),
211 ready: self.ready.clone(),
212 provider: self.provider.clone(),
213 }
214 }
215
216 pub fn ready(&self) {
217 if !self.is_stopping() {
218 self.ready.send_replace(true);
219 }
220 }
221
222 pub fn host(&self) -> OperationManager {
225 self.operations.clone()
226 }
227
228 pub fn records(&self) -> &crate::records::RecordManager<A> {
229 &self.records
230 }
231
232 pub fn state(&self) -> &StateManager<A> {
233 &self.state
234 }
235
236 pub fn streams(&self) -> &StreamManager<A> {
237 &self.streams
238 }
239
240 pub fn evidence(&self) -> &Evidence<A> {
241 &self.evidence
242 }
243
244 pub fn is_stopping(&self) -> bool {
245 *self.stopping.borrow()
246 }
247
248 pub async fn stopping(&mut self) {
249 while !*self.stopping.borrow_and_update() {
250 if self.stopping.changed().await.is_err() {
251 break;
252 }
253 }
254 }
255
256 pub async fn rest(&mut self, duration: Duration) -> bool {
257 self.until_stopping(executor::sleep(duration)).await.is_none()
258 }
259
260 pub async fn until_stopping<T>(&mut self, future: impl Future<Output = T>) -> Option<T> {
261 if self.is_stopping() {
262 return None;
263 }
264 let mut future = Box::pin(future);
265 let mut stop = Box::pin(self.stopping.changed());
266
267 std::future::poll_fn(|context| {
268 if let Poll::Ready(Ok(()) | Err(_)) = stop.as_mut().poll(context) {
269 return Poll::Ready(None);
270 }
271 future.as_mut().poll(context).map(Some)
272 })
273 .await
274 }
275}
276
277impl<A: Application> harmos_sdk::operations::Host for Service<A> {
278 type Error = Error;
279 type Instance<O: OperationDeclaration> = OperationInstance<O>;
280
281 fn context(&self) -> &CallContext {
282 harmos_sdk::operations::Host::context(&self.operations)
283 }
284
285 fn with_value<T: ContextValue>(&self, value: T) -> std::result::Result<Self, ContextError> {
286 let operations = harmos_sdk::operations::Host::with_value(&self.operations, value)?;
287 let mut child = self.clone_context();
288 child.operations = operations;
289 Ok(child)
290 }
291
292 fn submit<O: OperationDeclaration>(&self, request: O) -> impl Future<Output = Result<OperationInstance<O>>> + Send {
293 harmos_sdk::operations::Host::submit(&self.operations, request)
294 }
295
296 fn call<O: OperationDeclaration>(&self, request: O) -> impl Future<Output = Result<Response<O::Response>>> + Send {
297 harmos_sdk::operations::Host::call(&self.operations, request)
298 }
299
300 fn submit_with_resources<O: OperationDeclaration<Response = ()>, R: Send + Sync + 'static>(
301 &self, request: O, resources: Arc<R>,
302 ) -> impl Future<Output = Result<OperationInstance<O>>> + Send {
303 harmos_sdk::operations::Host::submit_with_resources::<O, R>(&self.operations, request, resources)
304 }
305}
306
307impl<A: Application> Service<A> {
308 fn clone_context(&self) -> Self {
309 Self {
310 state: self.state.clone(),
311 records: self.records.clone(),
312 operations: self.operations.clone(),
313 streams: self.streams.clone(),
314 evidence: self.evidence.clone(),
315 stopping: self.stopping.clone(),
316 ready: self.ready.clone(),
317 #[cfg(feature = "plugin-sidecar")]
318 provider: self.provider.clone(),
319 }
320 }
321}
322
323impl<A, F, Fut, E> Resident<A> for F
324where
325 A: Application,
326 F: FnMut(Service<A>) -> Fut + Send + 'static,
327 Fut: Future<Output = std::result::Result<(), Fault<E>>> + Send + 'static,
328 E: fmt::Debug + fmt::Display + Send + Sync + 'static,
329{
330 type Error = E;
331
332 fn run(&mut self, context: Service<A>) -> impl Future<Output = std::result::Result<(), Fault<Self::Error>>> + Send {
333 self(context)
334 }
335
336 async fn cleanup(&mut self) -> std::result::Result<(), Self::Error> {
339 Ok(())
340 }
341}
342
343pub(crate) fn validate(definition: &ServiceDefinition) -> Result<()> {
348 if definition.id.is_empty() {
349 return Err(Error::InvalidInput { field: "service name", reason: "must not be empty" });
350 }
351 if definition.id == crate::evidence::SOURCE {
352 return Err(Error::InvalidInput { field: "service name", reason: "artifact is reserved for evidence facts" });
353 }
354 definition.policy.validate().map_err(|source| Error::OperationDeclaration { source: source.into() })
355}
356
357