Compare commits

..

2 Commits

Author SHA1 Message Date
zach
d704a5068e refactor!: make UserData more generic 2024-03-18 10:24:47 -07:00
zach
054a29e91d v1.2.0 2024-03-12 08:52:04 -07:00
9 changed files with 19 additions and 126 deletions

View File

@@ -25,7 +25,6 @@ extism-manifest = { workspace = true }
extism-convert = { workspace = true, features = ["extism-path"] }
uuid = { version = "1", features = ["v4"] }
libc = "0.2"
wasi-common = "14.0.0"
[features]
default = ["http", "register-http", "register-filesystem"]

View File

@@ -1,7 +1,7 @@
use extism::*;
// pretend this is redis or something :)
type KVStore = std::collections::BTreeMap<String, Vec<u8>>;
type KVStore = std::sync::Arc<std::sync::Mutex<std::collections::BTreeMap<String, Vec<u8>>>>;
// When a first argument separated with a semicolon is provided to `host_fn` it is used as the
// variable name and type for the `UserData` parameter

View File

@@ -81,18 +81,18 @@ pub(crate) enum UserDataHandle {
/// using `UserData::get`. The `C` data is stored as a pointer and cleanup function and isn't usable from Rust. The cleanup function
/// will be called when the inner `CPtr` is dropped.
#[derive(Debug)]
pub enum UserData<T: Sized> {
pub enum UserData<T: Sync + Clone + Sized> {
C(Arc<CPtr>),
Rust(Arc<std::sync::Mutex<T>>),
Rust(T),
}
impl<T: Default> Default for UserData<T> {
impl<T: Default + Sync + Clone> Default for UserData<T> {
fn default() -> Self {
UserData::new(T::default())
}
}
impl<T> Clone for UserData<T> {
impl<T: Sync + Clone> Clone for UserData<T> {
fn clone(&self) -> Self {
match self {
UserData::C(ptr) => UserData::C(ptr.clone()),
@@ -101,7 +101,7 @@ impl<T> Clone for UserData<T> {
}
}
impl<T> UserData<T> {
impl<T: Sync + Clone> UserData<T> {
/// Create a new `UserData` from an existing pointer and free function, this is used
/// by the C API to wrap C pointers into user data
pub(crate) fn new_pointer(
@@ -126,12 +126,11 @@ impl<T> UserData<T> {
///
/// This will wrap the provided value in a reference-counted mutex
pub fn new(x: T) -> Self {
let data = Arc::new(std::sync::Mutex::new(x));
UserData::Rust(data)
UserData::Rust(x)
}
/// Get a copy of the inner value
pub fn get(&self) -> Result<Arc<std::sync::Mutex<T>>, Error> {
pub fn get(&self) -> Result<T, Error> {
match self {
UserData::C { .. } => anyhow::bail!("C UserData should not be used from Rust"),
UserData::Rust(data) => Ok(data.clone()),
@@ -150,8 +149,8 @@ impl Drop for CPtr {
}
}
unsafe impl<T> Send for UserData<T> {}
unsafe impl<T> Sync for UserData<T> {}
unsafe impl<T: Sync + Clone> Send for UserData<T> {}
unsafe impl<T: Sync + Clone> Sync for UserData<T> {}
unsafe impl Send for CPtr {}
unsafe impl Sync for CPtr {}
@@ -180,7 +179,7 @@ pub struct Function {
impl Function {
/// Create a new host function
pub fn new<T: 'static, F>(
pub fn new<T: 'static + Sync + Clone, F>(
name: impl Into<String>,
args: impl IntoIterator<Item = ValType>,
returns: impl IntoIterator<Item = ValType>,
@@ -211,7 +210,9 @@ impl Function {
namespace: None,
_user_data: match &user_data {
UserData::C(ptr) => UserDataHandle::C(ptr.clone()),
UserData::Rust(x) => UserDataHandle::Rust(x.clone()),
UserData::Rust(x) => {
UserDataHandle::Rust(std::sync::Arc::new(std::sync::Mutex::new(x.clone())))
}
},
}
}

View File

@@ -19,7 +19,6 @@ pub(crate) mod pdk;
mod plugin;
mod plugin_builder;
mod timer;
mod wasi_command;
/// Extism C API
pub mod sdk;
@@ -30,7 +29,6 @@ pub use extism_manifest::{Manifest, Wasm, WasmMetadata};
pub use function::{Function, UserData, Val, ValType, PTR};
pub use plugin::{CancelHandle, Plugin, WasmInput, EXTISM_ENV_MODULE, EXTISM_USER_MODULE};
pub use plugin_builder::{DebugOptions, PluginBuilder};
pub use wasi_command::WASICommand;
pub(crate) use internal::{Internal, Wasi};
pub(crate) use timer::{Timer, TimerAction};

View File

@@ -60,7 +60,7 @@ impl<'a> PluginBuilder<'a> {
}
/// Add a single host function
pub fn with_function<T: 'static, F>(
pub fn with_function<T: Sync + Clone + 'static, F>(
mut self,
name: impl Into<String>,
args: impl IntoIterator<Item = ValType>,
@@ -80,7 +80,7 @@ impl<'a> PluginBuilder<'a> {
}
/// Add a single host function in a specific namespace
pub fn with_function_in_namespace<T: 'static, F>(
pub fn with_function_in_namespace<T: Sync + Clone + 'static, F>(
mut self,
namespace: impl Into<String>,
name: impl Into<String>,

View File

@@ -9,7 +9,6 @@ const WASM_LOOP: &[u8] = include_bytes!("../../../wasm/loop.wasm");
const WASM_GLOBALS: &[u8] = include_bytes!("../../../wasm/globals.wasm");
const WASM_REFLECT: &[u8] = include_bytes!("../../../wasm/reflect.wasm");
const WASM_HTTP: &[u8] = include_bytes!("../../../wasm/http.wasm");
const WASM_COMMAND: &[u8] = include_bytes!("../../../wasm/command.wasm");
host_fn!(pub hello_world (a: String) -> String { Ok(a) });
@@ -454,7 +453,7 @@ fn hello_world_user_data(
_plugin: &mut CurrentPlugin,
inputs: &[Val],
outputs: &mut [Val],
user_data: UserData<std::fs::File>,
user_data: UserData<std::sync::Arc<std::sync::Mutex<std::fs::File>>>,
) -> Result<(), Error> {
let data = user_data.get()?;
let mut data = data.lock().unwrap();
@@ -471,7 +470,8 @@ fn test_userdata() {
if path.exists() {
std::fs::remove_file(&path).unwrap();
}
let file = std::fs::File::create(&path).unwrap();
let file =
std::sync::Arc::new(std::sync::Mutex::new(std::fs::File::create(&path).unwrap()));
let f = Function::new(
"hello_world",
[PTR],
@@ -687,26 +687,3 @@ fn test_linking() {
assert_eq!(plugin.call::<&str, i64>("run", "Hello, world!").unwrap(), 1);
}
}
#[test]
fn test_wasi_command() {
let cwasm = WASICommand::from_bytes(WASM_COMMAND);
let envs: Vec<(String, String)> = vec![
(String::from("var1"), String::from("value1")),
(String::from("var2"), String::from("value2")),
];
let args: Vec<String> = vec![String::from("zero"), String::from("one")];
let (output, stderr): (String, Vec<u8>) = cwasm.run(&envs, &args, "test").unwrap();
println!("stdout: {}", output);
eprintln!("stderr as bytes: {:?}", stderr);
eprintln!("stderr as String: {}", String::from_utf8(stderr).unwrap());
let (output, stderr): (String, Vec<u8>) = cwasm.run(&envs, &args, "test").unwrap();
println!("2| stdout: {}", output);
eprintln!("2| stderr as bytes: {:?}", stderr);
eprintln!(
"2| stderr as String: {}",
String::from_utf8(stderr).unwrap()
);
assert!(false);
}

View File

@@ -1,57 +0,0 @@
use crate::Error;
use crate::FromBytesOwned;
use crate::ToBytes;
use wasi_common::pipe::{ReadPipe, WritePipe};
pub struct WASICommand {
engine: wasmtime::Engine,
module: wasmtime::Module,
}
impl WASICommand {
pub fn from(engine: wasmtime::Engine, module: wasmtime::Module) -> WASICommand {
WASICommand { engine, module }
}
pub fn from_bytes(data: &[u8]) -> WASICommand {
let config = wasmtime::Config::new();
let engine = wasmtime::Engine::new(&config).unwrap();
let module = wasmtime::Module::from_binary(&engine, data).unwrap();
WASICommand::from(engine, module)
}
pub fn run<'a, T: ToBytes<'a>, U: FromBytesOwned, V: FromBytesOwned>(
&self,
envs: &[(String, String)],
args: &[String],
stdin: T,
) -> Result<(U, V), Error> {
let bytes = stdin.to_bytes()?;
let stdin = ReadPipe::from(bytes.as_ref());
let stdout = WritePipe::new_in_memory();
let stderr = WritePipe::new_in_memory();
let wasi_ctx = wasmtime_wasi::WasiCtxBuilder::new()
.args(args)?
.envs(envs)?
.stdin(Box::new(stdin.clone()))
.stdout(Box::new(stdout.clone()))
.stderr(Box::new(stderr.clone()))
.build();
let mut store = wasmtime::Store::new(&self.engine, wasi_ctx);
let mut linker = wasmtime::Linker::new(&self.engine);
wasmtime_wasi::add_to_linker(&mut linker, |wasi| wasi)?;
let instance = linker.instantiate(&mut store, &self.module)?;
let function_name = "_start";
let f = instance
.get_func(&mut store, function_name)
.expect("function exists");
f.call(&mut store, &[], &mut []).unwrap();
drop(store);
let contents: Vec<u8> = stdout.try_into_inner().unwrap().into_inner();
let stderr_contents: Vec<u8> = stderr.try_into_inner().unwrap().into_inner();
let stdout_output = U::from_bytes_owned(&contents)?;
let stderr_output = V::from_bytes_owned(&stderr_contents)?;
Ok((stdout_output, stderr_output))
}
}

View File

@@ -1,25 +0,0 @@
#include <stdio.h>
extern char **environ;
int main(int argc, char *argv[])
{
puts("test stdout");
printf("argc %d\n", argc);
for (int i = 0; i < argc; i++)
{
printf("argv[%d]: %s\n", i, argv[i]);
}
char **s = environ;
for (; *s; s++)
{
printf("env: %s\n", *s);
}
printf("stdin: ");
int c;
while ((c = getchar()) != EOF)
{
putchar(c);
}
fprintf(stderr, "test stderr\n");
}

Binary file not shown.