Skip to content
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

Merged
merged 6 commits into from May 21, 2024
Merged

Conversation

chrisdickinson
Copy link
Contributor

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.

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))
Copy link
Contributor Author

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.)

Copy link
Contributor

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

Copy link
Contributor

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.

Copy link
Contributor Author

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",
Copy link
Contributor

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)

Copy link
Contributor

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!

Copy link
Contributor Author

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.

runtime/src/plugin.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@zshipko zshipko left a 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

@chrisdickinson chrisdickinson merged commit 5d9c8c5 into main May 21, 2024
6 checks passed
@chrisdickinson chrisdickinson deleted the chris/20240509-per-call-context branch May 21, 2024 18:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants