// Example: Timeout Demonstration for circle_server_ws // // This example demonstrates how circle_server_ws handles Rhai scripts that exceed // the configured execution timeout (default 30 seconds). // // This example will attempt to start its own instance of circle_server_ws. // Ensure circle_server_ws is compiled (cargo build --bin circle_server_ws). use circle_client_ws::CircleWsClient; use tokio::time::{sleep, Duration}; use std::process::{Command, Child, Stdio}; use std::path::PathBuf; const EXAMPLE_SERVER_PORT: u16 = 8089; // Using a specific port for this example const WS_URL: &str = "ws://127.0.0.1:8089/ws"; const CIRCLE_NAME_FOR_EXAMPLE: &str = "timeout_example_circle"; const CIRCLE_SERVER_WS_BIN_NAME: &str = "circle_server_ws"; const SCRIPT_TIMEOUT_SECONDS: u64 = 30; // This is the server-side timeout we expect to hit // RAII guard for cleaning up child processes struct ChildProcessGuard { child: Child, name: String, } impl ChildProcessGuard { fn new(child: Child, name: String) -> Self { log::info!("{} process started with PID: {}", name, child.id()); Self { child, name } } } impl Drop for ChildProcessGuard { fn drop(&mut self) { log::info!("Cleaning up {} process (PID: {})...", self.name, self.child.id()); match self.child.kill() { Ok(_) => { log::info!("Successfully sent kill signal to {} (PID: {}).", self.name, self.child.id()); match self.child.wait() { Ok(status) => log::info!("{} (PID: {}) exited with status: {}", self.name, self.child.id(), status), Err(e) => log::warn!("Error waiting for {} (PID: {}): {}", self.name, self.child.id(), e), } } Err(e) => log::error!("Failed to kill {} (PID: {}): {}", self.name, self.child.id(), e), } } } fn find_target_bin_path(bin_name: &str) -> Result { let mut current_exe = std::env::current_exe().map_err(|e| format!("Failed to get current exe path: {}", e))?; // current_exe is typically target/debug/examples/timeout_demonstration // We want to find target/debug/[bin_name] current_exe.pop(); // remove executable name current_exe.pop(); // remove examples directory let target_debug_dir = current_exe; let bin_path = target_debug_dir.join(bin_name); if !bin_path.exists() { // Fallback: try CARGO_BIN_EXE_[bin_name] if running via `cargo run --example` which sets these if let Ok(cargo_bin_path_str) = std::env::var(format!("CARGO_BIN_EXE_{}", bin_name.to_uppercase())) { let cargo_bin_path = PathBuf::from(cargo_bin_path_str); if cargo_bin_path.exists() { return Ok(cargo_bin_path); } } // Fallback: try target/debug/[bin_name] relative to CARGO_MANIFEST_DIR (crate root) if let Ok(manifest_dir_str) = std::env::var("CARGO_MANIFEST_DIR") { let bin_path_rel_manifest = PathBuf::from(manifest_dir_str).join("target").join("debug").join(bin_name); if bin_path_rel_manifest.exists() { return Ok(bin_path_rel_manifest); } } return Err(format!("Binary '{}' not found at {:?}. Ensure it's built.", bin_name, bin_path)); } Ok(bin_path) } #[tokio::main] async fn main() -> Result<(), Box> { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); let server_bin_path = find_target_bin_path(CIRCLE_SERVER_WS_BIN_NAME)?; log::info!("Found server binary at: {:?}", server_bin_path); log::info!("Starting {} for circle '{}' on port {}...", CIRCLE_SERVER_WS_BIN_NAME, CIRCLE_NAME_FOR_EXAMPLE, EXAMPLE_SERVER_PORT); let server_process = Command::new(&server_bin_path) .args([ "--port", &EXAMPLE_SERVER_PORT.to_string(), "--circle-name", CIRCLE_NAME_FOR_EXAMPLE ]) .stdout(Stdio::piped()) // Pipe stdout to keep terminal clean, or Stdio::inherit() to see server logs .stderr(Stdio::piped()) // Pipe stderr as well .spawn() .map_err(|e| format!("Failed to start {}: {}. Ensure it is built.", CIRCLE_SERVER_WS_BIN_NAME, e))?; let _server_guard = ChildProcessGuard::new(server_process, CIRCLE_SERVER_WS_BIN_NAME.to_string()); log::info!("Giving the server a moment to start up..."); sleep(Duration::from_secs(3)).await; // Wait for server to initialize log::info!("Attempting to connect to WebSocket server at: {}", WS_URL); let mut client = CircleWsClient::new(WS_URL.to_string()); log::info!("Connecting client..."); if let Err(e) = client.connect().await { log::error!("Failed to connect to WebSocket server: {}", e); log::error!("Please check server logs if it failed to start correctly."); return Err(e.into()); } log::info!("Client connected successfully."); // This Rhai script is designed to run for much longer than the typical server timeout. let long_running_script = " log(\"Rhai: Starting long-running script...\"); let mut x = 0; for i in 0..9999999999 { // Extremely large loop x = x + i; if i % 100000000 == 0 { // log(\"Rhai: Loop iteration \" + i); } } // This part should not be reached if timeout works correctly. log(\"Rhai: Long-running script finished calculation (x = \" + x + \").\"); print(x); x ".to_string(); log::info!("Sending long-running script (expected to time out on server after ~{}s)...", SCRIPT_TIMEOUT_SECONDS); match client.play(long_running_script).await { Ok(play_result) => { log::warn!("Received unexpected success from play request: {:?}", play_result); log::warn!("This might indicate the script finished faster than expected, or the timeout didn't trigger."); } Err(e) => { log::info!("Received expected error from play request: {}", e); log::info!("This demonstrates the server timing out the script execution."); // You can further inspect the error details if CircleWsClientError provides them. // For example, if e.to_string() contains 'code: -32002' or 'timed out'. if e.to_string().contains("timed out") || e.to_string().contains("-32002") { log::info!("Successfully received timeout error from the server!"); } else { log::warn!("Received an error, but it might not be the expected timeout error: {}", e); } } } log::info!("Disconnecting client..."); client.disconnect().await; log::info!("Client disconnected."); log::info!("Timeout demonstration example finished."); Ok(()) }