260 lines
8.8 KiB
Rust
260 lines
8.8 KiB
Rust
//! WASM Module Manager
|
|
|
|
use std::{
|
|
collections::HashMap,
|
|
sync::{
|
|
atomic::{AtomicU64, Ordering},
|
|
Arc, RwLock,
|
|
},
|
|
};
|
|
|
|
use uuid::Uuid;
|
|
|
|
use crate::wasm::{
|
|
config::WasmRuntimeConfig,
|
|
errors::{Result, WasmError, WasmManagerError, WasmModuleError},
|
|
module::{WasmModule, WasmModuleAttachPoint},
|
|
runtime::WasmRuntime,
|
|
types::{WasmComponentInput, WasmComponentOutput},
|
|
};
|
|
|
|
pub struct WasmModuleManager {
|
|
modules: Arc<RwLock<HashMap<Uuid, WasmModule>>>,
|
|
runtime: Arc<WasmRuntime>,
|
|
// Metrics
|
|
total_executions: AtomicU64,
|
|
successful_executions: AtomicU64,
|
|
failed_executions: AtomicU64,
|
|
total_execution_time_ms: AtomicU64,
|
|
max_execution_time_ms: AtomicU64,
|
|
}
|
|
|
|
impl WasmModuleManager {
|
|
pub fn new(config: WasmRuntimeConfig) -> Result<Self> {
|
|
let runtime = Arc::new(WasmRuntime::new(config)?);
|
|
Ok(Self {
|
|
modules: Arc::new(RwLock::new(HashMap::new())),
|
|
runtime,
|
|
total_executions: AtomicU64::new(0),
|
|
successful_executions: AtomicU64::new(0),
|
|
failed_executions: AtomicU64::new(0),
|
|
total_execution_time_ms: AtomicU64::new(0),
|
|
max_execution_time_ms: AtomicU64::new(0),
|
|
})
|
|
}
|
|
|
|
pub fn with_default_config() -> Result<Self> {
|
|
Self::new(WasmRuntimeConfig::default())
|
|
}
|
|
|
|
/// Register a module (for workflow steps)
|
|
pub(crate) fn register_module_internal(&self, module: WasmModule) -> Result<()> {
|
|
let mut modules = self
|
|
.modules
|
|
.write()
|
|
.map_err(|e| WasmManagerError::LockFailed(e.to_string()))?;
|
|
modules.insert(module.module_uuid, module);
|
|
Ok(())
|
|
}
|
|
|
|
/// Remove a module (for workflow steps)
|
|
pub(crate) fn remove_module_internal(&self, module_uuid: Uuid) -> Result<()> {
|
|
let mut modules = self
|
|
.modules
|
|
.write()
|
|
.map_err(|e| WasmManagerError::LockFailed(e.to_string()))?;
|
|
if !modules.contains_key(&module_uuid) {
|
|
return Err(WasmManagerError::ModuleNotFound(module_uuid).into());
|
|
}
|
|
modules.remove(&module_uuid);
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn check_duplicate_sha256_hash(&self, sha256_hash: &[u8; 32]) -> Result<()> {
|
|
let modules = self
|
|
.modules
|
|
.read()
|
|
.map_err(|e| WasmManagerError::LockFailed(e.to_string()))?;
|
|
if modules
|
|
.values()
|
|
.any(|module: &WasmModule| module.module_meta.sha256_hash == *sha256_hash)
|
|
{
|
|
return Err(WasmModuleError::DuplicateSha256((*sha256_hash).into()).into());
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn get_all_modules(&self) -> Result<Vec<WasmModule>> {
|
|
let modules = self
|
|
.modules
|
|
.read()
|
|
.map_err(|e| WasmManagerError::LockFailed(e.to_string()))?;
|
|
Ok(modules.values().cloned().collect())
|
|
}
|
|
|
|
pub fn get_module(&self, module_uuid: Uuid) -> Result<Option<WasmModule>> {
|
|
let modules = self
|
|
.modules
|
|
.read()
|
|
.map_err(|e| WasmManagerError::LockFailed(e.to_string()))?;
|
|
Ok(modules.get(&module_uuid).cloned())
|
|
}
|
|
|
|
pub fn get_modules(&self) -> Result<Vec<WasmModule>> {
|
|
let modules = self
|
|
.modules
|
|
.read()
|
|
.map_err(|e| WasmManagerError::LockFailed(e.to_string()))?;
|
|
Ok(modules.values().cloned().collect())
|
|
}
|
|
|
|
/// get modules by attach point
|
|
pub fn get_modules_by_attach_point(
|
|
&self,
|
|
attach_point: WasmModuleAttachPoint,
|
|
) -> Result<Vec<WasmModule>> {
|
|
let modules = self
|
|
.modules
|
|
.read()
|
|
.map_err(|e| WasmManagerError::LockFailed(e.to_string()))?;
|
|
Ok(modules
|
|
.values()
|
|
.filter(|module| module.module_meta.attach_points.contains(&attach_point))
|
|
.cloned()
|
|
.collect())
|
|
}
|
|
|
|
pub fn get_runtime(&self) -> &Arc<WasmRuntime> {
|
|
&self.runtime
|
|
}
|
|
|
|
/// Execute WASM module using WebAssembly component model based on attach_point
|
|
pub async fn execute_module_interface(
|
|
&self,
|
|
module_uuid: Uuid,
|
|
attach_point: WasmModuleAttachPoint,
|
|
input: WasmComponentInput,
|
|
) -> Result<WasmComponentOutput> {
|
|
let start_time = std::time::Instant::now();
|
|
|
|
// First, get the WASM bytes with a read lock (faster)
|
|
let wasm_bytes = {
|
|
let modules = self
|
|
.modules
|
|
.read()
|
|
.map_err(|e| WasmManagerError::LockFailed(e.to_string()))?;
|
|
let module = modules
|
|
.get(&module_uuid)
|
|
.ok_or_else(|| WasmError::from(WasmManagerError::ModuleNotFound(module_uuid)))?;
|
|
|
|
// Clone the pre-loaded WASM bytes (already in memory, no file I/O)
|
|
module.module_meta.wasm_bytes.clone()
|
|
};
|
|
|
|
{
|
|
let mut modules = self
|
|
.modules
|
|
.write()
|
|
.map_err(|e| WasmManagerError::LockFailed(e.to_string()))?;
|
|
if let Some(module) = modules.get_mut(&module_uuid) {
|
|
// SystemTime::duration_since only fails if the system time is before UNIX_EPOCH,
|
|
// which should never happen in normal operation. If it does, use current time as fallback.
|
|
let now = std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.unwrap_or_else(|_| {
|
|
// Fallback to a reasonable timestamp if system time is invalid
|
|
// This should never occur in practice, but provides a safe fallback
|
|
std::time::Duration::from_nanos(0)
|
|
})
|
|
.as_nanos() as u64;
|
|
module.module_meta.last_accessed_at = now;
|
|
module.module_meta.access_count += 1;
|
|
}
|
|
}
|
|
|
|
let result = self
|
|
.runtime
|
|
.execute_component_async(wasm_bytes, attach_point, input)
|
|
.await;
|
|
|
|
// Record metrics
|
|
let execution_time_ms = start_time.elapsed().as_millis() as u64;
|
|
self.total_executions.fetch_add(1, Ordering::Relaxed);
|
|
self.total_execution_time_ms
|
|
.fetch_add(execution_time_ms, Ordering::Relaxed);
|
|
// Update max execution time
|
|
self.max_execution_time_ms
|
|
.fetch_max(execution_time_ms, Ordering::Relaxed);
|
|
|
|
if result.is_ok() {
|
|
self.successful_executions.fetch_add(1, Ordering::Relaxed);
|
|
} else {
|
|
self.failed_executions.fetch_add(1, Ordering::Relaxed);
|
|
}
|
|
|
|
result
|
|
}
|
|
|
|
/// Execute WASM module using WebAssembly component model (sync version)
|
|
pub fn execute_module_interface_sync(
|
|
&self,
|
|
module_uuid: Uuid,
|
|
attach_point: WasmModuleAttachPoint,
|
|
input: WasmComponentInput,
|
|
) -> Result<WasmComponentOutput> {
|
|
let handle = tokio::runtime::Handle::current();
|
|
handle.block_on(self.execute_module_interface(module_uuid, attach_point, input))
|
|
}
|
|
|
|
/// Get current metrics
|
|
pub fn get_metrics(&self) -> (u64, u64, u64, u64, u64) {
|
|
(
|
|
self.total_executions.load(Ordering::Relaxed),
|
|
self.successful_executions.load(Ordering::Relaxed),
|
|
self.failed_executions.load(Ordering::Relaxed),
|
|
self.total_execution_time_ms.load(Ordering::Relaxed),
|
|
self.max_execution_time_ms.load(Ordering::Relaxed),
|
|
)
|
|
}
|
|
|
|
/// Execute a WASM module for a given attach point
|
|
/// Returns the Action if successful, or None if execution failed
|
|
///
|
|
/// This is a convenience method that wraps execute_module_interface and handles
|
|
/// error logging automatically.
|
|
pub async fn execute_module_for_attach_point(
|
|
&self,
|
|
module: &WasmModule,
|
|
attach_point: WasmModuleAttachPoint,
|
|
input: WasmComponentInput,
|
|
) -> Option<crate::wasm::spec::sgl::router::middleware_types::Action> {
|
|
use tracing::error;
|
|
|
|
let action_result = self
|
|
.execute_module_interface(module.module_uuid, attach_point, input)
|
|
.await;
|
|
|
|
match action_result {
|
|
Ok(output) => match output {
|
|
WasmComponentOutput::MiddlewareAction(action) => Some(action),
|
|
},
|
|
Err(e) => {
|
|
error!(
|
|
"Failed to execute WASM module {}: {}",
|
|
module.module_meta.name, e
|
|
);
|
|
None
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for WasmModuleManager {
|
|
fn default() -> Self {
|
|
// with_default_config() should always succeed with default configuration.
|
|
// If it fails, it indicates a critical system configuration error.
|
|
Self::with_default_config()
|
|
.expect("Failed to create WasmModuleManager with default config. This should never happen with valid default configuration.")
|
|
}
|
|
}
|