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.
This commit is contained in:
Maofeng
2026-07-29 20:25:58 +08:00
parent 71654a8bcf
commit 778fbd2a06
2 changed files with 66 additions and 0 deletions
+49
View File
@@ -390,6 +390,55 @@ impl Runtime {
self.start(&key, init_config) 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. /// Returns counts without exposing internal Engine or Store state.
pub fn stats(&self) -> Result<RuntimeStats, RuntimeError> { pub fn stats(&self) -> Result<RuntimeStats, RuntimeError> {
let registered_services = self let registered_services = self
@@ -283,6 +283,23 @@ fn kv_capability_requires_a_backend_and_enforces_limits() {
runtime.stop(&key).unwrap(); 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] #[test]
fn registration_rejects_non_whitelisted_wasi_imports() { fn registration_rejects_non_whitelisted_wasi_imports() {
let runtime = Runtime::new(RuntimeConfig::default()).expect("runtime should start"); let runtime = Runtime::new(RuntimeConfig::default()).expect("runtime should start");