server_manager/manager/
impl.rs1use crate::*;
2
3impl Default for ServerManager {
4 fn default() -> Self {
5 let empty_hook: Hook = Arc::new(|| Box::pin(async {}));
6 Self {
7 pid_file: Default::default(),
8 stop_hook: empty_hook.clone(),
9 server_hook: empty_hook.clone(),
10 start_hook: empty_hook,
11 }
12 }
13}
14
15impl ServerManager {
19 pub fn new() -> Self {
20 Self::default()
21 }
22
23 pub fn set_pid_file<P: ToString>(&mut self, pid_file: P) -> &mut Self {
24 self.pid_file = pid_file.to_string();
25 self
26 }
27
28 pub fn set_start_hook<F, Fut>(&mut self, f: F) -> &mut Self
29 where
30 F: Fn() -> Fut + Send + Sync + 'static,
31 Fut: Future<Output = ()> + Send + 'static,
32 {
33 self.start_hook = Arc::new(move || Box::pin(f()));
34 self
35 }
36
37 pub fn set_server_hook<F, Fut>(&mut self, f: F) -> &mut Self
38 where
39 F: Fn() -> Fut + Send + Sync + 'static,
40 Fut: Future<Output = ()> + Send + 'static,
41 {
42 self.server_hook = Arc::new(move || Box::pin(f()));
43 self
44 }
45
46 pub fn set_stop_hook<F, Fut>(&mut self, f: F) -> &mut Self
47 where
48 F: Fn() -> Fut + Send + Sync + 'static,
49 Fut: Future<Output = ()> + Send + 'static,
50 {
51 self.stop_hook = Arc::new(move || Box::pin(f()));
52 self
53 }
54
55 pub fn get_pid_file(&self) -> &str {
56 &self.pid_file
57 }
58
59 pub fn get_start_hook(&self) -> &Hook {
60 &self.start_hook
61 }
62
63 pub fn get_server_hook(&self) -> &Hook {
64 &self.server_hook
65 }
66
67 pub fn get_stop_hook(&self) -> &Hook {
68 &self.stop_hook
69 }
70
71 pub async fn start(&self) {
75 (self.start_hook)().await;
76 if let Err(e) = self.write_pid_file() {
77 eprintln!("Failed to write pid file: {}", e);
78 return;
79 }
80 (self.server_hook)().await;
81 }
82
83 pub async fn stop(&self) -> ServerManagerResult {
91 (self.stop_hook)().await;
92 let pid: i32 = self.read_pid_file()?;
93 self.kill_process(pid)
94 }
95
96 #[cfg(not(windows))]
98 pub async fn start_daemon(&self) -> ServerManagerResult {
99 (self.start_hook)().await;
100 if std::env::var(RUNNING_AS_DAEMON).is_ok() {
101 self.write_pid_file()?;
102 let rt: Runtime = Runtime::new()?;
103 rt.block_on(async {
104 (self.server_hook)().await;
105 });
106 return Ok(());
107 }
108 let exe_path: PathBuf = std::env::current_exe()?;
109 let mut cmd: Command = Command::new(exe_path);
110 cmd.env(RUNNING_AS_DAEMON, RUNNING_AS_DAEMON_VALUE)
111 .stdout(Stdio::null())
112 .stderr(Stdio::null())
113 .stdin(Stdio::null());
114 cmd.spawn()
115 .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
116 Ok(())
117 }
118
119 #[cfg(windows)]
121 pub async fn start_daemon(&self) -> ServerManagerResult {
122 (self.start_hook)().await;
123 use std::os::windows::process::CommandExt;
124 if std::env::var(RUNNING_AS_DAEMON).is_ok() {
125 self.write_pid_file()?;
126 let rt: Runtime = Runtime::new()?;
127 rt.block_on(async {
128 (self.server_hook)().await;
129 });
130 return Ok(());
131 }
132 let exe_path: PathBuf = std::env::current_exe()?;
133 let mut cmd: Command = Command::new(exe_path);
134 cmd.env(RUNNING_AS_DAEMON, RUNNING_AS_DAEMON_VALUE)
135 .stdout(Stdio::null())
136 .stderr(Stdio::null())
137 .stdin(Stdio::null())
138 .creation_flags(0x00000008);
139 cmd.spawn()
140 .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
141 Ok(())
142 }
143
144 fn read_pid_file(&self) -> Result<i32, Box<dyn std::error::Error>> {
150 let pid_str: String = fs::read_to_string(&self.pid_file)?;
151 let pid: i32 = pid_str.trim().parse::<i32>()?;
152 Ok(pid)
153 }
154
155 fn write_pid_file(&self) -> ServerManagerResult {
161 if let Some(parent) = Path::new(&self.pid_file).parent() {
162 fs::create_dir_all(parent)?;
163 }
164 let pid: u32 = id();
165 fs::write(&self.pid_file, pid.to_string())?;
166 Ok(())
167 }
168
169 #[cfg(not(windows))]
179 fn kill_process(&self, pid: i32) -> ServerManagerResult {
180 let result: Result<Output, std::io::Error> = Command::new("kill")
181 .arg("-TERM")
182 .arg(pid.to_string())
183 .output();
184 match result {
185 Ok(output) if output.status.success() => Ok(()),
186 Ok(output) => Err(format!(
187 "Failed to kill process with pid: {}, error: {}",
188 pid,
189 String::from_utf8_lossy(&output.stderr)
190 )
191 .into()),
192 Err(e) => Err(format!("Failed to execute kill command: {}", e).into()),
193 }
194 }
195
196 #[cfg(windows)]
206 fn kill_process(&self, pid: i32) -> ServerManagerResult {
207 use std::ffi::c_void;
208 type DWORD = u32;
209 type BOOL = i32;
210 type HANDLE = *mut c_void;
211 type UINT = u32;
212 const PROCESS_TERMINATE: DWORD = 0x0001;
213 const PROCESS_ALL_ACCESS: DWORD = 0x1F0FFF;
214 unsafe extern "system" {
215 fn OpenProcess(
216 dwDesiredAccess: DWORD,
217 bInheritHandle: BOOL,
218 dwProcessId: DWORD,
219 ) -> HANDLE;
220 fn TerminateProcess(hProcess: HANDLE, uExitCode: UINT) -> BOOL;
221 fn CloseHandle(hObject: HANDLE) -> BOOL;
222 fn GetLastError() -> DWORD;
223 }
224 let process_id: DWORD = pid as DWORD;
225 let mut process_handle: HANDLE = unsafe { OpenProcess(PROCESS_TERMINATE, 0, process_id) };
226 if process_handle.is_null() {
227 process_handle = unsafe { OpenProcess(PROCESS_ALL_ACCESS, 0, process_id) };
228 }
229 if process_handle.is_null() {
230 let error_code = unsafe { GetLastError() };
231 return Err(format!(
232 "Failed to open process with pid: {}. Error code: {}",
233 pid, error_code
234 )
235 .into());
236 }
237 let terminate_result: BOOL = unsafe { TerminateProcess(process_handle, 1) };
238 if terminate_result == 0 {
239 let error_code = unsafe { GetLastError() };
240 unsafe {
241 CloseHandle(process_handle);
242 }
243 return Err(format!(
244 "Failed to terminate process with pid: {}. Error code: {}",
245 pid, error_code
246 )
247 .into());
248 }
249 unsafe {
250 CloseHandle(process_handle);
251 }
252 Ok(())
253 }
254
255 async fn run_with_cargo_watch(&self, run_args: &[&str], wait: bool) -> ServerManagerResult {
266 (self.start_hook)().await;
267 let cargo_watch_installed: Output = Command::new("cargo")
268 .arg("install")
269 .arg("--list")
270 .output()?;
271 if !String::from_utf8_lossy(&cargo_watch_installed.stdout).contains("cargo-watch") {
272 eprintln!("Cargo-watch not found. Attempting to install...");
273 let install_status: ExitStatus = Command::new("cargo")
274 .arg("install")
275 .arg("cargo-watch")
276 .stdout(Stdio::inherit())
277 .stderr(Stdio::inherit())
278 .spawn()?
279 .wait()?;
280 if !install_status.success() {
281 return Err("Failed to install cargo-watch. Please install it manually: `cargo install cargo-watch`".into());
282 }
283 eprintln!("Cargo-watch installed successfully.");
284 }
285 let mut command: Command = Command::new("cargo-watch");
286 command
287 .args(run_args)
288 .stdout(Stdio::inherit())
289 .stderr(Stdio::inherit())
290 .stdin(Stdio::inherit());
291 let mut child: Child = command
292 .spawn()
293 .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
294 if wait {
295 child
296 .wait()
297 .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
298 }
299 exit(0);
300 }
301
302 pub async fn hot_restart(&self, run_args: &[&str]) -> ServerManagerResult {
312 self.run_with_cargo_watch(run_args, false).await
313 }
314
315 pub async fn hot_restart_wait(&self, run_args: &[&str]) -> ServerManagerResult {
325 self.run_with_cargo_watch(run_args, true).await
326 }
327}