mirror of
https://github.com/ruvnet/RuView
synced 2026-08-08 20:11:43 +00:00
feat: Complete Rust port of WiFi-DensePose with modular crates
Major changes: - Organized Python v1 implementation into v1/ subdirectory - Created Rust workspace with 9 modular crates: - wifi-densepose-core: Core types, traits, errors - wifi-densepose-signal: CSI processing, phase sanitization, FFT - wifi-densepose-nn: Neural network inference (ONNX/Candle/tch) - wifi-densepose-api: Axum-based REST/WebSocket API - wifi-densepose-db: SQLx database layer - wifi-densepose-config: Configuration management - wifi-densepose-hardware: Hardware abstraction - wifi-densepose-wasm: WebAssembly bindings - wifi-densepose-cli: Command-line interface Documentation: - ADR-001: Workspace structure - ADR-002: Signal processing library selection - ADR-003: Neural network inference strategy - DDD domain model with bounded contexts Testing: - 69 tests passing across all crates - Signal processing: 45 tests - Neural networks: 21 tests - Core: 3 doc tests Performance targets: - 10x faster CSI processing (~0.5ms vs ~5ms) - 5x lower memory usage (~100MB vs ~500MB) - WASM support for browser deployment
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
"""
|
||||
Stop command implementation for WiFi-DensePose API
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import signal
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from src.config.settings import Settings
|
||||
from src.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
async def stop_command(
|
||||
settings: Settings,
|
||||
force: bool = False,
|
||||
timeout: int = 30
|
||||
) -> None:
|
||||
"""Stop the WiFi-DensePose API server."""
|
||||
|
||||
logger.info("Stopping WiFi-DensePose API server...")
|
||||
|
||||
# Get server status
|
||||
status = get_server_status(settings)
|
||||
|
||||
if not status["running"]:
|
||||
if status["pid_file_exists"]:
|
||||
logger.info("Server is not running, but PID file exists. Cleaning up...")
|
||||
_cleanup_pid_file(settings)
|
||||
else:
|
||||
logger.info("Server is not running")
|
||||
return
|
||||
|
||||
pid = status["pid"]
|
||||
logger.info(f"Found running server with PID {pid}")
|
||||
|
||||
try:
|
||||
if force:
|
||||
await _force_stop_server(pid, settings)
|
||||
else:
|
||||
await _graceful_stop_server(pid, timeout, settings)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to stop server: {e}")
|
||||
raise
|
||||
|
||||
|
||||
async def _graceful_stop_server(pid: int, timeout: int, settings: Settings) -> None:
|
||||
"""Stop server gracefully with timeout."""
|
||||
|
||||
logger.info(f"Attempting graceful shutdown (timeout: {timeout}s)...")
|
||||
|
||||
try:
|
||||
# Send SIGTERM for graceful shutdown
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
logger.info("Sent SIGTERM signal")
|
||||
|
||||
# Wait for process to terminate
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < timeout:
|
||||
try:
|
||||
# Check if process is still running
|
||||
os.kill(pid, 0)
|
||||
await asyncio.sleep(1)
|
||||
except OSError:
|
||||
# Process has terminated
|
||||
logger.info("Server stopped gracefully")
|
||||
_cleanup_pid_file(settings)
|
||||
return
|
||||
|
||||
# Timeout reached, force kill
|
||||
logger.warning(f"Graceful shutdown timeout ({timeout}s) reached, forcing stop...")
|
||||
await _force_stop_server(pid, settings)
|
||||
|
||||
except OSError as e:
|
||||
if e.errno == 3: # No such process
|
||||
logger.info("Process already terminated")
|
||||
_cleanup_pid_file(settings)
|
||||
else:
|
||||
logger.error(f"Failed to send signal to process {pid}: {e}")
|
||||
raise
|
||||
|
||||
|
||||
async def _force_stop_server(pid: int, settings: Settings) -> None:
|
||||
"""Force stop server immediately."""
|
||||
|
||||
logger.info("Force stopping server...")
|
||||
|
||||
try:
|
||||
# Send SIGKILL for immediate termination
|
||||
os.kill(pid, signal.SIGKILL)
|
||||
logger.info("Sent SIGKILL signal")
|
||||
|
||||
# Wait a moment for process to die
|
||||
await asyncio.sleep(2)
|
||||
|
||||
# Verify process is dead
|
||||
try:
|
||||
os.kill(pid, 0)
|
||||
logger.error(f"Process {pid} still running after SIGKILL")
|
||||
except OSError:
|
||||
logger.info("Server force stopped")
|
||||
|
||||
except OSError as e:
|
||||
if e.errno == 3: # No such process
|
||||
logger.info("Process already terminated")
|
||||
else:
|
||||
logger.error(f"Failed to force kill process {pid}: {e}")
|
||||
raise
|
||||
|
||||
finally:
|
||||
_cleanup_pid_file(settings)
|
||||
|
||||
|
||||
def _cleanup_pid_file(settings: Settings) -> None:
|
||||
"""Clean up PID file."""
|
||||
|
||||
pid_file = Path(settings.log_directory) / "wifi-densepose-api.pid"
|
||||
|
||||
if pid_file.exists():
|
||||
try:
|
||||
pid_file.unlink()
|
||||
logger.info("Cleaned up PID file")
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to remove PID file: {e}")
|
||||
|
||||
|
||||
def get_server_status(settings: Settings) -> dict:
|
||||
"""Get current server status."""
|
||||
|
||||
pid_file = Path(settings.log_directory) / "wifi-densepose-api.pid"
|
||||
|
||||
status = {
|
||||
"running": False,
|
||||
"pid": None,
|
||||
"pid_file": str(pid_file),
|
||||
"pid_file_exists": pid_file.exists(),
|
||||
}
|
||||
|
||||
if pid_file.exists():
|
||||
try:
|
||||
with open(pid_file, 'r') as f:
|
||||
pid = int(f.read().strip())
|
||||
|
||||
status["pid"] = pid
|
||||
|
||||
# Check if process is running
|
||||
try:
|
||||
os.kill(pid, 0) # Signal 0 just checks if process exists
|
||||
status["running"] = True
|
||||
except OSError:
|
||||
# Process doesn't exist
|
||||
status["running"] = False
|
||||
|
||||
except (ValueError, IOError):
|
||||
# Invalid PID file
|
||||
status["running"] = False
|
||||
|
||||
return status
|
||||
|
||||
|
||||
async def stop_all_background_tasks(settings: Settings) -> None:
|
||||
"""Stop all background tasks if they're running."""
|
||||
|
||||
logger.info("Stopping background tasks...")
|
||||
|
||||
try:
|
||||
# This would typically involve connecting to a task queue or
|
||||
# sending signals to background processes
|
||||
# For now, we'll just log the action
|
||||
|
||||
logger.info("Background tasks stop signal sent")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to stop background tasks: {e}")
|
||||
|
||||
|
||||
async def cleanup_resources(settings: Settings) -> None:
|
||||
"""Clean up system resources."""
|
||||
|
||||
logger.info("Cleaning up resources...")
|
||||
|
||||
try:
|
||||
# Close database connections
|
||||
from src.database.connection import get_database_manager
|
||||
|
||||
db_manager = get_database_manager(settings)
|
||||
await db_manager.close_all_connections()
|
||||
logger.info("Database connections closed")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to close database connections: {e}")
|
||||
|
||||
try:
|
||||
# Clean up temporary files
|
||||
temp_files = [
|
||||
Path(settings.log_directory) / "temp",
|
||||
Path(settings.backup_directory) / "temp",
|
||||
]
|
||||
|
||||
for temp_path in temp_files:
|
||||
if temp_path.exists() and temp_path.is_dir():
|
||||
import shutil
|
||||
shutil.rmtree(temp_path)
|
||||
logger.info(f"Cleaned up temporary directory: {temp_path}")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to clean up temporary files: {e}")
|
||||
|
||||
logger.info("Resource cleanup completed")
|
||||
|
||||
|
||||
def is_server_running(settings: Settings) -> bool:
|
||||
"""Check if server is currently running."""
|
||||
|
||||
status = get_server_status(settings)
|
||||
return status["running"]
|
||||
|
||||
|
||||
def get_server_pid(settings: Settings) -> Optional[int]:
|
||||
"""Get server PID if running."""
|
||||
|
||||
status = get_server_status(settings)
|
||||
return status["pid"] if status["running"] else None
|
||||
|
||||
|
||||
async def wait_for_server_stop(settings: Settings, timeout: int = 30) -> bool:
|
||||
"""Wait for server to stop with timeout."""
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
while time.time() - start_time < timeout:
|
||||
if not is_server_running(settings):
|
||||
return True
|
||||
await asyncio.sleep(1)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def send_reload_signal(settings: Settings) -> bool:
|
||||
"""Send reload signal to running server."""
|
||||
|
||||
status = get_server_status(settings)
|
||||
|
||||
if not status["running"]:
|
||||
logger.error("Server is not running")
|
||||
return False
|
||||
|
||||
try:
|
||||
# Send SIGHUP for reload
|
||||
os.kill(status["pid"], signal.SIGHUP)
|
||||
logger.info("Sent reload signal to server")
|
||||
return True
|
||||
|
||||
except OSError as e:
|
||||
logger.error(f"Failed to send reload signal: {e}")
|
||||
return False
|
||||
|
||||
|
||||
async def restart_server(settings: Settings, timeout: int = 30) -> None:
|
||||
"""Restart the server (stop then start)."""
|
||||
|
||||
logger.info("Restarting server...")
|
||||
|
||||
# Stop server if running
|
||||
if is_server_running(settings):
|
||||
await stop_command(settings, timeout=timeout)
|
||||
|
||||
# Wait for server to stop
|
||||
if not await wait_for_server_stop(settings, timeout):
|
||||
logger.error("Server did not stop within timeout, forcing restart")
|
||||
await stop_command(settings, force=True)
|
||||
|
||||
# Start server
|
||||
from src.commands.start import start_command
|
||||
await start_command(settings)
|
||||
|
||||
|
||||
def get_stop_status_summary(settings: Settings) -> dict:
|
||||
"""Get a summary of stop operation status."""
|
||||
|
||||
status = get_server_status(settings)
|
||||
|
||||
return {
|
||||
"server_running": status["running"],
|
||||
"pid": status["pid"],
|
||||
"pid_file_exists": status["pid_file_exists"],
|
||||
"can_stop": status["running"],
|
||||
"cleanup_needed": status["pid_file_exists"] and not status["running"],
|
||||
}
|
||||
Reference in New Issue
Block a user