Compare commits

..

10 Commits

Author SHA1 Message Date
zach
8bafdfb710 cleanup: add cost to each host function 2023-12-21 15:26:22 -08:00
zach
e1d33800f0 wip: add TimeoutManager::cost 2023-12-20 14:15:56 -08:00
zach
6084b69790 cleanup: remove scale factor from TimeoutManager 2023-12-18 12:44:20 -08:00
zach
5896729cfb feat: add extism_current_plugin_timeout_add_ms 2023-12-18 12:44:20 -08:00
zach
0f93c5ef9d feat: add TimeoutManager to add/subtract from a plugins timeout 2023-12-18 12:44:20 -08:00
zach
ab812d9281 cleanup: remove default timeout (#641)
The default for `timeout_ms` is currently 30s which was an arbitrary
decision but it forces a user to set `timeout_ms` to null to avoid
having plugins cancelled which is a little strange.
2023-12-18 11:05:12 -08:00
Chris Dickinson
2087398513 v1.0.0-rc7 2023-12-15 15:45:23 -08:00
Chris Dickinson
212e28bec3 fix: give extism_log_drain's param a named type (#638)
This fixes a test failure on the python-sdk [1]. (See also [2]). In
particular, while maturin creates bindings for `extism_log_drain` on
clang platforms, it seems that MSVC cannot generate the required binding
information for `ffi.py`. This results in an `extism_sys` wheel whose
shared object (in this case, a DLL) contains the `extism_log_drain`, but
whose Python FFI bindings don't contain a definition for that function.

Naming the function pointer type parameter resolves the issue. What a
strange issue!

[1]:
https://github.com/extism/python-sdk/actions/runs/7172934060/job/19669775001#step:9:35
[2]:
https://github.com/extism/python-sdk/pull/18#issuecomment-1850892835
2023-12-15 15:40:54 -08:00
zach
fd95729d8d fix(kernel): length function should return 0 for invalid offsets (#635)
Fixes #634 

- Updates `extism_length` to walks the allocation list to determine
valid offsets instead of assuming the provided offset is valid

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: zshipko <zshipko@users.noreply.github.com>
2023-12-14 16:55:13 -08:00
Muhammad Azeez
49e28892bc ci: release extism.dll.lib and extism.dll.a (#633)
Related to #141 and #584 
Follow up of #632
2023-12-13 22:29:29 +03:00
13 changed files with 119 additions and 129 deletions

View File

@@ -28,48 +28,56 @@ jobs:
target: 'x86_64-apple-darwin'
artifact: 'libextism.dylib'
static-artifact: 'libextism.a'
static-dll-artifact: ''
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'macos'
target: 'aarch64-apple-darwin'
artifact: 'libextism.dylib'
static-artifact: 'libextism.a'
static-dll-artifact: ''
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'ubuntu'
target: 'aarch64-unknown-linux-gnu'
artifact: 'libextism.so'
static-artifact: 'libextism.a'
static-dll-artifact: ''
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'ubuntu'
target: 'aarch64-unknown-linux-musl'
artifact: 'libextism.so'
static-artifact: 'libextism.a'
static-dll-artifact: ''
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'ubuntu'
target: 'x86_64-unknown-linux-gnu'
artifact: 'libextism.so'
static-artifact: 'libextism.a'
static-dll-artifact: ''
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'ubuntu'
target: 'x86_64-unknown-linux-musl'
artifact: ''
static-artifact: 'libextism.a'
static-dll-artifact: ''
pc-in: ''
static-pc-in: 'extism-static.pc.in'
- os: 'windows'
target: 'x86_64-pc-windows-gnu'
artifact: 'extism.dll'
static-artifact: 'libextism.a'
static-dll-artifact: 'libextism.dll.a'
pc-in: 'extism.pc.in'
static-pc-in: 'extism-static.pc.in'
- os: 'windows'
target: 'x86_64-pc-windows-msvc'
artifact: 'extism.dll'
static-artifact: 'extism.lib'
static-dll-artifact: 'extism.dll.lib'
pc-in: ''
static-pc-in: ''
@@ -158,7 +166,8 @@ jobs:
cp LICENSE ${SRC_DIR}
tar -C ${SRC_DIR} -czvf ${ARCHIVE} extism.h \
${{ matrix.artifact }} ${{ matrix.static-artifact }} \
${{ matrix.pc-in }} ${{ matrix.static-pc-in }}
${{ matrix.pc-in }} ${{ matrix.static-pc-in }} \
${{ matrix.static-dll-artifact }}
ls -ll ${ARCHIVE}
if &>/dev/null which shasum; then

View File

@@ -306,9 +306,26 @@ impl MemoryRoot {
if !Self::pointer_in_bounds_fast(offs) {
return None;
}
let ptr = offs - core::mem::size_of::<MemoryBlock>() as u64;
let ptr = ptr as *mut MemoryBlock;
Some(&mut *ptr)
// Get the first block
let mut block = self.blocks.as_mut_ptr();
// Only loop while the block pointer is less then the current position
while (block as u64) < self.blocks.as_ptr() as u64 + offs {
let b = &mut *block;
// Get the block status, this lets us know if we are able to re-use it
let status = b.status.load(Ordering::Acquire);
if status == MemoryStatus::Active as u8 && b.data.as_ptr() as Pointer == offs {
return Some(b);
}
// Get the next block
block = b.next_ptr();
}
None
}
}

View File

@@ -231,20 +231,15 @@ pub struct Manifest {
pub allowed_paths: Option<BTreeMap<PathBuf, PathBuf>>,
/// The plugin timeout, by default this is set to 30s
#[serde(default = "default_timeout")]
#[serde(default)]
pub timeout_ms: Option<u64>,
}
fn default_timeout() -> Option<u64> {
Some(30000)
}
impl Manifest {
/// Create a new manifest
pub fn new(wasm: impl IntoIterator<Item = impl Into<Wasm>>) -> Manifest {
Manifest {
wasm: wasm.into_iter().map(|x| x.into()).collect(),
timeout_ms: default_timeout(),
..Default::default()
}
}

View File

@@ -97,6 +97,11 @@ typedef void (*ExtismFunctionType)(ExtismCurrentPlugin *plugin,
ExtismSize n_outputs,
void *data);
/**
* Log drain callback
*/
typedef void (*ExtismLogDrainFunctionType)(const char *data, ExtismSize size);
#ifdef __cplusplus
@@ -132,12 +137,6 @@ ExtismSize extism_current_plugin_memory_length(ExtismCurrentPlugin *plugin, Exti
*/
void extism_current_plugin_memory_free(ExtismCurrentPlugin *plugin, ExtismMemoryHandle ptr);
/**
* Add milliseconds to a plug-in's timeout
* NOTE: this should only be called from host functions.
*/
bool extism_current_plugin_timeout_add_ms(ExtismCurrentPlugin *plugin, uint64_t ms);
/**
* Create a new host function
*
@@ -174,6 +173,13 @@ void extism_function_free(ExtismFunction *f);
*/
void extism_function_set_namespace(ExtismFunction *ptr, const char *namespace_);
/**
* Set the cost of an `ExtismFunction`, when set to 0 this has no effect, when set to `1` this will add
* the runtime of the function back to the plugin timer.
*/
void extism_function_set_cost(ExtismFunction *ptr,
double cost);
/**
* Create a new plugin with host functions, the functions passed to this function no longer need to be manually freed using
*
@@ -270,7 +276,7 @@ bool extism_log_custom(const char *log_level);
* Calls the provided callback function for each buffered log line.
* This is only needed when `extism_log_custom` is used.
*/
void extism_log_drain(void (*handler)(const char*, uintptr_t));
void extism_log_drain(ExtismLogDrainFunctionType handler);
/**
* Reset the Extism runtime, this will invalidate all allocated memory

View File

@@ -421,12 +421,6 @@ impl CurrentPlugin {
let length = self.memory_length(offs).unwrap_or_default();
(offs, length)
}
/// Create a new `TimeoutManager` that can be used to adjust timeouts for the
/// current plugin. Returns `None` when no timeout has been configured.
pub fn timeout_manager(&self) -> Option<TimeoutManager> {
TimeoutManager::new(self)
}
}
impl Internal for CurrentPlugin {

Binary file not shown.

View File

@@ -155,7 +155,7 @@ unsafe impl<T> Sync for UserData<T> {}
unsafe impl Send for CPtr {}
unsafe impl Sync for CPtr {}
type FunctionInner = dyn Fn(wasmtime::Caller<CurrentPlugin>, &[wasmtime::Val], &mut [wasmtime::Val]) -> Result<(), Error>
pub(crate) type FunctionInner = dyn Fn(wasmtime::Caller<CurrentPlugin>, &[wasmtime::Val], &mut [wasmtime::Val]) -> Result<(), Error>
+ Sync
+ Send;
@@ -174,6 +174,9 @@ pub struct Function {
/// Function handle
pub(crate) f: Arc<FunctionInner>,
/// Function cost (in terms of time)
pub(crate) cost: f64,
/// UserData
pub(crate) _user_data: UserDataHandle,
}
@@ -204,10 +207,12 @@ impl Function {
ty,
f: Arc::new(
move |mut caller: Caller<_>, inp: &[Val], outp: &mut [Val]| {
let plugin = caller.data_mut();
let x = data.clone();
f(caller.data_mut(), inp, outp, x)
f(plugin, inp, outp, x)
},
),
cost: 0.0,
namespace: None,
_user_data: match &user_data {
UserData::C(ptr) => UserDataHandle::C(ptr.clone()),
@@ -239,6 +244,23 @@ impl Function {
self
}
/// Function cost
pub fn cost(&self) -> f64 {
self.cost
}
/// Set host function cost
pub fn set_cost(&mut self, cost: f64) {
trace!("Setting cost for {} to {cost}", self.name);
self.cost = cost;
}
/// Update host function cost
pub fn with_cost(mut self, cost: f64) -> Self {
self.set_cost(cost);
self
}
/// Get function type
pub fn ty(&self) -> &wasmtime::FuncType {
&self.ty

View File

@@ -26,9 +26,9 @@ 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 timeout_manager::TimeoutManager;
pub(crate) use internal::{Internal, Wasi};
pub(crate) use timeout_manager::TimeoutManager;
pub(crate) use timer::{Timer, TimerAction};
pub(crate) use tracing::{debug, error, trace, warn};

View File

@@ -1,5 +1,3 @@
use std::cmp::Ordering;
/// All the functions in the file are exposed from inside WASM plugins
use crate::*;
@@ -142,26 +140,6 @@ pub(crate) fn var_set(
Ok(())
}
#[derive(Default, Debug)]
struct Resolver;
impl ureq::Resolver for Resolver {
fn resolve(&self, netloc: &str) -> std::io::Result<Vec<std::net::SocketAddr>> {
let addrs = std::net::ToSocketAddrs::to_socket_addrs(netloc)?.into_iter();
let mut addrs: Vec<_> = addrs.collect();
addrs.sort_by(|a, b| {
if a.is_ipv4() && b.is_ipv6() {
Ordering::Less
} else if a.is_ipv6() && b.is_ipv4() {
Ordering::Greater
} else {
Ordering::Equal
}
});
Ok(addrs)
}
}
/// Make an HTTP request
/// Params: i64 (offset to JSON encoded HttpRequest), i64 (offset to body or 0)
/// Returns: i64 (offset)
@@ -172,7 +150,6 @@ pub(crate) fn http_request(
) -> Result<(), Error> {
let data: &mut CurrentPlugin = caller.data_mut();
let http_req_offset = args!(input, 0, i64) as u64;
#[cfg(not(feature = "http"))]
{
let handle = match data.memory_handle(http_req_offset) {
@@ -224,8 +201,7 @@ pub(crate) fn http_request(
)));
}
let agent = ureq::builder().resolver(Resolver).build();
let mut r = agent.request(req.method.as_deref().unwrap_or("GET"), &req.url);
let mut r = ureq::request(req.method.as_deref().unwrap_or("GET"), &req.url);
for (k, v) in req.headers.iter() {
r = r.set(k, v);

View File

@@ -298,9 +298,13 @@ impl Plugin {
for f in &mut imports {
let name = f.name().to_string();
let ns = f.namespace().unwrap_or(EXTISM_USER_MODULE);
unsafe {
linker.func_new(ns, &name, f.ty().clone(), &*(f.f.as_ref() as *const _))?;
}
let cost = f.cost();
let inner: &function::FunctionInner = unsafe { &*(f.f.as_ref() as *const _) };
linker.func_new(ns, &name, f.ty().clone(), move |caller, params, results| {
let _timeout =
TimeoutManager::new(caller.data()).map(|x| x.with_cost(cost.clone()));
inner(caller, params, results)
})?;
}
let instance_pre = linker.instantiate_pre(main)?;

View File

@@ -38,6 +38,9 @@ pub type ExtismFunctionType = extern "C" fn(
data: *mut std::ffi::c_void,
);
/// Log drain callback
pub type ExtismLogDrainFunctionType = extern "C" fn(data: *const std::ffi::c_char, size: Size);
impl From<&wasmtime::Val> for ExtismVal {
fn from(value: &wasmtime::Val) -> Self {
match value.ty() {
@@ -143,25 +146,6 @@ pub unsafe extern "C" fn extism_current_plugin_memory_free(
}
}
/// Add milliseconds to a plug-in's timeout
/// NOTE: this should only be called from host functions.
#[no_mangle]
pub unsafe extern "C" fn extism_current_plugin_timeout_add_ms(
plugin: *mut CurrentPlugin,
ms: u64,
) -> bool {
if plugin.is_null() {
return false;
}
let plugin = &mut *plugin;
if let Some(mgr) = plugin.timeout_manager() {
return mgr.add(std::time::Duration::from_millis(ms)).is_ok();
}
false
}
/// Create a new host function
///
/// Arguments
@@ -274,6 +258,18 @@ pub unsafe extern "C" fn extism_function_set_namespace(
}
}
/// Set the cost of an `ExtismFunction`, when set to 0 this has no effect, when set to `1` this will add
/// the runtime of the function back to the plugin timer.
#[no_mangle]
pub unsafe extern "C" fn extism_function_set_cost(ptr: *mut ExtismFunction, cost: f64) {
let f = &mut *ptr;
if let Some(x) = f.0.get_mut() {
x.set_cost(cost);
} else {
debug!("Trying to set the cost of already registered function")
}
}
/// Create a new plugin with host functions, the functions passed to this function no longer need to be manually freed using
///
/// `wasm`: is a WASM module (wat or wasm) or a JSON encoded manifest
@@ -686,11 +682,11 @@ unsafe fn set_log_buffer(filter: &str) -> Result<(), Error> {
#[no_mangle]
/// Calls the provided callback function for each buffered log line.
/// This is only needed when `extism_log_custom` is used.
pub unsafe extern "C" fn extism_log_drain(handler: extern "C" fn(*const std::ffi::c_char, usize)) {
pub unsafe extern "C" fn extism_log_drain(handler: ExtismLogDrainFunctionType) {
if let Some(buf) = &mut LOG_BUFFER {
if let Ok(mut buf) = buf.buffer.lock() {
for (line, len) in buf.drain(..) {
handler(line.as_ptr(), len);
handler(line.as_ptr(), len as u64);
}
}
}

View File

@@ -198,6 +198,10 @@ fn test_kernel_allocations() {
// 512 bytes, test block re-use + splitting
let p = extism_alloc(&mut store, instance, 512);
assert_eq!(extism_length(&mut store, instance, p), 512);
assert_eq!(extism_length(&mut store, instance, p + 1), 0);
assert_eq!(extism_length(&mut store, instance, p + 2), 0);
assert_eq!(extism_length(&mut store, instance, p + 3), 0);
assert_eq!(extism_length(&mut store, instance, p + 4), 0);
extism_free(&mut store, instance, p);
// 128 bytes, should be split off the 512 byte block
@@ -210,7 +214,7 @@ fn test_kernel_allocations() {
let r = extism_alloc(&mut store, instance, 128);
assert!(p <= r && r < p + 512);
assert!(r > p);
assert_eq!(extism_length(&mut store, instance, q), 128);
assert_eq!(extism_length(&mut store, instance, r), 128);
extism_free(&mut store, instance, q);
// 100 pages

View File

@@ -1,20 +1,14 @@
use crate::*;
enum DropBehavior {
Add,
Sub,
}
/// `TimeoutManager` is used to control `Plugin` timeouts from within host functions
///
/// It can be used to add or subtract time from a plug-in's timeout. If a plugin is not
/// configured to have a timeout then this will have no effect.
pub struct TimeoutManager {
pub(crate) struct TimeoutManager {
start_time: std::time::Instant,
id: uuid::Uuid,
tx: std::sync::mpsc::Sender<TimerAction>,
cost: f64,
drop_behavior: Option<DropBehavior>,
}
impl TimeoutManager {
@@ -25,48 +19,27 @@ impl TimeoutManager {
start_time: std::time::Instant::now(),
id: plugin.id.clone(),
tx: Timer::tx(),
cost: 1.0,
drop_behavior: None,
cost: 0.0,
})
}
/// Add to the configured timeout
pub fn add(&self, duration: std::time::Duration) -> Result<(), Error> {
let d = duration.mul_f64(self.cost);
/// Add the amount of time this value has existed to the configured timeout
pub fn add_elapsed(&mut self) -> Result<(), Error> {
let cost = self.cost.abs();
let d = self.start_time.elapsed().mul_f64(cost);
let mut d: timer::ExtendTimeout = d.into();
if self.cost.is_sign_negative() {
d = -d;
}
self.tx.send(TimerAction::Extend {
id: self.id.clone(),
duration: d.into(),
duration: d,
})?;
self.start_time = std::time::Instant::now();
Ok(())
}
/// Subtract from the configured timeout
pub fn sub(&self, duration: std::time::Duration) -> Result<(), Error> {
let d: timer::ExtendTimeout = duration.mul_f64(self.cost).into();
self.tx.send(TimerAction::Extend {
id: self.id.clone(),
duration: -d,
})?;
Ok(())
}
/// When the `TimeoutManager` is dropped the elapsed duration since it was created will be
/// added to the plug-in's timeout
pub fn add_on_drop(mut self) -> Self {
self.drop_behavior = Some(DropBehavior::Add);
self
}
/// When the `TimeoutManager` is dropped the elapsed duration since it was created will be
/// added to the plug-in's timeout
pub fn sub_on_drop(mut self) -> Self {
self.drop_behavior = Some(DropBehavior::Sub);
self
}
/// Adjust the cost of added/subtracted values, this will scale all durations
/// submitted to this manager by the provided factor.
pub fn cost(mut self, cost: f64) -> Self {
pub fn with_cost(mut self, cost: f64) -> Self {
self.cost = cost;
self
}
@@ -74,19 +47,13 @@ impl TimeoutManager {
impl Drop for TimeoutManager {
fn drop(&mut self) {
if let Some(b) = &self.drop_behavior {
let duration = self.start_time.elapsed();
let x = match b {
DropBehavior::Add => self.add(duration),
DropBehavior::Sub => self.sub(duration),
};
if let Err(e) = x {
error!(
plugin = self.id.to_string(),
"unable to extend timeout: {}",
e.to_string()
);
}
let x = self.add_elapsed();
if let Err(e) = x {
error!(
plugin = self.id.to_string(),
"unable to extend timeout: {}",
e.to_string()
);
}
}
}