New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: per call context #711
Conversation
Add `plugin.call_with_context` and `current_plugin.context` methods, enabling per-call context to be looped from the guest invocation to any host functions it calls. In an HTTP server environment, this enables re-using a plugin across multiple requests while switching out backing connections and user information in host functions. (Imagine a host function, `update_user` -- previously the plugin would have to have been aware of the user to pass to the host function. Now that information is ambient.) This is a backwards-compatible change and requires no changes to existing plugins. Implement by adding a global, mutable externref to the extism kernel. Since most programming languages, including Rust, don't let you define these natively, we accomplish this by using `wasm-merge` to combine the kernel Wasm with Wasm generated by a WAT file containing only the global. (This pattern might be useful for other Wasm constructs we can't use directly from Rust, like `v128` in argument parameters.) Wasmtime requires extern refs to be `Any + Send + Sync + 'static`; we additionally add `Clone`. I haven't tried this with an `Arc` directly, but it should work at least for container structs that hold `Arc`'s themselves.
@@ -0,0 +1,3 @@ | |||
(module | |||
(global (export "extism_context") (mut externref) (ref.null extern)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could add a layer of protection around this by saying that "we don't accept any Wasm module that imports extism_context
." (This would protect against a malicious plugin that imports this externref, holds onto it across two calls, replacing the global value of the second call with the value from the first call.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah my first thought is we don't want people to be able to poke this and use some other plugin-runner's context
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
returning an error if a plugin is trying to import the global directly sounds like a good idea - I also noticed that the context global gets reset before each call regardless of whether call_with_context
is used, which makes me confident that a plugin couldn't access the context from another call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks – added that in 68da28b.
This commit also prevents linked modules from importing the extism kernel's memory directly (which I think would lead to similar problems.) I don't know of anyone doing that today – it would certainly seem to void the warranty label – but I can narrow up that check to just the context if we're worried!
let output: String = plugin | ||
.call_with_context( | ||
"reflect", | ||
"anything, really", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hah, this was my initial thought - "then what's the input?" but I realized that the context
value is only available in host functions (assuming we forbid access to the actual global somehow), which feels like a pretty powerful combination. this seems like a nice stepping-stone to allowing externref arguments while compilers are still catching up!
there could be some confusion around what to pass as input vs context (especially since we used to have a Context
type that meant something totally different)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe something like HostContext
or HostParam
might be more clear - open to other ideas too!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point – I renamed to host_context
/ HostContext
in the applicable places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good!
We could release this along with the changes from #715 since they both touch extism.h
Add
plugin.call_with_context
andcurrent_plugin.context
methods, enabling per-call context to be looped from the guest invocation to any host functions it calls. In an HTTP server environment, this enables re-using a plugin across multiple requests while switching out backing connections and user information in host functions. (Imagine a host function,update_user
-- previously the plugin would have to have been aware of the user to pass to the host function. Now that information is ambient.)This is a backwards-compatible change and requires no changes to existing plugins.
Implement by adding a global, mutable externref to the extism kernel. Since most programming languages, including Rust, don't let you define these natively, we accomplish this by using
wasm-merge
to combine the kernel Wasm with Wasm generated by a WAT file containing only the global.(This pattern might be useful for other Wasm constructs we can't use directly from Rust, like
v128
in argument parameters.)Wasmtime requires extern refs to be
Any + Send + Sync + 'static
; we additionally addClone
. I haven't tried this with anArc
directly, but it should work at least for container structs that holdArc
's themselves.