Compare commits

...

23 Commits

Author SHA1 Message Date
Timothée Lecomte
7230657f6f feat: expose building a CompiledPlugin with a fuel limit (#883)
Expose a new method `extism_compiled_plugin_new_with_fuel_limit` to
build a CompiledPlugin with a fuel limit.

Fixes #882
2025-11-25 09:11:25 -08:00
Brian G. Merrell
de81040c99 Update to wasmtime version with exceptions support (#880)
The [wasmtime v37
release](https://github.com/bytecodealliance/wasmtime/releases/tag/v37.0.0)
"now fully implements the WebAssembly exception-handling proposal."
Support for exception-handling is disabled by default, but will be made
default in the future (according to the release notes).

To use this new wasmtime feature, we do two things at a high level:

1. Update wasmtime dependency to version 37
2. Add a "wasmtime-exceptions" Extism feature, disabled by default.

Updating to the new wasmtime dependency required some semantical changes
to how the cache is configured.

There is also a new `ExternType` to match on when handling invalid
imports.

My use case for this feature is being able to execute Lua code from
Extism. In actuality, I'm running a Rust plugin, which is using
[mlua](https://github.com/mlua-rs/mlua) to execute the Lua. I have it
working locally with this change. Fwiw, mlua recently added [support to
compile to wasi](https://github.com/mlua-rs/mlua/issues/366), which
should make this all work end-to-end with no special patches if we can
get this merged.

This is my first time working on the Extism codebase, so please let me
know if I missed anything.
2025-10-09 15:46:23 -07:00
dependabot[bot]
b79e74d516 chore(deps): Update criterion requirement from 0.6.0 to 0.7.0 (#878)
Updates the requirements on
[criterion](https://github.com/bheisler/criterion.rs) to permit the
latest version.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/bheisler/criterion.rs/blob/master/CHANGELOG.md">criterion's
changelog</a>.</em></p>
<blockquote>
<h2>[0.7.0] - 2025-07-25</h2>
<ul>
<li>Bump version of criterion-plot to align dependencies.</li>
</ul>
<h2>[0.6.0] - 2025-05-17</h2>
<h3>Changed</h3>
<ul>
<li>MSRV bumped to 1.80</li>
<li>The <code>real_blackbox</code> feature no longer has any impact.
Criterion always uses <code>std::hint::black_box()</code> now.
Users of <code>criterion::black_box()</code> should switch to
<code>std::hint::black_box()</code>.</li>
<li><code>clap</code> dependency unpinned.</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>gnuplot version is now correctly detected when using certain Windows
binaries/configurations that used to fail</li>
</ul>
<h3>Added</h3>
<ul>
<li>Async benchmarking with Tokio may be done via a
<code>tokio::runtime::Handle</code>, not only a
<code>tokio::runtime::Runtime</code></li>
</ul>
<h2>[0.5.1] - 2023-05-26</h2>
<h3>Fixed</h3>
<ul>
<li>Quick mode (--quick) no longer crashes with measured times over 5
seconds when --noplot is not active</li>
</ul>
<h2>[0.5.0] - 2023-05-23</h2>
<h3>Changed</h3>
<ul>
<li>Replaced lazy_static dependency with once_cell</li>
<li>Improved documentation of the <code>html_reports</code> feature</li>
<li>Replaced atty dependency with is-terminal</li>
<li>MSRV bumped to 1.64</li>
<li>Upgraded clap dependency to v4</li>
<li>Upgraded tempfile dependency to v3.5.0</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Quick mode (<code>--quick</code>) no longer outputs 1ms for measured
times over 5 seconds</li>
<li>Documentation updates</li>
</ul>
<h2>[0.4.0] - 2022-09-10</h2>
<h3>Removed</h3>
<ul>
<li>The <code>Criterion::can_plot</code> function has been removed.</li>
<li>The <code>Criterion::bench_function_over_inputs</code> function has
been removed.</li>
<li>The <code>Criterion::bench_functions</code> function has been
removed.</li>
<li>The <code>Criterion::bench</code> function has been removed.</li>
</ul>
<h3>Changed</h3>
<ul>
<li>HTML report hidden behind non-default feature flag:
'html_reports'</li>
<li>Standalone support (ie without cargo-criterion) feature flag:
'cargo_bench_support'</li>
<li>MSRV bumped to 1.57</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="567405d253"><code>567405d</code></a>
release: bump criterion and criterion-plot versions (<a
href="https://redirect.github.com/bheisler/criterion.rs/issues/878">#878</a>)</li>
<li><a
href="ccccbcc152"><code>ccccbcc</code></a>
fix: deal with throughput in bits (<a
href="https://redirect.github.com/bheisler/criterion.rs/issues/861">#861</a>)</li>
<li><a
href="deb0eb021d"><code>deb0eb0</code></a>
feat: support throughput reports in bits (<a
href="https://redirect.github.com/bheisler/criterion.rs/issues/833">#833</a>)</li>
<li><a
href="d4fd7cc478"><code>d4fd7cc</code></a>
Add CI job checking library builds with oldest allowed dependencies (<a
href="https://redirect.github.com/bheisler/criterion.rs/issues/854">#854</a>)</li>
<li>See full diff in <a
href="https://github.com/bheisler/criterion.rs/compare/0.6.0...0.7.0">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-28 09:07:54 -07:00
zach
52c160b9ec v1.12.0 2025-07-14 11:04:16 -07:00
dependabot[bot]
f68a548df4 chore(deps): Update toml requirement from 0.8 to 0.9 (#874)
Updates the requirements on [toml](https://github.com/toml-rs/toml) to
permit the latest version.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="c28f9ac30f"><code>c28f9ac</code></a>
chore: Release</li>
<li><a
href="f3a2299148"><code>f3a2299</code></a>
docs: Update changelog</li>
<li><a
href="69f09d3093"><code>69f09d3</code></a>
fix(lex): Don't loop over ')' for forever (<a
href="https://redirect.github.com/toml-rs/toml/issues/1003">#1003</a>)</li>
<li><a
href="cc68ae4f42"><code>cc68ae4</code></a>
fix(lex): Don't loop over ')' for forever</li>
<li><a
href="8c8ef44ea1"><code>8c8ef44</code></a>
chore: Release</li>
<li><a
href="b60ac5bfe9"><code>b60ac5b</code></a>
fix(toml): Correct minimal version for indexmap (<a
href="https://redirect.github.com/toml-rs/toml/issues/998">#998</a>)</li>
<li><a
href="966bd40511"><code>966bd40</code></a>
fix(toml): Correct minimal version for indexmap</li>
<li><a
href="2ed2af6519"><code>2ed2af6</code></a>
docs(readme): Mention additional crates</li>
<li><a
href="c7d93e5524"><code>c7d93e5</code></a>
chore: Release</li>
<li><a
href="1ac3aa136c"><code>1ac3aa1</code></a>
chore: Release</li>
<li>Additional commits viewable in <a
href="https://github.com/toml-rs/toml/compare/toml-v0.8.0...toml-v0.9.2">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-14 09:44:00 -07:00
zach
3d15c76115 fix: set ureq http_status_as_error to false to allow access to headers/body of non-200 responses (#873)
Fixes #872
2025-07-10 12:39:31 -07:00
Nutomic
0f4c32e68d Disable unused wasmtime features (#858)
This PR makes it possible to disable all default dependencies for wasmtime

---------

Co-authored-by: zach <zachshipko@gmail.com>
Co-authored-by: zach <zach@dylibso.com>
2025-07-10 12:38:58 -07:00
Nutomic
9e5729b103 Add Pool.function_exists with caching (#869)
This wrapper caches the result of `plugin.function_exists`, to avoid
having to load a plugin from the pool every single time just to find out
if the given function exists. It can improve performance if there are
many plugin hooks without corresponding plugin functions.

---------

Co-authored-by: zach <zach@dylibso.com>
2025-07-08 09:32:36 -07:00
Binlogo
04cf39e751 docs: fix runtime with_config_key usage in runtime/README.md (#870)
Update the code to fit `with_config_key` function calling.
2025-07-08 10:09:01 -04:00
dependabot[bot]
7133dfc4e0 chore(deps): Update prost requirement from 0.13.1 to 0.14.1 (#865)
Updates the requirements on [prost](https://github.com/tokio-rs/prost)
to permit the latest version.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/tokio-rs/prost/blob/master/CHANGELOG.md">prost's
changelog</a>.</em></p>
<blockquote>
<h1>Prost version 0.14.1</h1>
<p><em>PROST!</em> is a <a
href="https://developers.google.com/protocol-buffers/">Protocol
Buffers</a> implementation for the <a
href="https://www.rust-lang.org/">Rust Language</a>. <code>prost</code>
generates simple, idiomatic Rust code from <code>proto2</code> and
<code>proto3</code> files.</p>
<h2>⚠️ Revert emission of <code>rerun</code> commands</h2>
<p>Version 0.14.1 reverts the emission of <code>rerun</code> commands.
Other than this change, it is identical to 0.14.0.</p>
<p>In version 0.14.0, <code>prost-build</code> began emitting
<code>rerun</code> commands. While intended to improve build
correctness, this change caused regressions for some users—for example,
those generating <code>protos</code> from an <code>includes</code>
directory. These edge cases are difficult to address reliably, so the
change has been rolled back in 0.14.1.</p>
<p>For more details, see [issue <a
href="https://redirect.github.com/tokio-rs/prost/issues/1296">#1296</a>](<a
href="https://redirect.github.com/tokio-rs/prost/issues/1296">tokio-rs/prost#1296</a>).</p>
<h2>Breaking changes</h2>
<ul>
<li>
<p>prost: Relax Message Debug trait bound (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1147">#1147</a>)</p>
<p>BREAKING CHANGE: <code>trait Debug</code> was a supertrait of
<code>trait Message</code>. This is no longer required by
<code>prost</code>. If your code relies on <code>trait Debug</code>
being implemented for every <code>impl Message</code>, you must now
explicitly state that you require both Debug and Message. For example:
<code>where M: Debug + Message</code></p>
</li>
<li>
<p>prost: Remove prost-derive feature (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1247">#1247</a>)</p>
<p>BREAKING CHANGE: Feature flag <code>prost-derive</code> is renamed to
<code>derive</code>. Please rename any usage of
<code>prost-derive</code> feature in your <code>Cargo.toml</code>.</p>
</li>
<li>
<p>prost-build: Prevent repeated fields to be boxed (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1237">#1237</a>)</p>
<p>BREAKING CHANGE: A repeated field that is manually marked as boxed
was typed as <code>Vec&lt;Box&lt;T&gt;&gt;</code>. Those fields are now
simply typed as <code>Vec&lt;T&gt;</code> to prevent double indirection.
The <code>boxed</code> configuration is effectively ignored for repeated
fields.</p>
</li>
<li>
<p>prost-build: Make <code>type_name_domain</code> cumulative (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1228">#1228</a>)</p>
<p>BREAKING CHANGE: The configuration for domain names of messages is
now cumulative. All calls to
<code>prost_build::Config::type_name_domain</code> are now concatenated.
The previous behavior was that only the arguments of the last call were
used. If you do multiple calls to type_name_domain, you need to remove
all but the last call to maintain the same behavior.</p>
</li>
<li>
<p>prost-build: Derive Eq and Hash trait for messages where possible (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1175">#1175</a>)</p>
<p>BREAKING CHANGE: <code>prost-build</code> will automatically derive
<code>trait Eq</code> and <code>trait Hash</code> for types where all
field support those as well. If you manually <code>impl Eq</code> and/or
<code>impl Hash</code> for generated types, then you need to remove the
manual implementation. If you use <code>type_attribute</code> to
<code>derive(Eq)</code> and/or <code>derive(Hash)</code>, then you need
to remove those.</p>
</li>
</ul>
<h2>Features</h2>
<ul>
<li>prost-types: Implement conversion <code>Duration</code> to/from
<code>chrono::TimeDelta</code> (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1236">#1236</a>)</li>
<li>prost-build: Prepare for 2024 keyword <code>gen</code> (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1257">#1257</a>)</li>
</ul>
<h2>Dependencies</h2>
<ul>
<li><em>(deps)</em> Update pulldown-cmark to 0.13 (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1259">#1259</a>)</li>
<li><em>(deps)</em> update criterion requirement from 0.5 to 0.6 (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1280">#1280</a>)</li>
</ul>
<h2>Documentation</h2>
<ul>
<li>Update dead link LICENSE in <code>prost-types/README.md</code> (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1262">#1262</a>)</li>
</ul>
<h2>Styling</h2>
<ul>
<li>Use DoubleEndedIterator::next_back (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1255">#1255</a>)</li>
<li>Fix typo (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1260">#1260</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="9965a988a7"><code>9965a98</code></a>
chore: Release version 0.14.1 (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1299">#1299</a>)</li>
<li><a
href="0caca2977d"><code>0caca29</code></a>
Revert &quot;feat(prost-build): emit <code>rerun</code> commands (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1140">#1140</a>)&quot;
(<a
href="https://redirect.github.com/tokio-rs/prost/issues/1297">#1297</a>)</li>
<li><a
href="3543eb8001"><code>3543eb8</code></a>
chore: Release version 0.14.0 (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1276">#1276</a>)</li>
<li><a
href="1e93f5612d"><code>1e93f56</code></a>
build(deps): update criterion requirement from 0.5 to 0.6 (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1280">#1280</a>)</li>
<li><a
href="bdd03fcb8d"><code>bdd03fc</code></a>
Update config.rs (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1270">#1270</a>)</li>
<li><a
href="fcf610edf5"><code>fcf610e</code></a>
ci: Run clippy with edition 2024 enabled (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1256">#1256</a>)</li>
<li><a
href="8a3d42e5a3"><code>8a3d42e</code></a>
docs: update dead link LICENSE in <code>prost-types/README.md</code> (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1262">#1262</a>)</li>
<li><a
href="97d5841c3b"><code>97d5841</code></a>
chore: fix typo (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1260">#1260</a>)</li>
<li><a
href="5c97cf88e7"><code>5c97cf8</code></a>
build(deps): Update pulldown-cmark to 0.13 (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1259">#1259</a>)</li>
<li><a
href="db871b4e71"><code>db871b4</code></a>
harden <code>ref mut</code> according to edition 2024 (<a
href="https://redirect.github.com/tokio-rs/prost/issues/1248">#1248</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/tokio-rs/prost/compare/v0.13.1...v0.14.1">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-25 11:21:24 -07:00
Nutomic
d1ba15484e Remove key param for PluginPool (#859)
As mentioned in
https://github.com/extism/extism/pull/696/files#r1993086470, the key
parameter doesnt seem very useful. You can achieve the same effect by
doing `HashMap<Key, PluginPool>` in the client code.

---------

Co-authored-by: zach <zach@dylibso.com>
2025-06-18 12:28:39 -07:00
nu-wa
dedd81d90f docs: add more information about wasmtime caching (#863)
Hello,

I encountered an error when trying to enable caching in my project, I
could not compile any Plugins due to a bad configuration file used by
the wasmtime crate (`failed to parse config file`). I managed to find
understand the error after seeing [some changes made by
wasmtime](https://github.com/bytecodealliance/wasmtime/pull/10859) and
its documentation. I was simply missing the `enabled` key from the
configuration, that has been removed from `wasmtime` (and its [cache
sister
crate](https://github.com/bytecodealliance/wasmtime/tree/main/crates/cache)).

So I added more information in the README regarding this specific issue,
and some extra behaviour that I noticed would happen regarding caching
configuration.

Hopefully this is helpful!
2025-06-18 11:09:23 -07:00
zach
2732ca198d feat: add Pool type for pooling plugin instances (#696) 2025-06-06 10:14:22 -07:00
Muhammad Azeez
30b4a7d2d3 fix: use gh release download instead of downloading from github action artifacts in dotnet workflow (#857)
It seems like at some point we changed the `release.yml` workflow to
create multiple artifacts instead of one `release-artifacts` tarball. I
changed the .NET Nuget workflow to be more like Python
2025-05-29 22:09:52 +03:00
zach
b6e5684461 v1.11.0 2025-05-27 15:37:19 -07:00
zach
36e05e8668 chore: add ToBytes/FromBytes implementations for bool (#855)
Fixes https://github.com/extism/extism/issues/838
2025-05-27 15:35:22 -07:00
dependabot[bot]
9415aaa08a chore(deps): Update cbindgen requirement from 0.28 to 0.29 (#854)
Updates the requirements on
[cbindgen](https://github.com/mozilla/cbindgen) to permit the latest
version.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/mozilla/cbindgen/releases">cbindgen's
releases</a>.</em></p>
<blockquote>
<h1>0.29.0</h1>
<ul>
<li>Support no-export annotation for statics and functions.</li>
<li>Fixed conditional fields of constexpr literal structs</li>
<li>Add rename rule for generated associated constant</li>
<li>Upgrade heck to 0.5</li>
<li>Add support for an optional nullable attribute</li>
<li>docs.md: Fix deprecated_with_note and deprecated_variant_with_note
being spelled as 'notes'</li>
<li>Fix generic with &quot;void&quot; default</li>
<li>Fixed error generation of structures using the keyword as inside
arrays</li>
<li>Added test for unsafe(no_mangle) attribute</li>
<li>Fixed handling of trait methods containing the unsafe attribute</li>
<li>Rename -Zparse-only</li>
</ul>
<h1>0.28.0</h1>
<ul>
<li>Parse unsafe attributes in <a
href="https://redirect.github.com/mozilla/cbindgen/pull/1020">mozilla/cbindgen#1020</a></li>
<li>Fix local override of enum prefix-with-name by jsgf in <a
href="https://redirect.github.com/mozilla/cbindgen/pull/1006">mozilla/cbindgen#1006</a></li>
<li>Add rename-all=prefix in <a
href="https://redirect.github.com/mozilla/cbindgen/pull/1021">mozilla/cbindgen#1021</a></li>
<li>ir: add support for UnsafeCell and SyncUnsafeCell by alekitto in <a
href="https://redirect.github.com/mozilla/cbindgen/pull/1003">mozilla/cbindgen#1003</a></li>
<li>Implement mangling for arrays in <a
href="https://redirect.github.com/mozilla/cbindgen/pull/1022">mozilla/cbindgen#1022</a></li>
<li>Fix: Ignore <code>CARGO_BUILD_TARGET</code> in tests by bryango in
<a
href="https://redirect.github.com/mozilla/cbindgen/pull/1010">mozilla/cbindgen#1010</a></li>
<li>Newline for each field for constexpr field constants by youknowone
in <a
href="https://redirect.github.com/mozilla/cbindgen/pull/988">mozilla/cbindgen#988</a></li>
<li>Fix clippy warnings by youknowone in <a
href="https://redirect.github.com/mozilla/cbindgen/pull/1026">mozilla/cbindgen#1026</a></li>
<li>Add aarch64/arm64 to CI by NickeZ in <a
href="https://redirect.github.com/mozilla/cbindgen/pull/1036">mozilla/cbindgen#1036</a></li>
<li>Add <code>unstable_ir</code> feature flag that makes the ir pub by
heesooy in <a
href="https://redirect.github.com/mozilla/cbindgen/pull/1011">mozilla/cbindgen#1011</a></li>
<li>Support generated a symbols file by TheElectronWill in <a
href="https://redirect.github.com/mozilla/cbindgen/pull/916">mozilla/cbindgen#916</a></li>
</ul>
<h1>0.27.0</h1>
<ul>
<li>Revert: The <code>Config</code> struct now has a private
member.</li>
<li>Allow users to specify a crate version for bindings generation (<a
href="https://redirect.github.com/mozilla/cbindgen/issues/901">#901</a>).</li>
<li>Update MSRV to 1.74 (<a
href="https://redirect.github.com/mozilla/cbindgen/issues/912">#912</a>,
<a
href="https://redirect.github.com/mozilla/cbindgen/issues/987">#987</a>).</li>
<li>Support #[deprecated] on enum variants (<a
href="https://redirect.github.com/mozilla/cbindgen/issues/933">#933</a>).</li>
<li>Support integrating the package_version information in a header file
comment (<a
href="https://redirect.github.com/mozilla/cbindgen/issues/939">#939</a>).</li>
<li>Add a language backend (<a
href="https://redirect.github.com/mozilla/cbindgen/issues/942">#942</a>).</li>
<li>Support generics with defaulted args (<a
href="https://redirect.github.com/mozilla/cbindgen/issues/959">#959</a>).</li>
<li>Add <code>VaList</code> compatibility (<a
href="https://redirect.github.com/mozilla/cbindgen/issues/970">#970</a>).</li>
</ul>
<h1>0.26.0</h1>
<ul>
<li>Fix swapping of <code>&gt;&gt;=</code> and <code>&lt;&lt;=</code> in
constants.</li>
<li>Add support for #[deprecated] (<a
href="https://redirect.github.com/mozilla/cbindgen/issues/860">#860</a>).</li>
<li>Built-in support for bitflags 2.0.</li>
<li>Support for &quot;C-unwind&quot; ABI.</li>
<li>Generate bindings for non-public extern items if they are
#[no_mangle].</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/mozilla/cbindgen/blob/master/CHANGES">cbindgen's
changelog</a>.</em></p>
<blockquote>
<h1>0.29.0</h1>
<pre><code>  * Support no-export annotation for statics and functions.
  * Fixed conditional fields of constexpr literal structs
  * Add rename rule for generated associated constant
  * Upgrade heck to 0.5
  * Add support for an optional nullable attribute
* docs.md: Fix deprecated_with_note and deprecated_variant_with_note
being spelled as 'notes'
  * Fix generic with &quot;void&quot; default
* Fixed error generation of structures using the keyword as inside
arrays
  * Added test for unsafe(no_mangle) attribute
  * Fixed handling of trait methods containing the unsafe attribute
  * Rename -Zparse-only
</code></pre>
<h1>0.28.0</h1>
<pre><code> * Parse unsafe attributes in
https://github.com/mozilla/cbindgen/pull/1020
* Fix local override of enum prefix-with-name by jsgf in
https://github.com/mozilla/cbindgen/pull/1006
* Add rename-all=prefix in https://github.com/mozilla/cbindgen/pull/1021
* ir: add support for UnsafeCell and SyncUnsafeCell by alekitto in
https://github.com/mozilla/cbindgen/pull/1003
* Implement mangling for arrays in
https://github.com/mozilla/cbindgen/pull/1022
* Fix: Ignore `CARGO_BUILD_TARGET` in tests by bryango in
https://github.com/mozilla/cbindgen/pull/1010
* Newline for each field for constexpr field constants by youknowone in
https://github.com/mozilla/cbindgen/pull/988
* Fix clippy warnings by youknowone in
https://github.com/mozilla/cbindgen/pull/1026
* Add aarch64/arm64 to CI by NickeZ in
https://github.com/mozilla/cbindgen/pull/1036
* Add `unstable_ir` feature flag that makes the ir pub by heesooy in
https://github.com/mozilla/cbindgen/pull/1011
* Support generated a symbols file by TheElectronWill in
https://github.com/mozilla/cbindgen/pull/916
</code></pre>
<h1>0.27.0</h1>
<pre><code>  * Revert: The `Config` struct now has a private member.
* Allow users to specify a crate version for bindings generation
([#901](https://github.com/mozilla/cbindgen/issues/901)).
* Update MSRV to 1.74
([#912](https://github.com/mozilla/cbindgen/issues/912),
[#987](https://github.com/mozilla/cbindgen/issues/987)).
* Support #[deprecated] on enum variants
([#933](https://github.com/mozilla/cbindgen/issues/933)).
* Support integrating the package_version information in a header file
comment ([#939](https://github.com/mozilla/cbindgen/issues/939)).
* Add a language backend
([#942](https://github.com/mozilla/cbindgen/issues/942)).
* Support generics with defaulted args
([#959](https://github.com/mozilla/cbindgen/issues/959)).
* Add `VaList` compatibility
([#970](https://github.com/mozilla/cbindgen/issues/970)).
</code></pre>
<h1>0.26.0</h1>
<pre><code>  * Fix swapping of `&gt;&gt;=` and `&lt;&lt;=` in constants.
* Add support for #[deprecated]
([#860](https://github.com/mozilla/cbindgen/issues/860)).
  * Built-in support for bitflags 2.0.
  * Support for &quot;C-unwind&quot; ABI.
* Generate bindings for non-public extern items if they are
#[no_mangle].
</code></pre>
<h2>0.25.0</h2>
<pre><code>  * Re-release of yanked 0.24.6 as a major release
</code></pre>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="802154245e"><code>8021542</code></a>
Release 0.29.0</li>
<li><a
href="f3a0ecd2a9"><code>f3a0ecd</code></a>
Support no-export annotation for statics and functions.</li>
<li><a
href="14fa2d0669"><code>14fa2d0</code></a>
conditional fields of constexpr literal structs</li>
<li><a
href="021f3f3a42"><code>021f3f3</code></a>
Add LiteralStructField</li>
<li><a
href="4b2396dabf"><code>4b2396d</code></a>
Github action: Add aarch64 to deploy</li>
<li><a
href="2320ac4296"><code>2320ac4</code></a>
Add rename rule for generated associated constant</li>
<li><a
href="5ac9dc046b"><code>5ac9dc0</code></a>
Upgrade heck to 0.5</li>
<li><a
href="9f9da30c60"><code>9f9da30</code></a>
Add support for an optional nullable attribute</li>
<li><a
href="36b9f0df42"><code>36b9f0d</code></a>
docs.md: Fix deprecated_with_note and deprecated_variant_with_note being
spel...</li>
<li><a
href="0328601dc1"><code>0328601</code></a>
Fix generic with &quot;void&quot; default</li>
<li>Additional commits viewable in <a
href="https://github.com/mozilla/cbindgen/compare/0.28.0...0.29.0">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-27 12:36:33 -07:00
dependabot[bot]
168459be0f chore(deps): Update criterion requirement from 0.5.1 to 0.6.0 (#852)
Updates the requirements on
[criterion](https://github.com/bheisler/criterion.rs) to permit the
latest version.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/bheisler/criterion.rs/blob/master/CHANGELOG.md">criterion's
changelog</a>.</em></p>
<blockquote>
<h2>[0.6.0] - 2025-05-17</h2>
<h3>Changed</h3>
<ul>
<li>MSRV bumped to 1.80</li>
<li>The <code>real_blackbox</code> feature no longer has any impact.
Criterion always uses <code>std::hint::black_box()</code> now.
Users of <code>criterion::black_box()</code> should switch to
<code>std::hint::black_box()</code>.</li>
<li><code>clap</code> dependency unpinned.</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>gnuplot version is now correctly detected when using certain Windows
binaries/configurations that used to fail</li>
</ul>
<h3>Added</h3>
<ul>
<li>Async benchmarking with Tokio may be done via a
<code>tokio::runtime::Handle</code>, not only a
<code>tokio::runtime::Runtime</code></li>
</ul>
<h2>[0.5.1] - 2023-05-26</h2>
<h3>Fixed</h3>
<ul>
<li>Quick mode (--quick) no longer crashes with measured times over 5
seconds when --noplot is not active</li>
</ul>
<h2>[0.5.0] - 2023-05-23</h2>
<h3>Changed</h3>
<ul>
<li>Replaced lazy_static dependency with once_cell</li>
<li>Improved documentation of the <code>html_reports</code> feature</li>
<li>Replaced atty dependency with is-terminal</li>
<li>MSRV bumped to 1.64</li>
<li>Upgraded clap dependency to v4</li>
<li>Upgraded tempfile dependency to v3.5.0</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Quick mode (<code>--quick</code>) no longer outputs 1ms for measured
times over 5 seconds</li>
<li>Documentation updates</li>
</ul>
<h2>[0.4.0] - 2022-09-10</h2>
<h3>Removed</h3>
<ul>
<li>The <code>Criterion::can_plot</code> function has been removed.</li>
<li>The <code>Criterion::bench_function_over_inputs</code> function has
been removed.</li>
<li>The <code>Criterion::bench_functions</code> function has been
removed.</li>
<li>The <code>Criterion::bench</code> function has been removed.</li>
</ul>
<h3>Changed</h3>
<ul>
<li>HTML report hidden behind non-default feature flag:
'html_reports'</li>
<li>Standalone support (ie without cargo-criterion) feature flag:
'cargo_bench_support'</li>
<li>MSRV bumped to 1.57</li>
<li><code>rayon</code> and <code>plotters</code> are optional (and
default) dependencies.</li>
<li>Status messages ('warming up', 'analyzing', etc) are printed to
stderr, benchmark results are printed to stdout.</li>
<li>Accept subsecond durations for <code>--warm-up-time</code>,
<code>--measurement-time</code> and <code>--profile-time</code>.</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="43bf90a64b"><code>43bf90a</code></a>
release version 0.6.0 (<a
href="https://redirect.github.com/bheisler/criterion.rs/issues/860">#860</a>)</li>
<li><a
href="92696e45c5"><code>92696e4</code></a>
deps: unpin clap (<a
href="https://redirect.github.com/bheisler/criterion.rs/issues/858">#858</a>)</li>
<li><a
href="5756a5d526"><code>5756a5d</code></a>
chore: bump MSRV to 1.80 (<a
href="https://redirect.github.com/bheisler/criterion.rs/issues/859">#859</a>)</li>
<li><a
href="9d887c0145"><code>9d887c0</code></a>
Fixed typo in faq.md (<a
href="https://redirect.github.com/bheisler/criterion.rs/issues/852">#852</a>)</li>
<li><a
href="59b791a587"><code>59b791a</code></a>
ci: test against MSRV and 1.87 (<a
href="https://redirect.github.com/bheisler/criterion.rs/issues/857">#857</a>)</li>
<li><a
href="ace1cc93ef"><code>ace1cc9</code></a>
Fix warnings from clippy (rust 1.87.0) (<a
href="https://redirect.github.com/bheisler/criterion.rs/issues/856">#856</a>)</li>
<li><a
href="7afab6ef7c"><code>7afab6e</code></a>
Commit Cargo.lock to make CI and local debugging more stable (<a
href="https://redirect.github.com/bheisler/criterion.rs/issues/855">#855</a>)</li>
<li><a
href="260e2f1c78"><code>260e2f1</code></a>
Update Cargo features' comments (<a
href="https://redirect.github.com/bheisler/criterion.rs/issues/825">#825</a>)</li>
<li><a
href="58130ff859"><code>58130ff</code></a>
Update to async-std v1.13 (<a
href="https://redirect.github.com/bheisler/criterion.rs/issues/812">#812</a>)</li>
<li><a
href="d2e705b855"><code>d2e705b</code></a>
Add rust-version 1.70 (<a
href="https://redirect.github.com/bheisler/criterion.rs/issues/813">#813</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/bheisler/criterion.rs/compare/0.5.1...0.6.0">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: zach <zach@dylibso.com>
2025-05-19 10:10:58 -07:00
AlessandroRuggiero
1ea498b5d9 fix: better suggestion when encoding is not implemented (#843)
Here is a code snippet that demonstrates the issue:
```rust
use extism_pdk::{encoding, plugin_fn, FnResult, FromBytes, Json, ToBytes};
use serde::{Serialize, Deserialize};

#[derive(Deserialize,FromBytes)]
pub struct Input {
    value:String
}

#[derive(Serialize, ToBytes)]
pub struct Output {
    value: f64,
}

#[plugin_fn]
pub fn convert(in_value: Input) -> FnResult<Output> {
    Ok(Output { value: in_value.value.parse().expect("Conversion failed") })
}
```

Because the structs don't derive `encoding` the rust-analyzer gives this
suggestion:
```
encoding needs to be specified

  = try: `#[encoding(ToJson)]`
```

I don't think this suggestion is correct because i could not find
`ToJson` anywhere. Adding `#[encoding(Json)]` solves the issue (as
described in the docs).
The final code would look as follows:
```rust
use extism_pdk::{encoding, plugin_fn, FnResult, FromBytes, Json, ToBytes};
use serde::{Serialize, Deserialize};

#[derive(Deserialize,FromBytes)]
#[encoding(Json)]
pub struct Input {
    value:String
}

#[derive(Serialize, ToBytes)]
#[encoding(Json)]
pub struct Output {
    value: f64,
}

#[plugin_fn]
pub fn convert(in_value: Input) -> FnResult<Output> {
    Ok(Output { value: in_value.value.parse().expect("Conversion failed") })
}
```

And compiles perfectly.

I think the suggestion given by the rust-analyzer is incorrect so i am
proposing a fix for this.

Since I'm new to the project, any feedback is appreciated.
Thanks!
_Alessandro Ruggiero_
2025-03-31 08:15:23 -07:00
Pascal Sommer
f8e16dc875 doc: explain how to see plug-in logs (#839)
I know that there is already a logging example in
`runtime/examples/log_callback.rs` but I think that's not where people
go to look to figure out how the logging functionality works. At least
for me that wasn't the case :)
2025-03-21 16:56:53 -07:00
Chris Dickinson
2524707334 fix: throw error on reentrant plugin call (#836)
Previously, code that called a plugin that called a host function that
called back into the plugin would hang in an attempt to attain the lock.
Switch to `try_lock` so we can surface the error.
2025-02-26 11:37:10 -08:00
zach
de65e22f68 Remove python from dependabot.yml (#835) 2025-02-20 11:07:52 -08:00
zach
59acffa8ac chore: include wasmtime 30 in supported bounds (#834)
Also updates dependabot config to bundle wasmtime updates so this can be
done automatically in the future.
2025-02-20 10:54:42 -08:00
28 changed files with 568 additions and 108 deletions

View File

@@ -9,33 +9,8 @@ updates:
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
ignore:
- dependency-name: "wasmtime"
- dependency-name: "wasi-common"
- dependency-name: "wiggle"
- package-ecosystem: "pip"
directory: "python"
schedule:
interval: "weekly"
- package-ecosystem: "mix"
directory: "elixir"
schedule:
interval: "weekly"
- package-ecosystem: "npm"
directory: "node"
schedule:
interval: "weekly"
- package-ecosystem: "composer"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "bundler"
directory: "ruby"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "weekly"

View File

@@ -20,10 +20,13 @@ jobs:
uses: actions/setup-dotnet@v3.0.3
with:
dotnet-version: 7.x
- uses: dawidd6/action-download-artifact@v6
with:
workflow: release.yml
name: release-artifacts
- name: download release
run: |
tag='${{ github.ref }}'
tag="${tag/refs\/tags\//}"
gh release download "$tag" -p 'libextism-*.tar.gz'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Extract Archive
run: |
extract_archive() {

View File

@@ -34,11 +34,11 @@ fn extract_encoding(attrs: &[Attribute]) -> Result<Path> {
.iter()
.filter(|attr| attr.path().is_ident("encoding"))
.collect();
ensure!(!encodings.is_empty(), "encoding needs to be specified"; try = "`#[encoding(ToJson)]`");
ensure!(!encodings.is_empty(), "encoding needs to be specified"; try = "`#[encoding(Json)]`");
ensure!(encodings.len() < 2, encodings[1], "only one encoding can be specified"; try = "remove `{}`", encodings[1].to_token_stream());
Ok(encodings[0].parse_args().map_err(
|e| error_message!(e.span(), "{e}"; note= "expects a path"; try = "`#[encoding(ToJson)]`"),
|e| error_message!(e.span(), "{e}"; note= "expects a path"; try = "`#[encoding(Json)]`"),
)?)
}

View File

@@ -1,6 +1,6 @@
error: encoding needs to be specified
= try: `#[encoding(ToJson)]`
= try: `#[encoding(Json)]`
--> tests/ui/invalid-encoding.rs:3:10
|
3 | #[derive(ToBytes)]
@@ -11,7 +11,7 @@ error: encoding needs to be specified
error: expected attribute arguments in parentheses: #[encoding(...)]
= note: expects a path
= try: `#[encoding(ToJson)]`
= try: `#[encoding(Json)]`
--> tests/ui/invalid-encoding.rs:7:3
|
7 | #[encoding]
@@ -20,7 +20,7 @@ error: expected attribute arguments in parentheses: #[encoding(...)]
error: expected parentheses: #[encoding(...)]
= note: expects a path
= try: `#[encoding(ToJson)]`
= try: `#[encoding(Json)]`
--> tests/ui/invalid-encoding.rs:11:12
|
11 | #[encoding = "string"]
@@ -29,7 +29,7 @@ error: expected parentheses: #[encoding(...)]
error: unexpected token
= note: expects a path
= try: `#[encoding(ToJson)]`
= try: `#[encoding(Json)]`
--> tests/ui/invalid-encoding.rs:15:21
|
15 | #[encoding(something, else)]

View File

@@ -13,7 +13,7 @@ description = "Traits to make Rust types usable with Extism"
anyhow = "1.0.75"
base64 = "~0.22"
bytemuck = {version = "1.14.0", optional = true }
prost = { version = "0.13.1", optional = true }
prost = { version = "0.14.1", optional = true }
protobuf = { version = "3.2.0", optional = true }
rmp-serde = { version = "1.1.2", optional = true }
serde = "1.0.186"

View File

@@ -141,6 +141,16 @@ impl FromBytesOwned for u32 {
}
}
impl FromBytesOwned for bool {
fn from_bytes_owned(data: &[u8]) -> Result<Self, Error> {
if let Some(x) = data.first() {
Ok(*x != 0)
} else {
Err(Error::msg("Expected one byte to read boolean value"))
}
}
}
impl FromBytesOwned for () {
fn from_bytes_owned(_: &[u8]) -> Result<Self, Error> {
Ok(())

View File

@@ -61,6 +61,17 @@ fn rountrip_option() {
assert_eq!(y.unwrap().0, z.unwrap().0);
}
#[test]
fn check_bool() {
// `None` case
let a = true.to_bytes().unwrap();
let b = false.to_bytes().unwrap();
assert_ne!(a, b);
assert_eq!(a, [1]);
assert_eq!(b, [0]);
}
#[cfg(all(feature = "raw", target_endian = "little"))]
mod raw_tests {
use crate::*;

View File

@@ -144,6 +144,14 @@ impl ToBytes<'_> for u32 {
}
}
impl ToBytes<'_> for bool {
type Bytes = [u8; 1];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok([*self as u8])
}
}
impl<'a, T: ToBytes<'a>> ToBytes<'a> for &'a T {
type Bytes = T::Bytes;

View File

@@ -9,35 +9,54 @@ repository.workspace = true
version.workspace = true
[dependencies]
wasmtime = {version = ">= 27.0.0, < 30.0.0"}
wasi-common = {version = ">= 27.0.0, < 30.0.0"}
wiggle = {version = ">= 27.0.0, < 30.0.0"}
wasmtime = { version="37", default-features = false, features = [
'cache',
'gc',
'gc-drc',
'cranelift',
'coredump',
'wat',
'parallel-compilation',
'pooling-allocator',
'demangle',
] }
wasi-common = "37"
wiggle = "37"
anyhow = "1"
serde = {version = "1", features = ["derive"]}
serde = { version = "1", features = ["derive"] }
serde_json = "1"
toml = "0.8"
toml = "0.9"
sha2 = "0.10"
tracing = "0.1"
tracing-subscriber = {version = "0.3.18", features = ["std", "env-filter", "fmt"]}
tracing-subscriber = { version = "0.3.18", features = [
"std",
"env-filter",
"fmt",
] }
url = "2"
glob = "0.3"
ureq = {version = "3.0", optional=true}
ureq = { version = "3.0", optional = true }
extism-manifest = { workspace = true }
extism-convert = { workspace = true, features = ["extism-path"] }
uuid = { version = "1", features = ["v4"] }
libc = "0.2"
[features]
default = ["http", "register-http", "register-filesystem"]
register-http = ["ureq"] # enables wasm to be downloaded using http
register-filesystem = [] # enables wasm to be loaded from disk
http = ["ureq"] # enables extism_http_request
default = ["http", "register-http", "register-filesystem", "wasmtime-default-features"]
register-http = ["ureq"] # enables wasm to be downloaded using http
register-filesystem = [] # enables wasm to be loaded from disk
http = ["ureq"] # enables extism_http_request
wasmtime-exceptions = [] # enables exception-handling proposal in wasmtime (requires wasmtime gc feature)
wasmtime-default-features = [
'wasmtime/default',
]
[build-dependencies]
cbindgen = { version = "0.28", default-features = false }
cbindgen = { version = "0.29", default-features = false }
[dev-dependencies]
criterion = "0.5.1"
criterion = "0.7.0"
quickcheck = "1"
rand = "0.9.0"

View File

@@ -24,7 +24,7 @@ There are a few environment variables that can be used for debugging purposes:
- `EXTISM_COREDUMP=extism.core`: write [coredump](https://github.com/WebAssembly/tool-conventions/blob/main/Coredump.md) to a file when a WebAssembly function traps
- `EXTISM_DEBUG=1`: generate debug information
- `EXTISM_PROFILE=perf|jitdump|vtune`: enable Wasmtime profiling
- `EXTISM_CACHE_CONFIG=path/to/config.toml`: enable Wasmtime cache, see [the docs](https://docs.wasmtime.dev/cli-cache.html) for details about configuration. Setting this to an empty string will disable caching.
- `EXTISM_CACHE_CONFIG=path/to/config.toml`: enable Wasmtime cache, details [here](#wasmtime-caching)
> *Note*: The debug and coredump info will only be written if the plug-in has an error.
@@ -88,7 +88,7 @@ println!("{:?}", res);
### Plug-in State
Plug-ins may be stateful or stateless. Plug-ins can maintain state b/w calls by the use of variables. Our count vowels plug-in remembers the total number of vowels it's ever counted in the "total" key in the result. You can see this by making subsequent calls to the export:
Plug-ins may be stateful or stateless. Plug-ins can maintain state between calls by the use of variables. Our count vowels plug-in remembers the total number of vowels it's ever counted in the "total" key in the result. You can see this by making subsequent calls to the export:
```rust
let res = plugin.call::<&str, &str>("count_vowels", "Hello, world!").unwrap();
@@ -112,7 +112,8 @@ let mut plugin = Plugin::new(&manifest, [], true);
let res = plugin.call::<&str, &str>("count_vowels", "Yellow, world!").unwrap();
println!("{}", res);
# => {"count": 3, "total": 3, "vowels": "aeiouAEIOU"}
let mut plugin = Plugin::new(&manifest, [], true).with_config_key("vowels", "aeiouyAEIOUY");
let manifest = Manifest::new([url]).with_config_key("vowels", "aeiouyAEIOUY");
let mut plugin = Plugin::new(&manifest, [], true).unwrap();
let res = plugin.call::<&str, &str>("count_vowels", "Yellow, world!").unwrap();
println!("{}", res);
# => {"count": 4, "total": 4, "vowels": "aeiouyAEIOUY"}
@@ -219,5 +220,52 @@ println!("{}", res);
# => {"count": 3, "total": 6, "vowels": "aeiouAEIOU"}
```
### Logging
Plug-ins can't directly print anything to the console. They can however use Extism's built-in logging functionality, for example the [`log!` macro in the rust-pdk](https://docs.rs/extism-pdk/1.4.0/extism_pdk/macro.log.html) or the [`logInfo` function in the haskell-pdk](https://hackage.haskell.org/package/extism-pdk-1.2.0.0/docs/Extism-PDK.html#v:logInfo).
Inside your host application, the rust-sdk emits these as [tracing](https://github.com/tokio-rs/tracing) events. The simplest way to make the logged messages visible is by adding the `tracing_subscriber` dependency to your crate and then initializing a tracing subscriber at the top of your main function:
```rust
tracing_subscriber::fmt::init();
```
### Wasmtime Caching
To enable or disable caching for plugin compilation, you need to provide a configuration file that will be used by the [wasmtime crate](https://github.com/bytecodealliance/wasmtime).
For more information and values that can be used for configuring caching, take a look at [the docs](https://docs.wasmtime.dev/cli-cache.html).
> *Note*: As of now extism uses wasmtime [`version = ">= 27.0.0, < 31.0.0"`](https://github.com/extism/extism/blob/v1.11.1/runtime/Cargo.toml#L12), but the `enabled` key requirement [was removed](https://github.com/bytecodealliance/wasmtime/pull/10859) from `wasmtime` and its documentation, this could explain the `failed to parse config file` error you might encounter without it.
An example configuration for caching would be:
```toml
[cache]
enabled = true # This value is required
directory = "/some/path"
```
You can :
- [Create a global `wasmtime` configuration file](#using-a-configuration-file) in `$HOME/.config/wasmtime/config.toml`.
- [Set the `EXTISM_CACHE_CONFIG` environment variable](#using-an-environment-variable)
- [Set the configuration file path using `PluginBuilder`](#using-pluginbuilder)
#### Using a configuration file
The [wasmtime](https://github.com/bytecodealliance/wasmtime) crate, by default, will look for a configuration file in your systems' default configuration directory (for example on UNIX systems: `$HOME/.config/wasmtime/config.toml`),
for more [information on this behaviour](`https://docs.rs/wasmtime/31.0.0/wasmtime/struct.Config.html#method.cache_config_load_default`).
#### Using an environment variable
You can set the `EXTISM_CACHE_CONFIG=path/to/config.toml` environment variable to set the path of the configuration file used by [wasmtime](https://github.com/bytecodealliance/wasmtime).
Setting the variable to an empty string will disable caching (it won't load any configuration file).
> *Note*: If the environment variable is not set, `wasmtime` will still try to read from a configuration file that may exist in your system's default configuration folder (e.g. `$HOME/.config/wasmtime/config.toml`).
The environment variable does not override the path you might have set using `PluginBuilder`. will only be checked for if you did not specify a cache configuration path in `PluginBuilder`.
#### Using PluginBuilder
If you use a [PluginBuilder](https://docs.rs/extism/latest/extism/struct.PluginBuilder.html), you can set the `wasmtime` configuration path using the [with_cache_config](https://docs.rs/extism/latest/extism/struct.PluginBuilder.html#method.with_cache_config) method.
This will override the `EXTISM_CACHE_CONFIG` environment variable if it's set, so you could have a "global" and per plugin configuration if needed.

View File

@@ -16,7 +16,7 @@ fn main() {
let res = plugin.call::<&str, &str>("try_read", "").unwrap();
println!("{:?}", res);
println!("{res:?}");
println!("-----------------------------------------------------");
@@ -30,7 +30,7 @@ fn main() {
);
let res2 = plugin.call::<&str, &str>("try_write", &line).unwrap();
println!("{:?}", res2);
println!("{res2:?}");
println!("done!");
}

View File

@@ -30,6 +30,6 @@ fn main() {
let res = plugin
.call::<&str, &str>("reflect", "Hello, world!")
.unwrap();
println!("{}", res);
println!("{res}");
}
}

View File

@@ -25,6 +25,6 @@ fn main() {
println!("Dumping logs");
for line in LOGS.lock().unwrap().iter() {
print!("{}", line);
print!("{line}");
}
}

View File

@@ -52,6 +52,6 @@ fn main() {
let res = plugin
.call::<&str, &str>("count_vowels", "Hello, world!")
.unwrap();
println!("{}", res);
println!("{res}");
}
}

View File

@@ -156,7 +156,7 @@ void extism_current_plugin_memory_free(ExtismCurrentPlugin *plugin, ExtismMemory
* - `n_outputs`: number of return types
* - `func`: the function to call
* - `user_data`: a pointer that will be passed to the function when it's called
* this value should live as long as the function exists
* this value should live as long as the function exists
* - `free_user_data`: a callback to release the `user_data` value when the resulting
* `ExtismFunction` is freed.
*
@@ -191,6 +191,17 @@ ExtismCompiledPlugin *extism_compiled_plugin_new(const uint8_t *wasm,
bool with_wasi,
char **errmsg);
/**
* Pre-compile an Extism plugin and set the number of instructions a plugin is allowed to execute
*/
ExtismCompiledPlugin *extism_compiled_plugin_new_with_fuel_limit(const uint8_t *wasm,
ExtismSize wasm_size,
const ExtismFunction **functions,
ExtismSize n_functions,
bool with_wasi,
uint64_t fuel_limit,
char **errmsg);
/**
* Free `ExtismCompiledPlugin`
*/

View File

@@ -477,10 +477,7 @@ impl CurrentPlugin {
offset: offs,
length,
});
match s {
Ok(s) => Some(s),
Err(_) => None,
}
s.ok()
}
#[doc(hidden)]

View File

@@ -284,8 +284,8 @@ impl Function {
/// A few things worth noting:
/// - The function always returns a `Result` that wraps the specified return type
/// - If a first parameter and type are passed (`_user_data` above) followed by a semicolon it will be
/// the name of the `UserData` parameter and can be used from inside the function
// definition.
/// the name of the `UserData` parameter and can be used from inside the function
// definition.
#[macro_export]
macro_rules! host_fn {
($pub:vis $name: ident ($($arg:ident : $argty:ty),*) $(-> $ret:ty)? $b:block) => {

View File

@@ -29,6 +29,7 @@ pub(crate) mod manifest;
pub(crate) mod pdk;
mod plugin;
mod plugin_builder;
mod pool;
mod readonly_dir;
mod timer;
@@ -43,6 +44,7 @@ pub use plugin::{
CancelHandle, CompiledPlugin, Plugin, WasmInput, EXTISM_ENV_MODULE, EXTISM_USER_MODULE,
};
pub use plugin_builder::{DebugOptions, PluginBuilder};
pub use pool::{Pool, PoolBuilder, PoolPlugin};
pub(crate) use internal::{Internal, Wasi};
pub(crate) use timer::{Timer, TimerAction};
@@ -96,7 +98,7 @@ pub fn set_log_callback<F: 'static + Clone + Fn(&str)>(
let x = tracing_subscriber::EnvFilter::builder()
.with_default_directive(tracing::Level::ERROR.into());
if is_level {
x.parse_lossy(format!("extism={}", filter))
x.parse_lossy(format!("extism={filter}"))
} else {
x.parse_lossy(filter)
}

View File

@@ -10,7 +10,7 @@ use crate::*;
fn hex(data: &[u8]) -> String {
let mut s = String::new();
for &byte in data {
write!(&mut s, "{:02x}", byte).unwrap();
write!(&mut s, "{byte:02x}").unwrap();
}
s
}

View File

@@ -258,12 +258,16 @@ pub(crate) fn http_request(
};
let buf: &[u8] = data.memory_bytes(handle)?;
let agent = ureq::agent();
let config = agent.configure_request(r.body(buf)?);
let config = agent
.configure_request(r.body(buf)?)
.http_status_as_error(false);
let req = config.timeout_global(timeout).build();
ureq::run(req)
} else {
let agent = ureq::agent();
let config = agent.configure_request(r.body(())?);
let config = agent
.configure_request(r.body(())?)
.http_status_as_error(false);
let req = config.timeout_global(timeout).build();
ureq::run(req)
};

View File

@@ -1,6 +1,8 @@
use std::{
any::Any,
collections::{BTreeMap, BTreeSet},
path::PathBuf,
sync::TryLockError,
};
use anyhow::Context;
@@ -59,26 +61,16 @@ impl CompiledPlugin {
.wasm_tail_call(true)
.wasm_function_references(true)
.wasm_gc(true);
#[cfg(feature = "wasmtime-exceptions")]
{
config.wasm_exceptions(true);
}
if builder.options.fuel.is_some() {
config.consume_fuel(true);
}
match &builder.options.cache_config {
Some(None) => (),
Some(Some(path)) => {
config.cache_config_load(path)?;
}
None => {
if let Ok(env) = std::env::var("EXTISM_CACHE_CONFIG") {
if !env.is_empty() {
config.cache_config_load(&env)?;
}
} else {
config.cache_config_load_default()?;
}
}
}
config.cache(Self::configure_cache(&builder.options.cache_config)?);
let engine = Engine::new(&config)?;
@@ -96,6 +88,43 @@ impl CompiledPlugin {
engine,
})
}
/// Return optional cache according to builder options.
fn configure_cache(
cache_opt: &Option<Option<std::path::PathBuf>>,
) -> Result<Option<wasmtime::Cache>, Error> {
match cache_opt {
// Explicitly disabled
Some(None) => Ok(None),
// Explicit path
Some(Some(p)) => {
let cache = wasmtime::Cache::from_file(Some(p.as_path()))?;
Ok(Some(cache))
}
// Unspecified, try environment, then system fallback
None => {
match std::env::var_os("EXTISM_CACHE_CONFIG") {
Some(val) => {
if val.is_empty() {
// Disable cache if env var exists but is empty
Ok(None)
} else {
let p = PathBuf::from(val);
let cache = wasmtime::Cache::from_file(Some(p.as_path()))?;
Ok(Some(cache))
}
}
None => {
// load cache configuration from the system default path
let cache = wasmtime::Cache::from_file(None)?;
Ok(Some(cache))
}
}
}
}
}
}
/// Plugin contains everything needed to execute a WASM function
@@ -190,6 +219,7 @@ pub(crate) fn profiling_strategy() -> ProfilingStrategy {
/// Defines an input type for Wasm data.
///
/// Types that implement `Into<WasmInput>` can be passed directly into `Plugin::new`
#[derive(Clone)]
pub enum WasmInput<'a> {
/// Raw Wasm module
Data(std::borrow::Cow<'a, [u8]>),
@@ -342,6 +372,7 @@ fn relink(
let (kind, ty) = match import.ty() {
ExternType::Func(t) => ("function", t.to_string()),
ExternType::Global(t) => ("global", t.content().to_string()),
ExternType::Tag(t) => ("tag", t.ty().to_string()),
ExternType::Table(t) => ("table", t.element().to_string()),
ExternType::Memory(_) => ("memory", String::new()),
};
@@ -961,8 +992,7 @@ impl Plugin {
}
Err(msg) => {
res = Err(Error::msg(format!(
"unable to load error message from memory: {}",
msg,
"unable to load error message from memory: {msg}",
)));
}
}
@@ -1088,7 +1118,12 @@ impl Plugin {
input: T,
) -> Result<U, Error> {
let lock = self.instance.clone();
let mut lock = lock.lock().unwrap();
let mut lock = lock.try_lock().map_err(|e| match e {
TryLockError::Poisoned(_) => anyhow::anyhow!(
"instance lock was poisoned; previous thread panicked while calling into wasm"
),
TryLockError::WouldBlock => anyhow::anyhow!("cannot make reentrant calls into plugin"),
})?;
let data = input.to_bytes()?;
self.raw_call(&mut lock, name, data, None::<()>)
.map_err(|e| e.0)
@@ -1113,7 +1148,12 @@ impl Plugin {
C: Any + Send + Sync + 'static,
{
let lock = self.instance.clone();
let mut lock = lock.lock().unwrap();
let mut lock = lock.try_lock().map_err(|e| match e {
TryLockError::Poisoned(_) => anyhow::anyhow!(
"instance lock was poisoned; previous thread panicked while calling into wasm"
),
TryLockError::WouldBlock => anyhow::anyhow!("cannot make reentrant calls into plugin"),
})?;
let data = input.to_bytes()?;
self.raw_call(&mut lock, name, data, Some(host_context))
.map_err(|e| e.0)
@@ -1132,7 +1172,18 @@ impl Plugin {
input: T,
) -> Result<U, (Error, i32)> {
let lock = self.instance.clone();
let mut lock = lock.lock().unwrap();
let mut lock = lock.try_lock().map_err(|e| match e {
TryLockError::Poisoned(_) => (
anyhow::anyhow!(
"instance lock was poisoned; previous thread panicked while calling into wasm"
),
-1,
),
TryLockError::WouldBlock => (
anyhow::anyhow!("cannot make reentrant calls into plugin"),
-1,
),
})?;
let data = input.to_bytes().map_err(|e| (e, -1))?;
self.raw_call(&mut lock, name, data, None::<()>)
.and_then(move |_| self.output().map_err(|e| (e, -1)))

View File

@@ -33,6 +33,7 @@ impl Default for DebugOptions {
}
/// PluginBuilder is used to configure and create `Plugin` instances
#[derive(Clone)]
pub struct PluginBuilder<'a> {
pub(crate) source: WasmInput<'a>,
pub(crate) config: Option<wasmtime::Config>,

195
runtime/src/pool.rs Normal file
View File

@@ -0,0 +1,195 @@
use crate::{Error, FromBytesOwned, Plugin, ToBytes};
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::RwLock;
// `PoolBuilder` is used to configure and create `Pool`s
#[derive(Debug, Clone)]
pub struct PoolBuilder {
/// Max number of concurrent instances for a plugin - by default this is set to
/// the output of `std::thread::available_parallelism`
pub max_instances: usize,
}
impl PoolBuilder {
/// Create a `PoolBuilder` with default values
pub fn new() -> Self {
Self::default()
}
/// Set the max number of parallel instances
pub fn with_max_instances(mut self, n: usize) -> Self {
self.max_instances = n;
self
}
/// Create a new `Pool` with the given configuration
pub fn build<F: 'static + Fn() -> Result<Plugin, Error>>(self, source: F) -> Pool {
Pool::new_from_builder(source, self)
}
}
impl Default for PoolBuilder {
fn default() -> Self {
PoolBuilder {
max_instances: std::thread::available_parallelism()
.expect("available parallelism")
.into(),
}
}
}
/// `PoolPlugin` is used by the pool to track the number of live instances of a particular plugin
#[derive(Clone, Debug)]
pub struct PoolPlugin(std::rc::Rc<std::cell::RefCell<Plugin>>);
impl PoolPlugin {
fn new(plugin: Plugin) -> Self {
Self(std::rc::Rc::new(std::cell::RefCell::new(plugin)))
}
/// Access the underlying plugin
pub fn plugin(&self) -> std::cell::RefMut<'_, Plugin> {
self.0.borrow_mut()
}
/// Helper to call a plugin function on the underlying plugin
pub fn call<'a, Input: ToBytes<'a>, Output: FromBytesOwned>(
&self,
name: impl AsRef<str>,
input: Input,
) -> Result<Output, Error> {
self.plugin().call(name.as_ref(), input)
}
/// Helper to get the underlying plugin's ID
pub fn id(&self) -> uuid::Uuid {
self.plugin().id
}
}
type PluginSource = dyn Fn() -> Result<Plugin, Error>;
struct PoolInner {
plugin_source: Box<PluginSource>,
instances: Vec<PoolPlugin>,
}
unsafe impl Send for PoolInner {}
unsafe impl Sync for PoolInner {}
/// `Pool` manages threadsafe access to a limited number of instances of multiple plugins
#[derive(Clone)]
pub struct Pool {
config: PoolBuilder,
inner: Arc<std::sync::Mutex<PoolInner>>,
existing_functions: Arc<RwLock<HashMap<String, bool>>>,
}
unsafe impl Send for Pool {}
unsafe impl Sync for Pool {}
impl Pool {
/// Create a new pool with the default configuration
pub fn new<F: 'static + Fn() -> Result<Plugin, Error>>(source: F) -> Self {
Self::new_from_builder(Box::new(source), PoolBuilder::default())
}
/// Create a new pool configured using a `PoolBuilder`
pub fn new_from_builder<F: 'static + Fn() -> Result<Plugin, Error>>(
source: F,
builder: PoolBuilder,
) -> Self {
Pool {
config: builder,
inner: Arc::new(std::sync::Mutex::new(PoolInner {
plugin_source: Box::new(source),
instances: Default::default(),
})),
existing_functions: RwLock::new(HashMap::default()).into(),
}
}
fn find_available(&self) -> Result<Option<PoolPlugin>, Error> {
let pool = self.inner.lock().unwrap();
for instance in pool.instances.iter() {
if std::rc::Rc::strong_count(&instance.0) == 1 {
return Ok(Some(instance.clone()));
}
}
Ok(None)
}
/// Get the number of live instances for a plugin
pub fn count(&self) -> usize {
self.inner.lock().unwrap().instances.len()
}
/// Get access to a plugin, this will create a new instance if needed (and allowed by the specified
/// max_instances). `Ok(None)` is returned if the timeout is reached before an available plugin could be
/// acquired
pub fn get(&self, timeout: std::time::Duration) -> Result<Option<PoolPlugin>, Error> {
let start = std::time::Instant::now();
let max = self.config.max_instances;
if let Some(avail) = self.find_available()? {
return Ok(Some(avail));
}
{
let mut pool = self.inner.lock().unwrap();
if pool.instances.len() < max {
let plugin = (*pool.plugin_source)()?;
let instance = PoolPlugin::new(plugin);
pool.instances.push(instance);
return Ok(Some(pool.instances.last().unwrap().clone()));
}
}
loop {
if let Ok(Some(x)) = self.find_available() {
return Ok(Some(x));
}
if std::time::Instant::now() - start > timeout {
return Ok(None);
}
std::thread::sleep(std::time::Duration::from_millis(100));
}
}
/// Access a plugin in a callback function. This calls `Pool::get` then the provided
/// callback. `Ok(None)` is returned if the timeout is reached before an available
/// plugin could be acquired
pub fn with_plugin<T>(
&self,
timeout: std::time::Duration,
f: impl FnOnce(&mut Plugin) -> Result<T, Error>,
) -> Result<Option<T>, Error> {
if let Some(plugin) = self.get(timeout)? {
return f(&mut plugin.plugin()).map(Some);
}
Ok(None)
}
/// Returns `true` if the given function exists, otherwise `false`. Results are cached
/// after the first call.
pub fn function_exists(&self, name: &str, timeout: std::time::Duration) -> Result<bool, Error> {
// read current value if any
let read = self.existing_functions.read().unwrap();
let exists_opt = read.get(name).cloned();
drop(read);
if let Some(exists) = exists_opt {
Ok(exists)
} else {
// load plugin and call function_exists
let plugin = self.get(timeout)?;
let exists = plugin.unwrap().0.borrow().function_exists(name);
// write result to hashmap
let mut write = self.existing_functions.write().unwrap();
write.insert(name.to_string(), exists);
Ok(exists)
}
}
}

View File

@@ -180,7 +180,7 @@ pub unsafe extern "C" fn extism_current_plugin_memory_free(
/// - `n_outputs`: number of return types
/// - `func`: the function to call
/// - `user_data`: a pointer that will be passed to the function when it's called
/// this value should live as long as the function exists
/// this value should live as long as the function exists
/// - `free_user_data`: a callback to release the `user_data` value when the resulting
/// `ExtismFunction` is freed.
///
@@ -363,6 +363,70 @@ pub unsafe extern "C" fn extism_compiled_plugin_new(
})
}
/// Pre-compile an Extism plugin and set the number of instructions a plugin is allowed to execute
#[no_mangle]
pub unsafe extern "C" fn extism_compiled_plugin_new_with_fuel_limit(
wasm: *const u8,
wasm_size: Size,
functions: *mut *const ExtismFunction,
n_functions: Size,
with_wasi: bool,
fuel_limit: u64,
errmsg: *mut *mut std::ffi::c_char,
) -> *mut CompiledPlugin {
trace!("Call to extism_plugin_new with wasm pointer {:?}", wasm);
let data = std::slice::from_raw_parts(wasm, wasm_size as usize);
let mut builder = PluginBuilder::new(data)
.with_wasi(with_wasi)
.with_fuel_limit(fuel_limit);
if !functions.is_null() {
let funcs = (0..n_functions)
.map(|i| unsafe { *functions.add(i as usize) })
.map(|ptr| {
if ptr.is_null() {
return Err("Cannot pass null pointer");
}
let ExtismFunction(func) = &*ptr;
let Some(func) = func.take() else {
return Err("Function cannot be registered with multiple different Plugins");
};
Ok(func)
})
.collect::<Result<Vec<_>, _>>()
.unwrap_or_else(|e| {
if !errmsg.is_null() {
let e = std::ffi::CString::new(e.to_string()).unwrap();
*errmsg = e.into_raw();
}
Vec::new()
});
if funcs.len() != n_functions as usize {
return std::ptr::null_mut();
}
builder = builder.with_functions(funcs);
}
CompiledPlugin::new(builder)
.map(|v| Box::into_raw(Box::new(v)))
.unwrap_or_else(|e| {
if !errmsg.is_null() {
let e = std::ffi::CString::new(format!(
"Unable to compile Extism plugin: {}",
e.root_cause(),
))
.unwrap();
*errmsg = e.into_raw();
}
std::ptr::null_mut()
})
}
/// Free `ExtismCompiledPlugin`
#[no_mangle]
pub unsafe extern "C" fn extism_compiled_plugin_free(plugin: *mut CompiledPlugin) {
@@ -871,7 +935,7 @@ fn set_log_file(log_file: impl Into<std::path::PathBuf>, filter: &str) -> Result
let x = tracing_subscriber::EnvFilter::builder()
.with_default_directive(tracing::Level::ERROR.into());
if is_level {
x.parse_lossy(format!("extism={}", filter))
x.parse_lossy(format!("extism={filter}"))
} else {
x.parse_lossy(filter)
}
@@ -926,7 +990,7 @@ unsafe fn set_log_buffer(filter: &str) -> Result<(), Error> {
let x = tracing_subscriber::EnvFilter::builder()
.with_default_directive(tracing::Level::ERROR.into());
if is_level {
x.parse_lossy(format!("extism={}", filter))
x.parse_lossy(format!("extism={filter}"))
} else {
x.parse_lossy(filter)
}

View File

@@ -16,7 +16,7 @@ fn test_issue_620() {
// Call test method, this does not work
let p = plugin.call::<(), String>("test", ()).unwrap();
println!("{}", p);
println!("{p}");
}
// https://github.com/extism/extism/issues/619
@@ -53,5 +53,5 @@ fn test_issue_775() {
Ok(code) => Err(code),
}
.unwrap();
println!("{}", p);
println!("{p}");
}

View File

@@ -1,3 +1,4 @@
mod issues;
mod kernel;
mod pool;
mod runtime;

63
runtime/src/tests/pool.rs Normal file
View File

@@ -0,0 +1,63 @@
use crate::*;
use std::time::Duration;
fn run_thread(p: Pool, i: u64) -> std::thread::JoinHandle<()> {
std::thread::spawn(move || {
std::thread::sleep(Duration::from_millis(i));
let s: String = p
.get(Duration::from_secs(1))
.unwrap()
.unwrap()
.call("count_vowels", "abc")
.unwrap();
println!("{s}");
})
}
fn init(max_instances: usize) -> Pool {
let data = include_bytes!("../../../wasm/code.wasm");
let plugin_builder =
extism::PluginBuilder::new(extism::Manifest::new([extism::Wasm::data(data)]))
.with_wasi(true);
PoolBuilder::new()
.with_max_instances(max_instances)
.build(move || plugin_builder.clone().build())
}
#[test]
fn test_threads() {
for i in 1..=3 {
let pool = init(i);
let threads = vec![
run_thread(pool.clone(), 1000),
run_thread(pool.clone(), 1000),
run_thread(pool.clone(), 1000),
run_thread(pool.clone(), 1000),
run_thread(pool.clone(), 1000),
run_thread(pool.clone(), 1000),
run_thread(pool.clone(), 500),
run_thread(pool.clone(), 500),
run_thread(pool.clone(), 500),
run_thread(pool.clone(), 500),
run_thread(pool.clone(), 500),
run_thread(pool.clone(), 0),
];
for t in threads {
t.join().unwrap();
}
assert!(pool.count() <= i);
}
}
#[test]
fn test_exists() -> Result<(), Error> {
let pool = init(1);
let timeout = Duration::from_secs(1);
assert!(pool.function_exists("count_vowels", timeout)?);
assert!(pool.function_exists("count_vowels", timeout)?);
assert!(!pool.function_exists("not_existing", timeout)?);
assert!(!pool.function_exists("not_existing", timeout)?);
Ok(())
}

View File

@@ -133,10 +133,7 @@ fn it_works() {
.unwrap();
let native_avg: std::time::Duration = native_sum / native_num_tests as u32;
println!(
"native function call (avg, N = {}): {:?}",
native_num_tests, native_avg
);
println!("native function call (avg, N = {native_num_tests}): {native_avg:?}");
let num_tests = test_times.len();
let sum: std::time::Duration = test_times
@@ -145,7 +142,7 @@ fn it_works() {
.unwrap();
let avg: std::time::Duration = sum / num_tests as u32;
println!("wasm function call (avg, N = {}): {:?}", num_tests, avg);
println!("wasm function call (avg, N = {num_tests}): {avg:?}");
// Check that log file was written to
if log {
@@ -212,7 +209,7 @@ fn test_cancel() {
let _output: Result<&[u8], Error> = plugin.call("loop_forever", "abc123");
let end = std::time::Instant::now();
let time = end - start;
println!("Cancelled plugin ran for {:?}", time);
println!("Cancelled plugin ran for {time:?}");
}
}
@@ -271,7 +268,7 @@ fn test_fuel_consumption() {
assert!(output.is_err());
let fuel_consumed = plugin.fuel_consumed().unwrap();
println!("Fuel consumed: {}", fuel_consumed);
println!("Fuel consumed: {fuel_consumed}");
assert!(fuel_consumed > 0);
}
@@ -440,7 +437,7 @@ fn test_memory_max() {
assert!(output.is_err());
let err = output.unwrap_err().root_cause().to_string();
println!("{:?}", err);
println!("{err:?}");
assert_eq!(err, "oom");
// Should pass with memory.max set to a large enough number
@@ -503,7 +500,7 @@ fn test_extism_error() {
let mut plugin = Plugin::new(&manifest, [f], true).unwrap();
let output: Result<String, Error> = plugin.call("count_vowels", "a".repeat(1024));
assert!(output.is_err());
println!("{:?}", output);
println!("{output:?}");
assert_eq!(output.unwrap_err().root_cause().to_string(), "TEST");
}
@@ -823,7 +820,7 @@ fn test_http_response_headers() {
.unwrap();
let req = HttpRequest::new("https://extism.org");
let Json(res): Json<HashMap<String, String>> = plugin.call("http_get", Json(req)).unwrap();
println!("{:?}", res);
println!("{res:?}");
assert_eq!(res["content-type"], "text/html; charset=utf-8");
}
@@ -838,6 +835,6 @@ fn test_http_response_headers_disabled() {
.unwrap();
let req = HttpRequest::new("https://extism.org");
let Json(res): Json<HashMap<String, String>> = plugin.call("http_get", Json(req)).unwrap();
println!("{:?}", res);
println!("{res:?}");
assert!(res.is_empty());
}