HoKim98 opened issue #11170:
Thanks for filing a feature request! Please fill out the TODOs below.
Feature
I want to use
wasmtimeinactix-web, which uses!Sendruntime and resources such asHTTPRequestandweb::Payload.But the current runtime requires functions and states to be
Sendable.I thought it's not mandatory to enforce
Sendtrait.
So I have tested on my local withoutSendand found no problem.Benefit
We can use
wasmtimein the thread-bounded (single-threaded) async runtimes.It's essential to use
wasmtimewithinactixecosystem, which provides the input parameters as!Send.Implementation
My current(initial) idea is to create a buddy methods like below:
/// Original method pub async fn instantiate_async( &self, mut store: impl AsContextMut<Data = T>, ) -> Result<Instance> where T: Send, { let mut store = store.as_context_mut(); assert!( store.0.async_support(), "must use sync instantiation when async support is disabled" ); store.on_fiber(|store| self.instantiate_impl(store)).await? } /// (New) buddy method pub async fn instantiate_async_single_rt( &self, mut store: impl AsContextMut<Data = T>, ) -> Result<Instance> { let mut store = store.as_context_mut(); assert!( store.0.async_support(), "must use sync instantiation when async support is disabled" ); store .on_fiber_single_rt(|store| self.instantiate_impl_single_rt(store)) .await? }Alternatives
My another idea is to use cargo features like
async-send.
But I think it may break the ecosystem (side-effect).
HoKim98 edited issue #11170:
Feature
I want to use
wasmtimeinactix-web, which uses!Sendruntime and resources such asHTTPRequestandweb::Payload.But the current runtime requires functions and states to be
Sendable.I thought it's not mandatory to enforce
Sendtrait.
So I have tested on my local withoutSendand found no problem.Benefit
We can use
wasmtimein the thread-bounded (single-threaded) async runtimes.It's essential to use
wasmtimewithinactixecosystem, which provides the input parameters as!Send.Implementation
My current(initial) idea is to create a buddy methods like below:
/// Original method pub async fn instantiate_async( &self, mut store: impl AsContextMut<Data = T>, ) -> Result<Instance> where T: Send, { let mut store = store.as_context_mut(); assert!( store.0.async_support(), "must use sync instantiation when async support is disabled" ); store.on_fiber(|store| self.instantiate_impl(store)).await? } /// (New) buddy method pub async fn instantiate_async_single_rt( &self, mut store: impl AsContextMut<Data = T>, ) -> Result<Instance> { let mut store = store.as_context_mut(); assert!( store.0.async_support(), "must use sync instantiation when async support is disabled" ); store .on_fiber_single_rt(|store| self.instantiate_impl_single_rt(store)) .await? }Alternatives
My another idea is to use cargo features like
async-send.
But I think it may break the ecosystem (side-effect).
HoKim98 edited issue #11170:
Feature
I want to use
wasmtimeinactix-web, which uses!Sendruntime and resources such asHTTPRequestandweb::Payload.But the current runtime requires functions and states to be
Sendable.I thought it's not mandatory to enforce
Sendtrait.
So I have tested on my local withoutSendand found no problem.Benefit
We can use
wasmtimein the thread-bounded (single-threaded) async runtimes.It's essential to use
wasmtimewithinactixecosystem, which provides the input parameters as!Send.Implementation
My current(initial) idea is to create a buddy methods like below:
/// Original method pub async fn instantiate_async( &self, mut store: impl AsContextMut<Data = T>, ) -> Result<Instance> where T: Send, { let mut store = store.as_context_mut(); assert!( store.0.async_support(), "must use sync instantiation when async support is disabled" ); store.on_fiber(|store| self.instantiate_impl(store)).await? } /// (New) buddy method pub async fn instantiate_async_single_rt( &self, mut store: impl AsContextMut<Data = T>, ) -> Result<Instance> { let mut store = store.as_context_mut(); assert!( store.0.async_support(), "must use sync instantiation when async support is disabled" ); store .on_fiber_single_rt(|store| self.instantiate_impl_single_rt(store)) .await? }The estimated number of lines: 100~300
Alternatives
My another idea is to use cargo features like
async-send.
But I think it may break the ecosystem (side-effect).
HoKim98 edited issue #11170:
Feature
I want to use
wasmtimeinactix-web, which uses!Sendruntime and resources such asHTTPRequestandweb::Payload.But the current runtime requires functions and states to be
Sendable.I thought it's not mandatory to enforce
Sendtrait.
So I have tested on my local withoutSendand found no problem.Benefit
We can use
wasmtimein the thread-bounded (single-threaded) async runtimes.It's essential to use
wasmtimewithinactixecosystem, which provides the input parameters as!Send.Implementation
My current(initial) idea is to create a buddy methods like below:
/// Original method pub async fn instantiate_async( &self, mut store: impl AsContextMut<Data = T>, ) -> Result<Instance> where T: Send, { let mut store = store.as_context_mut(); assert!( store.0.async_support(), "must use sync instantiation when async support is disabled" ); store.on_fiber(|store| self.instantiate_impl(store)).await? } /// (New) buddy method pub async fn instantiate_async_single_rt( &self, mut store: impl AsContextMut<Data = T>, ) -> Result<Instance> { let mut store = store.as_context_mut(); assert!( store.0.async_support(), "must use sync instantiation when async support is disabled" ); store .on_fiber_single_rt(|store| self.instantiate_impl_single_rt(store)) .await? }The estimated number of new lines: 100~300
Alternatives
My another idea is to use cargo features like
async-send.
But I think it may break the ecosystem (side-effect).
HoKim98 edited issue #11170:
Feature
I want to use
wasmtimeinactix-web, which uses!Sendruntime and resources such asHTTPRequestandweb::Payload.But the current runtime requires functions and states to be
Sendable.I thought it's not mandatory to enforce
Sendtrait.
So I have tested on my local withoutSendand found no problem.Benefit
We can use
wasmtimein the thread-bounded (single-threaded) async runtimes.It's essential to use
wasmtimewithinactixecosystem, which provides the input parameters as!Send.Implementation
My current(initial) idea is to create a buddy methods like below:
/// Original method pub async fn instantiate_async( &self, mut store: impl AsContextMut<Data = T>, ) -> Result<Instance> where T: Send, { let mut store = store.as_context_mut(); assert!( store.0.async_support(), "must use sync instantiation when async support is disabled" ); store.on_fiber(|store| self.instantiate_impl(store)).await? } /// (New) buddy method pub async fn instantiate_async_single_rt( &self, mut store: impl AsContextMut<Data = T>, ) -> Result<Instance> { let mut store = store.as_context_mut(); assert!( store.0.async_support(), "must use sync instantiation when async support is disabled" ); store .on_fiber_single_rt(|store| self.instantiate_impl_single_rt(store)) .await? }The estimated number of new lines: 100~500
Alternatives
My another idea is to use cargo features like
async-send.
But I think it may break the ecosystem (side-effect).
HoKim98 edited issue #11170:
Feature
I want to use
wasmtimeinactix-web, which uses!Sendruntime and resources such asHTTPRequestandweb::Payload.But the current runtime requires functions and states to be
Sendable.I thought it's not mandatory to enforce
Sendtrait.
So I have tested on my local withoutSendand found no problem.Benefit
We can use
wasmtimein the thread-bounded (single-threaded) async runtimes.It's essential to use
wasmtimewithinactixecosystem, which provides the input parameters as!Send.Implementation
My current(initial) idea is to create a buddy methods like below:
/// Original method pub async fn instantiate_async( &self, mut store: impl AsContextMut<Data = T>, ) -> Result<Instance> where T: Send, { let mut store = store.as_context_mut(); assert!( store.0.async_support(), "must use sync instantiation when async support is disabled" ); store.on_fiber(|store| self.instantiate_impl(store)).await? } /// (New) buddy method pub async fn instantiate_async_single_rt( &self, mut store: impl AsContextMut<Data = T>, ) -> Result<Instance> { let mut store = store.as_context_mut(); assert!( store.0.async_support(), "must use sync instantiation when async support is disabled" ); store .on_fiber_single_rt(|store| self.instantiate_impl_single_rt(store)) .await? }
- The estimated number of new lines: 100~500
- Side-effect: no
Alternatives
My another idea is to use cargo features like
async-send.
But I think it may break the ecosystem (side-effect).
HoKim98 edited issue #11170:
Feature
I want to use
wasmtimeinactix-web, which uses!Sendruntime and resources such asHTTPRequestandweb::Payload.But the current runtime requires functions and states to be
Sendable.I thought it's not mandatory to enforce
Sendtrait.
So I have tested on my local withoutSendand found no problem.Benefit
We can use
wasmtimein the thread-bounded (single-threaded) async runtimes.It's essential to use
wasmtimewithinactixecosystem, which provides the input parameters as!Send.Implementation
My current(initial) idea is to create buddy methods like below:
/// Original method pub async fn instantiate_async( &self, mut store: impl AsContextMut<Data = T>, ) -> Result<Instance> where T: Send, { let mut store = store.as_context_mut(); assert!( store.0.async_support(), "must use sync instantiation when async support is disabled" ); store.on_fiber(|store| self.instantiate_impl(store)).await? } /// (New) buddy method pub async fn instantiate_async_single_rt( &self, mut store: impl AsContextMut<Data = T>, ) -> Result<Instance> { let mut store = store.as_context_mut(); assert!( store.0.async_support(), "must use sync instantiation when async support is disabled" ); store .on_fiber_single_rt(|store| self.instantiate_impl_single_rt(store)) .await? }
- The estimated number of new lines: 100~500
- Side-effect: no
Alternatives
My another idea is to use cargo features like
async-send.
But I think it may break the ecosystem (side-effect).
pchickey commented on issue #11170:
Sorry - we have had this discussion in the past, and concluded that, with the current features available in the Rust type system, wasmtime's Send bounds are unfortunately infectious and will be used throughout the public API.
After maintaining wasmtime in tokio embeddings for many years, I'm now maintaining a single-threaded web server embedding for wasmtime where Send is a burden. Unfortunately I have solved this by adding a lot of blatantly false
unsafe impl Send for <my types> {}throughout my codebase. But, we've balanced that against all of the known approaches to manage Send's infectiousness in wasmtime, and decided that its the best of the difficult options.I won't rule out changing the design of wasmtime completely, but its a big project that requires a lot of time and attention from a skilled contributor, and will require a working prototype and RFC in order to land. If you are up for that project, lets talk about it more, but if not, we'll have to suffer together with the way it is now.
HoKim98 commented on issue #11170:
In my case, specifically when using the
wasm32-wasip2target (i.e., the version that doesn't explicitly depend on thetokioruntime), I haven't actually run into the issue you described.In particular, I found that the core
on_fibermethod has evolved over the past year to the point where it has no real dependency onSend. That said, in environments like thetokioruntime where async tasks may be executed across threads, the "infectious" nature of Send often forces us to inject an otherwise void dependency.To address this, I implemented buddy methods like the one that work correctly even in
!Sendenvironments. It's less of a new invention and more of a clone of the existing code adapted for!Send.Finally, for types like
trait Stream, theSendconstraint isn't tied to the struct itself but to its implementation, which makes workarounds likeunsafe impl Send for <my types>fundamentally impossible. While this is arguably a positive side effect ofSend’s strictness, it also means we can’t bypass it even when we're sure the code will never leave the current thread.Just to share a personal thought: I believe the implementation is fairly straightforward and easy to follow. A prototype is available at the link above, and buddy versions of the multi-stage test code are also planned. I’d love your take on whether you think this should go through an RFC, or if it’s fine to submit as a plain PR.
alexcrichton commented on issue #11170:
Personally I feel that duplicating Wasmtime's API surface area needs to be very well motivated and ideally is something we can avoid. To that end I'd agree with @pchickey that, while not great,
unsafe impl Sendis hopefully the way to go. To that ened @HoKim98 I'd like to dig in to why this workaround does not work. Can you share example code?I agree that your patch is relatively small and looks easy to apply. Where I would disagree I think is how that would be maintained over time:
- Wasmtime already has a 2x API surface area with sync and async, and this would become 3x with sync, async, and async-send.
- Your patch does not include any tests, and extensively testing both sync and async is not trivial (e.g. adding async + async-send tests would not be trivial).
- The correctness of your change relies on the two async methods being basically the same over time, and there is no guarantee that this is the case. There's a likely possibility that they diverge over time and there are little protections against this.
These are not necessarily showstoppers but to me this is a high bar to clear, possibly higher than you're anticipating. Maintenance over time is an important factor to deciding on API surface area and that's one of my chief concerns here.
I found that the core on_fiber method has evolved over the past year to the point where it has no real dependency on Send
There is a long comment here about this. Yes
Sendcan be removed, no it is not sound to remove it.
HoKim98 commented on issue #11170:
Hello @alexcrichton , thanks for sharing a great inspection.
I have actively reviewed your suggestion. I completely agree that each of the three milestones you suggested is a big deal and difficult to maintain.
Can you share example code?
I create a sample project that I suffer from: https://github.com/HoKim98/my-actix-wasm-project
If this problem could be solved simply, at least for me, I would no longer need this issue.
HoKim98 edited a comment on issue #11170:
Hello @alexcrichton , thanks for sharing a great inspection.
I have actively reviewed your suggestion. I completely agree that each of the three milestones you suggested is a big deal and difficult to maintain.
Can you share example code?
I created a sample project that I suffer from: https://github.com/HoKim98/my-actix-wasm-project
If this problem could be solved simply, at least for me, I would no longer need this issue.
alexcrichton commented on issue #11170:
For any variables live over that
.awaitpoint, you'll need to use something like:struct UnsafeSend<T>(T); unsafe impl<T> Send for UnsafeSend<T> {}and wrap live variables in
UnsafeSendfollowed by accessing them through theUnsafeSendafterwards. In theory that should resolve the issue.
HoKim98 closed issue #11170:
Feature
I want to use
wasmtimeinactix-web, which uses!Sendruntime and resources such asHTTPRequestandweb::Payload.But the current runtime requires functions and states to be
Sendable.I thought it's not mandatory to enforce
Sendtrait.
So I have tested on my local withoutSendand found no problem.Benefit
We can use
wasmtimein the thread-bounded (single-threaded) async runtimes.It's essential to use
wasmtimewithinactixecosystem, which provides the input parameters as!Send.Implementation
My current(initial) idea is to create buddy methods like below:
/// Original method pub async fn instantiate_async( &self, mut store: impl AsContextMut<Data = T>, ) -> Result<Instance> where T: Send, { let mut store = store.as_context_mut(); assert!( store.0.async_support(), "must use sync instantiation when async support is disabled" ); store.on_fiber(|store| self.instantiate_impl(store)).await? } /// (New) buddy method pub async fn instantiate_async_single_rt( &self, mut store: impl AsContextMut<Data = T>, ) -> Result<Instance> { let mut store = store.as_context_mut(); assert!( store.0.async_support(), "must use sync instantiation when async support is disabled" ); store .on_fiber_single_rt(|store| self.instantiate_impl_single_rt(store)) .await? }
- The estimated number of new lines: 100~500
- Side-effect: no
Alternatives
My another idea is to use cargo features like
async-send.
But I think it may break the ecosystem (side-effect).
HoKim98 commented on issue #11170:
Thank you @alexcrichton it helped me a lot!
Last updated: Dec 06 2025 at 07:03 UTC