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    /// Title: 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    /// Title: 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    /// Title: Stop the server
39    ///
40    /// Parameters:
41    /// - None
42    ///
43    /// Returns:
44    /// - `Result<(), Box<dyn std::error::Error>>`: 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) -> Result<(), Box<dyn std::error::Error>> {
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    /// Title: Start the server in daemon mode on non-Unix platforms
54    ///
55    /// Parameters:
56    /// - None
57    ///
58    /// Returns:
59    /// - `Result<(), Box<dyn std::error::Error>>`: 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) -> Result<(), Box<dyn std::error::Error>> {
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    /// Title: Start the server in daemon mode on Windows platforms
86    ///
87    /// Parameters:
88    /// - None
89    ///
90    /// Returns:
91    /// - `Result<(), Box<dyn std::error::Error>>`: Operation result.
92    ///
93    /// This function starts a detached process on Windows using Windows API.
94    pub fn start_daemon(&self) -> Result<(), Box<dyn std::error::Error>> {
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    /// Title: Read process ID from the PID file
117    ///
118    /// Parameters:
119    /// - None
120    ///
121    /// Returns:
122    /// - `Result<i32, Box<dyn std::error::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    /// Title: Write current process ID to the PID file
132    ///
133    /// Parameters:
134    /// - None
135    ///
136    /// Returns:
137    /// - `Result<(), Box<dyn std::error::Error>>`: 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) -> Result<(), Box<dyn std::error::Error>> {
141        if let Some(parent) = std::path::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    /// Title: Kill process by PID on Unix platforms
150    ///
151    /// Parameters:
152    /// - `pid`: The process ID to kill.
153    ///
154    /// Returns:
155    /// - `Result<(), Box<dyn std::error::Error>>`: 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) -> Result<(), Box<dyn std::error::Error>> {
160        let result: Result<std::process::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    /// Title: Kill process by PID on Windows platforms
179    ///
180    /// Parameters:
181    /// - ``pid``: The process ID to kill.
182    ///
183    /// Returns:
184    /// - ``Result<(), Box<dyn std::error::Error>>``: 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) -> Result<(), Box<dyn std::error::Error>> {
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}