1mod control;
2mod intercom;
3mod stats;
4mod status;
5
6pub use self::{
7 control::{Control, ControlReader, Controller},
8 intercom::{
9 Intercom, IntercomMsg, IntercomReceiver, IntercomSender, IntercomStats, IntercomStatus,
10 NoIntercom,
11 },
12 stats::Stats,
13 status::{Status, StatusReader, StatusUpdater},
14};
15use crate::{runtime::Runtime, watchdog::WatchdogQuery};
16use async_trait::async_trait;
17use futures_util::future::abortable;
18use std::future::Future;
19use thiserror::Error;
20use tokio::{runtime::Handle, task::JoinHandle};
21use tracing_futures::Instrument as _;
22
23pub type ServiceIdentifier = &'static str;
24
25#[async_trait]
26pub trait Service: Send + Sized + 'static {
27 const SERVICE_IDENTIFIER: ServiceIdentifier;
28
29 type IntercomMsg: IntercomMsg;
30
31 fn prepare(service_state: ServiceState<Self>) -> Self;
32
33 async fn start(self);
34}
35
36pub trait ManageService {
37 const SERVICE_IDENTIFIER: ServiceIdentifier;
38
39 type IntercomMsg: IntercomMsg;
40}
41
42impl<T: Service> ManageService for ServiceManager<T> {
43 const SERVICE_IDENTIFIER: ServiceIdentifier = T::SERVICE_IDENTIFIER;
44
45 type IntercomMsg = T::IntercomMsg;
46}
47
48#[derive(Clone, Debug, Error, PartialEq, Eq)]
49pub enum ServiceError {
50 #[error("Service cannot be started because status is: {status}")]
51 CannotStart { status: Status },
52}
53
54#[derive(Debug, Clone)]
55pub struct StatusReport {
56 pub identifier: ServiceIdentifier,
57 pub status: Status,
58 pub intercom: IntercomStatus,
59 pub started: u64,
60}
61
62pub struct ServiceManager<T: Service> {
63 identifier: ServiceIdentifier,
64
65 intercom_sender: IntercomSender<T::IntercomMsg>,
66 intercom_stats: IntercomStats,
67 started: u64,
68
69 status: StatusReader,
70 controller: Controller,
71 runtime: Handle,
72}
73
74pub struct ServiceRuntime<T: Service> {
79 service_state: ServiceState<T>,
80
81 status: StatusUpdater,
82 control: ControlReader,
83}
84
85pub struct ServiceState<T: Service> {
91 identifier: ServiceIdentifier,
92 handle: Handle,
93 intercom_receiver: IntercomReceiver<T::IntercomMsg>,
94 watchdog_query: WatchdogQuery,
95 status: StatusReader,
96}
97
98impl<T: Service> ServiceState<T> {
99 pub fn identifier(&self) -> ServiceIdentifier {
103 self.identifier
104 }
105
106 pub fn intercom_with<O: Service>(&self) -> Intercom<O> {
110 self.watchdog_query.intercom::<O>()
111 }
112
113 pub fn watchdog_controller(&self) -> &WatchdogQuery {
116 &self.watchdog_query
117 }
118
119 pub fn intercom_mut(&mut self) -> &mut IntercomReceiver<T::IntercomMsg> {
123 &mut self.intercom_receiver
124 }
125
126 pub fn status_reader(&self) -> &StatusReader {
130 &self.status
131 }
132
133 pub fn runtime_handle(&self) -> &Handle {
138 &self.handle
139 }
140
141 pub fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
147 where
148 F: Future + Send + 'static,
149 F::Output: Send + 'static,
150 {
151 self.runtime_handle().spawn(future)
152 }
153}
154
155impl<T: Service> ServiceManager<T> {
156 pub fn with_runtime(runtime: &mut Runtime) -> Self {
157 let identifier = T::SERVICE_IDENTIFIER;
158
159 let status = StatusReader::new(Status::shutdown());
160 let controller = runtime.block_on(async { Controller::new().await });
161 let (intercom_sender, _, intercom_stats) = intercom::channel();
162
163 Self {
164 identifier,
165 intercom_sender,
166 intercom_stats,
167 status,
168 controller,
169 runtime: runtime.handle().clone(),
170 started: 0,
171 }
172 }
173
174 pub fn intercom(&self) -> IntercomSender<T::IntercomMsg> {
175 self.intercom_sender.clone()
176 }
177
178 pub async fn status(&self) -> StatusReport {
179 StatusReport {
180 identifier: self.identifier,
181 status: self.status.status(),
182 intercom: self.intercom_stats.status().await,
183 started: self.started,
184 }
185 }
186
187 pub fn shutdown(&mut self) {
188 match self.status.status() {
189 Status::Shutdown { .. } | Status::ShuttingDown { .. } => {
190 }
193 Status::Starting { .. } | Status::Started { .. } => {
194 self.controller.send(Control::Shutdown)
197 }
198 }
199 }
200
201 pub fn runtime(
202 &mut self,
203 watchdog_query: WatchdogQuery,
204 ) -> Result<ServiceRuntime<T>, ServiceError> {
205 let status = self.status.status();
206 if !status.is_shutdown() {
207 Err(ServiceError::CannotStart { status })
208 } else {
209 let (intercom_sender, intercom_receiver, intercom_stats) =
210 intercom::channel::<T::IntercomMsg>();
211
212 self.intercom_sender = intercom_sender;
213 self.intercom_stats = intercom_stats;
214 self.started += 1;
215
216 Ok(ServiceRuntime {
217 service_state: ServiceState {
218 identifier: self.identifier,
219 handle: self.runtime.clone(),
220 status: self.status.clone(),
221 intercom_receiver,
222 watchdog_query,
223 },
224 status: self.status.updater(),
225 control: self.controller.reader(),
226 })
227 }
228 }
229}
230
231impl<T: Service> ServiceRuntime<T> {
232 pub fn start(self) {
233 let ServiceRuntime {
234 service_state,
235 status,
236 mut control,
237 } = self;
238
239 let service_identifier: &'static str = service_state.identifier;
240
241 status.update(Status::starting());
242
243 let watchdog_query = service_state.watchdog_query.clone();
244 let handle = service_state.handle.clone();
245 let runner = T::prepare(service_state);
246
247 let (runner, abort_handle) = abortable(async move {
248 let span = tracing::info_span!("service", service_identifier);
249 let _enter = span.enter();
250
251 runner.start().in_current_span().await
252 });
253
254 let mut service_join_handle = handle.spawn(runner);
255
256 watchdog_query.spawn(async move {
262 status.update(Status::started());
263
264 let span = tracing::debug_span!("service control", service_identifier);
265 let _enter = span.enter();
266
267 loop {
268 tokio::select! {
269 join_result = &mut service_join_handle => {
270 if let Err(join_error) = join_result {
271 tracing::error!(
278 "main process failed with following error: {:#?}",
279 join_error
280 );
281 } else {
282 }
285 status.update(Status::shutdown());
286 break;
287 }
288 control = control.updated() => {
289 match control {
290 Some(Control::Shutdown) => {
291 tracing::info!("shutting down...");
292
293 status.update(Status::shutting_down());
297 }
298 None | Some(Control::Kill) => {
299 tracing::info!("Terminating...");
300 status.update(Status::shutdown());
301 abort_handle.abort();
302 break;
303 }
304 }
305 }
306 };
307 }
308 });
309 }
310}
311
312impl<T: Service> Drop for ServiceManager<T> {
313 fn drop(&mut self) {
314 if !self.status.status().is_shutdown() {
315 self.controller.send(Control::Kill)
316 }
317 }
318}