use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use arc_swap::ArcSwapOption;
use nichlink_run_method::VerifiedPluginArtifact;
use crate::{HostError, PluginInstance, WasmBackend, WasmInstance};
use super::{ValidationChannel, WasmPluginSlot, validate_artifact};
pub(super) struct PendingPlugin {
generation: u64,
artifact: VerifiedPluginArtifact,
}
pub(super) struct LoadedPlugin {
pub(super) generation: u64,
pub(super) instance: WasmInstance,
}
pub(super) struct SlotState {
pub(super) definition: WasmPluginSlot,
pub(super) active: ArcSwapOption<LoadedPlugin>,
pub(super) pending: Mutex<Option<PendingPlugin>>,
pub(super) has_pending: AtomicBool,
pub(super) next_generation: AtomicU64,
pub(super) activation_error: Mutex<Option<String>>,
}
impl SlotState {
pub(super) fn install(
&self,
channel: ValidationChannel,
artifact: VerifiedPluginArtifact,
) -> Result<u64, HostError> {
validate_artifact(self.definition, channel, &artifact)?;
let mut pending = self
.pending
.lock()
.map_err(|_| HostError::State("plugin slot lock was poisoned".to_owned()))?;
let generation = self.next_generation.fetch_add(1, Ordering::Relaxed) + 1;
*pending = Some(PendingPlugin {
generation,
artifact,
});
self.has_pending.store(true, Ordering::Release);
Ok(generation)
}
pub(super) fn activate(&self, backend: WasmBackend) -> Result<(), HostError> {
if !self.has_pending.load(Ordering::Acquire) {
return Ok(());
}
let mut pending = self
.pending
.lock()
.map_err(|_| HostError::State("plugin slot lock was poisoned".to_owned()))?;
let Some(candidate) = pending.take() else {
self.has_pending.store(false, Ordering::Release);
return Ok(());
};
let loaded = backend.load(candidate.artifact).and_then(|instance| {
instance.health_check()?;
Ok(LoadedPlugin {
generation: candidate.generation,
instance,
})
});
match loaded {
Ok(loaded) => {
self.active.store(Some(Arc::new(loaded)));
self.has_pending.store(false, Ordering::Release);
self.record_activation(None);
Ok(())
}
Err(error) => {
self.has_pending.store(false, Ordering::Release);
self.record_activation(Some(error.to_string()));
Err(error)
}
}
}
fn record_activation(&self, error: Option<String>) {
if let Ok(mut slot) = self.activation_error.lock() {
*slot = error;
}
}
}