server_manager/manager/
impl.rs1use crate::*;
2
3impl<F, Fut> ServerManager<F>
7where
8 F: Fn() -> Fut,
9 Fut: std::future::Future<Output = ()>,
10{
11 pub fn new(config: ServerManagerConfig, server_fn: F) -> Self {
22 Self { config, server_fn }
23 }
24
25 pub async fn start(&self) {
29 if let Err(e) = self.write_pid_file() {
30 eprintln!("Failed to write pid file: {}", e);
31 return;
32 }
33 (self.server_fn)().await;
34 }
35
36 pub async fn stop(&self) -> ServerManagerResult {
44 (self.config.stop_hook)().await;
45 let pid: i32 = self.read_pid_file()?;
46 self.kill_process(pid)
47 }
48
49 #[cfg(not(windows))]
51 pub async fn start_daemon(&self) -> ServerManagerResult {
52 (self.config.start_hook)().await;
53 if std::env::var(RUNNING_AS_DAEMON).is_ok() {
54 self.write_pid_file()?;
55 let rt: Runtime = Runtime::new()?;
56 rt.block_on(async {
57 (self.server_fn)().await;
58 });
59 return Ok(());
60 }
61 let exe_path: PathBuf = std::env::current_exe()?;
62 let mut cmd: Command = Command::new(exe_path);
63 cmd.env(RUNNING_AS_DAEMON, RUNNING_AS_DAEMON_VALUE)
64 .stdout(Stdio::null())
65 .stderr(Stdio::null())
66 .stdin(Stdio::null());
67 cmd.spawn()
68 .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
69 Ok(())
70 }
71
72 #[cfg(windows)]
74 pub async fn start_daemon(&self) -> ServerManagerResult {
75 (self.config.start_hook)().await;
76 use std::os::windows::process::CommandExt;
77 if std::env::var(RUNNING_AS_DAEMON).is_ok() {
78 self.write_pid_file()?;
79 let rt: Runtime = Runtime::new()?;
80 rt.block_on(async {
81 (self.server_fn)().await;
82 });
83 return Ok(());
84 }
85 let exe_path: PathBuf = std::env::current_exe()?;
86 let mut cmd: Command = Command::new(exe_path);
87 cmd.env(RUNNING_AS_DAEMON, RUNNING_AS_DAEMON_VALUE)
88 .stdout(Stdio::null())
89 .stderr(Stdio::null())
90 .stdin(Stdio::null())
91 .creation_flags(0x00000008);
92 cmd.spawn()
93 .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
94 Ok(())
95 }
96
97 fn read_pid_file(&self) -> Result<i32, Box<dyn std::error::Error>> {
103 let pid_str: String = fs::read_to_string(&self.config.pid_file)?;
104 let pid: i32 = pid_str.trim().parse::<i32>()?;
105 Ok(pid)
106 }
107
108 fn write_pid_file(&self) -> ServerManagerResult {
114 if let Some(parent) = Path::new(&self.config.pid_file).parent() {
115 fs::create_dir_all(parent)?;
116 }
117 let pid: u32 = id();
118 fs::write(&self.config.pid_file, pid.to_string())?;
119 Ok(())
120 }
121
122 #[cfg(not(windows))]
132 fn kill_process(&self, pid: i32) -> ServerManagerResult {
133 let result: Result<Output, std::io::Error> = Command::new("kill")
134 .arg("-TERM")
135 .arg(pid.to_string())
136 .output();
137 match result {
138 Ok(output) if output.status.success() => Ok(()),
139 Ok(output) => Err(format!(
140 "Failed to kill process with pid: {}, error: {}",
141 pid,
142 String::from_utf8_lossy(&output.stderr)
143 )
144 .into()),
145 Err(e) => Err(format!("Failed to execute kill command: {}", e).into()),
146 }
147 }
148
149 #[cfg(windows)]
159 fn kill_process(&self, pid: i32) -> ServerManagerResult {
160 use std::ffi::c_void;
161 type DWORD = u32;
162 type BOOL = i32;
163 type HANDLE = *mut c_void;
164 type UINT = u32;
165 const PROCESS_TERMINATE: DWORD = 0x0001;
166 const PROCESS_ALL_ACCESS: DWORD = 0x1F0FFF;
167 unsafe extern "system" {
168 fn OpenProcess(
169 dwDesiredAccess: DWORD,
170 bInheritHandle: BOOL,
171 dwProcessId: DWORD,
172 ) -> HANDLE;
173 fn TerminateProcess(hProcess: HANDLE, uExitCode: UINT) -> BOOL;
174 fn CloseHandle(hObject: HANDLE) -> BOOL;
175 fn GetLastError() -> DWORD;
176 }
177 let process_id: DWORD = pid as DWORD;
178 let mut process_handle: HANDLE = unsafe { OpenProcess(PROCESS_TERMINATE, 0, process_id) };
179 if process_handle.is_null() {
180 process_handle = unsafe { OpenProcess(PROCESS_ALL_ACCESS, 0, process_id) };
181 }
182 if process_handle.is_null() {
183 let error_code = unsafe { GetLastError() };
184 return Err(format!(
185 "Failed to open process with pid: {}. Error code: {}",
186 pid, error_code
187 )
188 .into());
189 }
190 let terminate_result: BOOL = unsafe { TerminateProcess(process_handle, 1) };
191 if terminate_result == 0 {
192 let error_code = unsafe { GetLastError() };
193 unsafe {
194 CloseHandle(process_handle);
195 }
196 return Err(format!(
197 "Failed to terminate process with pid: {}. Error code: {}",
198 pid, error_code
199 )
200 .into());
201 }
202 unsafe {
203 CloseHandle(process_handle);
204 }
205 Ok(())
206 }
207
208 fn run_with_cargo_watch(&self, run_args: &[&str], wait: bool) -> ServerManagerResult {
219 let cargo_watch_installed: Output = Command::new("cargo")
220 .arg("install")
221 .arg("--list")
222 .output()?;
223 if !String::from_utf8_lossy(&cargo_watch_installed.stdout).contains("cargo-watch") {
224 eprintln!("Cargo-watch not found. Attempting to install...");
225 let install_status: ExitStatus = Command::new("cargo")
226 .arg("install")
227 .arg("cargo-watch")
228 .stdout(Stdio::inherit())
229 .stderr(Stdio::inherit())
230 .spawn()?
231 .wait()?;
232 if !install_status.success() {
233 return Err("Failed to install cargo-watch. Please install it manually: `cargo install cargo-watch`".into());
234 }
235 eprintln!("Cargo-watch installed successfully.");
236 }
237 let mut command: Command = Command::new("cargo-watch");
238 command
239 .args(run_args)
240 .stdout(Stdio::inherit())
241 .stderr(Stdio::inherit())
242 .stdin(Stdio::inherit());
243 let mut child: Child = command
244 .spawn()
245 .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
246 if wait {
247 child
248 .wait()
249 .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
250 }
251 exit(0);
252 }
253
254 pub fn hot_restart(&self, run_args: &[&str]) -> ServerManagerResult {
264 self.run_with_cargo_watch(run_args, false)
265 }
266
267 pub fn hot_restart_wait(&self, run_args: &[&str]) -> ServerManagerResult {
277 self.run_with_cargo_watch(run_args, true)
278 }
279}