From 778fbd2a064d6501c094afbf5e62abd66cbd9c08 Mon Sep 17 00:00:00 2001 From: Maofeng Date: Wed, 29 Jul 2026 20:25:58 +0800 Subject: [PATCH] feat(runtime): unregister service revisions Add a lifecycle-serialized unregister operation that stops a resident Actor before releasing its registered and compiled Component. Return the existing not-registered and unavailable errors without leaving a partially removed live revision, and cover Actor and registration count cleanup. --- crates/wasmeld-runtime/src/runtime.rs | 49 +++++++++++++++++++ .../tests/runtime_components.rs | 17 +++++++ 2 files changed, 66 insertions(+) diff --git a/crates/wasmeld-runtime/src/runtime.rs b/crates/wasmeld-runtime/src/runtime.rs index 7cc064e..6322273 100644 --- a/crates/wasmeld-runtime/src/runtime.rs +++ b/crates/wasmeld-runtime/src/runtime.rs @@ -390,6 +390,55 @@ impl Runtime { self.start(&key, init_config) } + /// Stops and removes one registered service revision. + /// + /// Callers must switch any external deployment reference away from this + /// revision before unregistering it. + pub fn unregister(&self, key: &ServiceKey) -> Result<(), RuntimeError> { + let _lifecycle = self + .inner + .lifecycle + .lock() + .map_err(|_| RuntimeError::LockPoisoned)?; + + if !self + .inner + .services + .lock() + .map_err(|_| RuntimeError::LockPoisoned)? + .contains_key(key) + { + return Err(RuntimeError::ServiceNotRegistered(key.clone())); + } + + let actor = self + .inner + .actors + .lock() + .map_err(|_| RuntimeError::LockPoisoned)? + .get(key) + .cloned(); + if let Some(actor) = actor { + if actor.is_available() { + actor.stop()?; + } else if actor.is_alive() { + return Err(RuntimeError::ActorUnavailable(key.clone())); + } + self.inner + .actors + .lock() + .map_err(|_| RuntimeError::LockPoisoned)? + .remove(key); + } + + self.inner + .services + .lock() + .map_err(|_| RuntimeError::LockPoisoned)? + .remove(key); + Ok(()) + } + /// Returns counts without exposing internal Engine or Store state. pub fn stats(&self) -> Result { let registered_services = self diff --git a/crates/wasmeld-runtime/tests/runtime_components.rs b/crates/wasmeld-runtime/tests/runtime_components.rs index 70ddb15..c63a567 100644 --- a/crates/wasmeld-runtime/tests/runtime_components.rs +++ b/crates/wasmeld-runtime/tests/runtime_components.rs @@ -283,6 +283,23 @@ fn kv_capability_requires_a_backend_and_enforces_limits() { runtime.stop(&key).unwrap(); } +#[test] +fn unregister_stops_actor_and_releases_compiled_revision() { + let runtime = Runtime::new(RuntimeConfig::default()).expect("runtime should start"); + let key = register_component(&runtime, "unregister", "echo_component.wasm", test_limits()); + runtime.start(&key, Vec::new()).unwrap(); + + runtime.unregister(&key).unwrap(); + + let stats = runtime.stats().unwrap(); + assert_eq!(stats.registered_services, 0); + assert_eq!(stats.running_actors, 0); + assert!(matches!( + runtime.start(&key, Vec::new()), + Err(RuntimeError::ServiceNotRegistered(missing)) if missing == key + )); +} + #[test] fn registration_rejects_non_whitelisted_wasi_imports() { let runtime = Runtime::new(RuntimeConfig::default()).expect("runtime should start");