Skip to main content

nichlink_plugin_host/
lazy_wasm.rs

1//! Lazy, slot-scoped activation of verified Wasm plugins.
2//! 已验证 Wasm 插件的懒加载、按槽激活。
3
4use std::collections::BTreeMap;
5use std::sync::Mutex;
6use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
7
8use arc_swap::ArcSwapOption;
9use nichlink_run_method::{FlowContract, FrameworkId, PluginMode, VerifiedPluginArtifact};
10
11use crate::{HostError, PluginInstance, WasmBackend};
12
13#[path = "lazy_wasm/slot_state.rs"]
14mod slot_state;
15use slot_state::SlotState;
16
17/// Trust lane enabled for one runtime plugin slot.
18/// 运行时插件槽允许使用的信任通道。
19///
20/// The definition lives in the kernel `plugin` module; this alias keeps the
21/// historical `nichlink_plugin_host::ValidationChannel` path.
22/// 定义本体在 kernel 的 `plugin` 模块;本别名保留
23/// `nichlink_plugin_host::ValidationChannel` 历史路径。
24pub use nichlink_run_method::PluginChannel as ValidationChannel;
25
26/// A release-time opening for one Wasm extension or replacement.
27/// 正式发布时保留的一个 Wasm 扩展或替换入口。
28#[derive(Clone, Copy, Debug)]
29pub struct WasmPluginSlot {
30    /// Slot name, unique within one table and used to address installs and calls.
31    /// 槽名,在单个表内唯一,用于寻址安装与调用。
32    pub name: &'static str,
33    /// Framework whose artifacts this slot admits.
34    /// 该槽接纳的 framework。
35    pub framework: FrameworkId,
36    /// Whether the plugin extends or replaces the framework.
37    /// 插件是扩展还是替换该 framework。
38    pub mode: PluginMode,
39    /// Flow contract the artifact must match before activation.
40    /// 激活前工件必须匹配的数据流合同。
41    pub contract: FlowContract,
42    /// Trust lanes allowed to install into this slot; must be non-empty.
43    /// 允许安装进该槽的信任通道;不得为空。
44    pub channels: &'static [ValidationChannel],
45}
46
47impl WasmPluginSlot {
48    /// Build a slot definition; `const` so tables can live in static storage.
49    /// 构造槽定义;为 `const`,便于把表放在静态存储中。
50    pub const fn new(
51        name: &'static str,
52        framework: FrameworkId,
53        mode: PluginMode,
54        contract: FlowContract,
55        channels: &'static [ValidationChannel],
56    ) -> Self {
57        Self {
58            name,
59            framework,
60            mode,
61            contract,
62            channels,
63        }
64    }
65}
66
67/// Lazily activates verified Wasm plugins in explicitly retained slots.
68/// 仅在显式保留的槽中懒加载已验证 Wasm 插件。
69pub struct WasmPluginTable {
70    backend: WasmBackend,
71    slots: BTreeMap<&'static str, SlotState>,
72}
73
74impl WasmPluginTable {
75    /// Build a table over the default backend, rejecting malformed slot definitions.
76    /// 在默认后端上建表,并拒绝非法的槽定义。
77    pub fn new(slots: &'static [WasmPluginSlot]) -> Result<Self, HostError> {
78        Self::with_backend(slots, WasmBackend::default())
79    }
80
81    /// Build a table with an explicit backend so callers can supply their own limits.
82    /// 用显式后端建表,便于调用方提供自己的限制。
83    pub fn with_backend(
84        slots: &'static [WasmPluginSlot],
85        backend: WasmBackend,
86    ) -> Result<Self, HostError> {
87        let mut states = BTreeMap::new();
88        for definition in slots {
89            if definition.name.trim().is_empty() {
90                return Err(HostError::Slot("plugin slot name is empty".to_owned()));
91            }
92            if definition.channels.is_empty() {
93                return Err(HostError::Slot(format!(
94                    "plugin slot `{}` has no validation channel",
95                    definition.name
96                )));
97            }
98            if states
99                .insert(
100                    definition.name,
101                    SlotState {
102                        definition: *definition,
103                        active: ArcSwapOption::empty(),
104                        pending: Mutex::new(None),
105                        has_pending: AtomicBool::new(false),
106                        next_generation: AtomicU64::new(0),
107                        activation_error: Mutex::new(None),
108                    },
109                )
110                .is_some()
111            {
112                return Err(HostError::Slot(format!(
113                    "duplicate plugin slot `{}`",
114                    definition.name
115                )));
116            }
117        }
118        Ok(Self {
119            backend,
120            slots: states,
121        })
122    }
123
124    /// Queue verified bytes without compiling or instantiating them.
125    /// 挂起已验证字节,不在安装阶段编译或实例化。
126    pub fn install(
127        &self,
128        slot: &str,
129        channel: ValidationChannel,
130        artifact: VerifiedPluginArtifact,
131    ) -> Result<u64, HostError> {
132        self.slot(slot)?.install(channel, artifact)
133    }
134
135    /// Activate a pending generation on demand, then invoke it.
136    /// 首次使用时激活待发布代,再执行调用。
137    pub fn call(&self, slot: &str, operation: &str, input: &[u8]) -> Result<Vec<u8>, HostError> {
138        let state = self.slot(slot)?;
139        state.activate(self.backend)?;
140        let active = state
141            .active
142            .load_full()
143            .ok_or_else(|| HostError::Slot(format!("plugin slot `{slot}` is not installed")))?;
144        active.instance.call(operation, input)
145    }
146
147    /// Report whether the slot has an active generation and nothing pending.
148    /// 报告该槽是否已有激活代际且没有待处理代际。
149    pub fn is_loaded(&self, slot: &str) -> Result<bool, HostError> {
150        let state = self.slot(slot)?;
151        Ok(!state.has_pending.load(Ordering::Acquire) && state.active.load().is_some())
152    }
153
154    /// Return the active generation, or `None` while the slot is not yet activated.
155    /// 返回激活代际;槽尚未激活时返回 `None`。
156    pub fn generation(&self, slot: &str) -> Result<Option<u64>, HostError> {
157        Ok(self
158            .slot(slot)?
159            .active
160            .load_full()
161            .map(|active| active.generation))
162    }
163
164    /// Report why the last activation attempt failed.
165    /// 报告上一次激活尝试失败的原因。
166    ///
167    /// `None` means the last attempt succeeded or no pending generation has been
168    /// activated yet. The report exists because a failed activation changes
169    /// nothing a poller can already see: `active` keeps the previous generation,
170    /// so `is_loaded` stays `true` and `generation` keeps returning the old
171    /// number while every later call fails. Silence was the one answer a
172    /// readiness poll could not act on.
173    /// `None` 表示上次尝试成功,或还没有待发布代被激活过。之所以需要这份报告:激活失败不会
174    /// 改变轮询方已经能看到的东西——`active` 仍是上一代,因此 `is_loaded` 保持 `true`、
175    /// `generation` 继续返回旧编号,而此后每次调用都失败。沉默正是就绪轮询唯一无法据以行动的
176    /// 回报。
177    pub fn activation_error(&self, slot: &str) -> Result<Option<String>, HostError> {
178        self.slot(slot)?
179            .activation_error
180            .lock()
181            .map(|error| error.clone())
182            .map_err(|_| HostError::State("plugin slot lock was poisoned".to_owned()))
183    }
184
185    fn slot(&self, name: &str) -> Result<&SlotState, HostError> {
186        self.slots
187            .get(name)
188            .ok_or_else(|| HostError::Slot(format!("unknown plugin slot `{name}`")))
189    }
190}
191
192fn validate_artifact(
193    slot: WasmPluginSlot,
194    channel: ValidationChannel,
195    artifact: &VerifiedPluginArtifact,
196) -> Result<(), HostError> {
197    nichlink_run_method::validate_artifact(
198        slot.name,
199        slot.framework,
200        slot.mode,
201        slot.contract,
202        slot.channels,
203        channel,
204        artifact,
205    )
206    .map_err(|error| match error {
207        nichlink_run_method::SlotValidationError::Policy(message) => HostError::Policy(message),
208        nichlink_run_method::SlotValidationError::Contract(message) => HostError::Contract(message),
209    })
210}