Skip to main content

cordis_cli/
worker.rs

1//! Worker runtime: boot the loader, watch the config, exit on signals.
2
3use cordis::Context;
4use cordis_loader::{Loader, LoaderConfig};
5use std::path::Path;
6use std::sync::Arc;
7
8/// Exit code asking the daemon for a hot restart.
9pub const EXIT_RESTART: i32 = 51;
10/// Exit code telling the daemon to quit without restarting.
11pub const EXIT_QUIT: i32 = 52;
12
13/// Handle exposed as the `worker` service so plugins can stop or restart
14/// the process (upstream's `ctx.loader.exit` / full-reload protocol).
15pub struct WorkerHandle {
16    inner: Arc<WorkerInner>,
17}
18
19impl WorkerHandle {
20    /// Hot restart: dispose everything and ask the daemon for a new worker.
21    pub fn restart(&self) -> ! {
22        self.inner.teardown();
23        std::process::exit(EXIT_RESTART);
24    }
25
26    /// Quit: dispose everything and tell the daemon not to restart.
27    pub fn shutdown(&self) -> ! {
28        self.inner.teardown();
29        std::process::exit(EXIT_QUIT);
30    }
31}
32
33/// Everything the worker owns; shared with the signal handler.
34struct WorkerInner {
35    root: Context,
36    loader: Option<Loader>,
37}
38
39impl WorkerInner {
40    fn teardown(&self) {
41        if let Some(loader) = &self.loader {
42            let _ = loader.dispose();
43        }
44        let _ = self.root.fiber().and_then(|fiber| fiber.dispose());
45    }
46}
47
48/// Run the worker process: load dotenv, boot the loader, watch the entry
49/// file, and block until a signal (or a `worker` service call) exits the
50/// process. Never returns.
51pub fn run(config_path: &Path) -> ! {
52    let root = Context::new();
53    let loader = match Loader::open(&root, LoaderConfig::new(config_path)) {
54        Ok(loader) => loader,
55        Err(error) => {
56            eprintln!(
57                "cordis: failed to start from {}: {error}",
58                config_path.display()
59            );
60            std::process::exit(EXIT_QUIT);
61        }
62    };
63    let inner = Arc::new(WorkerInner {
64        root: root.clone(),
65        loader: Some(loader.clone()),
66    });
67
68    let handle = Arc::new(WorkerHandle {
69        inner: Arc::clone(&inner),
70    });
71    if let Err(error) = root.provide_arc("worker", handle) {
72        eprintln!("cordis: could not expose the worker service: {error}");
73    }
74
75    let signal_inner = Arc::clone(&inner);
76    if ctrlc::set_handler(move || {
77        eprintln!("cordis: signal received, shutting down");
78        signal_inner.teardown();
79        std::process::exit(EXIT_QUIT);
80    })
81    .is_err()
82    {
83        eprintln!("cordis: could not install signal handlers");
84    }
85
86    match loader.watch() {
87        Ok(_watcher) => {}
88        Err(error) => eprintln!(
89            "cordis: config hot reload disabled ({error}); restart manually to apply changes"
90        ),
91    }
92
93    eprintln!(
94        "cordis: worker ready ({} entries, config: {})",
95        loader.tree().entries().len(),
96        config_path.display()
97    );
98
99    // The worker's work happens on fiber threads and watcher callbacks;
100    // park until a signal or service call ends the process.
101    loop {
102        std::thread::park();
103    }
104}