Stream: wasmtime

Topic: Thread safety of wasmtime (python)?


view this post on Zulip Christoph Brewing (Oct 16 2024 at 14:11):

Given I take an ordinary WASM component (single threaded) and provide Python bindings to it,
When I call it from Polars by using its lazy api,
Then I can see my application crash with hints to wasmtime and different threads in the stack trace.

Hence, my hypothesis: wasmtime and Polars crash over some concurrency issue.
Therefore my question: Is wasmtime (python) thread-safe? What do I have to consider when using it in a multi-threaded environment?

P.s. The error does NOT appear in case that Polars application is explicitly executed in a single threaded way.

P.P.s. I do not see such weird exceptions when using the same WASM component in a highly concurrent Tokio (pure Rust) application.

DataFrames for the new era

view this post on Zulip Christoph Brewing (Oct 16 2024 at 14:27):

@Joel Dice I would be very interested in your thoughts about that!

view this post on Zulip Joel Dice (Oct 16 2024 at 14:57):

I take it you're using wasmtime-py? @Alex Crichton would know more about the internals there, but yeah, I wouldn't be surprised if the API is not thread-safe. Maybe we need to add some explicit locking and/or defer everything to a worker thread.

When using the Rust API for Wasmtime, the compiler catches any Send/Sync violations, so you tend to get thread safety by construction, but I imagine there are no such guardrails in the Python API (or the C API on which it is built).

view this post on Zulip Christoph Brewing (Oct 16 2024 at 15:49):

Yes, wasmtime-py v25.0.0

view this post on Zulip Brett Cannon (Oct 16 2024 at 19:06):

Python itself will make it thread-safe due to the GIL if wasmtime-py isn't releasing the GIL itself. But if you're doing something outside of Python or wasmtime-py is releasing the GIL then there could be a race condition.

view this post on Zulip Joel Dice (Oct 16 2024 at 19:14):

wasmtime-py is basically a Python API on top of a C API on top of a Rust implementation, so presumably the problem is in one of the lower layers beneath Python.

BTW, Alex will be unavailable until October 22nd (and might not remember the details regarding this anyway), so @Christoph Brewing if you need a quick fix I'd recommend conservatively assuming wasmtime-py is not thread safe, meaning you should refactor your app so all the Wasmtime calls happen on the same thread.

view this post on Zulip Joel Dice (Oct 16 2024 at 19:18):

Assuming the C API type wasmtime_store_t is just a handle to a wasmtime::Store, then the fact that the latter is not thread-safe would imply the former isn't either. In Rust parlance a Store (and handles to it) is Send (meaning it can be passed from one thread to another) but not Sync (meaning it can't safely be shared among multiple threads simultaneously).

view this post on Zulip Alex Crichton (Oct 16 2024 at 21:40):

The wasmtime-py package uses ctypes in Python which I believe means that the GIL is released whenever Wasmtime-native code is executing. That means that there's no protection against concurrency, and that's a known issue with wasmtime-py. There's some discussion in an issue which I never got around to making a dedicated issue for though, so the "known" part is "well at least it's in my head" which I realize isn't the most useful...

In any case Joel is right in that the Store is the main part here. Without any protections in Python (which is a bug in the wasmtime-py library) the thread-safety story in Python is the same as that from C. That means that you can for example use a Module on multiple threads (and Engine too) but Store can only be used on one thread at a time. All instances/functions live within a single Store so you can't, for example, call two functions on an instance in parallel on two threads.

To directly answer your initial questions:

Therefore my question: Is wasmtime (python) thread-safe?

Not currently. It's subject to the same requirements as the C API. This is a bug in wasmtime-py I'd love to see fixed one day, however. (sorry you're running into this)

What do I have to consider when using it in a multi-threaded environment?

The general rule of thumb is a Store, and everything that works with it, should stick to a single thread. It can move around as a "unit" between threads, but can only be used on one thread at a time. The Engine and Module types are threadsafe.

The Linker type is also safe to use across threads, so long as the method isn't mutating the Linker. For example a linker populated with define_func is safe to use across threads. A Linker populated with define, however, is not.

Linker "accessors" such as get or instantiate should be safe to use across threads though.

I have a really simple component with this WIT world: package local:wavedrom; world wavedrom { export render-json: func(json: string) -> string; } After running through ComponentizeJS, it results i...

Last updated: Dec 23 2024 at 14:03 UTC