glory_cli/service/
serve.rs

1use std::sync::Arc;
2
3use crate::{
4    config::Project,
5    ext::{anyhow::Result, append_str_to_filename, determine_pdb_filename, fs},
6    logger::GRAY,
7    signal::{Interrupt, ReloadSignal, ServerRestart},
8};
9use camino::Utf8PathBuf;
10use tokio::{
11    process::{Child, Command},
12    select,
13    task::JoinHandle,
14};
15
16pub async fn spawn(proj: &Arc<Project>) -> JoinHandle<Result<()>> {
17    let mut int = Interrupt::subscribe_shutdown();
18    let proj = proj.clone();
19    let mut change = ServerRestart::subscribe();
20    tokio::spawn(async move {
21        let mut server = ServerProcess::start_new(&proj).await?;
22        loop {
23            select! {
24              res = change.recv() => {
25                if let Ok(()) = res {
26                      server.restart().await?;
27                      ReloadSignal::send_full();
28                }
29              },
30              _ = int.recv() => {
31                    server.kill().await;
32                    return Ok(())
33              },
34            }
35        }
36    })
37}
38
39struct ServerProcess {
40    process: Option<Child>,
41    envs: Vec<(&'static str, String)>,
42    binary: Utf8PathBuf,
43}
44
45impl ServerProcess {
46    fn new(proj: &Project) -> Self {
47        Self {
48            process: None,
49            envs: proj.to_envs(),
50            binary: proj.bin.exe_file.clone(),
51        }
52    }
53
54    async fn start_new(proj: &Project) -> Result<Self> {
55        let mut me = Self::new(proj);
56        me.start().await?;
57        Ok(me)
58    }
59
60    async fn kill(&mut self) {
61        if let Some(proc) = self.process.as_mut() {
62            if let Err(e) = proc.kill().await {
63                log::error!("Serve error killing server process: {e}");
64            } else {
65                log::trace!("Serve stopped");
66            }
67            self.process = None;
68        }
69    }
70
71    async fn restart(&mut self) -> Result<()> {
72        self.kill().await;
73        self.start().await?;
74        log::trace!("Serve restarted");
75        Ok(())
76    }
77
78    async fn start(&mut self) -> Result<()> {
79        let bin = &self.binary;
80        let child = if bin.exists() {
81            // windows doesn't like to overwrite a running binary, so we copy it to a new name
82            let bin_path = if cfg!(target_os = "windows") {
83                // solution to allow cargo to overwrite a running binary on some platforms:
84                //   copy cargo's output bin to [filename].serving and then run it
85                let new_bin_path = append_str_to_filename(bin, ".serving")?;
86                log::debug!(
87                    "Copying server binary {} to {}",
88                    GRAY.paint(bin.as_str()),
89                    GRAY.paint(new_bin_path.as_str())
90                );
91                fs::copy(bin, &new_bin_path).await?;
92                // also copy the .pdb file if it exists to allow debugging to attach
93                if let Some(pdb) = determine_pdb_filename(bin) {
94                    let new_pdb_path = append_str_to_filename(&pdb, ".serving")?;
95                    log::debug!(
96                        "Copying server binary debug info {} to {}",
97                        GRAY.paint(pdb.as_str()),
98                        GRAY.paint(new_pdb_path.as_str())
99                    );
100                    fs::copy(&pdb, &new_pdb_path).await?;
101                }
102                new_bin_path
103            } else {
104                bin.clone()
105            };
106
107            log::debug!("Serve running {}", GRAY.paint(bin_path.as_str()));
108            let cmd = Some(Command::new(bin_path).envs(self.envs.clone()).spawn()?);
109            let port = self
110                .envs
111                .iter()
112                .find_map(|(k, v)| if k == &"GLORY_SITE_ADDR" { Some(v.to_string()) } else { None })
113                .unwrap_or_default();
114            log::info!("Serving at http://{port}");
115            cmd
116        } else {
117            log::debug!("Serve no exe found {}", GRAY.paint(bin.as_str()));
118            None
119        };
120        self.process = child;
121        Ok(())
122    }
123}