diff --git a/crates/plugin_runtime/build.rs b/crates/plugin_runtime/build.rs index 0bae17d7cd..75a8d1d78f 100644 --- a/crates/plugin_runtime/build.rs +++ b/crates/plugin_runtime/build.rs @@ -54,13 +54,9 @@ fn main() { } /// Creates a default engine for compiling Wasm. -/// N.B.: this must create the same `Engine` as -/// the `create_default_engine` function -/// in `plugin_runtime/src/plugin.rs`. fn create_default_engine() -> Engine { let mut config = Config::default(); config.async_support(true); - config.consume_fuel(true); Engine::new(&config).expect("Could not create engine") } diff --git a/crates/plugin_runtime/src/lib.rs b/crates/plugin_runtime/src/lib.rs index 555d925874..75a1950114 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() + let mut runtime = PluginBuilder::new_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 1db81375bf..9436a11bc6 100644 --- a/crates/plugin_runtime/src/plugin.rs +++ b/crates/plugin_runtime/src/plugin.rs @@ -1,5 +1,6 @@ use std::future::Future; +use std::time::Duration; use std::{fs::File, marker::PhantomData, path::Path}; use anyhow::{anyhow, Error}; @@ -54,6 +55,33 @@ impl Clone for WasiFn { } } +pub enum PluginYield { + Epoch { + delta: u64, + epoch: std::time::Duration, + }, + Fuel { + initial: u64, + refill: u64, + }, +} + +impl PluginYield { + pub fn default_epoch() -> Self { + PluginYield::Epoch { + delta: 1, + epoch: Duration::from_millis(1), + } + } + + pub fn default_fuel() -> Self { + PluginYield::Fuel { + initial: 1000, + refill: 1000, + } + } +} + /// This struct is used to build a new [`Plugin`], using the builder pattern. /// Create a new default plugin with `PluginBuilder::new_with_default_ctx`, /// and add host-side exported functions using `host_function` and `host_function_async`. @@ -62,43 +90,66 @@ pub struct PluginBuilder { wasi_ctx: WasiCtx, engine: Engine, linker: Linker, - fuel_refill: u64, -} - -/// Creates a default engine for compiling Wasm. -/// N.B.: this must create the same `Engine` as -/// the `create_default_engine` function -/// in `plugin_runtime/build.rs`. -pub fn create_default_engine() -> Result { - let mut config = Config::default(); - config.async_support(true); - config.consume_fuel(true); - Engine::new(&config) + 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) -> Result { - let engine = create_default_engine()?; + pub fn new(wasi_ctx: WasiCtx, yield_when: PluginYield) -> Result { + let mut config = Config::default(); + config.async_support(true); + + match yield_when { + PluginYield::Epoch { .. } => { + config.epoch_interruption(true); + } + PluginYield::Fuel { .. } => { + config.consume_fuel(true); + } + } + + let engine = Engine::new(&config)?; let linker = Linker::new(&engine); Ok(PluginBuilder { wasi_ctx, engine, linker, - fuel_refill: 1000, + 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() -> Result { + pub fn new_with_default_ctx(yield_when: PluginYield) -> Result { let wasi_ctx = WasiCtxBuilder::new() .inherit_stdout() .inherit_stderr() .build(); - Self::new(wasi_ctx) + Self::new(wasi_ctx, 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.") + } } /// Add an `async` host function. See [`host_function`] for details. @@ -243,7 +294,14 @@ impl PluginBuilder { /// Initializes a [`Plugin`] from a given compiled Wasm module. /// Both binary (`.wasm`) and text (`.wat`) module formats are supported. + /// 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 } } @@ -330,9 +388,16 @@ impl Plugin { Module::new(&engine, module)? }; - // set up automatic yielding after fuel expires - store.out_of_fuel_async_yield(u64::MAX, plugin.fuel_refill); - store.add_fuel(plugin.fuel_refill).unwrap(); + // set up automatic yielding based on configuration + match plugin.yield_when { + PluginYield::Epoch { delta, .. } => { + store.epoch_deadline_async_yield_and_update(delta); + } + PluginYield::Fuel { initial, refill } => { + store.add_fuel(initial).unwrap(); + store.out_of_fuel_async_yield(u64::MAX, refill); + } + } // load the provided module into the asynchronous runtime linker.module_async(&mut store, "", &module).await?; diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index f5bfa661b3..ab0e726b51 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -4,12 +4,12 @@ use client::http::HttpClient; use futures::lock::Mutex; use gpui::executor::Background; use language::{LanguageServerName, LspAdapter}; -use plugin_runtime::{Plugin, PluginBuilder, WasiFn}; +use plugin_runtime::{Plugin, PluginBuilder, PluginYield, WasiFn}; 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()? + let plugin = PluginBuilder::new_with_default_ctx(PluginYield::default_epoch())? .host_function_async("command", |command: String| async move { let mut args = command.split(' '); let command = args.next().unwrap();