feat: Improve service manager testing and error handling

- Add comprehensive testing instructions to README.
- Improve error handling in examples to prevent crashes.
- Enhance launchctl error handling for production safety.
- Improve zinit error handling for production safety.
- Remove obsolete plan_to_fix.md file.
- Update Rhai integration tests for improved robustness.
- Improve service manager creation on Linux with systemd fallback.
This commit is contained in:
Mahmoud-Emad
2025-07-02 12:05:03 +03:00
parent a63cbe2bd9
commit 95122dffee
12 changed files with 473 additions and 422 deletions

View File

@@ -6,10 +6,25 @@ use std::path::PathBuf;
use tokio::process::Command;
use tokio::runtime::Runtime;
// Shared runtime for async operations
static ASYNC_RUNTIME: Lazy<Runtime> = Lazy::new(|| {
Runtime::new().expect("Failed to create async runtime for LaunchctlServiceManager")
});
// Shared runtime for async operations - production-safe initialization
static ASYNC_RUNTIME: Lazy<Option<Runtime>> = Lazy::new(|| Runtime::new().ok());
/// Get the async runtime, creating a temporary one if the static runtime failed
fn get_runtime() -> Result<Runtime, ServiceManagerError> {
// Try to use the static runtime first
if let Some(_runtime) = ASYNC_RUNTIME.as_ref() {
// We can't return a reference to the static runtime because we need ownership
// for block_on, so we create a new one. This is a reasonable trade-off for safety.
Runtime::new().map_err(|e| {
ServiceManagerError::Other(format!("Failed to create async runtime: {}", e))
})
} else {
// Static runtime failed, try to create a new one
Runtime::new().map_err(|e| {
ServiceManagerError::Other(format!("Failed to create async runtime: {}", e))
})
}
}
#[derive(Debug)]
pub struct LaunchctlServiceManager {
@@ -221,8 +236,9 @@ impl ServiceManager for LaunchctlServiceManager {
}
fn start(&self, config: &ServiceConfig) -> Result<(), ServiceManagerError> {
// Use the shared runtime for async operations
ASYNC_RUNTIME.block_on(async {
// Use production-safe runtime for async operations
let runtime = get_runtime()?;
runtime.block_on(async {
let label = self.get_service_label(&config.name);
// Check if service is already loaded
@@ -249,7 +265,8 @@ impl ServiceManager for LaunchctlServiceManager {
}
fn start_existing(&self, service_name: &str) -> Result<(), ServiceManagerError> {
ASYNC_RUNTIME.block_on(async {
let runtime = get_runtime()?;
runtime.block_on(async {
let label = self.get_service_label(service_name);
let plist_path = self.get_plist_path(service_name);
@@ -300,8 +317,9 @@ impl ServiceManager for LaunchctlServiceManager {
// First start the service
self.start(config)?;
// Then wait for confirmation using the shared runtime
ASYNC_RUNTIME.block_on(async {
// Then wait for confirmation using production-safe runtime
let runtime = get_runtime()?;
runtime.block_on(async {
self.wait_for_service_status(&config.name, timeout_secs)
.await
})
@@ -315,15 +333,17 @@ impl ServiceManager for LaunchctlServiceManager {
// First start the existing service
self.start_existing(service_name)?;
// Then wait for confirmation using the shared runtime
ASYNC_RUNTIME.block_on(async {
// Then wait for confirmation using production-safe runtime
let runtime = get_runtime()?;
runtime.block_on(async {
self.wait_for_service_status(service_name, timeout_secs)
.await
})
}
fn stop(&self, service_name: &str) -> Result<(), ServiceManagerError> {
ASYNC_RUNTIME.block_on(async {
let runtime = get_runtime()?;
runtime.block_on(async {
let _label = self.get_service_label(service_name);
let plist_path = self.get_plist_path(service_name);
@@ -359,7 +379,8 @@ impl ServiceManager for LaunchctlServiceManager {
}
fn status(&self, service_name: &str) -> Result<ServiceStatus, ServiceManagerError> {
ASYNC_RUNTIME.block_on(async {
let runtime = get_runtime()?;
runtime.block_on(async {
let label = self.get_service_label(service_name);
let plist_path = self.get_plist_path(service_name);
@@ -397,7 +418,8 @@ impl ServiceManager for LaunchctlServiceManager {
service_name: &str,
lines: Option<usize>,
) -> Result<String, ServiceManagerError> {
ASYNC_RUNTIME.block_on(async {
let runtime = get_runtime()?;
runtime.block_on(async {
let log_path = self.get_log_path(service_name);
if !log_path.exists() {
@@ -421,7 +443,8 @@ impl ServiceManager for LaunchctlServiceManager {
}
fn list(&self) -> Result<Vec<String>, ServiceManagerError> {
ASYNC_RUNTIME.block_on(async {
let runtime = get_runtime()?;
runtime.block_on(async {
let list_output = self.run_launchctl(&["list"]).await?;
let services: Vec<String> = list_output
@@ -456,8 +479,9 @@ impl ServiceManager for LaunchctlServiceManager {
);
}
// Remove the plist file using the shared runtime
ASYNC_RUNTIME.block_on(async {
// Remove the plist file using production-safe runtime
let runtime = get_runtime()?;
runtime.block_on(async {
let plist_path = self.get_plist_path(service_name);
if plist_path.exists() {
tokio::fs::remove_file(&plist_path).await?;