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.
@Joel Dice I would be very interested in your thoughts about that!
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).
Yes, wasmtime-py
v25.0.0
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.
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.
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).
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.
Last updated: Nov 22 2024 at 16:03 UTC