Compare commits

...

60 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
zach
1f46f9842d v1.10.0 2025-02-10 10:44:48 -08:00
zach
b249f09b90 fix(ci): remove path parameter from download-artifact action in release workflow (#831) 2025-02-10 10:28:23 -08:00
zach
4e638e14b1 chore: update wasmtime bounds to include more versions (#828)
Upgrades from wasmtime 26 to wasmtime 27-29, allowing us to support more
versions (including the possibility of supporting future releases
without changes)
2025-02-06 17:03:58 -08:00
zach
d7956ff08c fix(ci): use new download-artifact api (#827) 2025-02-04 13:49:19 -08:00
dependabot[bot]
87c3384f1e chore(deps): Update ureq requirement from 2.5 to 3.0 (#825)
Closes #824 

Updates the requirements on [ureq](https://github.com/algesten/ureq) to
permit the latest version.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/algesten/ureq/blob/main/CHANGELOG.md">ureq's
changelog</a>.</em></p>
<blockquote>
<h1>3.0.3</h1>
<ul>
<li>Use the same data in CONNECT and Host header for proxied requests
(<a
href="https://redirect.github.com/algesten/ureq/issues/967">#967</a>)</li>
<li>Set default scheme in proxy uri (<a
href="https://redirect.github.com/algesten/ureq/issues/966">#966</a>)</li>
<li>Redact URI and Location header on debug level (<a
href="https://redirect.github.com/algesten/ureq/issues/964">#964</a>)</li>
<li>Downgrade all logging to debug and below (<a
href="https://redirect.github.com/algesten/ureq/issues/964">#964</a>)</li>
</ul>
<h1>3.0.2</h1>
<ul>
<li>Remove dependency on once_cell (<a
href="https://redirect.github.com/algesten/ureq/issues/959">#959</a>)</li>
<li>Fix bug parsing partial redirects (<a
href="https://redirect.github.com/algesten/ureq/issues/958">#958</a>)</li>
<li>Expose typestate variables (<a
href="https://redirect.github.com/algesten/ureq/issues/956">#956</a>)</li>
</ul>
<h1>3.0.1</h1>
<ul>
<li>Fix excessive stack sizes (<a
href="https://redirect.github.com/algesten/ureq/issues/950">#950</a>)</li>
<li>Do not enable <strong>json</strong> by default (breaking, but it was
a mistake) (<a
href="https://redirect.github.com/algesten/ureq/issues/948">#948</a>)</li>
</ul>
<h1>3.0.0</h1>
<ul>
<li>Replace RequestBuilder Deref with explicit wrappers (<a
href="https://redirect.github.com/algesten/ureq/issues/944">#944</a>)</li>
<li>Remove dependency on <code>url</code> crate (<a
href="https://redirect.github.com/algesten/ureq/issues/943">#943</a>)</li>
<li>Feature <code>Config::save_redirect_history</code> (<a
href="https://redirect.github.com/algesten/ureq/issues/939">#939</a>)</li>
</ul>
<h1>3.0.0-rc5</h1>
<ul>
<li><code>TlsConfig::unversioned_rustls_crypto_provider()</code> (<a
href="https://redirect.github.com/algesten/ureq/issues/931">#931</a>)</li>
<li>Feature <code>rustls-no-provider</code> to compile without ring (<a
href="https://redirect.github.com/algesten/ureq/issues/931">#931</a>)</li>
<li>Fix CONNECT proxy Host header (<a
href="https://redirect.github.com/algesten/ureq/issues/936">#936</a>)</li>
<li>Re-enable CONNECT proxy support (<a
href="https://redirect.github.com/algesten/ureq/issues/932">#932</a>)</li>
<li>Body::content_length (<a
href="https://redirect.github.com/algesten/ureq/issues/927">#927</a>)</li>
<li>Handle Authorization: Basic from URI (<a
href="https://redirect.github.com/algesten/ureq/issues/923">#923</a>)</li>
<li>Remove many uses of Box::new() from Connector chain (<a
href="https://redirect.github.com/algesten/ureq/issues/919">#919</a>)</li>
</ul>
<h1>3.0.0-rc4</h1>
<ul>
<li>Default to <code>TooManyRedirects</code> error (<a
href="https://redirect.github.com/algesten/ureq/issues/916">#916</a>)</li>
<li>Add <code>ConfigBuilder::max_redirects_will_error()</code> (<a
href="https://redirect.github.com/algesten/ureq/issues/916">#916</a>)</li>
<li>Add new <code>SendBody::into_reader()</code> (<a
href="https://redirect.github.com/algesten/ureq/issues/914">#914</a>)</li>
<li>Fix completely broken PEM parsing (<a
href="https://redirect.github.com/algesten/ureq/issues/912">#912</a>)</li>
<li>Improve ergonomics for <code>AutoHeaderValue</code> (<a
href="https://redirect.github.com/algesten/ureq/issues/896">#896</a>)</li>
</ul>
<h1>3.0.0-rc3</h1>
<ul>
<li>Re-export ureq_proto::ArrayVec (<a
href="https://redirect.github.com/algesten/ureq/issues/891">#891</a>)</li>
<li>Expose typestate variables, but #[doc(hidden)] (<a
href="https://redirect.github.com/algesten/ureq/issues/889">#889</a>)</li>
<li>Clarify versioning and MSRV policy (<a
href="https://redirect.github.com/algesten/ureq/issues/887">#887</a>)</li>
<li>Get last used uri via <code>ResponseExt::get_uri()</code> (<a
href="https://redirect.github.com/algesten/ureq/issues/884">#884</a>)</li>
<li>Expose more things for 3rd party Transport impls (<a
href="https://redirect.github.com/algesten/ureq/issues/886">#886</a>)</li>
<li>Make accessor fn for <code>Config</code> and <code>TlsConfig</code>
(<a
href="https://redirect.github.com/algesten/ureq/issues/886">#886</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="5cb17e8e1f"><code>5cb17e8</code></a>
Update doc</li>
<li><a
href="81127cfc38"><code>81127cf</code></a>
3.0.3</li>
<li><a
href="89d593c31c"><code>89d593c</code></a>
proxy: use the same data for CONNECT and Host header</li>
<li><a
href="e2e27ea654"><code>e2e27ea</code></a>
Set default proxy scheme in proxy uri</li>
<li><a
href="7533c1ad45"><code>7533c1a</code></a>
Update changelog</li>
<li><a
href="0d274df41b"><code>0d274df</code></a>
Use more constants for headers</li>
<li><a
href="0a4f6d7d75"><code>0a4f6d7</code></a>
Redact Location header</li>
<li><a
href="50347206a1"><code>5034720</code></a>
Downgrade info! level to debug!</li>
<li><a
href="4173dd49d7"><code>4173dd4</code></a>
ureq-proto 0.3.0</li>
<li><a
href="3d5f182fa3"><code>3d5f182</code></a>
3.0.2</li>
<li>Additional commits viewable in <a
href="https://github.com/algesten/ureq/compare/2.5.0...3.0.3">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-02-04 11:30:53 -08:00
dependabot[bot]
07047eaab0 chore(deps): Update rand requirement from 0.8.5 to 0.9.0 (#826)
Updates the requirements on [rand](https://github.com/rust-random/rand)
to permit the latest version.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/rust-random/rand/blob/master/CHANGELOG.md">rand's
changelog</a>.</em></p>
<blockquote>
<h2>[0.9.0] - 2025-01-27</h2>
<h3>Security and unsafe</h3>
<ul>
<li>Policy: &quot;rand is not a crypto library&quot; (<a
href="https://redirect.github.com/rust-random/rand/issues/1514">#1514</a>)</li>
<li>Remove fork-protection from <code>ReseedingRng</code> and
<code>ThreadRng</code>. Instead, it is recommended to call
<code>ThreadRng::reseed</code> on fork. (<a
href="https://redirect.github.com/rust-random/rand/issues/1379">#1379</a>)</li>
<li>Use <code>zerocopy</code> to replace some <code>unsafe</code> code
(<a
href="https://redirect.github.com/rust-random/rand/issues/1349">#1349</a>,
<a
href="https://redirect.github.com/rust-random/rand/issues/1393">#1393</a>,
<a
href="https://redirect.github.com/rust-random/rand/issues/1446">#1446</a>,
<a
href="https://redirect.github.com/rust-random/rand/issues/1502">#1502</a>)</li>
</ul>
<h3>Dependencies</h3>
<ul>
<li>Bump the MSRV to 1.63.0 (<a
href="https://redirect.github.com/rust-random/rand/issues/1207">#1207</a>,
<a
href="https://redirect.github.com/rust-random/rand/issues/1246">#1246</a>,
<a
href="https://redirect.github.com/rust-random/rand/issues/1269">#1269</a>,
<a
href="https://redirect.github.com/rust-random/rand/issues/1341">#1341</a>,
<a
href="https://redirect.github.com/rust-random/rand/issues/1416">#1416</a>,
<a
href="https://redirect.github.com/rust-random/rand/issues/1536">#1536</a>);
note that 1.60.0 may work for dependents when using
<code>--ignore-rust-version</code></li>
<li>Update to <code>rand_core</code> v0.9.0 (<a
href="https://redirect.github.com/rust-random/rand/issues/1558">#1558</a>)</li>
</ul>
<h3>Features</h3>
<ul>
<li>Support <code>std</code> feature without <code>getrandom</code> or
<code>rand_chacha</code> (<a
href="https://redirect.github.com/rust-random/rand/issues/1354">#1354</a>)</li>
<li>Enable feature <code>small_rng</code> by default (<a
href="https://redirect.github.com/rust-random/rand/issues/1455">#1455</a>)</li>
<li>Remove implicit feature <code>rand_chacha</code>; use
<code>std_rng</code> instead. (<a
href="https://redirect.github.com/rust-random/rand/issues/1473">#1473</a>)</li>
<li>Rename feature <code>serde1</code> to <code>serde</code> (<a
href="https://redirect.github.com/rust-random/rand/issues/1477">#1477</a>)</li>
<li>Rename feature <code>getrandom</code> to <code>os_rng</code> (<a
href="https://redirect.github.com/rust-random/rand/issues/1537">#1537</a>)</li>
<li>Add feature <code>thread_rng</code> (<a
href="https://redirect.github.com/rust-random/rand/issues/1547">#1547</a>)</li>
</ul>
<h3>API changes: rand_core traits</h3>
<ul>
<li>Add fn <code>RngCore::read_adapter</code> implementing
<code>std::io::Read</code> (<a
href="https://redirect.github.com/rust-random/rand/issues/1267">#1267</a>)</li>
<li>Add trait <code>CryptoBlockRng: BlockRngCore</code>; make
<code>trait CryptoRng: RngCore</code> (<a
href="https://redirect.github.com/rust-random/rand/issues/1273">#1273</a>)</li>
<li>Add traits <code>TryRngCore</code>, <code>TryCryptoRng</code> (<a
href="https://redirect.github.com/rust-random/rand/issues/1424">#1424</a>,
<a
href="https://redirect.github.com/rust-random/rand/issues/1499">#1499</a>)</li>
<li>Rename <code>fn SeedableRng::from_rng</code> -&gt;
<code>try_from_rng</code> and add infallible variant <code>fn
from_rng</code> (<a
href="https://redirect.github.com/rust-random/rand/issues/1424">#1424</a>)</li>
<li>Rename <code>fn SeedableRng::from_entropy</code> -&gt;
<code>from_os_rng</code> and add fallible variant <code>fn
try_from_os_rng</code> (<a
href="https://redirect.github.com/rust-random/rand/issues/1424">#1424</a>)</li>
<li>Add bounds <code>Clone</code> and <code>AsRef</code> to associated
type <code>SeedableRng::Seed</code> (<a
href="https://redirect.github.com/rust-random/rand/issues/1491">#1491</a>)</li>
</ul>
<h3>API changes: Rng trait and top-level fns</h3>
<ul>
<li>Rename fn <code>rand::thread_rng()</code> to
<code>rand::rng()</code> and remove from the prelude (<a
href="https://redirect.github.com/rust-random/rand/issues/1506">#1506</a>)</li>
<li>Remove fn <code>rand::random()</code> from the prelude (<a
href="https://redirect.github.com/rust-random/rand/issues/1506">#1506</a>)</li>
<li>Add top-level fns <code>random_iter</code>,
<code>random_range</code>, <code>random_bool</code>,
<code>random_ratio</code>, <code>fill</code> (<a
href="https://redirect.github.com/rust-random/rand/issues/1488">#1488</a>)</li>
<li>Re-introduce fn <code>Rng::gen_iter</code> as
<code>random_iter</code> (<a
href="https://redirect.github.com/rust-random/rand/issues/1305">#1305</a>,
<a
href="https://redirect.github.com/rust-random/rand/issues/1500">#1500</a>)</li>
<li>Rename fn <code>Rng::gen</code> to <code>random</code> to avoid
conflict with the new <code>gen</code> keyword in Rust 2024 (<a
href="https://redirect.github.com/rust-random/rand/issues/1438">#1438</a>)</li>
<li>Rename fns <code>Rng::gen_range</code> to <code>random_range</code>,
<code>gen_bool</code> to <code>random_bool</code>,
<code>gen_ratio</code> to <code>random_ratio</code> (<a
href="https://redirect.github.com/rust-random/rand/issues/1505">#1505</a>)</li>
<li>Annotate panicking methods with <code>#[track_caller]</code> (<a
href="https://redirect.github.com/rust-random/rand/issues/1442">#1442</a>,
<a
href="https://redirect.github.com/rust-random/rand/issues/1447">#1447</a>)</li>
</ul>
<h3>API changes: RNGs</h3>
<ul>
<li>Fix <code>&lt;SmallRng as SeedableRng&gt;::Seed</code> size to 256
bits (<a
href="https://redirect.github.com/rust-random/rand/issues/1455">#1455</a>)</li>
<li>Remove first parameter (<code>rng</code>) of
<code>ReseedingRng::new</code> (<a
href="https://redirect.github.com/rust-random/rand/issues/1533">#1533</a>)</li>
</ul>
<h3>API changes: Sequences</h3>
<ul>
<li>Split trait <code>SliceRandom</code> into
<code>IndexedRandom</code>, <code>IndexedMutRandom</code>,
<code>SliceRandom</code> (<a
href="https://redirect.github.com/rust-random/rand/issues/1382">#1382</a>)</li>
<li>Add <code>IndexedRandom::choose_multiple_array</code>,
<code>index::sample_array</code> (<a
href="https://redirect.github.com/rust-random/rand/issues/1453">#1453</a>,
<a
href="https://redirect.github.com/rust-random/rand/issues/1469">#1469</a>)</li>
</ul>
<h3>API changes: Distributions: renames</h3>
<ul>
<li>Rename module <code>rand::distributions</code> to
<code>rand::distr</code> (<a
href="https://redirect.github.com/rust-random/rand/issues/1470">#1470</a>)</li>
<li>Rename distribution <code>Standard</code> to
<code>StandardUniform</code> (<a
href="https://redirect.github.com/rust-random/rand/issues/1526">#1526</a>)</li>
<li>Move <code>distr::Slice</code> -&gt;
<code>distr::slice::Choose</code>, <code>distr::EmptySlice</code> -&gt;
<code>distr::slice::Empty</code> (<a
href="https://redirect.github.com/rust-random/rand/issues/1548">#1548</a>)</li>
<li>Rename trait <code>distr::DistString</code> -&gt;
<code>distr::SampleString</code> (<a
href="https://redirect.github.com/rust-random/rand/issues/1548">#1548</a>)</li>
<li>Rename <code>distr::DistIter</code> -&gt; <code>distr::Iter</code>,
<code>distr::DistMap</code> -&gt; <code>distr::Map</code> (<a
href="https://redirect.github.com/rust-random/rand/issues/1548">#1548</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="96f8df65ee"><code>96f8df6</code></a>
Prepare 0.9.0 release (<a
href="https://redirect.github.com/rust-random/rand/issues/1558">#1558</a>)</li>
<li><a
href="34da3214df"><code>34da321</code></a>
Enable <code>stdarch_x86_avx512</code> for cpu has <code>avx512bw</code>
(<a
href="https://redirect.github.com/rust-random/rand/issues/1551">#1551</a>)</li>
<li><a
href="b4b1eb7579"><code>b4b1eb7</code></a>
Re-org with distr::slice, distr::weighted modules (<a
href="https://redirect.github.com/rust-random/rand/issues/1548">#1548</a>)</li>
<li><a
href="16eb7de94a"><code>16eb7de</code></a>
Add the <code>thread_rng</code> feature flag (<a
href="https://redirect.github.com/rust-random/rand/issues/1547">#1547</a>)</li>
<li><a
href="afa24e49b4"><code>afa24e4</code></a>
Fix test status badges (<a
href="https://redirect.github.com/rust-random/rand/issues/1544">#1544</a>)</li>
<li><a
href="c681dfc345"><code>c681dfc</code></a>
Create FUNDING.yml</li>
<li><a
href="9f05e22afb"><code>9f05e22</code></a>
Update: getrandom v0.3.0 rc.0 (<a
href="https://redirect.github.com/rust-random/rand/issues/1541">#1541</a>)</li>
<li><a
href="88c310b189"><code>88c310b</code></a>
Fix docs.rs build options (<a
href="https://redirect.github.com/rust-random/rand/issues/1539">#1539</a>)</li>
<li><a
href="b879689a60"><code>b879689</code></a>
Adjust GH Actions (<a
href="https://redirect.github.com/rust-random/rand/issues/1538">#1538</a>)</li>
<li><a
href="3fac49fe89"><code>3fac49f</code></a>
Prepare 0.9.0-beta.0 (<a
href="https://redirect.github.com/rust-random/rand/issues/1535">#1535</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/rust-random/rand/compare/0.8.5...0.9.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-02-04 10:21:53 -08:00
zach
1e281e93cd cleanup: remove android release (#823)
Also switches to actions-rust-lang/setup-rust-toolchain@v1 since
actions-rs/toolchain is deprecated
2025-01-28 15:53:13 -08:00
zach
f57d987d48 ci: fix android release, attempt #2 (#818) 2025-01-21 12:08:18 -08:00
zach
9da6d43f05 ci: install android libunwind before build step (#817)
Fixes #816
2025-01-21 11:41:10 -08:00
zach
d1a248e19e cleanup: stop timer from using 100% cpu when no timeouts are set (#814)
Fixes an issue reported on discord
(https://discord.com/channels/1011124058408112148/1154513155209298041/1329622656235864156)
where the timer thread is taking up an entire CPU
2025-01-20 14:01:37 -08:00
dependabot[bot]
7b2db7588b chore(deps): Update cbindgen requirement from 0.27 to 0.28 (#815)
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.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>
</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.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
  * Update MSRV to 1.57
* Support variadic arguments (`...`)
([#805](https://github.com/mozilla/cbindgen/issues/805))
* Add --depfile option
([#820](https://github.com/mozilla/cbindgen/issues/820))
  * Breaking changes: The `Config` struct now has a private member.
</code></pre>
<h2>0.24.6 (YANKED: depfile option was breaking, see <a
href="https://redirect.github.com/mozilla/cbindgen/issues/841">#841</a>)</h2>
<pre><code>  * Update MSRV to 1.57
* Support variadic arguments (`...`)
([#805](https://github.com/mozilla/cbindgen/issues/805))
* Add --depfile option
([#820](https://github.com/mozilla/cbindgen/issues/820))
</code></pre>
<h2>0.24.5</h2>
<pre><code>  * Don't enforce tempfile version.
</code></pre>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="bd78bbe59b"><code>bd78bbe</code></a>
Release 0.28.0</li>
<li><a
href="8ca9c4c20f"><code>8ca9c4c</code></a>
tests: Fix symbol file and tests.</li>
<li><a
href="152f91dee0"><code>152f91d</code></a>
Appease clippy.</li>
<li><a
href="70b9d7a980"><code>70b9d7a</code></a>
tests: Run rustfmt.</li>
<li><a
href="87afbf9e01"><code>87afbf9</code></a>
Add a way to generate a list of symbols for dynamic linkage, resolves <a
href="https://redirect.github.com/mozilla/cbindgen/issues/907">#907</a></li>
<li><a
href="80c50c643a"><code>80c50c6</code></a>
Add <code>unstable_ir</code> feature flag that makes the ir pub</li>
<li><a
href="e82815e99a"><code>e82815e</code></a>
Refactor arm64 build to matrix strategy</li>
<li><a
href="a5e1443a45"><code>a5e1443</code></a>
Add aarch64/arm64 to CI</li>
<li><a
href="b9b8f8878a"><code>b9b8f88</code></a>
Fix clippy warnings</li>
<li><a
href="89a9faa97c"><code>89a9faa</code></a>
newlines for constexpr</li>
<li>Additional commits viewable in <a
href="https://github.com/mozilla/cbindgen/compare/v0.27.0...0.28.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-01-20 14:01:02 -08:00
Benjamin Eckel
a367bc77a3 Add Android targets (#812)
Closes #811

Keep in mind that these targets seem to be tier 3 support and not quite
stable, but i think it's fine to open for experimenting:
https://docs.wasmtime.dev/stability-tiers.html
2025-01-14 10:23:59 -08:00
pwnintended
98800fe8a0 feat: added a function to track fuel consumption (#807)
This PR adds a function to get the fuel consumed by a plugin. This is
useful for systems that want to fairly balance compute. It's my first
time doing anything in rust so let me know if something needs
improvements!

---------

Co-authored-by: zach <zach@dylibso.com>
2024-12-16 16:04:05 -08:00
zach
7e3665ae8c fix: improve sdk error messages around imports (#806)
Closes https://github.com/extism/extism/issues/801


Updates the error from:

```
Unable to compile Extism plugin: incompatible import type for `extism:host/user::wrong_type`
```

to:

```
Unable to compile Extism plugin: types incompatible: expected type `(func (result i32))`, found type `(func (result i64))`
```
2024-12-13 11:45:24 -08:00
Muhammad Azeez
3cfde7966d Trigger NuGet package publishing when a new release is published (#805)
This makes sure the latest releases are always available on NuGet too
2024-12-05 19:44:33 +03:00
zach
5d18cc71eb chore: fix clippy (#799) 2024-12-02 16:46:48 -08:00
Miles Johnson
4f599d4b20 Change function_exists to &self (#796)
I wrap the `Plugin` instance in a `RwLock` and because `function_exists`
requires `&mut self`, I have to acquire a write lock everytime to just
check if a function exists, which causes lock contention.

This changes it to `&self` since it doesn't need to mutate.
2024-12-02 09:46:38 -08:00
Chris Dickinson
4db57de98e fix: remove unwrap() from extism_compiled_plugin_new
`extism_compiled_plugin_new` would panic on bad manifest input (there's
a test in the python sdk [1] that hits this directly.) Catch the error and
set `errmsg` appropriately instead.

Additionally: golf the function pointer casting for the various `plugin_new` calls,
with the goal of reducing the number of allocations by leaning on iterator size
hints (and reducing the scope of `unsafe{}` use.) This part can be severed: it
introduces a breaking change. Previously `NULL` values were silently accepted in
the `functions**` list; now they fail. This maintains consistency with behavior on
"consumed" `ExtismFunction` pointer.

[1]: https://github.com/extism/python-sdk/blob/main/tests/test_extism.py#L50-L53
2024-11-25 13:16:00 -08:00
zach
9134635b37 cleanup: return better errors for wasi command modules (#793) 2024-11-25 08:49:57 -08:00
dependabot[bot]
75428f26e2 chore(deps): Bump dawidd6/action-download-artifact from 2 to 6 in /.github/workflows (#792)
Bumps
[dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact)
from 2 to 6.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/dawidd6/action-download-artifact/releases">dawidd6/action-download-artifact's
releases</a>.</em></p>
<blockquote>
<h2>v6</h2>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/dawidd6/action-download-artifact/compare/v5...v6">https://github.com/dawidd6/action-download-artifact/compare/v5...v6</a></p>
<h2>v5</h2>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/dawidd6/action-download-artifact/compare/v4...v5">https://github.com/dawidd6/action-download-artifact/compare/v4...v5</a></p>
<h2>v4</h2>
<h2>What's Changed</h2>
<ul>
<li><strong>VERSIONING CHANGE</strong>: now there will only be major
releases of this action, e.g. v5, v6 and so on</li>
<li>build(deps): bump undici from 5.28.3 to 5.28.4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/dawidd6/action-download-artifact/pull/284">dawidd6/action-download-artifact#284</a></li>
<li>build(deps): bump <code>@​actions/artifact</code> from 2.1.4 to
2.1.5 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/dawidd6/action-download-artifact/pull/285">dawidd6/action-download-artifact#285</a></li>
<li>build(deps): bump <code>@​actions/artifact</code> from 2.1.5 to
2.1.7 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/dawidd6/action-download-artifact/pull/287">dawidd6/action-download-artifact#287</a></li>
<li>build(deps): bump adm-zip from 0.5.12 to 0.5.13 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/dawidd6/action-download-artifact/pull/289">dawidd6/action-download-artifact#289</a></li>
<li>Set allow_forks to false by default by <a
href="https://github.com/timweri"><code>@​timweri</code></a> in <a
href="https://redirect.github.com/dawidd6/action-download-artifact/pull/290">dawidd6/action-download-artifact#290</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/timweri"><code>@​timweri</code></a> made
their first contribution in <a
href="https://redirect.github.com/dawidd6/action-download-artifact/pull/290">dawidd6/action-download-artifact#290</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/dawidd6/action-download-artifact/compare/v3...v4">https://github.com/dawidd6/action-download-artifact/compare/v3...v4</a></p>
<h2>v3.1.4</h2>
<h2>What's Changed</h2>
<ul>
<li>build(deps): bump adm-zip from 0.5.10 to 0.5.12 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/dawidd6/action-download-artifact/pull/282">dawidd6/action-download-artifact#282</a></li>
<li>build(deps): bump <code>@​actions/artifact</code> from 2.1.2 to
2.1.4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/dawidd6/action-download-artifact/pull/280">dawidd6/action-download-artifact#280</a></li>
<li>fix: accept expired artifacts with documentation url by <a
href="https://github.com/wdconinc"><code>@​wdconinc</code></a> in <a
href="https://redirect.github.com/dawidd6/action-download-artifact/pull/283">dawidd6/action-download-artifact#283</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/wdconinc"><code>@​wdconinc</code></a>
made their first contribution in <a
href="https://redirect.github.com/dawidd6/action-download-artifact/pull/283">dawidd6/action-download-artifact#283</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/dawidd6/action-download-artifact/compare/v3...v3.1.4">https://github.com/dawidd6/action-download-artifact/compare/v3...v3.1.4</a></p>
<h2>v3.1.3</h2>
<h2>What's Changed</h2>
<ul>
<li>node_modules: upgrade by <a
href="https://github.com/dawidd6"><code>@​dawidd6</code></a> in <a
href="https://redirect.github.com/dawidd6/action-download-artifact/pull/276">dawidd6/action-download-artifact#276</a></li>
<li>build(deps): bump <code>@​actions/artifact</code> from 2.1.1 to
2.1.2 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/dawidd6/action-download-artifact/pull/277">dawidd6/action-download-artifact#277</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/dawidd6/action-download-artifact/compare/v3.1.2...v3.1.3">https://github.com/dawidd6/action-download-artifact/compare/v3.1.2...v3.1.3</a></p>
<h2>v3.1.2</h2>
<h2>What's Changed</h2>
<ul>
<li>Read workflow_search input as a boolean by <a
href="https://github.com/klutchell"><code>@​klutchell</code></a> in <a
href="https://redirect.github.com/dawidd6/action-download-artifact/pull/273">dawidd6/action-download-artifact#273</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/klutchell"><code>@​klutchell</code></a>
made their first contribution in <a
href="https://redirect.github.com/dawidd6/action-download-artifact/pull/273">dawidd6/action-download-artifact#273</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/dawidd6/action-download-artifact/compare/v3.1.1...v3.1.2">https://github.com/dawidd6/action-download-artifact/compare/v3.1.1...v3.1.2</a></p>
<h2>v3.1.1</h2>
<h2>What's Changed</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="bf251b5aa9"><code>bf251b5</code></a>
node_modules: upgrade</li>
<li><a
href="93c6296611"><code>93c6296</code></a>
README: v5</li>
<li><a
href="deb3bb8325"><code>deb3bb8</code></a>
node_modules: upgrade</li>
<li><a
href="1d93f37db2"><code>1d93f37</code></a>
README: v4</li>
<li><a
href="854e2de939"><code>854e2de</code></a>
Set allow_forks to false by default (<a
href="https://redirect.github.com/dawidd6/action-download-artifact/issues/290">#290</a>)</li>
<li><a
href="436c9d3774"><code>436c9d3</code></a>
build(deps): bump adm-zip from 0.5.12 to 0.5.13 (<a
href="https://redirect.github.com/dawidd6/action-download-artifact/issues/289">#289</a>)</li>
<li><a
href="14040524bb"><code>1404052</code></a>
build(deps): bump <code>@​actions/artifact</code> from 2.1.5 to 2.1.7
(<a
href="https://redirect.github.com/dawidd6/action-download-artifact/issues/287">#287</a>)</li>
<li><a
href="8a9be734dc"><code>8a9be73</code></a>
build(deps): bump <code>@​actions/artifact</code> from 2.1.4 to 2.1.5
(<a
href="https://redirect.github.com/dawidd6/action-download-artifact/issues/285">#285</a>)</li>
<li><a
href="df593bbd04"><code>df593bb</code></a>
build(deps): bump undici from 5.28.3 to 5.28.4 (<a
href="https://redirect.github.com/dawidd6/action-download-artifact/issues/284">#284</a>)</li>
<li><a
href="09f2f74827"><code>09f2f74</code></a>
fix: accept expired artifacts with documentation url (<a
href="https://redirect.github.com/dawidd6/action-download-artifact/issues/283">#283</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/dawidd6/action-download-artifact/compare/v2...v6">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dawidd6/action-download-artifact&package-manager=github_actions&previous-version=2&new-version=6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

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)
You can disable automated security fix PRs for this repo from the
[Security Alerts page](https://github.com/extism/extism/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-25 07:50:29 -08:00
Steve Manuel
7beeee35f1 feat: add overview on generating bindings (#789)
Primarily adds the Generating Bindings section, but also updates the PDK
and SDK list to include more languages.
2024-11-19 22:48:00 -08:00
Chris Dickinson
6cf8251d90 v1.9.0 2024-11-19 14:11:47 -08:00
zach
4d0799ca37 docs: add comment to fs example (#788)
Since it's using a read-only path, we should provide some information
about how to use a read/write path to make this program run without
failing.
2024-11-15 18:21:29 -08:00
zach
14477ceb39 feat: add CompiledPlugin (#784) 2024-11-14 12:32:22 -08:00
zach
af67a6990c chore: update to wasmtime 26 (#783) 2024-11-07 12:36:58 -08:00
zach
72c47fceaf v1.8.0 2024-10-22 09:39:13 -07:00
Benjamin Eckel
f52969aadb Add codeowners file (#780) 2024-10-22 09:35:04 -07:00
zach
7775c57a81 fix: use f32::to_bits and f64::to_bits when constructing Val (#779)
Fixes #739
2024-10-22 09:33:04 -07:00
zach
7b6664d019 feat: add ability to access response headers when using extism:host/env::http_request (#774)
- Adds `extism:host/env::http_headers` to access HTTP response headers
- Adds `PluginBuilder::with_http_response_headers` to enable response
headers from Rust
- Adds `extism_plugin_allow_http_response_headers` to enable response
headers using the C API

TODO:
- [x] Update a PDK to use `extism:host/env::http_headers` so I can test
this
2024-10-14 10:09:28 -07:00
Gavin Hayes
a91846a34b fix(plugin_call): set rc to EXIT_SIGNALED_SIGABRT when wasmtime bails out on plugin call (#776)
Fixes #775

134/EXIT_SIGNALED_SIGABRT was chosen as the wasmtime CLI exits with it
executes `unreachable`.
2024-10-10 15:03:59 -04:00
Han Yang
876a3be147 Fix: no method named free found for mutable reference &mut current_plugin::CurrentPlugin in the current scope (#773)
think there's a typo for this block when the `http` feature is disabled
2024-10-04 07:48:29 -07:00
zach
520d72e408 v1.7.0 2024-09-24 10:17:01 -07:00
zach
3ac3d9abcf cleanup: host takes ownership of memory blocks it gets as arguments (#743)
This PR changes the host to take ownership of memory blocks passed into
Extism host functions, see:
https://github.com/extism/js-sdk/pull/71#issuecomment-2233687933
2024-09-23 10:42:45 -07:00
SebastianHambura
8222164eca Adds more details about with_wasmtime_config() limitations (#770)
PR for https://github.com/extism/extism/pull/764#issuecomment-2368337805

---------

Co-authored-by: Sebastian Hambura <sebastian.hambur@embl.de>
2024-09-23 08:04:29 -07:00
zach
fa81270a5f cleanup(kernel): only try to re-use free blocks before memory.grow (#765)
Updates the kernel to only scan for free blocks before a `memory.grow`
operation, this should improve performance of `alloc` by not iterating
over every block for each allocation.
2024-09-19 15:55:02 -07:00
zach
7bf41c2c7f feat: add PluginBuilder::with_wasmtime_config (#764)
An alternative to #763, this PR allows an initial `wasmtime::Config` to
be passed in when building a plugin. Some of these values may be
overwritten by the Extism runtime, but it allows for things like static
memory size and other low-level details to be handled directly instead
of us having to wrap every option ourselves.
2024-09-19 11:27:27 -07:00
Fabien Benetou
d3a68e2c0c chore: define pdk term in README (#766)
from https://extism.org/docs/concepts/pdk
2024-09-19 08:53:18 -06:00
44 changed files with 1521 additions and 454 deletions

1
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1 @@
* @zshipko

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

@@ -1,4 +1,4 @@
on:
on:
pull_request:
paths:
- .github/actions/extism/**
@@ -52,7 +52,7 @@ jobs:
shell: bash
run: cargo build --release -p ${{ env.LIBEXTISM_CRATE }}
- name: Upload artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: libextism-${{ matrix.os }}
path: |
@@ -116,4 +116,9 @@ jobs:
path: target/**
key: ${{ runner.os }}-target-${{ github.sha }}
- run: cargo install cargo-criterion
- run: cargo criterion
- run:
cargo criterion
- run: |
git fetch
git checkout main
cargo criterion

View File

@@ -1,4 +1,6 @@
on:
release:
types: [published, edited]
workflow_dispatch:
name: Release .NET Native NuGet Packages
@@ -18,10 +20,13 @@ jobs:
uses: actions/setup-dotnet@v3.0.3
with:
dotnet-version: 7.x
- uses: dawidd6/action-download-artifact@v2
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

@@ -7,11 +7,6 @@ on:
name: Release
env:
RUNTIME_MANIFEST: runtime/Cargo.toml
RUNTIME_CRATE: libextism
RUSTFLAGS: -C target-feature=-crt-static
ARTIFACT_DIR: release-artifacts
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -21,6 +16,11 @@ jobs:
release:
name: ${{ matrix.os }} ${{ matrix.target }}
runs-on: ${{ matrix.os }}-latest
env:
RUNTIME_MANIFEST: runtime/Cargo.toml
RUNTIME_CRATE: libextism
RUSTFLAGS: -C target-feature=-crt-static
ARTIFACT_DIR: release-artifacts-${{ matrix.os }}-${{ matrix.target }}
strategy:
matrix:
include:
@@ -98,13 +98,11 @@ jobs:
pyproject="$(cat extism-maturin/pyproject.toml)"
<<<"$pyproject" >extism-maturin/pyproject.toml sed -e 's/^version = "0.0.0.replaced-by-ci"/version = "'"$version"'"/g'
- name: Install Rust
uses: actions-rs/toolchain@v1
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
target: ${{ matrix.target }}
override: true
toolchain: stable
- uses: Swatinem/rust-cache@v2
with:
@@ -113,11 +111,15 @@ jobs:
cache-on-failure: "true"
- name: Build Target (${{ matrix.os }} ${{ matrix.target }})
uses: actions-rs/cargo@v1
with:
use-cross: ${{ matrix.os != 'windows' }}
command: build
args: --release --target ${{ matrix.target }} -p ${{ env.RUNTIME_CRATE }}
if: ${{ matrix.os != 'windows' }}
run: |
cargo install cross
cross build --release --target ${{ matrix.target }} -p ${{ env.RUNTIME_CRATE }}
- name: Build Target (${{ matrix.os }} ${{ matrix.target }})
if: ${{ matrix.os == 'windows' }}
run: |
cargo build --release --target ${{ matrix.target }} -p ${{ env.RUNTIME_CRATE }}
- uses: actions/setup-python@v4
with:
@@ -190,7 +192,7 @@ jobs:
ls -ll ${DEST_DIR}
- name: Upload Artifact to Summary
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ env.ARTIFACT_DIR }}
path: ${{ env.ARTIFACT_DIR }}
@@ -209,9 +211,10 @@ jobs:
needs: [release]
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: ${{ env.ARTIFACT_DIR }}
pattern: release-artifacts-*
merge-multiple: true
- uses: "marvinpinto/action-automatic-releases@latest"
with:

View File

@@ -36,6 +36,22 @@ function linking, and more. Extism users build:
- web applications
- & much more...
# Supported Targets
We currently provide releases for the following targets:
- aarch64-apple-darwin
- aarch64-unknown-linux-gnu
- aarch64-unknown-linux-musl
- x86_64-apple-darwin
- x86_64-pc-windows-gnu
- x86_64-pc-windows-msvc
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
For Android we suggest taking a look at the [Chicory SDK](https://github.com/extism/chicory-sdk) for a pure Java
Extism runtime.
# Run WebAssembly In Your App
Pick a SDK to import into your program, and refer to the documentation to get
@@ -61,11 +77,11 @@ started:
# Compile WebAssembly to run in Extism Hosts
Extism Hosts (running the SDK) must execute WebAssembly code that has a PDK
library compiled in to the `.wasm` binary. PDKs make it easy for plug-in /
extension code authors to read input from the host and return data back, read
provided configuration, set/get variables, make outbound HTTP calls if allowed,
and more.
Extism Hosts (running the SDK) must execute WebAssembly code that has a
[PDK, or Plug-in Development Kit](https://extism.org/docs/concepts/pdk), library
compiled in to the `.wasm` binary. PDKs make it easy for plug-in / extension
code authors to read input from the host and return data back, read provided
configuration, set/get variables, make outbound HTTP calls if allowed, and more.
Pick a PDK to import into your Wasm program, and refer to the documentation to
get started:
@@ -74,13 +90,73 @@ get started:
| ------------------ | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | --------------------------------------------------------- |
| Rust PDK | <img alt="Rust PDK" src="https://extism.org/img/sdk-languages/rust.svg" width="50px"/> | https://github.com/extism/rust-pdk | [Crates.io](https://crates.io/crates/extism-pdk) |
| JS PDK | <img alt="JS PDK" src="https://extism.org/img/sdk-languages/js.svg" width="50px"/> | https://github.com/extism/js-pdk | N/A |
| Python PDK | <img alt="Python PDK" src="https://extism.org/img/sdk-languages/python.svg" width="50px"/> | https://github.com/extism/python-pdk | N/A |
| Go PDK | <img alt="Go PDK" src="https://extism.org/img/sdk-languages/go.svg" width="50px"/> | https://github.com/extism/go-pdk | [Go mod](https://pkg.go.dev/github.com/extism/go-pdk) |
| Haskell PDK | <img alt="Haskell PDK" src="https://extism.org/img/sdk-languages/haskell.svg" width="50px"/> | https://github.com/extism/haskell-pdk | [Hackage](https://hackage.haskell.org/package/extism-pdk) |
| AssemblyScript PDK | <img alt="AssemblyScript PDK" src="https://extism.org/img/sdk-languages/assemblyscript.svg" width="50px"/> | https://github.com/extism/assemblyscript-pdk | [NPM](https://www.npmjs.com/package/@extism/as-pdk) |
| .NET PDK | <img alt=".NET PDK" src="https://extism.org/img/sdk-languages/dotnet.svg" width="50px"/> | https://github.com/extism/dotnet-pdk <br/>(supports C# & F#!) | https://www.nuget.org/packages/Extism.Pdk |
| .NET PDK | <img alt=".NET PDK" src="https://extism.org/img/sdk-languages/dotnet.svg" width="50px"/> | https://github.com/extism/dotnet-pdk <br/>(supports C# & F#!) | [Nuget](https://www.nuget.org/packages/Extism.Pdk) |
| C PDK | <img alt="C PDK" src="https://extism.org/img/sdk-languages/c.svg" width="50px"/> | https://github.com/extism/c-pdk | N/A |
| C++ PDK | <img alt="C++ PDK" src="https://extism.org/img/sdk-languages/cpp.svg" width="50px"/> | https://github.com/extism/cpp-pdk | N/A |
| Zig PDK | <img alt="Zig PDK" src="https://extism.org/img/sdk-languages/zig.svg" width="50px"/> | https://github.com/extism/zig-pdk | N/A |
# Generating Bindings
It's often very useful to define a schema to describe the function signatures
and types you want to use between Extism SDK and PDK languages.
[XTP Bindgen](https://github.com/dylibso/xtp-bindgen) is an open source
framework to generate PDK bindings for Extism plug-ins. It's used by the
[XTP Platform](https://www.getxtp.com/), but can be used outside of the platform
to define any Extism compatible plug-in system.
## 1. Install the `xtp` CLI.
See installation instructions
[here](https://docs.xtp.dylibso.com/docs/cli#installation).
## 2. Create a schema using our OpenAPI-inspired IDL:
```yaml
version: v1-draft
exports:
CountVowels:
input:
type: string
contentType: text/plain; charset=utf-8
output:
$ref: "#/components/schemas/VowelReport"
contentType: application/json
# components.schemas defined in example-schema.yaml...
```
> See an example in [example-schema.yaml](./example-schema.yaml), or a full
> "kitchen sink" example on
> [the docs page](https://docs.xtp.dylibso.com/docs/concepts/xtp-schema/).
## 3. Generate bindings to use from your plugins:
```
xtp plugin init --schema-file ./example-schema.yaml
> 1. TypeScript
2. Go
3. Rust
4. Python
5. C#
6. Zig
7. C++
8. GitHub Template
9. Local Template
```
This will create an entire boilerplate plugin project for you to get started
with. Implement the empty function(s), and run `xtp plugin build` to compile
your plugin.
> For more information about XTP Bindgen, see the
> [dylibso/xtp-bindgen](https://github.com/dylibso/xtp-bindgen) repository and
> the official
> [XTP Schema documentation](https://docs.xtp.dylibso.com/docs/concepts/xtp-schema).
# Support
## Discord

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

@@ -55,7 +55,7 @@ encoding!(pub Json, serde_json::to_vec, serde_json::from_slice);
#[cfg(feature = "msgpack")]
encoding!(pub Msgpack, rmp_serde::to_vec, rmp_serde::from_slice);
impl<'a> ToBytes<'a> for serde_json::Value {
impl ToBytes<'_> for serde_json::Value {
type Bytes = Vec<u8>;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
@@ -85,7 +85,7 @@ impl<T: AsRef<[u8]>> From<T> for Base64<T> {
}
}
impl<'a, T: AsRef<[u8]>> ToBytes<'a> for Base64<T> {
impl<T: AsRef<[u8]>> ToBytes<'_> for Base64<T> {
type Bytes = String;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
@@ -124,7 +124,7 @@ impl<T: prost::Message> From<T> for Prost<T> {
}
#[cfg(feature = "prost")]
impl<'a, T: prost::Message> ToBytes<'a> for Prost<T> {
impl<T: prost::Message> ToBytes<'_> for Prost<T> {
type Bytes = Vec<u8>;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
@@ -146,7 +146,7 @@ impl<T: Default + prost::Message> FromBytesOwned for Prost<T> {
pub struct Protobuf<T: protobuf::Message>(pub T);
#[cfg(feature = "protobuf")]
impl<'a, T: protobuf::Message> ToBytes<'a> for Protobuf<T> {
impl<T: protobuf::Message> ToBytes<'_> for Protobuf<T> {
type Bytes = Vec<u8>;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {

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,8 +61,19 @@ 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 tests {
mod raw_tests {
use crate::*;
#[test]

View File

@@ -61,21 +61,21 @@ pub trait ToBytes<'a> {
fn to_bytes(&self) -> Result<Self::Bytes, Error>;
}
impl<'a> ToBytes<'a> for () {
impl ToBytes<'_> for () {
type Bytes = [u8; 0];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok([])
}
}
impl<'a> ToBytes<'a> for Vec<u8> {
impl ToBytes<'_> for Vec<u8> {
type Bytes = Vec<u8>;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self.clone())
}
}
impl<'a> ToBytes<'a> for String {
impl ToBytes<'_> for String {
type Bytes = String;
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
Ok(self.clone())
@@ -96,7 +96,7 @@ impl<'a> ToBytes<'a> for &'a str {
}
}
impl<'a> ToBytes<'a> for f64 {
impl ToBytes<'_> for f64 {
type Bytes = [u8; 8];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
@@ -104,7 +104,7 @@ impl<'a> ToBytes<'a> for f64 {
}
}
impl<'a> ToBytes<'a> for f32 {
impl ToBytes<'_> for f32 {
type Bytes = [u8; 4];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
@@ -112,7 +112,7 @@ impl<'a> ToBytes<'a> for f32 {
}
}
impl<'a> ToBytes<'a> for i64 {
impl ToBytes<'_> for i64 {
type Bytes = [u8; 8];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
@@ -120,7 +120,7 @@ impl<'a> ToBytes<'a> for i64 {
}
}
impl<'a> ToBytes<'a> for i32 {
impl ToBytes<'_> for i32 {
type Bytes = [u8; 4];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
@@ -128,7 +128,7 @@ impl<'a> ToBytes<'a> for i32 {
}
}
impl<'a> ToBytes<'a> for u64 {
impl ToBytes<'_> for u64 {
type Bytes = [u8; 8];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
@@ -136,7 +136,7 @@ impl<'a> ToBytes<'a> for u64 {
}
}
impl<'a> ToBytes<'a> for u32 {
impl ToBytes<'_> for u32 {
type Bytes = [u8; 4];
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
@@ -144,6 +144,14 @@ impl<'a> ToBytes<'a> 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;

28
example-schema.yaml Normal file
View File

@@ -0,0 +1,28 @@
# yaml-language-server: $schema=https://xtp.dylibso.com/assets/wasm/schema.json
# Learn more at https://docs.xtp.dylibso.com/docs/concepts/xtp-schema
version: v1-draft
exports:
CountVowels:
input:
type: string
contentType: text/plain; charset=utf-8
output:
$ref: "#/components/schemas/VowelReport"
contentType: application/json
components:
schemas:
VowelReport:
description: The result of counting vowels on the Vowels input.
properties:
count:
type: integer
format: int32
description: The count of vowels for input string.
total:
type: integer
format: int32
description: The cumulative amount of vowels counted, if this keeps state across multiple function calls.
nullable: true
vowels:
type: string
description: The set of vowels used to get the count, e.g. "aAeEiIoOuU"

View File

@@ -255,15 +255,6 @@ impl MemoryRoot {
pub unsafe fn alloc(&mut self, length: u64) -> Option<&'static mut MemoryBlock> {
let self_position = self.position.load(Ordering::Acquire);
let self_length = self.length.load(Ordering::Acquire);
let b = self.find_free_block(length, self_position);
// If there's a free block then re-use it
if let Some(b) = b {
b.used = length as usize;
b.status
.store(MemoryStatus::Active as u8, Ordering::Release);
return Some(b);
}
// Get the current index for a new block
let curr = self.blocks.as_ptr() as u64 + self_position;
@@ -275,6 +266,21 @@ impl MemoryRoot {
// When the allocation is larger than the number of bytes available
// we will need to try to grow the memory
if length_with_block >= mem_left {
// If the current position is large enough to hold the length of the block being
// allocated then check for existing free blocks that can be re-used before
// growing memory
if length_with_block <= self_position {
let b = self.find_free_block(length, self_position);
// If there's a free block then re-use it
if let Some(b) = b {
b.used = length as usize;
b.status
.store(MemoryStatus::Active as u8, Ordering::Release);
return Some(b);
}
}
// Calculate the number of pages needed to cover the remaining bytes
let npages = num_pages(length_with_block - mem_left);
let x = core::arch::wasm32::memory_grow(0, npages);
@@ -489,6 +495,8 @@ pub unsafe fn store_u64(p: Pointer, x: u64) {
/// Set the range of the input data in memory
/// h must always be a handle so that length works on it
/// len must match length(handle)
/// **Note**: this function takes ownership of the handle passed in
/// the caller should not `free` this value
#[no_mangle]
pub unsafe fn input_set(h: Handle, len: u64) {
let root = MemoryRoot::new();
@@ -503,6 +511,8 @@ pub unsafe fn input_set(h: Handle, len: u64) {
}
/// Set the range of the output data in memory
/// **Note**: this function takes ownership of the handle passed in
/// the caller should not `free` this value
#[no_mangle]
pub unsafe fn output_set(p: Pointer, len: u64) {
let root = MemoryRoot::new();
@@ -548,6 +558,8 @@ pub unsafe fn reset() {
/// Set the error message offset, the handle passed to this
/// function should not be freed after this call
/// **Note**: this function takes ownership of the handle passed in
/// the caller should not `free` this value
#[no_mangle]
pub unsafe fn error_set(h: Handle) {
let root = MemoryRoot::new();
@@ -582,45 +594,6 @@ mod test {
use crate::*;
use wasm_bindgen_test::*;
// See https://github.com/extism/extism/pull/659
#[wasm_bindgen_test]
fn test_659() {
unsafe {
// Warning: These offsets will need to change if we adjust the kernel memory layout at all
reset();
assert_eq!(alloc(1065), 77);
assert_eq!(alloc(288), 1154);
assert_eq!(alloc(128), 1454);
assert_eq!(length(1154), 288);
assert_eq!(length(1454), 128);
free(1454);
assert_eq!(alloc(213), 1594);
length_unsafe(1594);
assert_eq!(alloc(511), 1819);
assert_eq!(alloc(4), 1454);
assert_eq!(length(1454), 4);
assert_eq!(length(1819), 511);
assert_eq!(alloc(13), 2342);
assert_eq!(length(2342), 13);
assert_eq!(alloc(336), 2367);
assert_eq!(alloc(1077), 2715);
assert_eq!(length(2367), 336);
assert_eq!(length(2715), 1077);
free(2715);
assert_eq!(alloc(1094), 3804);
length_unsafe(3804);
// Allocate 4 bytes, expect to receive address 3788
assert_eq!(alloc(4), 3788);
assert_eq!(alloc(4), 3772);
assert_eq!(length(3772), 4);
// Address 3788 has not been freed yet, so expect it to have 4 bytes allocated
assert_eq!(length(3788), 4);
}
}
#[wasm_bindgen_test]
fn test_oom() {
let size = 1024 * 1024 * 5;

View File

@@ -269,8 +269,8 @@ pub struct Manifest {
/// Config values are made accessible using the PDK `extism_config_get` function
#[serde(default)]
pub config: BTreeMap<String, String>,
#[serde(default)]
#[serde(default)]
/// Specifies which hosts may be accessed via HTTP, if this is empty then
/// no hosts may be accessed. Wildcards may be used.
pub allowed_hosts: Option<Vec<String>>,
@@ -417,14 +417,14 @@ mod wasmdata {
}
}
impl<'a> From<Manifest> for std::borrow::Cow<'a, [u8]> {
impl From<Manifest> for std::borrow::Cow<'_, [u8]> {
fn from(m: Manifest) -> Self {
let s = serde_json::to_vec(&m).unwrap();
std::borrow::Cow::Owned(s)
}
}
impl<'a> From<&Manifest> for std::borrow::Cow<'a, [u8]> {
impl From<&Manifest> for std::borrow::Cow<'_, [u8]> {
fn from(m: &Manifest) -> Self {
let s = serde_json::to_vec(&m).unwrap();
std::borrow::Cow::Owned(s)

View File

@@ -9,37 +9,56 @@ repository.workspace = true
version.workspace = true
[dependencies]
wasmtime = ">= 20.0.0, < 24.0.0"
wasi-common = ">= 20.0.0, < 24.0.0"
wiggle = ">= 20.0.0, < 24.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 = "2.5", 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.27", default-features = false }
cbindgen = { version = "0.29", default-features = false }
[dev-dependencies]
criterion = "0.5.1"
criterion = "0.7.0"
quickcheck = "1"
rand = "0.8.5"
rand = "0.9.0"
[[bench]]
name = "bench"

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

@@ -6,6 +6,7 @@ const COUNT_VOWELS: &[u8] = include_bytes!("../../wasm/code.wasm");
const REFLECT: &[u8] = include_bytes!("../../wasm/reflect.wasm");
const ECHO: &[u8] = include_bytes!("../../wasm/echo.wasm");
const CONSUME: &[u8] = include_bytes!("../../wasm/consume.wasm");
const ALLOCATIONS: &[u8] = include_bytes!("../../wasm/allocations.wasm");
host_fn!(hello_world (a: String) -> String { Ok(a) });
@@ -35,6 +36,31 @@ pub fn create_plugin(c: &mut Criterion) {
});
}
pub fn create_compiled(c: &mut Criterion) {
let mut g = c.benchmark_group("create");
g.noise_threshold(1.0);
g.significance_level(0.2);
g.bench_function("create_compiled", |b| {
b.iter(|| {
let plugin = PluginBuilder::new(COUNT_VOWELS).with_wasi(true);
let _compiled = CompiledPlugin::new(plugin).unwrap();
})
});
}
pub fn create_plugin_compiled(c: &mut Criterion) {
let mut g = c.benchmark_group("create");
g.noise_threshold(1.0);
g.significance_level(0.2);
let plugin = PluginBuilder::new(COUNT_VOWELS).with_wasi(true);
let compiled = CompiledPlugin::new(plugin).unwrap();
g.bench_function("create_plugin_compiled", |b| {
b.iter(|| {
let _plugin = Plugin::new_from_compiled(&compiled).unwrap();
})
});
}
pub fn create_plugin_no_cache(c: &mut Criterion) {
let mut g = c.benchmark_group("create");
g.noise_threshold(1.0);
@@ -168,6 +194,17 @@ pub fn reflect(c: &mut Criterion) {
}
}
pub fn allocations(c: &mut Criterion) {
let mut g = c.benchmark_group("allocations");
let mut plugin = PluginBuilder::new(ALLOCATIONS).build().unwrap();
g.bench_function("allocations", |b| {
b.iter(|| {
plugin.call::<_, ()>("allocations", "").unwrap();
})
});
}
// This is an apples-to-apples comparison of a linked wasm "reflect" function to our host "reflect"
// function.
pub fn reflect_linked(c: &mut Criterion) {
@@ -260,13 +297,16 @@ pub fn reflect_linked(c: &mut Criterion) {
criterion_group!(
benches,
allocations,
consume,
echo,
reflect,
reflect_linked,
basic,
create_plugin,
create_plugin_compiled,
create_plugin_no_cache,
create_compiled,
count_vowels
);
criterion_main!(benches);

View File

@@ -24,6 +24,7 @@ fn main() {
.rename_item("CurrentPlugin", "ExtismCurrentPlugin")
.rename_item("CancelHandle", "ExtismCancelHandle")
.rename_item("Plugin", "ExtismPlugin")
.rename_item("CompiledPlugin", "ExtismCompiledPlugin")
.rename_item("Function", "ExtismFunction")
.with_style(cbindgen::Style::Type)
.generate()

View File

@@ -2,6 +2,8 @@ use extism::*;
fn main() {
let url = Wasm::file("../wasm/read_write.wasm");
let manifest = Manifest::new([url])
// This will fail because we're using a readonly path (specified with the `ro:` prefix)
// to overwrite the data file, remove `ro:` from the path on the following line
.with_allowed_path("ro:src/tests/data".to_string(), "/data")
.with_config_key("path", "/data/data.txt");
@@ -14,10 +16,11 @@ fn main() {
let res = plugin.call::<&str, &str>("try_read", "").unwrap();
println!("{:?}", res);
println!("{res:?}");
println!("-----------------------------------------------------");
// If the allowed path is readonly then writing back to the file should fail
println!("trying to write file: ");
let line = format!(
"Hello World at {:?}\n",
@@ -27,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

@@ -52,6 +52,8 @@ typedef enum {
*/
typedef struct ExtismCancelHandle ExtismCancelHandle;
typedef struct ExtismCompiledPlugin ExtismCompiledPlugin;
/**
* CurrentPlugin stores data that is available to the caller in PDK functions, this should
* only be accessed from inside a host function
@@ -154,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.
*
@@ -179,6 +181,32 @@ void extism_function_free(ExtismFunction *f);
*/
void extism_function_set_namespace(ExtismFunction *ptr, const char *namespace_);
/**
* Pre-compile an Extism plugin
*/
ExtismCompiledPlugin *extism_compiled_plugin_new(const uint8_t *wasm,
ExtismSize wasm_size,
const ExtismFunction **functions,
ExtismSize n_functions,
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`
*/
void extism_compiled_plugin_free(ExtismCompiledPlugin *plugin);
/**
* Create a new plugin with host functions, the functions passed to this function no longer need to be manually freed using
*
@@ -195,6 +223,11 @@ ExtismPlugin *extism_plugin_new(const uint8_t *wasm,
bool with_wasi,
char **errmsg);
/**
* Create a new plugin from an `ExtismCompiledPlugin`
*/
ExtismPlugin *extism_plugin_new_from_compiled(const ExtismCompiledPlugin *compiled, char **errmsg);
/**
* Create a new plugin and set the number of instructions a plugin is allowed to execute
*/
@@ -206,13 +239,18 @@ ExtismPlugin *extism_plugin_new_with_fuel_limit(const uint8_t *wasm,
uint64_t fuel_limit,
char **errmsg);
/**
* Enable HTTP response headers in plugins using `extism:host/env::http_request`
*/
void extism_plugin_allow_http_response_headers(ExtismPlugin *plugin);
/**
* Free the error returned by `extism_plugin_new`, errors returned from `extism_plugin_error` don't need to be freed
*/
void extism_plugin_new_error_free(char *err);
/**
* Remove a plugin from the registry and free associated memory
* Free `ExtismPlugin`
*/
void extism_plugin_free(ExtismPlugin *plugin);

View File

@@ -14,6 +14,7 @@ pub struct CurrentPlugin {
pub(crate) linker: *mut wasmtime::Linker<CurrentPlugin>,
pub(crate) wasi: Option<Wasi>,
pub(crate) http_status: u16,
pub(crate) http_headers: Option<std::collections::BTreeMap<String, String>>,
pub(crate) available_pages: Option<u32>,
pub(crate) memory_limiter: Option<MemoryLimiter>,
pub(crate) id: uuid::Uuid,
@@ -55,7 +56,12 @@ impl wasmtime::ResourceLimiter for MemoryLimiter {
Ok(true)
}
fn table_growing(&mut self, _current: u32, desired: u32, maximum: Option<u32>) -> Result<bool> {
fn table_growing(
&mut self,
_current: usize,
desired: usize,
maximum: Option<usize>,
) -> Result<bool> {
if let Some(max) = maximum {
return Ok(desired <= max);
}
@@ -200,15 +206,16 @@ impl CurrentPlugin {
anyhow::bail!("expected extism_context to be an externref value",)
};
match xs
.data_mut(&mut *store)?
.downcast_mut::<Box<dyn std::any::Any + Send + Sync>>()
{
Some(xs) => match xs.downcast_mut::<T>() {
Some(xs) => Ok(xs),
None => anyhow::bail!("could not downcast extism_context inner value"),
},
None => anyhow::bail!("could not downcast extism_context"),
if let Some(d) = xs.data_mut(&mut *store)? {
match d.downcast_mut::<Box<dyn std::any::Any + Send + Sync>>() {
Some(xs) => match xs.downcast_mut::<T>() {
Some(xs) => Ok(xs),
None => anyhow::bail!("could not downcast extism_context inner value"),
},
None => anyhow::bail!("could not downcast extism_context"),
}
} else {
anyhow::bail!("extism_context not found")
}
}
@@ -332,6 +339,7 @@ impl CurrentPlugin {
manifest: extism_manifest::Manifest,
wasi: bool,
available_pages: Option<u32>,
allow_http_response_headers: bool,
id: uuid::Uuid,
) -> Result<Self, Error> {
let wasi = if wasi {
@@ -394,6 +402,11 @@ impl CurrentPlugin {
memory_limiter,
id,
start_time: std::time::Instant::now(),
http_headers: if allow_http_response_headers {
Some(BTreeMap::new())
} else {
None
},
})
}
@@ -464,10 +477,7 @@ impl CurrentPlugin {
offset: offs,
length,
});
match s {
Ok(s) => Some(s),
Err(_) => None,
}
s.ok()
}
#[doc(hidden)]

BIN
runtime/src/extism-runtime.wasm Executable file → Normal file

Binary file not shown.

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;
@@ -39,8 +40,11 @@ pub use current_plugin::CurrentPlugin;
pub use extism_convert::{FromBytes, FromBytesOwned, ToBytes};
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::{
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};
@@ -94,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
}
@@ -86,14 +86,16 @@ fn to_module(engine: &Engine, wasm: &extism_manifest::Wasm) -> Result<(String, M
#[cfg(feature = "register-http")]
{
// Setup request
let mut req = ureq::request(method.as_deref().unwrap_or("GET"), url);
let mut req = ureq::http::request::Builder::new()
.method(method.as_deref().unwrap_or("GET").to_uppercase().as_str())
.uri(url);
for (k, v) in headers.iter() {
req = req.set(k, v);
req = req.header(k, v);
}
// Fetch WASM code
let mut r = req.call()?.into_reader();
let mut r = ureq::run(req.body(())?)?.into_body().into_reader();
let mut data = Vec::new();
r.read_to_end(&mut data)?;

View File

@@ -20,6 +20,8 @@ macro_rules! args {
/// Get a configuration value
/// Params: i64 (offset)
/// Returns: i64 (offset)
/// **Note**: this function takes ownership of the handle passed in
/// the caller should not `free` this value
pub(crate) fn config_get(
mut caller: Caller<CurrentPlugin>,
input: &[Val],
@@ -38,6 +40,7 @@ pub(crate) fn config_get(
};
let val = data.manifest.config.get(key);
let ptr = val.map(|x| (x.len(), x.as_ptr()));
data.memory_free(handle)?;
let mem = match ptr {
Some((len, ptr)) => {
let bytes = unsafe { std::slice::from_raw_parts(ptr, len) };
@@ -55,6 +58,9 @@ pub(crate) fn config_get(
/// Get a variable
/// Params: i64 (offset)
/// Returns: i64 (offset)
/// **Note**: this function takes ownership of the handle passed in
/// the caller should not `free` this value, but the return value
/// will need to be freed
pub(crate) fn var_get(
mut caller: Caller<CurrentPlugin>,
input: &[Val],
@@ -73,6 +79,8 @@ pub(crate) fn var_get(
};
let val = data.vars.get(key);
let ptr = val.map(|x| (x.len(), x.as_ptr()));
data.memory_free(handle)?;
let mem = match ptr {
Some((len, ptr)) => {
let bytes = unsafe { std::slice::from_raw_parts(ptr, len) };
@@ -90,6 +98,8 @@ pub(crate) fn var_get(
/// Set a variable, if the value offset is 0 then the provided key will be removed
/// Params: i64 (key offset), i64 (value offset)
/// Returns: none
/// **Note**: this function takes ownership of the handles passed in
/// the caller should not `free` these values
pub(crate) fn var_set(
mut caller: Caller<CurrentPlugin>,
input: &[Val],
@@ -104,12 +114,12 @@ pub(crate) fn var_set(
let voffset = args!(input, 1, i64) as u64;
let key_offs = args!(input, 0, i64) as u64;
let key_handle = match data.memory_handle(key_offs) {
Some(h) => h,
None => anyhow::bail!("invalid handle offset for var key: {key_offs}"),
};
let key = {
let handle = match data.memory_handle(key_offs) {
Some(h) => h,
None => anyhow::bail!("invalid handle offset for var key: {key_offs}"),
};
let key = data.memory_str(handle)?;
let key = data.memory_str(key_handle)?;
let key_len = key.len();
let key_ptr = key.as_ptr();
unsafe { std::str::from_utf8_unchecked(std::slice::from_raw_parts(key_ptr, key_len)) }
@@ -118,6 +128,7 @@ pub(crate) fn var_set(
// Remove if the value offset is 0
if voffset == 0 {
data.vars.remove(key);
data.memory_free(key_handle)?;
return Ok(());
}
@@ -144,6 +155,9 @@ pub(crate) fn var_set(
let value = data.memory_bytes(handle)?.to_vec();
data.memory_free(handle)?;
data.memory_free(key_handle)?;
// Insert the value from memory into the `vars` map
data.vars.insert(key.to_string(), value);
@@ -153,6 +167,9 @@ pub(crate) fn var_set(
/// Make an HTTP request
/// Params: i64 (offset to JSON encoded HttpRequest), i64 (offset to body or 0)
/// Returns: i64 (offset)
/// **Note**: this function takes ownership of the handles passed in
/// the caller should not `free` these values, the result will need to
/// be freed.
pub(crate) fn http_request(
#[allow(unused_mut)] mut caller: Caller<CurrentPlugin>,
input: &[Val],
@@ -166,6 +183,7 @@ pub(crate) fn http_request(
Some(h) => h,
None => anyhow::bail!("http_request input is invalid: {http_req_offset}"),
};
data.memory_free(handle)?;
let req: extism_manifest::HttpRequest = serde_json::from_slice(data.memory_bytes(handle)?)?;
output[0] = Val::I64(0);
anyhow::bail!(
@@ -176,12 +194,16 @@ pub(crate) fn http_request(
#[cfg(feature = "http")]
{
data.http_headers.iter_mut().for_each(|x| x.clear());
data.http_status = 0;
use std::io::Read;
let handle = match data.memory_handle(http_req_offset) {
Some(h) => h,
None => anyhow::bail!("invalid handle offset for http request: {http_req_offset}"),
};
let req: extism_manifest::HttpRequest = serde_json::from_slice(data.memory_bytes(handle)?)?;
data.memory_free(handle)?;
let body_offset = args!(input, 1, i64) as u64;
@@ -211,17 +233,22 @@ pub(crate) fn http_request(
)));
}
let mut r = ureq::request(req.method.as_deref().unwrap_or("GET"), &req.url);
let mut r = ureq::http::request::Builder::new()
.method(
req.method
.as_deref()
.unwrap_or("GET")
.to_uppercase()
.as_str(),
)
.uri(&req.url);
for (k, v) in req.headers.iter() {
r = r.set(k, v);
r = r.header(k, v);
}
// Set HTTP timeout to respect the manifest timeout
if let Some(remaining) = data.time_remaining() {
r = r.timeout(remaining);
}
let timeout = data.time_remaining();
let res = if body_offset > 0 {
let handle = match data.memory_handle(body_offset) {
Some(h) => h,
@@ -230,27 +257,48 @@ pub(crate) fn http_request(
}
};
let buf: &[u8] = data.memory_bytes(handle)?;
r.send_bytes(buf)
let agent = ureq::agent();
let config = agent
.configure_request(r.body(buf)?)
.http_status_as_error(false);
let req = config.timeout_global(timeout).build();
ureq::run(req)
} else {
r.call()
let agent = ureq::agent();
let config = agent
.configure_request(r.body(())?)
.http_status_as_error(false);
let req = config.timeout_global(timeout).build();
ureq::run(req)
};
if let Some(handle) = data.memory_handle(body_offset) {
data.memory_free(handle)?;
}
let reader = match res {
Ok(res) => {
data.http_status = res.status();
Some(res.into_reader())
if let Some(headers) = &mut data.http_headers {
for (name, h) in res.headers() {
if let Ok(h) = h.to_str() {
headers.insert(name.as_str().to_string(), h.to_string());
}
}
}
data.http_status = res.status().as_u16();
Some(res.into_body().into_reader())
}
Err(e) => {
// Catch timeout and return
if let Some(d) = data.time_remaining() {
if e.kind() == ureq::ErrorKind::Io && d.as_nanos() == 0 {
if matches!(e, ureq::Error::Timeout(_)) && d.as_nanos() == 0 {
anyhow::bail!("timeout");
}
}
let msg = e.to_string();
if let Some(res) = e.into_response() {
data.http_status = res.status();
Some(res.into_reader())
if let ureq::Error::StatusCode(res) = e {
data.http_status = res;
None
} else {
return Err(Error::msg(msg));
}
@@ -294,6 +342,24 @@ pub(crate) fn http_status_code(
Ok(())
}
/// Get the HTTP response headers from the last HTTP request
/// Params: none
/// Returns: i64 (offset)
pub(crate) fn http_headers(
mut caller: Caller<CurrentPlugin>,
_input: &[Val],
output: &mut [Val],
) -> Result<(), Error> {
let data: &mut CurrentPlugin = caller.data_mut();
if let Some(h) = &data.http_headers {
let headers = serde_json::to_string(h)?;
data.memory_set_val(&mut output[0], headers)?;
} else {
output[0] = Val::I64(0);
}
Ok(())
}
pub fn log(
level: tracing::Level,
mut caller: Caller<CurrentPlugin>,
@@ -302,19 +368,21 @@ pub fn log(
) -> Result<(), Error> {
let data: &mut CurrentPlugin = caller.data_mut();
let offset = args!(input, 0, i64) as u64;
// Check if the current log level should be logged
let global_log_level = tracing::level_filters::LevelFilter::current();
if global_log_level == tracing::level_filters::LevelFilter::OFF || level > global_log_level {
if let Some(handle) = data.memory_handle(offset) {
data.memory_free(handle)?;
}
return Ok(());
}
let offset = args!(input, 0, i64) as u64;
let handle = match data.memory_handle(offset) {
Some(h) => h,
None => anyhow::bail!("invalid handle offset for log message: {offset}"),
};
let id = data.id.to_string();
let buf = data.memory_str(handle);
@@ -338,12 +406,16 @@ pub fn log(
},
Err(_) => tracing::error!(plugin = id, "unable to log message: {:?}", buf),
}
data.memory_free(handle)?;
Ok(())
}
/// Write to logs (warning)
/// Params: i64 (offset)
/// Returns: none
/// **Note**: this function takes ownership of the handle passed in
/// the caller should not `free` this value
pub(crate) fn log_warn(
caller: Caller<CurrentPlugin>,
input: &[Val],
@@ -355,6 +427,8 @@ pub(crate) fn log_warn(
/// Write to logs (info)
/// Params: i64 (offset)
/// Returns: none
/// **Note**: this function takes ownership of the handle passed in
/// the caller should not `free` this value
pub(crate) fn log_info(
caller: Caller<CurrentPlugin>,
input: &[Val],
@@ -366,6 +440,8 @@ pub(crate) fn log_info(
/// Write to logs (debug)
/// Params: i64 (offset)
/// Returns: none
/// **Note**: this function takes ownership of the handle passed in
/// the caller should not `free` this value
pub(crate) fn log_debug(
caller: Caller<CurrentPlugin>,
input: &[Val],
@@ -377,6 +453,8 @@ pub(crate) fn log_debug(
/// Write to logs (error)
/// Params: i64 (offset)
/// Returns: none
/// **Note**: this function takes ownership of the handle passed in
/// the caller should not `free` this value
pub(crate) fn log_error(
caller: Caller<CurrentPlugin>,
input: &[Val],
@@ -388,6 +466,8 @@ pub(crate) fn log_error(
/// Write to logs (trace)
/// Params: i64 (offset)
/// Returns: none
/// **Note**: this function takes ownership of the handle passed in
/// the caller should not `free` this value
pub(crate) fn log_trace(
caller: Caller<CurrentPlugin>,
input: &[Val],

View File

@@ -2,9 +2,11 @@ use std::{
any::Any,
collections::{BTreeMap, BTreeSet},
path::PathBuf,
sync::TryLockError,
};
use anyhow::Context;
use plugin_builder::PluginBuilderOptions;
use crate::*;
@@ -38,6 +40,93 @@ impl CancelHandle {
}
}
#[derive(Clone)]
pub struct CompiledPlugin {
pub(crate) manifest: Manifest,
pub(crate) modules: BTreeMap<String, Module>,
pub(crate) options: PluginBuilderOptions,
pub(crate) engine: wasmtime::Engine,
}
impl CompiledPlugin {
/// Create a new pre-compiled plugin
pub fn new(builder: PluginBuilder) -> Result<CompiledPlugin, Error> {
let mut config = builder.config.unwrap_or_default();
config
.async_support(false)
.epoch_interruption(true)
.debug_info(builder.options.debug_options.debug_info)
.coredump_on_trap(builder.options.debug_options.coredump.is_some())
.profiler(builder.options.debug_options.profiling_strategy)
.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);
}
config.cache(Self::configure_cache(&builder.options.cache_config)?);
let engine = Engine::new(&config)?;
let (manifest, modules) = manifest::load(&engine, builder.source)?;
if modules.len() <= 1 {
anyhow::bail!("No wasm modules provided");
} else if !modules.contains_key(MAIN_KEY) {
anyhow::bail!("No main module provided");
}
Ok(CompiledPlugin {
manifest,
modules,
options: builder.options,
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
pub struct Plugin {
/// A unique ID for each plugin
@@ -130,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]>),
@@ -139,7 +229,7 @@ pub enum WasmInput<'a> {
ManifestRef(&'a Manifest),
}
impl<'a> From<Manifest> for WasmInput<'a> {
impl From<Manifest> for WasmInput<'_> {
fn from(value: Manifest) -> Self {
WasmInput::Manifest(value)
}
@@ -169,7 +259,7 @@ impl<'a> From<&'a str> for WasmInput<'a> {
}
}
impl<'a> From<Vec<u8>> for WasmInput<'a> {
impl From<Vec<u8>> for WasmInput<'_> {
fn from(value: Vec<u8>) -> Self {
WasmInput::Data(value.into())
}
@@ -256,6 +346,7 @@ fn relink(
var_set(I64, I64);
http_request(I64, I64) -> I64;
http_status_code() -> I32;
http_headers() -> I64;
log_warn(I64);
log_info(I64);
log_debug(I64);
@@ -264,6 +355,37 @@ fn relink(
get_log_level() -> I32;
);
for (name, module) in modules.iter() {
if name == EXTISM_ENV_MODULE {
continue;
}
for import in module.imports() {
if import.module() == EXTISM_ENV_MODULE
&& modules[EXTISM_ENV_MODULE]
.get_export(import.name())
.is_none()
&& linker
.get(&mut store, EXTISM_ENV_MODULE, import.name())
.is_none()
{
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()),
};
anyhow::bail!(
"Invalid {kind} import from extism:host/env: {} {ty}\n\n\
Note: This may indicate that the PDK that was used to build this plugin has additional features that aren't \
available in this version of the SDK, try updating the SDK to the latest version.",
import.name(),
)
}
}
}
let mut linked = BTreeSet::new();
linker.module(&mut store, EXTISM_ENV_MODULE, &modules[EXTISM_ENV_MODULE])?;
linked.insert(EXTISM_ENV_MODULE.to_string());
@@ -310,82 +432,45 @@ impl Plugin {
imports: impl IntoIterator<Item = Function>,
with_wasi: bool,
) -> Result<Plugin, Error> {
Self::build_new(
wasm.into(),
imports,
with_wasi,
Default::default(),
None,
None,
)
Self::new_from_compiled(&CompiledPlugin::new(
PluginBuilder::new(wasm)
.with_functions(imports)
.with_wasi(with_wasi),
)?)
}
pub(crate) fn build_new(
wasm: WasmInput<'_>,
imports: impl IntoIterator<Item = Function>,
with_wasi: bool,
debug_options: DebugOptions,
cache_dir: Option<Option<PathBuf>>,
fuel: Option<u64>,
) -> Result<Plugin, Error> {
// Setup wasmtime types
let mut config = Config::new();
config
.epoch_interruption(true)
.debug_info(debug_options.debug_info)
.coredump_on_trap(debug_options.coredump.is_some())
.profiler(debug_options.profiling_strategy)
.wasm_tail_call(true)
.wasm_function_references(true)
.wasm_gc(true);
if fuel.is_some() {
config.consume_fuel(true);
}
match cache_dir {
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()?;
}
}
}
let engine = Engine::new(&config)?;
let (manifest, modules) = manifest::load(&engine, wasm)?;
if modules.len() <= 1 {
anyhow::bail!("No wasm modules provided");
} else if !modules.contains_key(MAIN_KEY) {
anyhow::bail!("No main module provided");
}
let available_pages = manifest.memory.max_pages;
/// Create a new plugin from a pre-compiled plugin
pub fn new_from_compiled(compiled: &CompiledPlugin) -> Result<Plugin, Error> {
let available_pages = compiled.manifest.memory.max_pages;
debug!("Available pages: {available_pages:?}");
let id = uuid::Uuid::new_v4();
let mut store = Store::new(
&engine,
CurrentPlugin::new(manifest, with_wasi, available_pages, id)?,
&compiled.engine,
CurrentPlugin::new(
compiled.manifest.clone(),
compiled.options.wasi,
available_pages,
compiled.options.http_response_headers,
id,
)?,
);
store.set_epoch_deadline(1);
if let Some(fuel) = fuel {
if let Some(fuel) = compiled.options.fuel {
store.set_fuel(fuel)?;
}
let imports: Vec<Function> = imports.into_iter().collect();
let (instance_pre, linker, host_context) =
relink(&engine, &mut store, &imports, &modules, with_wasi)?;
let imports: Vec<Function> = compiled.options.functions.to_vec();
let (instance_pre, linker, host_context) = relink(
&compiled.engine,
&mut store,
&imports,
&compiled.modules,
compiled.options.wasi,
)?;
let timer_tx = Timer::tx();
let mut plugin = Plugin {
modules,
modules: compiled.modules.clone(),
linker,
instance: std::sync::Arc::new(std::sync::Mutex::new(None)),
instance_pre,
@@ -397,10 +482,10 @@ impl Plugin {
instantiations: 0,
output: Output::default(),
store_needs_reset: false,
debug_options,
debug_options: compiled.options.debug_options.clone(),
_functions: imports,
error_msg: None,
fuel,
fuel: compiled.options.fuel,
host_context,
};
@@ -430,6 +515,7 @@ impl Plugin {
internal.manifest.clone(),
internal.wasi.is_some(),
internal.available_pages,
internal.http_headers.is_some(),
self.id,
)?,
);
@@ -505,7 +591,7 @@ impl Plugin {
}
/// Returns `true` if the given function exists, otherwise `false`
pub fn function_exists(&mut self, function: impl AsRef<str>) -> bool {
pub fn function_exists(&self, function: impl AsRef<str>) -> bool {
self.modules[MAIN_KEY]
.get_export(function.as_ref())
.map(|x| {
@@ -800,15 +886,20 @@ impl Plugin {
// Set host context
let r = if let Some(host_context) = host_context {
let inner = self
if let Some(inner) = self
.host_context
.data_mut(&mut self.store)
.map_err(|x| (x, -1))?;
if let Some(inner) = inner.downcast_mut::<Box<dyn std::any::Any + Send + Sync>>() {
let x: Box<T> = Box::new(host_context);
*inner = x;
.map_err(|x| (x, -1))?
{
if let Some(inner) = inner.downcast_mut::<Box<dyn std::any::Any + Send + Sync>>() {
let x: Box<T> = Box::new(host_context);
*inner = x;
}
Some(self.host_context)
} else {
None
}
Some(self.host_context)
} else {
None
};
@@ -851,7 +942,7 @@ impl Plugin {
let mut res = func.call(self.store_mut(), &[], results.as_mut_slice());
// Reset host context
if let Ok(inner) = self.host_context.data_mut(&mut self.store) {
if let Ok(Some(inner)) = self.host_context.data_mut(&mut self.store) {
if let Some(inner) = inner.downcast_mut::<Box<dyn std::any::Any + Send + Sync>>() {
let x: Box<dyn Any + Send + Sync> = Box::new(());
*inner = x;
@@ -864,19 +955,24 @@ impl Plugin {
let _ = self.timer_tx.send(TimerAction::Stop { id: self.id });
self.store_needs_reset = name == "_start";
let mut rc = 0;
let mut rc = -1;
if self.store.get_fuel().is_ok_and(|x| x == 0) {
res = Err(Error::msg("plugin ran out of fuel"));
rc = -1;
} else {
// Get extism error
self.get_output_after_call().map_err(|x| (x, -1))?;
if !results.is_empty() {
rc = results[0].i32().unwrap_or(-1);
debug!(plugin = self.id.to_string(), "got return code: {}", rc);
let output_res = self.get_output_after_call().map_err(|x| (x, -1));
// Get the return code
if output_res.is_ok() && res.is_ok() {
rc = 0;
if !results.is_empty() {
rc = results[0].i32().unwrap_or(-1);
debug!(plugin = self.id.to_string(), "got return code: {}", rc);
}
}
if self.output.error_offset != 0 && self.output.error_length != 0 {
// on extism error
if output_res.is_ok() && self.extism_error_is_set() {
let handle = MemoryHandle {
offset: self.output.error_offset,
length: self.output.error_length,
@@ -896,11 +992,18 @@ impl Plugin {
}
Err(msg) => {
res = Err(Error::msg(format!(
"Call to Extism plugin function {name} encountered an error: {}",
msg,
"unable to load error message from memory: {msg}",
)));
}
}
// on wasmtime error
} else if let Err(e) = &res {
if e.is::<wasmtime::Trap>() {
rc = 134; // EXIT_SIGNALED_SIGABRT
}
// if there was an error retrieving the output
} else {
output_res?;
}
}
@@ -955,11 +1058,12 @@ impl Plugin {
plugin = self.id.to_string(),
"WASI exit code: {}", exit_code
);
if exit_code == 0 {
if exit_code == 0 && !self.extism_error_is_set() {
return Ok(0);
}
return Err((e.context("WASI exit code"), exit_code));
return Err((e, exit_code));
}
// Handle timeout interrupts
@@ -987,6 +1091,10 @@ impl Plugin {
}
}
fn extism_error_is_set(&self) -> bool {
self.output.error_offset != 0 && self.output.error_length != 0
}
/// Call a function by name with the given input, the return value is
/// the output data returned by the plugin. The return type can be anything that implements
/// [FromBytes]. This data will be invalidated next time the plugin is called.
@@ -1010,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)
@@ -1035,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)
@@ -1054,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)))
@@ -1082,6 +1211,25 @@ impl Plugin {
anyhow::bail!("Plugin::clear_error failed, extism:host/env::error_set not found")
}
}
/// Returns the amount of fuel consumed by the plugin.
///
/// This function calculates the difference between the initial fuel and the remaining fuel.
/// If either the initial fuel or the remaining fuel is not set, it returns `None`.
///
/// # Returns
///
/// * `Some(u64)` - The amount of fuel consumed.
/// * `None` - If the initial fuel or remaining fuel is not set.
pub fn fuel_consumed(&self) -> Option<u64> {
self.fuel.map(|x| {
x.saturating_sub(
self.store
.get_fuel()
.expect("fuel support should be enabled to use fuel"),
)
})
}
}
// Enumerates the PDK languages that need some additional initialization
@@ -1125,7 +1273,7 @@ macro_rules! typed_plugin {
impl TryFrom<$crate::Plugin> for $name {
type Error = $crate::Error;
fn try_from(mut x: $crate::Plugin) -> Result<Self, Self::Error> {
fn try_from(x: $crate::Plugin) -> Result<Self, Self::Error> {
$(
if !x.function_exists(stringify!($f)) {
return Err($crate::Error::msg(format!("Invalid function: {}", stringify!($f))));

View File

@@ -33,13 +33,21 @@ impl Default for DebugOptions {
}
/// PluginBuilder is used to configure and create `Plugin` instances
#[derive(Clone)]
pub struct PluginBuilder<'a> {
source: WasmInput<'a>,
wasi: bool,
functions: Vec<Function>,
debug_options: DebugOptions,
cache_config: Option<Option<PathBuf>>,
fuel: Option<u64>,
pub(crate) source: WasmInput<'a>,
pub(crate) config: Option<wasmtime::Config>,
pub(crate) options: PluginBuilderOptions,
}
#[derive(Clone)]
pub(crate) struct PluginBuilderOptions {
pub(crate) wasi: bool,
pub(crate) functions: Vec<Function>,
pub(crate) debug_options: DebugOptions,
pub(crate) cache_config: Option<Option<PathBuf>>,
pub(crate) fuel: Option<u64>,
pub(crate) http_response_headers: bool,
}
impl<'a> PluginBuilder<'a> {
@@ -47,17 +55,21 @@ impl<'a> PluginBuilder<'a> {
pub fn new(plugin: impl Into<WasmInput<'a>>) -> Self {
PluginBuilder {
source: plugin.into(),
wasi: false,
functions: vec![],
debug_options: DebugOptions::default(),
cache_config: None,
fuel: None,
config: None,
options: PluginBuilderOptions {
wasi: false,
functions: vec![],
debug_options: DebugOptions::default(),
cache_config: None,
fuel: None,
http_response_headers: false,
},
}
}
/// Enables WASI if the argument is set to `true`
pub fn with_wasi(mut self, wasi: bool) -> Self {
self.wasi = wasi;
self.options.wasi = wasi;
self
}
@@ -76,7 +88,8 @@ impl<'a> PluginBuilder<'a> {
+ Sync
+ Send,
{
self.functions
self.options
.functions
.push(Function::new(name, args, returns, user_data, f));
self
}
@@ -97,74 +110,97 @@ impl<'a> PluginBuilder<'a> {
+ Sync
+ Send,
{
self.functions
self.options
.functions
.push(Function::new(name, args, returns, user_data, f).with_namespace(namespace));
self
}
/// Add multiple host functions
pub fn with_functions(mut self, f: impl IntoIterator<Item = Function>) -> Self {
self.functions.extend(f);
self.options.functions.extend(f);
self
}
/// Set profiling strategy
pub fn with_profiling_strategy(mut self, p: wasmtime::ProfilingStrategy) -> Self {
self.debug_options.profiling_strategy = p;
self.options.debug_options.profiling_strategy = p;
self
}
/// Enable Wasmtime coredump on trap
pub fn with_coredump(mut self, path: impl Into<std::path::PathBuf>) -> Self {
self.debug_options.coredump = Some(path.into());
self.options.debug_options.coredump = Some(path.into());
self
}
/// Enable Extism memory dump when plugin calls return an error
pub fn with_memdump(mut self, path: impl Into<std::path::PathBuf>) -> Self {
self.debug_options.memdump = Some(path.into());
self.options.debug_options.memdump = Some(path.into());
self
}
/// Compile with debug info
pub fn with_debug_info(mut self) -> Self {
self.debug_options.debug_info = true;
self.options.debug_options.debug_info = true;
self
}
/// Configure debug options
pub fn with_debug_options(mut self, options: DebugOptions) -> Self {
self.debug_options = options;
self.options.debug_options = options;
self
}
/// Set wasmtime compilation cache config path
pub fn with_cache_config(mut self, dir: impl Into<PathBuf>) -> Self {
self.cache_config = Some(Some(dir.into()));
self.options.cache_config = Some(Some(dir.into()));
self
}
/// Turn wasmtime compilation caching off
pub fn with_cache_disabled(mut self) -> Self {
self.cache_config = Some(None);
self.options.cache_config = Some(None);
self
}
// Limit the number of instructions that can be executed
/// Limit the number of instructions that can be executed
pub fn with_fuel_limit(mut self, fuel: u64) -> Self {
self.fuel = Some(fuel);
self.options.fuel = Some(fuel);
self
}
/// Configure an initial wasmtime config to be passed to the plugin
///
/// **Warning**: some values might be overwritten by the Extism runtime. In particular:
/// - async_support
/// - epoch_interruption
/// - debug_info
/// - coredump_on_trap
/// - profiler
/// - wasm_tail_call
/// - wasm_function_references
/// - wasm_gc
///
/// See the implementation details of [PluginBuilder::build] and [Plugin::build_new] to verify which values are overwritten.
pub fn with_wasmtime_config(mut self, config: wasmtime::Config) -> Self {
self.config = Some(config);
self
}
/// Enables `http_response_headers`, which allows for plugins to access response headers when using `extism:host/env::http_request`
pub fn with_http_response_headers(mut self, allow: bool) -> Self {
self.options.http_response_headers = allow;
self
}
/// Generate a new plugin with the configured settings
pub fn build(self) -> Result<Plugin, Error> {
Plugin::build_new(
self.source,
self.functions,
self.wasi,
self.debug_options,
self.cache_config,
self.fuel,
)
Plugin::new_from_compiled(&CompiledPlugin::new(self)?)
}
/// Build new `CompiledPlugin`
pub fn compile(self) -> Result<CompiledPlugin, Error> {
CompiledPlugin::new(self)
}
}

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

@@ -48,32 +48,32 @@ pub type ExtismFunctionType = extern "C" fn(
pub type ExtismLogDrainFunctionType = extern "C" fn(data: *const std::ffi::c_char, size: Size);
impl ExtismVal {
fn from_val(value: &wasmtime::Val, ctx: impl AsContext) -> Self {
match value.ty(ctx) {
wasmtime::ValType::I32 => ExtismVal {
fn from_val(value: &wasmtime::Val, ctx: impl AsContext) -> Result<Self, Error> {
match value.ty(ctx)? {
wasmtime::ValType::I32 => Ok(ExtismVal {
t: ValType::I32,
v: ValUnion {
i32: value.unwrap_i32(),
},
},
wasmtime::ValType::I64 => ExtismVal {
}),
wasmtime::ValType::I64 => Ok(ExtismVal {
t: ValType::I64,
v: ValUnion {
i64: value.unwrap_i64(),
},
},
wasmtime::ValType::F32 => ExtismVal {
}),
wasmtime::ValType::F32 => Ok(ExtismVal {
t: ValType::F32,
v: ValUnion {
f32: value.unwrap_f32(),
},
},
wasmtime::ValType::F64 => ExtismVal {
}),
wasmtime::ValType::F64 => Ok(ExtismVal {
t: ValType::F64,
v: ValUnion {
f64: value.unwrap_f64(),
},
},
}),
t => todo!("{}", t),
}
}
@@ -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.
///
@@ -227,7 +227,7 @@ pub unsafe extern "C" fn extism_function_new(
let store = &*plugin.store;
let inputs: Vec<_> = inputs
.iter()
.map(|x| ExtismVal::from_val(x, store))
.map(|x| ExtismVal::from_val(x, store).unwrap())
.collect();
let mut output_tmp: Vec<_> = output_types
.iter()
@@ -266,8 +266,8 @@ pub unsafe extern "C" fn extism_function_new(
match tmp.t {
ValType::I32 => *out = Val::I32(tmp.v.i32),
ValType::I64 => *out = Val::I64(tmp.v.i64),
ValType::F32 => *out = Val::F32(tmp.v.f32 as u32),
ValType::F64 => *out = Val::F64(tmp.v.f64 as u64),
ValType::F32 => *out = Val::F32(tmp.v.f32.to_bits()),
ValType::F64 => *out = Val::F64(tmp.v.f64.to_bits()),
_ => todo!(),
}
}
@@ -302,6 +302,143 @@ pub unsafe extern "C" fn extism_function_set_namespace(
}
}
/// Pre-compile an Extism plugin
#[no_mangle]
pub unsafe extern "C" fn extism_compiled_plugin_new(
wasm: *const u8,
wasm_size: Size,
functions: *mut *const ExtismFunction,
n_functions: Size,
with_wasi: bool,
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);
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()
})
}
/// 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) {
if plugin.is_null() {
return;
}
let plugin = Box::from_raw(plugin);
trace!("called extism_compiled_plugin_free");
drop(plugin)
}
/// 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
@@ -318,36 +455,70 @@ pub unsafe extern "C" fn extism_plugin_new(
with_wasi: bool,
errmsg: *mut *mut std::ffi::c_char,
) -> *mut Plugin {
trace!("Call to extism_plugin_new with wasm pointer {:?}", wasm);
let data = std::slice::from_raw_parts(wasm, wasm_size as usize);
let mut funcs = vec![];
if !functions.is_null() {
for i in 0..n_functions {
unsafe {
let f = *functions.add(i as usize);
if f.is_null() {
continue;
let funcs = if functions.is_null() {
vec![]
} else {
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");
}
if let Some(f) = (*f).0.take() {
funcs.push(f);
} else {
let e = std::ffi::CString::new(
"Function cannot be registered with multiple different Plugins",
)
.unwrap();
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()
});
let plugin = Plugin::new(data, funcs, with_wasi);
if funcs.len() != n_functions as usize {
return std::ptr::null_mut();
}
funcs
};
Plugin::new(data, funcs, with_wasi)
.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()
})
}
/// Create a new plugin from an `ExtismCompiledPlugin`
#[no_mangle]
pub unsafe extern "C" fn extism_plugin_new_from_compiled(
compiled: *const CompiledPlugin,
errmsg: *mut *mut std::ffi::c_char,
) -> *mut Plugin {
let plugin = Plugin::new_from_compiled(&*compiled);
match plugin {
Err(e) => {
if !errmsg.is_null() {
let e = std::ffi::CString::new(format!("Unable to create Extism plugin: {}", e))
.unwrap();
let e = std::ffi::CString::new(format!(
"Unable to create Extism plugin: {}",
e.root_cause(),
))
.unwrap();
*errmsg = e.into_raw();
}
std::ptr::null_mut()
@@ -372,42 +543,69 @@ pub unsafe extern "C" fn extism_plugin_new_with_fuel_limit(
wasm
);
let data = std::slice::from_raw_parts(wasm, wasm_size as usize);
let mut funcs = vec![];
if !functions.is_null() {
for i in 0..n_functions {
unsafe {
let f = *functions.add(i as usize);
if f.is_null() {
continue;
let funcs = if functions.is_null() {
vec![]
} else {
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");
}
if let Some(f) = (*f).0.take() {
funcs.push(f);
} else {
let e = std::ffi::CString::new(
"Function cannot be registered with multiple different Plugins",
)
.unwrap();
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()
});
let plugin = Plugin::build_new(
data.into(),
funcs,
with_wasi,
Default::default(),
None,
Some(fuel_limit),
);
if funcs.len() != n_functions as usize {
return std::ptr::null_mut();
}
funcs
};
let compiled = match CompiledPlugin::new(
PluginBuilder::new(data)
.with_functions(funcs)
.with_wasi(with_wasi)
.with_fuel_limit(fuel_limit),
) {
Ok(x) => x,
Err(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();
}
return std::ptr::null_mut();
}
};
let plugin = Plugin::new_from_compiled(&compiled);
match plugin {
Err(e) => {
if !errmsg.is_null() {
let e = std::ffi::CString::new(format!("Unable to create Extism plugin: {}", e))
.unwrap();
let e = std::ffi::CString::new(format!(
"Unable to create Extism plugin: {}",
e.root_cause(),
))
.unwrap();
*errmsg = e.into_raw();
}
std::ptr::null_mut()
@@ -416,6 +614,13 @@ pub unsafe extern "C" fn extism_plugin_new_with_fuel_limit(
}
}
/// Enable HTTP response headers in plugins using `extism:host/env::http_request`
#[no_mangle]
pub unsafe extern "C" fn extism_plugin_allow_http_response_headers(plugin: *mut Plugin) {
let plugin = &mut *plugin;
plugin.store.data_mut().http_headers = Some(BTreeMap::new());
}
/// Free the error returned by `extism_plugin_new`, errors returned from `extism_plugin_error` don't need to be freed
#[no_mangle]
pub unsafe extern "C" fn extism_plugin_new_error_free(err: *mut std::ffi::c_char) {
@@ -425,7 +630,7 @@ pub unsafe extern "C" fn extism_plugin_new_error_free(err: *mut std::ffi::c_char
drop(std::ffi::CString::from_raw(err))
}
/// Remove a plugin from the registry and free associated memory
/// Free `ExtismPlugin`
#[no_mangle]
pub unsafe extern "C" fn extism_plugin_free(plugin: *mut Plugin) {
if plugin.is_null() {
@@ -730,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)
}
@@ -758,7 +963,7 @@ fn set_log_file(log_file: impl Into<std::path::PathBuf>, filter: &str) -> Result
Ok(())
}
static mut LOG_BUFFER: Option<LogBuffer> = None;
static LOG_BUFFER: std::sync::Mutex<Option<LogBuffer>> = std::sync::Mutex::new(None);
/// Enable a custom log handler, this will buffer logs until `extism_log_drain` is called
/// Log level should be one of: info, error, trace, debug, warn
@@ -785,13 +990,13 @@ 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)
}
});
LOG_BUFFER = Some(LogBuffer::default());
let buf = LOG_BUFFER.clone().unwrap();
*LOG_BUFFER.lock().unwrap() = Some(LogBuffer::default());
let buf = LOG_BUFFER.lock().unwrap().clone().unwrap();
cfg.with_ansi(false)
.with_writer(move || buf.clone())
.try_init()
@@ -803,7 +1008,7 @@ unsafe fn set_log_buffer(filter: &str) -> Result<(), Error> {
/// 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: ExtismLogDrainFunctionType) {
if let Some(buf) = LOG_BUFFER.as_mut() {
if let Some(buf) = LOG_BUFFER.lock().unwrap().as_mut() {
if let Ok(mut buf) = buf.buffer.lock() {
for (line, len) in buf.drain(..) {
handler(line.as_ptr(), len as u64);

View File

@@ -1,6 +1,7 @@
use crate::*;
const WASM_EMPTY: &[u8] = include_bytes!("../../../wasm/empty.wasm");
const WASM_UNREACHABLE: &[u8] = include_bytes!("../../../wasm/unreachable.wasm");
// https://github.com/extism/extism/issues/620
#[test]
@@ -15,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
@@ -26,3 +27,31 @@ host_fn!(
Ok(path.display().to_string())
}
);
// https://github.com/extism/extism/issues/775
#[test]
fn test_issue_775() {
// Load and build plugin
let url = Wasm::data(WASM_UNREACHABLE);
let manifest = Manifest::new([url]);
let mut plugin = PluginBuilder::new(manifest)
.with_wasi(true)
.build()
.unwrap();
// Call test method
let lock = plugin.instance.clone();
let mut lock = lock.lock().unwrap();
let res = plugin.raw_call(&mut lock, "do_unreachable", b"", None::<()>);
let p = match res {
Err(e) => {
if e.1 == 0 {
Err(e.1)
} else {
Ok(e.1)
}
}
Ok(code) => Err(code),
}
.unwrap();
println!("{p}");
}

View File

@@ -153,9 +153,29 @@ fn test_kernel_allocations() {
// Test allocations
assert_eq!(extism_alloc(&mut store, instance, 0), 0);
// 512 bytes, test block re-use + splitting
let p = extism_alloc(&mut store, instance, 65535);
let first_alloc = p;
assert_eq!(extism_length(&mut store, instance, p), 65535);
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);
// Should re-use the previous block
let q = extism_alloc(&mut store, instance, 65535);
assert_eq!(q, p);
assert_eq!(extism_length(&mut store, instance, q), 65535);
extism_free(&mut store, instance, q);
let r = extism_alloc(&mut store, instance, 65535 - 24);
assert_eq!(r, q);
assert_eq!(extism_length(&mut store, instance, q), 65535 - 24);
extism_free(&mut store, instance, r);
// 1 byte
let p = extism_alloc(&mut store, instance, 1);
let first_alloc = p;
assert!(p > 0);
assert_eq!(extism_length(&mut store, instance, p), 1);
assert_eq!(extism_length_unsafe(&mut store, instance, p), 1);
@@ -175,37 +195,8 @@ fn test_kernel_allocations() {
assert_eq!(extism_length(&mut store, instance, p), 64 - i);
assert_eq!(extism_length_unsafe(&mut store, instance, p), 64 - i);
extism_free(&mut store, instance, p);
// should re-use the last allocation
let q = extism_alloc(&mut store, instance, 64 - i);
assert_eq!(p, q);
assert_eq!(extism_length(&mut store, instance, q), 64 - i);
assert_eq!(extism_length_unsafe(&mut store, instance, q), 64 - i);
extism_free(&mut store, instance, q);
}
// 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
let q = extism_alloc(&mut store, instance, 128);
assert!(p <= q && q < p + 512);
assert_eq!(extism_length(&mut store, instance, q), 128);
extism_free(&mut store, instance, q);
// 128 bytes, same as above
let r = extism_alloc(&mut store, instance, 128);
assert!(p <= r && r < p + 512);
assert!(r > p);
assert_eq!(extism_length(&mut store, instance, r), 128);
extism_free(&mut store, instance, q);
// 100 pages
let p = extism_alloc(&mut store, instance, 6553600);
assert!(p > 0);
@@ -421,7 +412,7 @@ quickcheck! {
quickcheck! {
fn check_alloc_with_load_and_store(amounts: Vec<u16>) -> bool {
use rand::Rng;
let mut rng = rand::thread_rng();
let mut rng = rand::rng();
let (mut store, mut instance) = init_kernel_test();
let instance = &mut instance;
for a in amounts {
@@ -434,7 +425,7 @@ quickcheck! {
}
for _ in 0..16 {
let i = rng.gen_range(ptr..ptr+a as u64);
let i = rng.random_range(ptr..ptr+a as u64);
extism_store_u8(&mut store, instance, i, i as u8);
if extism_load_u8(&mut store, instance, i as u64) != i as u8 {
return false

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

@@ -1,7 +1,7 @@
use extism_manifest::MemoryOptions;
use extism_manifest::{HttpRequest, MemoryOptions};
use crate::*;
use std::{io::Write, time::Instant};
use std::{collections::HashMap, io::Write, time::Instant};
const WASM: &[u8] = include_bytes!("../../../wasm/code-functions.wasm");
const WASM_NO_FUNCTIONS: &[u8] = include_bytes!("../../../wasm/code.wasm");
@@ -9,6 +9,7 @@ const WASM_LOOP: &[u8] = include_bytes!("../../../wasm/loop.wasm");
const WASM_GLOBALS: &[u8] = include_bytes!("../../../wasm/globals.wasm");
const WASM_REFLECT: &[u8] = include_bytes!("../../../wasm/reflect.wasm");
const WASM_HTTP: &[u8] = include_bytes!("../../../wasm/http.wasm");
const WASM_HTTP_HEADERS: &[u8] = include_bytes!("../../../wasm/http_headers.wasm");
const WASM_FS: &[u8] = include_bytes!("../../../wasm/read_write.wasm");
host_fn!(pub hello_world (a: String) -> String { Ok(a) });
@@ -132,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
@@ -144,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 {
@@ -211,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:?}");
}
}
@@ -257,6 +255,23 @@ fn test_fuel() {
}
}
#[test]
fn test_fuel_consumption() {
let manifest = Manifest::new([extism_manifest::Wasm::data(WASM_LOOP)]);
let mut plugin = PluginBuilder::new(manifest)
.with_wasi(true)
.with_fuel_limit(10000)
.build()
.unwrap();
let output: Result<&[u8], Error> = plugin.call("loop_forever", "abc123");
assert!(output.is_err());
let fuel_consumed = plugin.fuel_consumed().unwrap();
println!("Fuel consumed: {fuel_consumed}");
assert!(fuel_consumed > 0);
}
#[test]
#[cfg(feature = "http")]
fn test_http_timeout() {
@@ -422,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
@@ -446,7 +461,7 @@ fn hello_world_set_error(
_user_data: UserData<()>,
) -> Result<(), Error> {
plugin.set_error("TEST")?;
outputs[0] = inputs[0].clone();
outputs[0] = inputs[0];
Ok(())
}
@@ -485,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");
}
@@ -543,7 +558,7 @@ fn hello_world_user_data(
let mut data = data.lock().unwrap();
let s = _plugin.memory_get_val(&inputs[0])?;
data.write_all(s)?;
outputs[0] = inputs[0].clone();
outputs[0] = inputs[0];
Ok(())
}
@@ -793,3 +808,33 @@ fn test_readonly_dirs() {
"Expected try_write to fail, but it succeeded."
);
}
#[test]
#[cfg(feature = "http")]
fn test_http_response_headers() {
let mut plugin = PluginBuilder::new(
Manifest::new([Wasm::data(WASM_HTTP_HEADERS)]).with_allowed_host("extism.org"),
)
.with_http_response_headers(true)
.build()
.unwrap();
let req = HttpRequest::new("https://extism.org");
let Json(res): Json<HashMap<String, String>> = plugin.call("http_get", Json(req)).unwrap();
println!("{res:?}");
assert_eq!(res["content-type"], "text/html; charset=utf-8");
}
#[test]
#[cfg(feature = "http")]
fn test_http_response_headers_disabled() {
let mut plugin = PluginBuilder::new(
Manifest::new([Wasm::data(WASM_HTTP_HEADERS)]).with_allowed_host("extism.org"),
)
.with_http_response_headers(false)
.build()
.unwrap();
let req = HttpRequest::new("https://extism.org");
let Json(res): Json<HashMap<String, String>> = plugin.call("http_get", Json(req)).unwrap();
println!("{res:?}");
assert!(res.is_empty());
}

View File

@@ -22,18 +22,18 @@ pub(crate) struct Timer {
#[cfg(not(target_family = "windows"))]
extern "C" fn cleanup_timer() {
let mut timer = match unsafe { TIMER.lock() } {
let mut timer = match TIMER.lock() {
Ok(x) => x,
Err(e) => e.into_inner(),
};
drop(timer.take());
}
static mut TIMER: std::sync::Mutex<Option<Timer>> = std::sync::Mutex::new(None);
static TIMER: std::sync::Mutex<Option<Timer>> = std::sync::Mutex::new(None);
impl Timer {
pub(crate) fn tx() -> std::sync::mpsc::Sender<TimerAction> {
let mut timer = match unsafe { TIMER.lock() } {
let mut timer = match TIMER.lock() {
Ok(x) => x,
Err(e) => e.into_inner(),
};
@@ -92,25 +92,39 @@ impl Timer {
loop {
if plugins.is_empty() {
if let Ok(x) = rx.recv() {
handle!(x)
handle!(x);
}
}
plugins = plugins
.into_iter()
.filter(|(_k, (engine, end))| {
if let Some(end) = end {
let now = std::time::Instant::now();
if end <= &now {
engine.increment_epoch();
return false;
let mut timeout: Option<std::time::Duration> = None;
plugins.retain(|_k, (engine, end)| {
if let Some(end) = end {
let now = std::time::Instant::now();
if *end <= now {
engine.increment_epoch();
return false;
} else {
let time_left =
(*end - now).saturating_sub(std::time::Duration::from_millis(1));
if let Some(t) = &timeout {
if time_left < *t {
timeout = Some(time_left);
}
} else {
timeout = Some(time_left);
}
}
true
})
.collect();
}
for x in rx.try_iter() {
true
});
if let Some(timeout) = timeout {
if let Ok(x) = rx.recv_timeout(timeout) {
handle!(x)
}
} else if let Ok(x) = rx.recv() {
handle!(x)
}
}

BIN
wasm/allocations.wasm Executable file

Binary file not shown.

BIN
wasm/http_headers.wasm Executable file

Binary file not shown.

BIN
wasm/unreachable.wasm Executable file

Binary file not shown.