rush_sync_server/commands/stop/
command.rs

1// Fixed src/commands/stop/command.rs
2use crate::commands::command::Command;
3use crate::core::prelude::*;
4use crate::server::types::{ServerContext, ServerStatus};
5use crate::server::utils::validation::find_server;
6use std::future::Future;
7use std::pin::Pin;
8use std::time::Duration;
9use tokio::time::timeout;
10
11#[derive(Debug, Default)]
12pub struct StopCommand;
13
14impl StopCommand {
15    pub fn new() -> Self {
16        Self
17    }
18}
19
20impl Command for StopCommand {
21    fn name(&self) -> &'static str {
22        "stop"
23    }
24
25    fn description(&self) -> &'static str {
26        "Stop a web server (persistent)"
27    }
28
29    fn matches(&self, command: &str) -> bool {
30        command.trim().to_lowercase().starts_with("stop")
31    }
32
33    fn execute_sync(&self, args: &[&str]) -> Result<String> {
34        if args.is_empty() {
35            return Err(AppError::Validation(
36                "Server-ID/Name fehlt! Verwende 'stop <ID>'".to_string(),
37            ));
38        }
39
40        // DON'T use block_on - instead use spawn_blocking for config loading
41        let config_result = std::thread::spawn(|| {
42            let rt = tokio::runtime::Runtime::new().unwrap();
43            rt.block_on(Config::load())
44        })
45        .join()
46        .map_err(|_| AppError::Validation("Failed to load config".to_string()))??;
47
48        let ctx = crate::server::shared::get_shared_context();
49
50        self.stop_server(&config_result, ctx, args[0])
51    }
52
53    fn execute_async<'a>(
54        &'a self,
55        args: &'a [&'a str],
56    ) -> Pin<Box<dyn Future<Output = Result<String>> + Send + 'a>> {
57        Box::pin(async move {
58            if args.is_empty() {
59                return Err(AppError::Validation(
60                    "Server-ID/Name fehlt! Verwende 'stop <ID>'".to_string(),
61                ));
62            }
63
64            let config = Config::load().await?;
65            let ctx = crate::server::shared::get_shared_context();
66
67            self.stop_server_async(&config, ctx, args[0]).await
68        })
69    }
70
71    fn supports_async(&self) -> bool {
72        true
73    }
74
75    fn priority(&self) -> u8 {
76        67
77    }
78}
79
80impl StopCommand {
81    fn stop_server(
82        &self,
83        config: &Config,
84        ctx: &ServerContext,
85        identifier: &str,
86    ) -> Result<String> {
87        let server_info = {
88            let servers = ctx.servers.read().unwrap();
89            find_server(&servers, identifier)?.clone()
90        };
91
92        if server_info.status != ServerStatus::Running {
93            return Ok(format!(
94                "Server '{}' is not active (Status: {})",
95                server_info.name, server_info.status
96            ));
97        }
98
99        log::info!(
100            "Stopping server {} on port {}",
101            server_info.id,
102            server_info.port
103        );
104
105        let handle_removed = {
106            let mut handles = ctx.handles.write().unwrap();
107            handles.remove(&server_info.id)
108        };
109
110        if let Some(handle) = handle_removed {
111            self.shutdown_server_gracefully(handle, server_info.id.clone(), config);
112            self.update_server_status(ctx, &server_info.id, ServerStatus::Stopped);
113
114            // Persistence update
115            let server_id = server_info.id.clone();
116            tokio::spawn(async move {
117                crate::server::shared::persist_server_update(&server_id, ServerStatus::Stopped)
118                    .await;
119            });
120
121            // Use configurable startup delay for consistent timing
122            std::thread::sleep(Duration::from_millis(config.server.startup_delay_ms));
123
124            let running_count = {
125                let servers = ctx.servers.read().unwrap();
126                servers
127                    .values()
128                    .filter(|s| s.status == ServerStatus::Running)
129                    .count()
130            };
131
132            Ok(format!(
133                "Server '{}' stopped [PERSISTENT] ({}/{} running)",
134                server_info.name, running_count, config.server.max_concurrent
135            ))
136        } else {
137            self.update_server_status(ctx, &server_info.id, ServerStatus::Stopped);
138
139            // Persistence update
140            let server_id = server_info.id.clone();
141            tokio::spawn(async move {
142                crate::server::shared::persist_server_update(&server_id, ServerStatus::Stopped)
143                    .await;
144            });
145
146            Ok(format!(
147                "Server '{}' was already stopped [PERSISTENT]",
148                server_info.name
149            ))
150        }
151    }
152
153    async fn stop_server_async(
154        &self,
155        config: &Config,
156        ctx: &ServerContext,
157        identifier: &str,
158    ) -> Result<String> {
159        let server_info = {
160            let servers = ctx.servers.read().unwrap();
161            find_server(&servers, identifier)?.clone()
162        };
163
164        if server_info.status != ServerStatus::Running {
165            return Ok(format!(
166                "Server '{}' is not active (Status: {})",
167                server_info.name, server_info.status
168            ));
169        }
170
171        log::info!(
172            "Stopping server {} on port {} (async)",
173            server_info.id,
174            server_info.port
175        );
176
177        let handle_removed = {
178            let mut handles = ctx.handles.write().unwrap();
179            handles.remove(&server_info.id)
180        };
181
182        if let Some(handle) = handle_removed {
183            // Use configured shutdown timeout
184            let shutdown_timeout_duration = Duration::from_secs(config.server.shutdown_timeout);
185
186            match timeout(shutdown_timeout_duration, handle.stop(true)).await {
187                Ok(_) => log::info!("Server {} stopped gracefully", server_info.id),
188                Err(_) => {
189                    log::warn!("Server {} shutdown timeout, forcing stop", server_info.id);
190                    handle.stop(false).await;
191                }
192            }
193
194            self.update_server_status(ctx, &server_info.id, ServerStatus::Stopped);
195
196            // Persistence update
197            let server_id = server_info.id.clone();
198            tokio::spawn(async move {
199                crate::server::shared::persist_server_update(&server_id, ServerStatus::Stopped)
200                    .await;
201            });
202
203            let running_count = {
204                let servers = ctx.servers.read().unwrap();
205                servers
206                    .values()
207                    .filter(|s| s.status == ServerStatus::Running)
208                    .count()
209            };
210
211            Ok(format!(
212                "Server '{}' stopped [PERSISTENT] ({}/{} running)",
213                server_info.name, running_count, config.server.max_concurrent
214            ))
215        } else {
216            self.update_server_status(ctx, &server_info.id, ServerStatus::Stopped);
217
218            // Persistence update
219            let server_id = server_info.id.clone();
220            tokio::spawn(async move {
221                crate::server::shared::persist_server_update(&server_id, ServerStatus::Stopped)
222                    .await;
223            });
224
225            Ok(format!(
226                "Server '{}' was already stopped [PERSISTENT]",
227                server_info.name
228            ))
229        }
230    }
231
232    // Updated to use config for shutdown timeout
233    fn shutdown_server_gracefully(
234        &self,
235        handle: actix_web::dev::ServerHandle,
236        server_id: String,
237        config: &Config,
238    ) {
239        let shutdown_timeout = config.server.shutdown_timeout;
240
241        std::thread::spawn(move || {
242            let rt = tokio::runtime::Runtime::new().unwrap();
243            rt.block_on(async move {
244                match timeout(Duration::from_secs(shutdown_timeout), handle.stop(true)).await {
245                    Ok(_) => log::info!("Server {} stopped gracefully", server_id),
246                    Err(_) => {
247                        log::warn!(
248                            "Server {} shutdown timeout ({}s), forcing stop",
249                            server_id,
250                            shutdown_timeout
251                        );
252                        handle.stop(false).await;
253                    }
254                }
255            });
256        });
257    }
258
259    fn update_server_status(&self, ctx: &ServerContext, server_id: &str, status: ServerStatus) {
260        if let Ok(mut servers) = ctx.servers.write() {
261            if let Some(server) = servers.get_mut(server_id) {
262                server.status = status;
263            }
264        }
265    }
266}