server_manager/manager/
impl.rs

1use crate::*;
2
3impl<F, Fut> ServerManager<F>
4where
5    F: Fn() -> Fut,
6    Fut: std::future::Future<Output = ()>,
7{
8    /// Create a new ServerManager instance
9    ///
10    /// Parameters:
11    /// - `config`: The server configuration containing PID file path and log paths.
12    /// - `server_fn`: A closure representing the asynchronous server function.
13    ///
14    /// Returns:
15    /// - `ServerManager<F>`: A new instance of ServerManager.
16    pub fn new(config: ServerManagerConfig, server_fn: F) -> Self {
17        Self { config, server_fn }
18    }
19
20    /// Start the server in foreground mode
21    ///
22    /// Parameters:
23    /// - None
24    ///
25    /// Returns:
26    /// - `()`: No return value.
27    ///
28    /// This function writes the current process ID to the PID file specified in the configuration
29    /// and then runs the server function asynchronously.
30    pub async fn start(&self) {
31        if let Err(e) = self.write_pid_file() {
32            eprintln!("Failed to write pid file: {}", e);
33            return;
34        }
35        (self.server_fn)().await;
36    }
37
38    /// Stop the server
39    ///
40    /// Parameters:
41    /// - None
42    ///
43    /// Returns:
44    /// - `ServerManagerResult`: Operation result.
45    ///
46    /// This function reads the process ID from the PID file and attempts to kill the process using a SIGTERM signal.
47    pub fn stop(&self) -> ServerManagerResult {
48        let pid: i32 = self.read_pid_file()?;
49        self.kill_process(pid)
50    }
51
52    /// Start the server in daemon (background) mode on Unix platforms.
53    /// Start the server in daemon mode on non-Unix platforms
54    ///
55    /// Parameters:
56    /// - None
57    ///
58    /// Returns:
59    /// - `ServerManagerResult`: Operation result.
60    ///
61    /// This function returns an error because daemon mode is not supported on non-Unix platforms.
62    #[cfg(not(windows))]
63    pub fn start_daemon(&self) -> ServerManagerResult {
64        if std::env::var(RUNNING_AS_DAEMON).is_ok() {
65            self.write_pid_file()?;
66            let rt: Runtime = Runtime::new()?;
67            rt.block_on(async {
68                (self.server_fn)().await;
69            });
70            return Ok(());
71        }
72        let exe_path: PathBuf = std::env::current_exe()?;
73        let mut cmd: Command = Command::new(exe_path);
74        cmd.env(RUNNING_AS_DAEMON, RUNNING_AS_DAEMON_VALUE)
75            .stdout(Stdio::null())
76            .stderr(Stdio::null())
77            .stdin(Stdio::null());
78        cmd.spawn()
79            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
80        Ok(())
81    }
82
83    #[cfg(windows)]
84    /// Start the server in daemon (background) mode on Windows platforms
85    /// Start the server in daemon mode on Windows platforms
86    ///
87    /// Parameters:
88    /// - None
89    ///
90    /// Returns:
91    /// - `ServerManagerResult`: Operation result.
92    ///
93    /// This function starts a detached process on Windows using Windows API.
94    pub fn start_daemon(&self) -> ServerManagerResult {
95        use std::os::windows::process::CommandExt;
96        if std::env::var(RUNNING_AS_DAEMON).is_ok() {
97            self.write_pid_file()?;
98            let rt: Runtime = Runtime::new()?;
99            rt.block_on(async {
100                (self.server_fn)().await;
101            });
102            return Ok(());
103        }
104        let exe_path: PathBuf = std::env::current_exe()?;
105        let mut cmd: Command = Command::new(exe_path);
106        cmd.env(RUNNING_AS_DAEMON, RUNNING_AS_DAEMON_VALUE)
107            .stdout(Stdio::null())
108            .stderr(Stdio::null())
109            .stdin(Stdio::null())
110            .creation_flags(0x00000008);
111        cmd.spawn()
112            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
113        Ok(())
114    }
115
116    /// Read process ID from the PID file
117    ///
118    /// Parameters:
119    /// - None
120    ///
121    /// Returns:
122    /// - `Result<i32, Box<dyn Error>>`: The process ID if successful.
123    ///
124    /// This function reads the content of the PID file specified in the configuration and parses it as an integer.    
125    fn read_pid_file(&self) -> Result<i32, Box<dyn std::error::Error>> {
126        let pid_str: String = fs::read_to_string(&self.config.pid_file)?;
127        let pid: i32 = pid_str.trim().parse::<i32>()?;
128        Ok(pid)
129    }
130
131    /// Write current process ID to the PID file
132    ///
133    /// Parameters:
134    /// - None
135    ///
136    /// Returns:
137    /// - `ServerManagerResult`: Operation result.
138    ///
139    /// This function obtains the current process ID and writes it as a string to the PID file specified in the configuration.
140    fn write_pid_file(&self) -> ServerManagerResult {
141        if let Some(parent) = Path::new(&self.config.pid_file).parent() {
142            fs::create_dir_all(parent)?;
143        }
144        let pid: u32 = id();
145        fs::write(&self.config.pid_file, pid.to_string())?;
146        Ok(())
147    }
148
149    /// Kill process by PID on Unix platforms
150    ///
151    /// Parameters:
152    /// - `pid`: The process ID to kill.
153    ///
154    /// Returns:
155    /// - `ServerManagerResult`: Operation result.
156    ///
157    /// This function sends a SIGTERM signal to the process with the given PID using libc::kill.
158    #[cfg(not(windows))]
159    fn kill_process(&self, pid: i32) -> ServerManagerResult {
160        let result: Result<Output, std::io::Error> = Command::new("kill")
161            .arg("-TERM")
162            .arg(pid.to_string())
163            .output();
164        match result {
165            Ok(output) if output.status.success() => Ok(()),
166            Ok(output) => Err(format!(
167                "Failed to kill process with pid: {}, error: {}",
168                pid,
169                String::from_utf8_lossy(&output.stderr)
170            )
171            .into()),
172            Err(e) => Err(format!("Failed to execute kill command: {}", e).into()),
173        }
174    }
175
176    #[cfg(windows)]
177    /// Kill process by PID on Windows platforms
178    /// Kill process by PID on Windows platforms
179    ///
180    /// Parameters:
181    /// - ``pid``: The process ID to kill.
182    ///
183    /// Returns:
184    /// - ``ServerManagerResult``: Operation result.
185    ///
186    /// This function attempts to kill the process with the given PID using Windows API.
187    /// If opening or terminating the process fails, the detailed error code is returned.
188    fn kill_process(&self, pid: i32) -> ServerManagerResult {
189        use std::ffi::c_void;
190        type DWORD = u32;
191        type BOOL = i32;
192        type HANDLE = *mut c_void;
193        type UINT = u32;
194        const PROCESS_TERMINATE: DWORD = 0x0001;
195        const PROCESS_ALL_ACCESS: DWORD = 0x1F0FFF;
196        unsafe extern "system" {
197            fn OpenProcess(
198                dwDesiredAccess: DWORD,
199                bInheritHandle: BOOL,
200                dwProcessId: DWORD,
201            ) -> HANDLE;
202            fn TerminateProcess(hProcess: HANDLE, uExitCode: UINT) -> BOOL;
203            fn CloseHandle(hObject: HANDLE) -> BOOL;
204            fn GetLastError() -> DWORD;
205        }
206        let process_id: DWORD = pid as DWORD;
207        let mut process_handle: HANDLE = unsafe { OpenProcess(PROCESS_TERMINATE, 0, process_id) };
208        if process_handle.is_null() {
209            process_handle = unsafe { OpenProcess(PROCESS_ALL_ACCESS, 0, process_id) };
210        }
211        if process_handle.is_null() {
212            let error_code = unsafe { GetLastError() };
213            return Err(format!(
214                "Failed to open process with pid: {}. Error code: {}",
215                pid, error_code
216            )
217            .into());
218        }
219        let terminate_result: BOOL = unsafe { TerminateProcess(process_handle, 1) };
220        if terminate_result == 0 {
221            let error_code = unsafe { GetLastError() };
222            unsafe {
223                CloseHandle(process_handle);
224            }
225            return Err(format!(
226                "Failed to terminate process with pid: {}. Error code: {}",
227                pid, error_code
228            )
229            .into());
230        }
231        unsafe {
232            CloseHandle(process_handle);
233        }
234        Ok(())
235    }
236
237    /// Run the server with cargo-watch
238    ///
239    /// Parameters:
240    /// - `run_args`: Arguments to pass to `cargo-watch`.
241    /// - `wait`: Whether to wait for the process to finish.
242    ///
243    /// Returns:
244    /// - `ServerManagerResult`: Operation result.
245    fn run_with_cargo_watch(&self, run_args: &[&str], wait: bool) -> ServerManagerResult {
246        let cargo_watch_installed: Output = Command::new("cargo")
247            .arg("install")
248            .arg("--list")
249            .output()?;
250        if !String::from_utf8_lossy(&cargo_watch_installed.stdout).contains("cargo-watch") {
251            eprintln!("Cargo-watch not found. Attempting to install...");
252            let install_status: ExitStatus = Command::new("cargo")
253                .arg("install")
254                .arg("cargo-watch")
255                .stdout(Stdio::inherit())
256                .stderr(Stdio::inherit())
257                .spawn()?
258                .wait()?;
259            if !install_status.success() {
260                return Err("Failed to install cargo-watch. Please install it manually: `cargo install cargo-watch`".into());
261            }
262            eprintln!("Cargo-watch installed successfully.");
263        }
264        let mut command: Command = Command::new("cargo-watch");
265        command
266            .args(run_args)
267            .stdout(Stdio::inherit())
268            .stderr(Stdio::inherit())
269            .stdin(Stdio::inherit());
270        let mut child: Child = command
271            .spawn()
272            .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
273        if wait {
274            child
275                .wait()
276                .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
277        }
278        exit(0);
279    }
280
281    /// Start the server with hot-reloading using cargo-watch
282    ///
283    /// Parameters:
284    /// - `run_args`: Arguments to pass to `cargo-watch`.
285    ///
286    /// Returns:
287    /// - `ServerManagerResult`: Operation result.
288    ///
289    /// This function checks for `cargo-watch` installation, installs it if missing,
290    /// and then uses it to run the server with hot-reloading.
291    pub fn hot_restart(&self, run_args: &[&str]) -> ServerManagerResult {
292        self.run_with_cargo_watch(run_args, false)
293    }
294
295    /// Start the server with hot-reloading using cargo-watch and exit immediately
296    ///
297    /// Parameters:
298    /// - `run_args`: Arguments to pass to `cargo-watch`.
299    ///
300    /// Returns:
301    /// - `ServerManagerResult`: Operation result.
302    ///
303    /// Same as `hot_restart` but does not wait for the process to finish.
304    pub fn hot_restart_wait(&self, run_args: &[&str]) -> ServerManagerResult {
305        self.run_with_cargo_watch(run_args, true)
306    }
307}