1use std::future::Future;
8use std::sync::Arc;
9use std::time::Instant;
10use tracing::{debug, error, info, instrument, warn};
11
12use crate::config::Config;
13use crate::error::{Error, Result};
14use crate::shutdown::{ShutdownCoordinator, ShutdownReason};
15use crate::signal::{SignalConfig, SignalHandler};
16use crate::subsystem::{Subsystem, SubsystemId, SubsystemManager};
17
18#[cfg(feature = "config-watch")]
19use arc_swap::ArcSwap;
20#[cfg(feature = "config-watch")]
21use notify::RecommendedWatcher;
22
23type SubsystemRegistrationFn = Box<dyn FnOnce(&SubsystemManager) -> SubsystemId + Send + 'static>;
25
26pub struct Daemon {
28 config: Arc<Config>,
30 #[cfg(feature = "config-watch")]
32 config_shared: Arc<ArcSwap<Config>>,
33 shutdown_coordinator: ShutdownCoordinator,
35 subsystem_manager: SubsystemManager,
37 signal_handler: Option<Arc<SignalHandler>>,
39 #[cfg(feature = "config-watch")]
41 _config_watcher: Option<RecommendedWatcher>,
42 started_at: Option<Instant>,
44}
45
46impl Daemon {
47 #[must_use]
49 pub fn builder(config: Config) -> DaemonBuilder {
50 DaemonBuilder::new(config)
51 }
52
53 pub fn with_defaults() -> Result<DaemonBuilder> {
59 let config = Config::new()?;
60 Ok(Self::builder(config))
61 }
62
63 #[instrument(skip(self), fields(daemon_name = %self.config.name))]
71 pub async fn run(mut self) -> Result<()> {
72 info!(daemon_name = %self.config.name, "Starting daemon");
73 self.started_at = Some(Instant::now());
74
75 self.init_logging()?;
77
78 self.config.validate()?;
80
81 #[cfg(feature = "scheduler-hints")]
83 {
84 crate::scheduler::apply_process_hints(&self.config);
85 crate::scheduler::apply_runtime_hints();
86 }
87
88 if let Err(e) = self.subsystem_manager.start_all().await {
90 error!(error = %e, "Failed to start all subsystems");
91 return Err(e);
92 }
93
94 #[cfg(any(feature = "tokio", feature = "async-std"))]
97 let signal_task = self.signal_handler.as_ref().map(|signal_handler| {
98 let handler = Arc::clone(signal_handler);
99 Self::spawn_signal_handler(handler)
100 });
101
102 #[cfg(not(any(feature = "tokio", feature = "async-std")))]
103 let _signal_task: Option<()> = None;
104
105 info!("Daemon started successfully, waiting for shutdown signal");
107
108 loop {
110 if self.shutdown_coordinator.is_shutdown() {
111 break;
112 }
113
114 if self.config.monitoring.health_checks {
116 let health_results = self.subsystem_manager.run_health_checks();
117 let unhealthy: Vec<_> = health_results
118 .iter()
119 .filter(|(_, _, healthy)| !healthy)
120 .map(|(id, name, _)| (id, name))
121 .collect();
122
123 if !unhealthy.is_empty() {
124 warn!("Unhealthy subsystems detected: {:?}", unhealthy);
125 }
127 }
128
129 #[cfg(feature = "tokio")]
131 tokio::time::sleep(self.config.health_check_interval()).await;
132
133 #[cfg(all(feature = "async-std", not(feature = "tokio")))]
134 async_std::task::sleep(self.config.health_check_interval()).await;
135 }
136
137 info!("Shutdown initiated, beginning graceful shutdown");
139
140 if let Some(signal_handler) = &self.signal_handler {
142 signal_handler.stop();
143 }
144
145 #[cfg(any(feature = "tokio", feature = "async-std"))]
147 if let Some(task) = signal_task {
148 #[cfg(feature = "tokio")]
149 {
150 if let Err(e) = task.await {
151 warn!(error = %e, "Signal handler task failed");
152 }
153 }
154
155 #[cfg(all(feature = "async-std", not(feature = "tokio")))]
156 {
157 if let Err(e) = task.await {
158 warn!(error = %e, "Signal handler task failed");
159 }
160 }
161 }
162
163 if let Err(e) = self.subsystem_manager.stop_all().await {
165 error!(error = %e, "Failed to stop all subsystems gracefully");
166 }
167
168 if let Err(e) = self.shutdown_coordinator.wait_for_shutdown().await {
170 warn!(error = %e, "Graceful shutdown timeout exceeded");
171
172 if let Err(e) = self.shutdown_coordinator.wait_for_force_shutdown().await {
174 error!(error = %e, "Force shutdown timeout exceeded, exiting immediately");
175 }
176 }
177
178 let elapsed = self.started_at.map(|t| t.elapsed());
179 info!(uptime = ?elapsed, "Daemon shutdown complete");
180
181 Ok(())
182 }
183
184 fn init_logging(&self) -> Result<()> {
186 use tracing_subscriber::fmt::format::FmtSpan;
187 use tracing_subscriber::{EnvFilter, FmtSubscriber};
188
189 let level: tracing::Level = self.config.logging.level.into();
190 let filter = EnvFilter::from_default_env().add_directive(level.into());
191
192 if self.config.is_json_logging() {
194 #[cfg(feature = "json-logs")]
195 {
196 let base_subscriber = FmtSubscriber::builder()
197 .with_env_filter(filter)
198 .with_span_events(FmtSpan::CLOSE)
199 .with_target(true)
200 .with_thread_ids(true)
201 .with_thread_names(true);
202
203 let json_subscriber = base_subscriber
204 .json()
205 .flatten_event(true)
206 .with_current_span(false);
207
208 tracing::subscriber::set_global_default(json_subscriber.finish()).map_err(|e| {
209 Error::config(format!("Failed to initialize JSON logging: {e}"))
210 })?;
211
212 return Ok(()); }
214
215 #[cfg(not(feature = "json-logs"))]
216 {
217 return Err(Error::config(
218 "JSON logging requested but feature not enabled",
219 ));
220 }
221 }
222
223 let base_subscriber = FmtSubscriber::builder()
225 .with_env_filter(filter)
226 .with_span_events(FmtSpan::CLOSE)
227 .with_target(true)
228 .with_thread_ids(true)
229 .with_thread_names(true);
230
231 let regular_subscriber = base_subscriber
232 .with_ansi(self.config.is_colored_logging())
233 .compact();
234
235 tracing::subscriber::set_global_default(regular_subscriber.finish())
236 .map_err(|e| Error::config(format!("Failed to initialize logging: {e}")))?;
237
238 debug!(
239 "Logging initialized with level: {:?}",
240 self.config.logging.level
241 );
242 Ok(())
243 }
244
245 #[cfg(feature = "tokio")]
247 fn spawn_signal_handler(handler: Arc<SignalHandler>) -> tokio::task::JoinHandle<Result<()>> {
248 tokio::spawn(async move { handler.handle_signals().await })
249 }
250
251 #[cfg(all(feature = "async-std", not(feature = "tokio")))]
252 fn spawn_signal_handler(
253 handler: Arc<SignalHandler>,
254 ) -> async_std::task::JoinHandle<Result<()>> {
255 async_std::task::spawn(async move { handler.handle_signals().await })
256 }
257
258 pub fn get_stats(&self) -> DaemonStats {
260 let subsystem_stats = self.subsystem_manager.get_stats();
261 let shutdown_stats = self.shutdown_coordinator.get_stats();
262 let total_restarts = subsystem_stats.total_restarts;
263
264 DaemonStats {
265 name: self.config.name.clone(),
266 uptime: self.started_at.map(|t| t.elapsed()),
267 is_shutdown: shutdown_stats.is_shutdown,
268 shutdown_reason: shutdown_stats.reason,
269 subsystem_stats,
270 total_restarts,
271 }
272 }
273
274 pub fn shutdown(&self) -> bool {
276 self.shutdown_coordinator
277 .initiate_shutdown(ShutdownReason::Requested)
278 }
279
280 pub fn is_running(&self) -> bool {
282 !self.shutdown_coordinator.is_shutdown()
283 }
284
285 pub fn config(&self) -> &Config {
287 &self.config
288 }
289
290 #[cfg(feature = "config-watch")]
294 pub fn config_snapshot(&self) -> Arc<Config> {
295 self.config_shared.load_full()
296 }
297}
298
299impl Clone for Daemon {
300 fn clone(&self) -> Self {
301 Self {
302 config: Arc::clone(&self.config),
303 #[cfg(feature = "config-watch")]
304 config_shared: Arc::clone(&self.config_shared),
305 shutdown_coordinator: self.shutdown_coordinator.clone(),
306 subsystem_manager: self.subsystem_manager.clone(),
307 signal_handler: self.signal_handler.clone(),
308 #[cfg(feature = "config-watch")]
309 _config_watcher: None,
310 started_at: self.started_at,
311 }
312 }
313}
314
315#[derive(Debug, Clone)]
317pub struct DaemonStats {
318 pub name: String,
320 pub uptime: Option<std::time::Duration>,
322 pub is_shutdown: bool,
324 pub shutdown_reason: Option<crate::shutdown::ShutdownReason>,
326 pub subsystem_stats: crate::subsystem::SubsystemStats,
328 pub total_restarts: u64,
330}
331
332pub struct DaemonBuilder {
334 config: Config,
335 subsystems: Vec<SubsystemRegistrationFn>,
337 signal_config: Option<SignalConfig>,
338 enable_signals: bool,
339}
340
341impl DaemonBuilder {
342 #[must_use]
344 pub fn new(config: Config) -> Self {
345 Self {
346 config,
347 subsystems: Vec::with_capacity(16),
349 signal_config: None,
350 enable_signals: true,
351 }
352 }
353
354 #[must_use]
356 pub fn with_signal_config(mut self, config: SignalConfig) -> Self {
357 self.signal_config = Some(config);
358 self
359 }
360
361 #[must_use]
363 pub const fn without_signals(mut self) -> Self {
364 self.enable_signals = false;
365 self
366 }
367
368 #[must_use]
370 pub fn with_signals(mut self, sigterm: bool, sigint: bool) -> Self {
371 let mut config = SignalConfig::new();
372 if !sigterm {
373 config = config.without_sigterm();
374 }
375 if !sigint {
376 config = config.without_sigint();
377 }
378 self.signal_config = Some(config);
379 self
380 }
381
382 #[must_use]
395 pub fn with_task<F, Fut>(mut self, name: &str, task_fn: F) -> Self
396 where
397 F: Fn(crate::shutdown::ShutdownHandle) -> Fut + Send + Sync + 'static,
398 Fut: Future<Output = Result<()>> + Send + 'static,
399 {
400 let name = name.to_string();
402 let subsystem_fn = Box::new(move |subsystem_manager: &SubsystemManager| {
403 subsystem_manager.register_fn(&name, task_fn)
404 });
405
406 self.subsystems.push(subsystem_fn);
407 self
408 }
409
410 #[must_use]
422 pub fn with_subsystem<S>(mut self, subsystem: S) -> Self
423 where
424 S: Subsystem + Send + Sync + 'static,
425 {
426 let subsystem_fn = Box::new(move |subsystem_manager: &SubsystemManager| {
427 subsystem_manager.register(subsystem)
428 });
429
430 self.subsystems.push(subsystem_fn);
431 self
432 }
433
434 #[must_use]
448 pub fn with_subsystem_fn<F>(mut self, name: &str, register_fn: F) -> Self
449 where
450 F: FnOnce(&SubsystemManager) -> SubsystemId + Send + 'static,
451 {
452 debug!("Adding subsystem registration function for {}", name);
453 self.subsystems.push(Box::new(register_fn));
454 self
455 }
456
457 pub fn build(self) -> Result<Daemon> {
464 self.config.validate()?;
466
467 let shutdown_coordinator =
469 ShutdownCoordinator::new(self.config.shutdown.force, self.config.shutdown.kill);
470
471 let subsystem_manager = SubsystemManager::new(shutdown_coordinator.clone());
473
474 for subsystem_fn in self.subsystems {
476 let id = subsystem_fn(&subsystem_manager);
477 debug!(subsystem_id = id, "Registered subsystem");
478 }
479
480 let signal_handler = if self.enable_signals {
482 Some(Arc::new(SignalHandler::new(shutdown_coordinator.clone())))
483 } else {
484 None
485 };
486
487 let config_arc = Arc::new(self.config);
489
490 #[cfg(feature = "config-watch")]
491 let config_shared: Arc<ArcSwap<Config>> = Arc::new(ArcSwap::from(config_arc.clone()));
492
493 #[cfg(feature = "config-watch")]
495 let mut config_watcher: Option<RecommendedWatcher> = None;
496
497 #[cfg(feature = "config-watch")]
498 {
499 if config_arc.hot_reload {
500 let swap = Arc::clone(&config_shared);
501 match Config::watch_file(crate::DEFAULT_CONFIG_FILE, move |res| match res {
502 Ok(new_cfg) => {
503 swap.store(Arc::new(new_cfg));
504 info!(
505 "Configuration hot-reloaded from {}",
506 crate::DEFAULT_CONFIG_FILE
507 );
508 }
509 Err(e) => {
510 warn!(error = %e, "Configuration reload failed");
511 }
512 }) {
513 Ok(w) => {
514 config_watcher = Some(w);
515 info!("Config watcher started for {}", crate::DEFAULT_CONFIG_FILE);
516 }
517 Err(e) => {
518 warn!(error = %e, "Failed to start config watcher; continuing without hot-reload");
519 }
520 }
521 }
522 }
523
524 Ok(Daemon {
525 config: config_arc,
526 #[cfg(feature = "config-watch")]
527 config_shared,
528 shutdown_coordinator,
529 subsystem_manager,
530 signal_handler,
531 #[cfg(feature = "config-watch")]
532 _config_watcher: config_watcher,
533 started_at: None,
534 })
535 }
536
537 pub async fn run(self) -> Result<()> {
544 let daemon = self.build()?;
545 daemon.run().await
546 }
547}
548
549#[macro_export]
551macro_rules! subsystem {
552 ($name:expr, $closure:expr) => {
553 Box::new(move |shutdown: $crate::shutdown::ShutdownHandle| {
554 Box::pin($closure(shutdown)) as Pin<Box<dyn Future<Output = $crate::Result<()>> + Send>>
555 })
556 };
557}
558
559#[macro_export]
561macro_rules! task {
562 ($name:expr, $body:expr) => {
563 |shutdown: $crate::shutdown::ShutdownHandle| async move {
564 #[cfg(feature = "tokio")]
565 let mut shutdown = shutdown;
566 loop {
567 #[cfg(feature = "tokio")]
568 {
569 tokio::select! {
570 _ = shutdown.cancelled() => {
571 tracing::info!("Task '{}' shutting down", $name);
572 break;
573 }
574 _ = async { $body } => {}
575 }
576 }
577
578 #[cfg(all(feature = "async-std", not(feature = "tokio")))]
579 {
580 if shutdown.is_shutdown() {
581 tracing::info!("Task '{}' shutting down", $name);
582 break;
583 }
584 $body;
586 async_std::task::sleep(std::time::Duration::from_millis(10)).await;
588 }
589 }
590 Ok(())
591 }
592 };
593}
594
595#[cfg(test)]
596mod tests {
597 use super::*;
598 use std::pin::Pin;
599 use std::time::Duration;
600
601 async fn test_subsystem(shutdown: crate::shutdown::ShutdownHandle) -> Result<()> {
602 #[cfg(feature = "tokio")]
603 let mut shutdown = shutdown;
604 loop {
605 #[cfg(feature = "tokio")]
606 {
607 tokio::select! {
608 () = shutdown.cancelled() => break,
609 () = tokio::time::sleep(Duration::from_millis(10)) => {}
610 }
611 }
612
613 #[cfg(all(feature = "async-std", not(feature = "tokio")))]
614 {
615 if shutdown.is_shutdown() {
616 break;
617 }
618 async_std::task::sleep(Duration::from_millis(10)).await;
619 }
620 }
621 Ok(())
622 }
623
624 #[cfg(feature = "tokio")]
625 #[cfg_attr(miri, ignore)]
626 #[tokio::test]
627 async fn test_daemon_builder() {
628 let test_result = tokio::time::timeout(Duration::from_secs(5), async {
630 let config = Config::new().unwrap();
631 let daemon = Daemon::builder(config)
632 .with_subsystem_fn("test", |subsystem_manager| {
633 subsystem_manager.register_fn("test_subsystem", test_subsystem)
634 })
635 .build()
636 .unwrap();
637
638 assert!(daemon.is_running());
639 assert_eq!(daemon.config().name, "proc-daemon");
640
641 daemon.shutdown();
643 })
644 .await;
645
646 assert!(test_result.is_ok(), "Test timed out after 5 seconds");
647 }
648
649 #[cfg(all(feature = "async-std", not(feature = "tokio")))]
650 #[async_std::test]
651 async fn test_daemon_builder() {
652 let test_result = async_std::future::timeout(Duration::from_secs(5), async {
654 let config = Config::new().unwrap();
655 let daemon = Daemon::builder(config)
656 .with_subsystem_fn("test", |subsystem_manager| {
657 subsystem_manager.register_fn("test_subsystem", test_subsystem)
658 })
659 .build()
660 .unwrap();
661
662 assert!(daemon.is_running());
663 assert_eq!(daemon.config().name, "proc-daemon");
664
665 daemon.shutdown();
667 })
668 .await;
669
670 assert!(test_result.is_ok(), "Test timed out after 5 seconds");
671 }
672
673 #[cfg(feature = "tokio")]
674 #[cfg_attr(miri, ignore)]
675 #[tokio::test]
676 async fn test_daemon_with_defaults() {
677 let test_result = tokio::time::timeout(Duration::from_secs(5), async {
679 let builder = Daemon::with_defaults().unwrap();
680 let daemon = builder
681 .with_task("simple_task", |_shutdown| async {
682 tokio::time::sleep(Duration::from_millis(10)).await;
683 Ok(())
684 })
685 .build()
686 .unwrap();
687
688 assert!(daemon.is_running());
689
690 daemon.shutdown();
692 })
693 .await;
694
695 assert!(test_result.is_ok(), "Test timed out after 5 seconds");
696 }
697
698 #[cfg(all(feature = "async-std", not(feature = "tokio")))]
699 #[async_std::test]
700 async fn test_daemon_with_defaults() {
701 let test_result = async_std::future::timeout(Duration::from_secs(5), async {
703 let builder = Daemon::with_defaults().unwrap();
704 let daemon = builder
705 .with_task("simple_task", |_shutdown| async {
706 async_std::task::sleep(Duration::from_millis(10)).await;
707 Ok(())
708 })
709 .build()
710 .unwrap();
711
712 assert!(daemon.is_running());
713
714 daemon.shutdown();
716 })
717 .await;
718
719 assert!(test_result.is_ok(), "Test timed out after 5 seconds");
720 }
721
722 #[cfg(feature = "tokio")]
723 #[cfg_attr(miri, ignore)]
724 #[tokio::test]
725 async fn test_daemon_shutdown() {
726 let test_result = tokio::time::timeout(Duration::from_secs(5), async {
728 let config = Config::builder()
729 .name("test-daemon")
730 .shutdown_timeout(Duration::from_millis(100))
731 .unwrap()
732 .build()
733 .unwrap();
734
735 let daemon = Daemon::builder(config)
736 .with_subsystem_fn("test", |subsystem_manager| {
737 subsystem_manager.register_fn("test_subsystem", test_subsystem)
738 })
739 .without_signals()
740 .build()
741 .unwrap();
742
743 daemon.shutdown();
745 assert!(!daemon.is_running());
746 })
747 .await;
748
749 assert!(test_result.is_ok(), "Test timed out after 5 seconds");
750 }
751
752 #[cfg(all(feature = "async-std", not(feature = "tokio")))]
753 #[async_std::test]
754 async fn test_daemon_shutdown() {
755 let test_result = async_std::future::timeout(Duration::from_secs(5), async {
757 let config = Config::builder()
758 .name("test-daemon")
759 .shutdown_timeout(Duration::from_millis(100))
760 .unwrap()
761 .build()
762 .unwrap();
763
764 let daemon = Daemon::builder(config)
765 .with_subsystem_fn("test", |subsystem_manager| {
766 subsystem_manager.register_fn("test_subsystem", test_subsystem)
767 })
768 .without_signals()
769 .build()
770 .unwrap();
771
772 daemon.shutdown();
774 assert!(!daemon.is_running());
775 })
776 .await;
777
778 assert!(test_result.is_ok(), "Test timed out after 5 seconds");
779 }
780
781 #[test]
782 fn test_daemon_stats() {
783 let config = Config::new().unwrap();
784 let daemon = Daemon::builder(config).build().unwrap();
785
786 let stats = daemon.get_stats();
787 assert_eq!(stats.name, "proc-daemon");
788 assert!(stats.uptime.is_none()); assert!(!stats.is_shutdown);
790 }
791
792 struct TestSubsystemStruct {
793 name: String,
794 }
795
796 impl TestSubsystemStruct {
797 fn new(name: &str) -> Self {
798 Self {
799 name: name.to_string(),
800 }
801 }
802 }
803
804 impl Subsystem for TestSubsystemStruct {
805 fn run(
806 &self,
807 shutdown: crate::shutdown::ShutdownHandle,
808 ) -> Pin<Box<dyn Future<Output = Result<()>> + Send>> {
809 Box::pin(async move {
810 #[cfg(feature = "tokio")]
811 let mut shutdown = shutdown;
812 loop {
813 #[cfg(feature = "tokio")]
814 {
815 tokio::select! {
816 () = shutdown.cancelled() => break,
817 () = tokio::time::sleep(Duration::from_millis(10)) => {}
818 }
819 }
820
821 #[cfg(all(feature = "async-std", not(feature = "tokio")))]
822 {
823 if shutdown.is_shutdown() {
824 break;
825 }
826 async_std::task::sleep(Duration::from_millis(10)).await;
827 }
828 }
829 Ok(())
830 })
831 }
832
833 fn name(&self) -> &str {
834 &self.name
835 }
836 }
837
838 #[cfg(feature = "tokio")]
839 #[cfg_attr(miri, ignore)]
840 #[tokio::test]
841 async fn test_daemon_with_struct_subsystem() {
842 let test_result = tokio::time::timeout(Duration::from_secs(5), async {
844 let config = Config::new().unwrap();
845 let subsystem = TestSubsystemStruct::new("struct_test");
846
847 let daemon = Daemon::builder(config)
848 .with_subsystem(subsystem)
849 .without_signals()
850 .build()
851 .unwrap();
852
853 let stats = daemon.get_stats();
854 assert_eq!(stats.subsystem_stats.total_subsystems, 1);
855
856 daemon.shutdown();
858 })
859 .await;
860
861 assert!(test_result.is_ok(), "Test timed out after 5 seconds");
862 }
863
864 #[cfg(all(feature = "async-std", not(feature = "tokio")))]
865 #[async_std::test]
866 async fn test_daemon_with_struct_subsystem() {
867 let test_result = async_std::future::timeout(Duration::from_secs(5), async {
869 let config = Config::new().unwrap();
870 let subsystem = TestSubsystemStruct::new("struct_test");
871
872 let daemon = Daemon::builder(config)
873 .with_subsystem(subsystem)
874 .without_signals()
875 .build()
876 .unwrap();
877
878 let stats = daemon.get_stats();
879 assert_eq!(stats.subsystem_stats.total_subsystems, 1);
880
881 daemon.shutdown();
883 })
884 .await;
885
886 assert!(test_result.is_ok(), "Test timed out after 5 seconds");
887 }
888
889 #[cfg(feature = "tokio")]
890 #[cfg_attr(miri, ignore)]
891 #[tokio::test]
892 async fn test_daemon_signal_configuration() {
893 let test_result = tokio::time::timeout(Duration::from_secs(5), async {
895 let config = Config::new().unwrap();
896 let signal_config = SignalConfig::new().with_sighup().without_sigint();
897
898 let daemon = Daemon::builder(config)
899 .with_signal_config(signal_config)
900 .build()
901 .unwrap();
902
903 assert!(daemon.signal_handler.is_some());
904
905 daemon.shutdown();
907 })
908 .await;
909
910 assert!(test_result.is_ok(), "Test timed out after 5 seconds");
911 }
912
913 #[cfg(all(feature = "async-std", not(feature = "tokio")))]
914 #[async_std::test]
915 async fn test_daemon_signal_configuration() {
916 let test_result = async_std::future::timeout(Duration::from_secs(5), async {
918 let config = Config::new().unwrap();
919 let signal_config = SignalConfig::new().with_sighup().without_sigint();
920
921 let daemon = Daemon::builder(config)
922 .with_signal_config(signal_config)
923 .build()
924 .unwrap();
925
926 assert!(daemon.signal_handler.is_some());
927
928 daemon.shutdown();
930 })
931 .await;
932
933 assert!(test_result.is_ok(), "Test timed out after 5 seconds");
934 }
935
936 #[cfg(feature = "tokio")]
937 #[cfg_attr(miri, ignore)]
938 #[tokio::test]
939 async fn test_macro_usage() {
940 let test_result = tokio::time::timeout(Duration::from_secs(5), async {
942 let config = Config::new().unwrap();
943
944 let daemon = Daemon::builder(config)
945 .with_task(
946 "macro_test",
947 task!("macro_test", {
948 tokio::time::sleep(Duration::from_millis(1)).await;
949 }),
950 )
951 .without_signals()
952 .build()
953 .unwrap();
954
955 let stats = daemon.get_stats();
956 assert_eq!(stats.subsystem_stats.total_subsystems, 1);
957
958 daemon.shutdown();
960 })
961 .await;
962
963 assert!(test_result.is_ok(), "Test timed out after 5 seconds");
964 }
965
966 #[cfg(all(feature = "async-std", not(feature = "tokio")))]
967 #[async_std::test]
968 async fn test_macro_usage() {
969 let test_result = async_std::future::timeout(Duration::from_secs(5), async {
971 let config = Config::new().unwrap();
972
973 let daemon = Daemon::builder(config)
974 .with_task(
975 "macro_test",
976 task!("macro_test", {
977 async_std::task::sleep(Duration::from_millis(1)).await;
978 }),
979 )
980 .without_signals()
981 .build()
982 .unwrap();
983
984 let stats = daemon.get_stats();
985 assert_eq!(stats.subsystem_stats.total_subsystems, 1);
986
987 daemon.shutdown();
989 })
990 .await;
991
992 assert!(test_result.is_ok(), "Test timed out after 5 seconds");
993 }
994}