Remove epoch-based metering

This commit is contained in:
Isaac Clayton 2022-07-13 18:03:14 +02:00
parent 562e22814f
commit 8bb8e851df
5 changed files with 41 additions and 152 deletions

View File

@ -154,8 +154,6 @@ Plugins in the `plugins` directory are automatically recompiled and serialized t
- `plugin.wasm.pre` is the plugin compiled to Wasm *and additionally* precompiled to host-platform-agnostic cranelift-specific IR. This should be about 700KB for debug builds and 500KB in release builds. Each plugin takes about 1 or 2 seconds to compile to native code using cranelift, so precompiling plugins drastically reduces the startup time required to begin to run a plugin.
> TODO: Rework precompiled plugins.
For all intents and purposes, it is *highly recommended* that you use precompiled plugins where possible, as they are much more lightweight and take much less time to instantiate.
### Instantiating a plugin

View File

@ -43,8 +43,7 @@ fn main() {
assert!(build_successful);
// Find all compiled binaries
let epoch_engine = create_epoch_engine();
let fuel_engine = create_fuel_engine();
let engine = create_default_engine();
let binaries = std::fs::read_dir(base.join("target/wasm32-wasi").join(profile_target))
.expect("Could not find compiled plugins in target");
@ -62,35 +61,29 @@ fn main() {
if let Some(path) = is_wasm() {
let out_path = base.join("bin").join(path.file_name().unwrap());
std::fs::copy(&path, &out_path).expect("Could not copy compiled plugin to bin");
precompile(&out_path, &epoch_engine, "epoch");
precompile(&out_path, &fuel_engine, "fuel");
precompile(&out_path, &engine);
}
}
}
fn create_epoch_engine() -> Engine {
let mut config = Config::default();
config.async_support(true);
config.epoch_interruption(true);
Engine::new(&config).expect("Could not create engine")
}
fn create_fuel_engine() -> Engine {
/// Creates an engine with the default configuration.
/// N.B. This must create an engine with the same config as the one
/// in `plugin_runtime/build.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")
Engine::new(&config).expect("Could not create precompilation engine")
}
fn precompile(path: &Path, engine: &Engine, engine_name: &str) {
fn precompile(path: &Path, engine: &Engine) {
let bytes = std::fs::read(path).expect("Could not read wasm module");
let compiled = engine
.precompile_module(&bytes)
.expect("Could not precompile module");
let out_path = path.parent().unwrap().join(&format!(
"{}.{}",
"{}.pre",
path.file_name().unwrap().to_string_lossy(),
engine_name,
));
let mut out_file = std::fs::File::create(out_path)
.expect("Could not create output file for precompiled module");

View File

@ -23,7 +23,7 @@ mod tests {
}
async {
let mut runtime = PluginBuilder::new_fuel_with_default_ctx(PluginYield::default_fuel())
let mut runtime = PluginBuilder::new_default()
.unwrap()
.host_function("mystery_number", |input: u32| input + 7)
.unwrap()

View File

@ -1,6 +1,5 @@
use std::future::Future;
use std::time::Duration;
use std::{fs::File, marker::PhantomData, path::Path};
use anyhow::{anyhow, Error};
@ -55,34 +54,14 @@ impl<A: Serialize, R: DeserializeOwned> Clone for WasiFn<A, R> {
}
}
pub struct PluginYieldEpoch {
delta: u64,
epoch: std::time::Duration,
}
pub struct PluginYieldFuel {
pub struct Metering {
initial: u64,
refill: u64,
}
pub enum PluginYield {
Epoch {
yield_epoch: PluginYieldEpoch,
initialize_incrementer: Box<dyn FnOnce(Engine) -> () + Send>,
},
Fuel(PluginYieldFuel),
}
impl PluginYield {
pub fn default_epoch() -> PluginYieldEpoch {
PluginYieldEpoch {
delta: 1,
epoch: Duration::from_millis(1),
}
}
pub fn default_fuel() -> PluginYieldFuel {
PluginYieldFuel {
impl Default for Metering {
fn default() -> Self {
Metering {
initial: 1000,
refill: 1000,
}
@ -97,110 +76,44 @@ pub struct PluginBuilder {
wasi_ctx: WasiCtx,
engine: Engine,
linker: Linker<WasiCtxAlloc>,
yield_when: PluginYield,
metering: Metering,
}
/// Creates an engine with the default configuration.
/// N.B. This must create an engine with the same config as the one
/// in `plugin_runtime/build.rs`.
fn create_default_engine() -> Result<Engine, Error> {
let mut config = Config::default();
config.async_support(true);
config.consume_fuel(true);
Engine::new(&config)
}
impl PluginBuilder {
/// Creates an engine with the proper configuration given the yield mechanism in use
fn create_engine(yield_when: &PluginYield) -> Result<(Engine, Linker<WasiCtxAlloc>), Error> {
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((engine, linker))
}
/// Create a new [`PluginBuilder`] with the given WASI context.
/// Using the default context is a safe bet, see [`new_with_default_context`].
/// This plugin will yield after each fixed configurable epoch.
pub fn new_epoch<C>(
wasi_ctx: WasiCtx,
yield_epoch: PluginYieldEpoch,
spawn_detached_future: C,
) -> Result<Self, Error>
where
C: FnOnce(std::pin::Pin<Box<dyn Future<Output = ()> + Send + 'static>>) -> ()
+ Send
+ 'static,
{
// we can't create the future until after initializing
// because we need the engine to load the plugin
let epoch = yield_epoch.epoch;
let initialize_incrementer = Box::new(move |engine: Engine| {
spawn_detached_future(Box::pin(async move {
loop {
smol::Timer::after(epoch).await;
engine.increment_epoch();
}
}))
});
let yield_when = PluginYield::Epoch {
yield_epoch,
initialize_incrementer,
};
let (engine, linker) = Self::create_engine(&yield_when)?;
Ok(PluginBuilder {
wasi_ctx,
engine,
linker,
yield_when,
})
}
/// Create a new [`PluginBuilder`] with the given WASI context.
/// Using the default context is a safe bet, see [`new_with_default_context`].
/// This plugin will yield after a configurable amount of fuel is consumed.
pub fn new_fuel(wasi_ctx: WasiCtx, yield_fuel: PluginYieldFuel) -> Result<Self, Error> {
let yield_when = PluginYield::Fuel(yield_fuel);
let (engine, linker) = Self::create_engine(&yield_when)?;
pub fn new(wasi_ctx: WasiCtx, metering: Metering) -> Result<Self, Error> {
let engine = create_default_engine()?;
let linker = Linker::new(&engine);
Ok(PluginBuilder {
wasi_ctx,
engine,
linker,
yield_when,
metering,
})
}
/// Create a new `WasiCtx` that inherits the
/// host processes' access to `stdout` and `stderr`.
fn default_ctx() -> WasiCtx {
WasiCtxBuilder::new()
/// Create a new `PluginBuilder` with the default `WasiCtx` (see [`default_ctx`]).
/// This plugin will yield after a configurable amount of fuel is consumed.
pub fn new_default() -> Result<Self, Error> {
let default_ctx = WasiCtxBuilder::new()
.inherit_stdout()
.inherit_stderr()
.build()
}
/// Create a new `PluginBuilder` with the default `WasiCtx` (see [`default_ctx`]).
/// This plugin will yield after each fixed configurable epoch.
pub fn new_epoch_with_default_ctx<C>(
yield_epoch: PluginYieldEpoch,
spawn_detached_future: C,
) -> Result<Self, Error>
where
C: FnOnce(std::pin::Pin<Box<dyn Future<Output = ()> + Send + 'static>>) -> ()
+ Send
+ 'static,
{
Self::new_epoch(Self::default_ctx(), yield_epoch, spawn_detached_future)
}
/// Create a new `PluginBuilder` with the default `WasiCtx` (see [`default_ctx`]).
/// This plugin will yield after a configurable amount of fuel is consumed.
pub fn new_fuel_with_default_ctx(yield_fuel: PluginYieldFuel) -> Result<Self, Error> {
Self::new_fuel(Self::default_ctx(), yield_fuel)
.build();
let metering = Metering::default();
Self::new(default_ctx, metering)
}
/// Add an `async` host function. See [`host_function`] for details.
@ -433,19 +346,8 @@ impl Plugin {
};
// set up automatic yielding based on configuration
match plugin.yield_when {
PluginYield::Epoch {
yield_epoch: PluginYieldEpoch { delta, .. },
initialize_incrementer,
} => {
store.epoch_deadline_async_yield_and_update(delta);
initialize_incrementer(engine);
}
PluginYield::Fuel(PluginYieldFuel { initial, refill }) => {
store.add_fuel(initial).unwrap();
store.out_of_fuel_async_yield(u64::MAX, refill);
}
}
store.add_fuel(plugin.metering.initial).unwrap();
store.out_of_fuel_async_yield(u64::MAX, plugin.metering.refill);
// load the provided module into the asynchronous runtime
linker.module_async(&mut store, "", &module).await?;

View File

@ -5,16 +5,12 @@ use collections::HashMap;
use futures::lock::Mutex;
use gpui::executor::Background;
use language::{LanguageServerName, LspAdapter};
use plugin_runtime::{Plugin, PluginBinary, PluginBuilder, PluginYield, WasiFn};
use plugin_runtime::{Plugin, PluginBinary, PluginBuilder, WasiFn};
use std::{any::Any, path::PathBuf, sync::Arc};
use util::ResultExt;
pub async fn new_json(executor: Arc<Background>) -> Result<PluginLspAdapter> {
let executor_ref = executor.clone();
let plugin =
PluginBuilder::new_epoch_with_default_ctx(PluginYield::default_epoch(), move |future| {
executor_ref.spawn(future).detach()
})?
let plugin = PluginBuilder::new_default()?
.host_function_async("command", |command: String| async move {
let mut args = command.split(' ');
let command = args.next().unwrap();
@ -26,7 +22,7 @@ pub async fn new_json(executor: Arc<Background>) -> Result<PluginLspAdapter> {
.map(|output| output.stdout)
})?
.init(PluginBinary::Precompiled(include_bytes!(
"../../../../plugins/bin/json_language.wasm.epoch"
"../../../../plugins/bin/json_language.wasm.pre"
)))
.await?;