server_manager/impl.rs
1use super::*;
2
3/// Provides a default implementation for `ServerManager`.
4impl Default for ServerManager {
5 /// Creates a default `ServerManager` instance with empty hooks and no PID file configured.
6 #[inline(always)]
7 fn default() -> Self {
8 let empty_hook: ServerManagerHook = Arc::new(|| Box::pin(async {}));
9 Self {
10 pid_file: Default::default(),
11 stop_hook: empty_hook.clone(),
12 server_hook: empty_hook.clone(),
13 start_hook: empty_hook,
14 }
15 }
16}
17
18/// Implementation of server management operations.
19///
20/// Provides methods for starting, stopping and managing server processes.
21impl ServerManager {
22 /// Creates a new `ServerManager` instance.
23 ///
24 /// This is a convenience method that calls `ServerManager::default()`.
25 #[inline(always)]
26 pub fn new() -> Self {
27 Self::default()
28 }
29
30 /// Starts the server in foreground mode.
31 ///
32 /// Writes the current process ID to the PID file and executes the server function.
33 pub async fn start(&self) {
34 (self.get_start_hook())().await;
35 if let Err(e) = self.write_pid_file() {
36 eprintln!("Failed to write pid file: {e}");
37 return;
38 }
39 (self.get_server_hook())().await;
40 }
41
42 /// Stops the running server process.
43 ///
44 /// Reads PID from file and terminates the process.
45 ///
46 /// # Returns
47 ///
48 /// - `ServerManagerResult` - Operation result.
49 pub async fn stop(&self) -> ServerManagerResult {
50 (self.get_stop_hook())().await;
51 let pid: i32 = self.read_pid_file()?;
52 self.kill_process(pid)
53 }
54
55 /// Starts the server in daemon (background) mode on Unix platforms.
56 ///
57 /// # Returns
58 ///
59 /// - `ServerManagerResult` - Operation result.
60 #[cfg(not(windows))]
61 pub async fn start_daemon(&self) -> ServerManagerResult {
62 (self.get_start_hook())().await;
63 if std::env::var(RUNNING_AS_DAEMON).is_ok() {
64 self.write_pid_file()?;
65 let rt: Runtime = Runtime::new()?;
66 rt.block_on(async {
67 (self.get_server_hook())().await;
68 });
69 return Ok(());
70 }
71 let exe_path: PathBuf = std::env::current_exe()?;
72 let mut cmd: Command = Command::new(exe_path);
73 cmd.env(RUNNING_AS_DAEMON, RUNNING_AS_DAEMON_VALUE)
74 .stdout(Stdio::null())
75 .stderr(Stdio::null())
76 .stdin(Stdio::null());
77 cmd.spawn()
78 .map_err(|error: Error| Box::new(error) as Box<dyn std::error::Error>)?;
79 Ok(())
80 }
81
82 /// Starts the server in daemon (background) mode on Windows platforms.
83 ///
84 /// # Returns
85 ///
86 /// - `ServerManagerResult` - Operation result.
87 #[cfg(windows)]
88 pub async fn start_daemon(&self) -> ServerManagerResult {
89 (self.get_start_hook())().await;
90 if std::env::var(RUNNING_AS_DAEMON).is_ok() {
91 self.write_pid_file()?;
92 let rt: Runtime = Runtime::new()?;
93 rt.block_on(async {
94 (self.get_server_hook())().await;
95 });
96 return Ok(());
97 }
98 let exe_path: PathBuf = std::env::current_exe()?;
99 let mut cmd: Command = Command::new(exe_path);
100 cmd.env(RUNNING_AS_DAEMON, RUNNING_AS_DAEMON_VALUE)
101 .stdout(Stdio::null())
102 .stderr(Stdio::null())
103 .stdin(Stdio::null())
104 .creation_flags(0x00000008);
105 cmd.spawn()
106 .map_err(|error: Error| Box::new(error) as Box<dyn std::error::Error>)?;
107 Ok(())
108 }
109
110 /// Reads process ID from the PID file.
111 ///
112 /// # Returns
113 ///
114 /// - `Result<i32, Box<dyn std::error::Error>>` - Process ID if successful.
115 fn read_pid_file(&self) -> Result<i32, Box<dyn std::error::Error>> {
116 let pid_str: String = fs::read_to_string(self.get_pid_file())?;
117 let pid: i32 = pid_str.trim().parse::<i32>()?;
118 Ok(pid)
119 }
120
121 /// Writes current process ID to the PID file.
122 ///
123 /// # Returns
124 ///
125 /// - `ServerManagerResult` - Operation result.
126 fn write_pid_file(&self) -> ServerManagerResult {
127 if let Some(parent) = Path::new(self.get_pid_file()).parent() {
128 fs::create_dir_all(parent)?;
129 }
130 let pid: u32 = id();
131 fs::write(self.get_pid_file(), pid.to_string())?;
132 Ok(())
133 }
134
135 /// Kills process by PID on Unix platforms.
136 ///
137 /// # Arguments
138 ///
139 /// - `i32` - The ID of the process to terminate.
140 ///
141 /// # Returns
142 ///
143 /// - `ServerManagerResult` - Operation result.
144 #[cfg(not(windows))]
145 fn kill_process(&self, pid: i32) -> ServerManagerResult {
146 match Command::new(KILL)
147 .arg(KILL_SIGNAL)
148 .arg(pid.to_string())
149 .output()
150 {
151 Ok(output) if output.status.success() => Ok(()),
152 Ok(output) => Err(format!(
153 "Failed to kill process with pid: {}, error: {}",
154 pid,
155 String::from_utf8_lossy(&output.stderr)
156 )
157 .into()),
158 Err(e) => Err(format!("Failed to execute kill command: {}", e).into()),
159 }
160 }
161
162 /// Kills process by PID on Windows platforms.
163 ///
164 /// # Arguments
165 ///
166 /// - `i32` - The ID of the process to terminate.
167 ///
168 /// # Returns
169 ///
170 /// - `ServerManagerResult` - Operation result.
171 #[cfg(windows)]
172 fn kill_process(&self, pid: i32) -> ServerManagerResult {
173 unsafe extern "system" {
174 /// Opens a process object with the requested access rights.
175 ///
176 /// # Arguments
177 ///
178 /// - `u32` - The access rights requested for the process handle.
179 /// - `i32` - Whether the returned handle is inheritable by child processes.
180 /// - `u32` - The identifier of the process to open.
181 ///
182 /// # Returns
183 ///
184 /// - `*mut c_void` - The process handle, or a null pointer on failure.
185 fn OpenProcess(
186 dwDesiredAccess: u32,
187 bInheritHandle: i32,
188 dwProcessId: u32,
189 ) -> *mut c_void;
190 /// Terminates a process and all of its child processes.
191 ///
192 /// # Arguments
193 ///
194 /// - `*mut c_void` - The process handle returned by `OpenProcess`.
195 /// - `u32` - The exit code reported for the terminated process.
196 ///
197 /// # Returns
198 ///
199 /// - `i32` - Non-zero when the process was terminated, zero on failure.
200 fn TerminateProcess(hProcess: *mut c_void, uExitCode: u32) -> i32;
201 /// Closes an open process handle.
202 ///
203 /// # Arguments
204 ///
205 /// - `*mut c_void` - The process handle to close.
206 ///
207 /// # Returns
208 ///
209 /// - `i32` - Non-zero when the handle was closed, zero on failure.
210 fn CloseHandle(hObject: *mut c_void) -> i32;
211 /// Reads the calling thread's last recorded Win32 error code.
212 ///
213 /// # Returns
214 ///
215 /// - `u32` - The last recorded error code.
216 fn GetLastError() -> u32;
217 }
218 let process_id: u32 = pid as u32;
219 let mut process_handle: *mut c_void = unsafe { OpenProcess(0x0001, 0, process_id) };
220 if process_handle.is_null() {
221 process_handle = unsafe { OpenProcess(0x1F0FFF, 0, process_id) };
222 }
223 if process_handle.is_null() {
224 let error_code: u32 = unsafe { GetLastError() };
225 return Err(format!(
226 "Failed to open process with pid: {pid}. Error code: {error_code}"
227 )
228 .into());
229 }
230 let terminate_result: i32 = unsafe { TerminateProcess(process_handle, 1) };
231 if terminate_result == 0 {
232 let error_code: u32 = unsafe { GetLastError() };
233 unsafe {
234 CloseHandle(process_handle);
235 }
236 return Err(format!(
237 "Failed to terminate process with pid: {pid}. Error code: {error_code}"
238 )
239 .into());
240 }
241 unsafe {
242 CloseHandle(process_handle);
243 }
244 Ok(())
245 }
246
247 /// Runs the server with cargo-watch.
248 ///
249 /// # Arguments
250 ///
251 /// - `&[&str]` - A slice of string arguments to pass to `cargo-watch`.
252 /// - `bool` - A boolean indicating whether to wait for the `cargo-watch` process to complete.
253 ///
254 /// # Returns
255 ///
256 /// - `ServerManagerResult` - Operation result.
257 async fn run_with_cargo_watch(&self, run_args: &[&str], wait: bool) -> ServerManagerResult {
258 (self.get_start_hook())().await;
259 let cargo_watch_installed: Output = Command::new(CARGO)
260 .arg(INSTALL)
261 .args(CARGO_WATCH_INSTALL_LIST_ARGS)
262 .output()?;
263 if !String::from_utf8_lossy(&cargo_watch_installed.stdout).contains(CARGO_WATCH) {
264 eprintln!("{CARGO_WATCH_ABSENT_MESSAGE}");
265 let install_status: ExitStatus = Command::new(CARGO)
266 .args(CARGO_INSTALL_ARGS)
267 .stdout(Stdio::inherit())
268 .stderr(Stdio::inherit())
269 .spawn()?
270 .wait()?;
271 if !install_status.success() {
272 return Err(CARGO_WATCH_INSTALL_FAILED_MESSAGE.into());
273 }
274 eprintln!("{CARGO_WATCH_INSTALLED_MESSAGE}");
275 }
276 let mut command: Command = Command::new(CARGO_WATCH);
277 command
278 .args(run_args)
279 .stdout(Stdio::inherit())
280 .stderr(Stdio::inherit())
281 .stdin(Stdio::inherit());
282 let mut child: Child = command
283 .spawn()
284 .map_err(|error: Error| Box::new(error) as Box<dyn std::error::Error>)?;
285 if wait {
286 child
287 .wait()
288 .map_err(|error: Error| Box::new(error) as Box<dyn std::error::Error>)?;
289 }
290 exit(0);
291 }
292
293 /// Starts the server with hot-reloading using `cargo-watch` in detached mode.
294 ///
295 /// This function spawns `cargo-watch` and returns immediately.
296 ///
297 /// # Arguments
298 ///
299 /// - `&[&str]` - A slice of string arguments to pass to `cargo-watch`.
300 ///
301 /// # Returns
302 ///
303 /// - `ServerManagerResult` - Operation result.
304 pub async fn watch_detached(&self, run_args: &[&str]) -> ServerManagerResult {
305 self.run_with_cargo_watch(run_args, false).await
306 }
307
308 /// Starts the server with hot-reloading using `cargo-watch` and waits for it to complete.
309 ///
310 /// This function is blocking and will wait for the `cargo-watch` process to exit.
311 ///
312 /// # Arguments
313 ///
314 /// - `&[&str]` - A slice of string arguments to pass to `cargo-watch`.
315 ///
316 /// # Returns
317 ///
318 /// - `ServerManagerResult` - Operation result.
319 pub async fn watch(&self, run_args: &[&str]) -> ServerManagerResult {
320 self.run_with_cargo_watch(run_args, true).await
321 }
322}