From 8974b0c490b4c8c4c3f49efba1b2adc715a38dfd Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Wed, 13 Jul 2022 12:41:47 +0200 Subject: [PATCH] Work on supporting both epoch and fuel --- crates/plugin_runtime/src/lib.rs | 2 +- crates/plugin_runtime/src/plugin.rs | 148 +++++++++++++------- crates/zed/src/languages/language_plugin.rs | 5 +- 3 files changed, 99 insertions(+), 56 deletions(-) diff --git a/crates/plugin_runtime/src/lib.rs b/crates/plugin_runtime/src/lib.rs index 75a1950114..b59f03a55f 100644 --- a/crates/plugin_runtime/src/lib.rs +++ b/crates/plugin_runtime/src/lib.rs @@ -23,7 +23,7 @@ mod tests { } async { - let mut runtime = PluginBuilder::new_with_default_ctx(PluginYield::default_fuel()) + let mut runtime = PluginBuilder::new_fuel_with_default_ctx(PluginYield::default_fuel()) .unwrap() .host_function("mystery_number", |input: u32| input + 7) .unwrap() diff --git a/crates/plugin_runtime/src/plugin.rs b/crates/plugin_runtime/src/plugin.rs index 9436a11bc6..777d40c265 100644 --- a/crates/plugin_runtime/src/plugin.rs +++ b/crates/plugin_runtime/src/plugin.rs @@ -55,27 +55,43 @@ impl Clone for WasiFn { } } +pub struct PluginYieldEpoch { + delta: u64, + epoch: std::time::Duration, +} + +impl Into for PluginYieldEpoch { + fn into(self) -> PluginYield { + PluginYield::Epoch(self) + } +} + +pub struct PluginYieldFuel { + initial: u64, + refill: u64, +} + +impl Into for PluginYieldFuel { + fn into(self) -> PluginYield { + PluginYield::Fuel(self) + } +} + pub enum PluginYield { - Epoch { - delta: u64, - epoch: std::time::Duration, - }, - Fuel { - initial: u64, - refill: u64, - }, + Epoch(PluginYieldEpoch), + Fuel(PluginYieldFuel), } impl PluginYield { - pub fn default_epoch() -> Self { - PluginYield::Epoch { + pub fn default_epoch() -> PluginYieldEpoch { + PluginYieldEpoch { delta: 1, epoch: Duration::from_millis(1), } } - pub fn default_fuel() -> Self { - PluginYield::Fuel { + pub fn default_fuel() -> PluginYieldFuel { + PluginYieldFuel { initial: 1000, refill: 1000, } @@ -91,65 +107,94 @@ pub struct PluginBuilder { engine: Engine, linker: Linker, yield_when: PluginYield, - created_epoch_incrementer: bool, } impl PluginBuilder { - /// Create a new [`PluginBuilder`] with the given WASI context. - /// Using the default context is a safe bet, see [`new_with_default_context`]. - pub fn new(wasi_ctx: WasiCtx, yield_when: PluginYield) -> Result { + fn create_engine(yield_when: &PluginYield) -> Result<(Engine, Linker), Error> { let mut config = Config::default(); config.async_support(true); + let engine = Engine::new(&config)?; + let linker = Linker::new(&engine); match yield_when { - PluginYield::Epoch { .. } => { + PluginYield::Epoch(_) => { config.epoch_interruption(true); } - PluginYield::Fuel { .. } => { + PluginYield::Fuel(_) => { config.consume_fuel(true); } } - let engine = Engine::new(&config)?; - let linker = Linker::new(&engine); + Ok((engine, linker)) + } + + /// Create a new [`PluginBuilder`] with the given WASI context. + /// Using the default context is a safe bet, see [`new_with_default_context`]. + pub fn new_epoch( + wasi_ctx: WasiCtx, + yield_epoch: PluginYieldEpoch, + callback: C, + ) -> Result + where + C: FnOnce(std::pin::Pin + Send + 'static>>) -> (), + { + let epoch = yield_epoch.epoch; + let yield_when = PluginYield::Epoch(yield_epoch); + let (engine, linker) = Self::create_engine(&yield_when)?; + + let engine_ref = &engine; + + // we can't create the future until after initializing + // because we need the engine to load the plugin + // we could use an Arc, but that'd suck + callback(Box::pin(async move { + loop { + smol::Timer::after(epoch).await; + engine_ref.increment_epoch(); + } + })); Ok(PluginBuilder { wasi_ctx, engine, linker, yield_when, - created_epoch_incrementer: false, }) } - /// Create a new `PluginBuilder` that inherits the - /// host processes' access to `stdout` and `stderr`. - pub fn new_with_default_ctx(yield_when: PluginYield) -> Result { - let wasi_ctx = WasiCtxBuilder::new() - .inherit_stdout() - .inherit_stderr() - .build(); - Self::new(wasi_ctx, yield_when) + pub fn new_fuel(wasi_ctx: WasiCtx, yield_fuel: PluginYieldFuel) -> Result { + let yield_when = PluginYield::Fuel(yield_fuel); + let (engine, linker) = Self::create_engine(&yield_when)?; + + Ok(PluginBuilder { + wasi_ctx, + engine, + linker, + yield_when, + }) } - /// Creates a epoch incrementer if this plugin is configured to increment epochs. - /// Will panic if this plugin is not configured to increment epochs. - pub fn create_epoch_incrementer( - &mut self, - ) -> std::pin::Pin + Send + 'static>> { - if let PluginYield::Epoch { epoch, .. } = self.yield_when { - self.created_epoch_incrementer = true; - let epoch = epoch.clone(); - let engine = &self.engine; - Box::pin(async move { - loop { - smol::Timer::after(epoch); - engine.increment_epoch(); - } - }) - } else { - panic!("Tried creating an epoch incrementer, but one does not yet exist.") - } + /// Create a new `WasiCtx` that inherits the + /// host processes' access to `stdout` and `stderr`. + fn default_ctx() -> WasiCtx { + WasiCtxBuilder::new() + .inherit_stdout() + .inherit_stderr() + .build() + } + + pub fn new_epoch_with_default_ctx( + yield_epoch: PluginYieldEpoch, + callback: C, + ) -> Result + where + C: FnOnce(std::pin::Pin + Send + 'static>>) -> (), + { + Self::new_epoch(Self::default_ctx(), yield_epoch, callback) + } + + pub fn new_fuel_with_default_ctx(yield_fuel: PluginYieldFuel) -> Result { + Self::new_fuel(Self::default_ctx(), yield_fuel) } /// Add an `async` host function. See [`host_function`] for details. @@ -297,11 +342,6 @@ impl PluginBuilder { /// Will panic if this is plugin uses `PluginYield::Epoch`, /// but an epoch incrementer has not yet been created. pub async fn init>(self, precompiled: bool, module: T) -> Result { - if let PluginYield::Epoch { .. } = self.yield_when { - if !self.created_epoch_incrementer { - panic!("Must create epoch incrementer to run epoch-based plugin"); - } - } Plugin::init(precompiled, module.as_ref().to_vec(), self).await } } @@ -390,10 +430,10 @@ impl Plugin { // set up automatic yielding based on configuration match plugin.yield_when { - PluginYield::Epoch { delta, .. } => { + PluginYield::Epoch(PluginYieldEpoch { delta, .. }) => { store.epoch_deadline_async_yield_and_update(delta); } - PluginYield::Fuel { initial, refill } => { + PluginYield::Fuel(PluginYieldFuel { initial, refill }) => { store.add_fuel(initial).unwrap(); store.out_of_fuel_async_yield(u64::MAX, refill); } diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index ab0e726b51..78242865ab 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -9,7 +9,10 @@ use std::{any::Any, path::PathBuf, sync::Arc}; use util::ResultExt; pub async fn new_json(executor: Arc) -> Result { - let plugin = PluginBuilder::new_with_default_ctx(PluginYield::default_epoch())? + let plugin = + PluginBuilder::new_epoch_with_default_ctx(PluginYield::default_epoch(), |future| { + executor.spawn(future).detach() + })? .host_function_async("command", |command: String| async move { let mut args = command.split(' '); let command = args.next().unwrap();