Destructor17 opened issue #12809:
Feature
Epoch interruption has no effect on memory.atomic.wait* instructions since they just block and give no opportunity for epoch checking to occur. So it would be nice to make threads work with epoch interruption.
Benefit
Being able to interrupt threads that stale on waiting for something to happen on another thread.
I'm personally interested in being able to do the following with programs targeting
wasip1-threads:
- Terminating all threads on some event (a trap, proc_exit, etc.)
- Pausing all threads for debug purposes
Implementation
I believe this problem can be solved by doing the following:
- Extending
wasmtime::runtime::vm::WaitResultwith an new case. (e.g.Interrupted = 3)- Adding a function to notify all shared memories in engine to return that new case. Maybe even call it on each
Engine::increment_epoch- Wrapping each call to memory_atomic_wait* builtin function with a loop. That loop would check epoch and continue if returned value is
Interrupted, and break otherwise. It may worth it to gate this transformation behind some condition, like weather epoch interruption is enabled, or a dedicated flag inConfig.This is what I initially tried to do on my own, but
wasmtime-internal-craneliftcrate scared me a bit, so I guess it is better to be done by someone who knows what to do.I'm believe there is no need to try to do the same with fuel consumption.
Alternatives
Alternative 1. Almost the same can be done by transforming WASM. Same loop, but timeout is limited to, let's say, 10 ms. Not sure when to break that loop. It comes with huge performance penalty of program that spawn lots of stale threads.
Alternative 2. Notify all waits on all shared memories without doing any changes, probably multiple times, SO memory.atomic.wait* would simply return 0 (Ok). May break some logic in guest code, so it is suitable only for terminating.
Destructor17 edited issue #12809:
Feature
Epoch interruption has no effect on memory.atomic.wait* instructions since they just block and give no opportunity for epoch checking to occur. So it would be nice to make threads work with epoch interruption.
Benefit
Being able to interrupt threads that stale on waiting for something to happen on another thread.
I'm personally interested in being able to do the following with programs targeting
wasip1-threads:
- Terminating all threads on some event (a trap, proc_exit, etc.)
- Pausing all threads for debug purposes
Implementation
I believe this problem can be solved by doing the following:
- Extending
wasmtime::runtime::vm::WaitResultwith an new case. (e.g.Interrupted = 3)- Adding a function to notify all shared memories in engine to return that new case. Maybe even call it on each
Engine::increment_epoch- Wrapping each call to memory_atomic_wait* builtin function with a loop. That loop would check epoch and continue if returned value is
Interrupted, and break otherwise. It may worth it to gate this transformation behind some condition, like weather epoch interruption is enabled, or a dedicated flag inConfig.This is what I initially tried to do on my own, but
wasmtime-internal-craneliftcrate scared me a bit, so I guess it is better to be done by someone who knows what to do.I believe there is no need to try to do the same with fuel consumption.
Alternatives
Alternative 1. Almost the same can be done by transforming WASM. Same loop, but timeout is limited to, let's say, 10 ms. Not sure when to break that loop. It comes with huge performance penalty of program that spawn lots of stale threads.
Alternative 2. Notify all waits on all shared memories without doing any changes, probably multiple times, SO memory.atomic.wait* would simply return 0 (Ok). May break some logic in guest code, so it is suitable only for terminating.
Destructor17 edited issue #12809:
Feature
Epoch interruption has no effect on memory.atomic.wait* instructions since they just block and give no opportunity for epoch checking to occur. So it would be nice to make threads work with epoch interruption.
Benefit
Being able to interrupt threads that stale on waiting for something to happen on another thread.
I'm personally interested in being able to do the following with programs targeting
wasip1-threads:
- Terminating all threads on some event (a trap, proc_exit, etc.)
- Pausing all threads for debug purposes
Implementation
I believe this problem can be solved by doing the following:
- Extending
wasmtime::runtime::vm::WaitResultwith an new case. (e.g.Interrupted = 3)- Adding a function to notify all shared memories in engine to return that new case. Maybe even call it on each
Engine::increment_epoch- Wrapping each call to memory_atomic_wait* builtin function with a loop. That loop would check epoch and continue if returned value is
Interrupted, and break otherwise. It may worth it to gate this transformation behind some condition, like weather epoch interruption is enabled, or a dedicated flag inConfig.This is what I initially tried to do on my own, but
wasmtime-internal-craneliftcrate scared me a bit, so I guess it is better to be done by someone who knows what to do.I believe there is no need to try to do the same for fuel consumption.
Alternatives
Alternative 1. Almost the same can be done by transforming WASM. Same loop, but timeout is limited to, let's say, 10 ms. Not sure when to break that loop. It comes with huge performance penalty of program that spawn lots of stale threads.
Alternative 2. Notify all waits on all shared memories without doing any changes, probably multiple times, so memory.atomic.wait* instructions would simply return 0 (Ok). May break some logic in guest code, so it is suitable only for terminating.
Destructor17 edited issue #12809:
Feature
Currently epoch interruption has no effect on memory.atomic.wait* instructions since they just block and give no opportunity for epoch checking to occur. It would be nice to make threads work with epoch interruption.
Benefit
Being able to interrupt threads that stale on waiting for something to happen on another thread.
I'm personally interested in being able to do the following with programs targeting
wasip1-threads:
- Terminating all threads on some event (a trap, proc_exit, etc.)
- Pausing all threads for debug purposes
Implementation
I believe this problem can be solved by doing the following:
- Extending
wasmtime::runtime::vm::WaitResultwith an new case. (e.g.Interrupted = 3)- Adding a function to notify all shared memories in engine to return that new case. Maybe even call it on each
Engine::increment_epoch- Wrapping each call to memory_atomic_wait* builtin function with a loop. That loop would check epoch and continue if returned value is
Interrupted, and break otherwise. It may worth it to gate this transformation behind some condition, like weather epoch interruption is enabled, or a dedicated flag inConfig.This is what I initially tried to do on my own, but
wasmtime-internal-craneliftcrate scared me a bit, so I guess it is better to be done by someone who knows what to do.I believe there is no need to try to do the same for fuel consumption.
Alternatives
Alternative 1. Almost the same can be done by transforming WASM. Same loop, but timeout is limited to, let's say, 10 ms. Not sure when to break that loop. It comes with huge performance penalty of program that spawn lots of stale threads.
Alternative 2. Notify all waits on all shared memories without doing any changes, probably multiple times, so memory.atomic.wait* instructions would simply return 0 (Ok). May break some logic in guest code, so it is suitable only for terminating.
Destructor17 edited issue #12809:
Feature
Currently epoch interruption has no effect on memory.atomic.wait* instructions since they just block and give no opportunity for epoch checking to occur. It would be nice to make threads work with epoch interruption.
Benefit
Being able to interrupt threads that stale on waiting for something to happen on another thread.
I'm personally interested in being able to do the following with programs targeting
wasip1-threads:
- Terminating all threads on some event (a trap, proc_exit, etc.)
- Pausing all threads for debug purposes
Implementation
I believe this problem can be solved by doing the following:
- Extending
wasmtime::runtime::vm::WaitResultwith an new case. (e.g.Interrupted = 3)- Adding a function to notify all shared memories in engine to return that new case. Maybe even every call it on each
Engine::increment_epoch- Wrapping each call to memory_atomic_wait* builtin function with a loop. That loop would check epoch and continue if returned value is
Interrupted, and break otherwise. It may worth it to gate this transformation behind some condition, like weather epoch interruption is enabled, or a dedicated flag inConfig.This is what I initially tried to do on my own, but
wasmtime-internal-craneliftcrate scared me a bit, so I guess it is better to be done by someone who knows what to do.I believe there is no need to try to do the same for fuel consumption.
Alternatives
Alternative 1. Almost the same can be done by transforming WASM. Same loop, but timeout is limited to, let's say, 10 ms. Not sure when to break that loop. It comes with huge performance penalty of program that spawn lots of stale threads.
Alternative 2. Notify all waits on all shared memories without doing any changes, probably multiple times, so memory.atomic.wait* instructions would simply return 0 (Ok). May break some logic in guest code, so it is suitable only for terminating.
Destructor17 edited issue #12809:
Feature
Currently epoch interruption has no effect on memory.atomic.wait* instructions since they just block and give no opportunity for epoch checking to occur. It would be nice to make threads work with epoch interruption.
Benefit
Being able to interrupt threads that stale on waiting for something to happen on another thread.
I'm personally interested in being able to do the following with programs targeting
wasip1-threads:
- Terminating all threads on some event (a trap, proc_exit, etc.)
- Pausing all threads for debug purposes
Implementation
I believe this problem can be solved by doing the following:
- Extending
wasmtime::runtime::vm::WaitResultwith an new case. (e.g.Interrupted = 3)- Adding a function to notify all shared memories in engine to return that new case. Maybe even call it on each
Engine::increment_epoch- Wrapping each call to memory_atomic_wait* builtin function with a loop. That loop would check epoch and continue if returned value is
Interrupted, and break otherwise. It may worth it to gate this transformation behind some condition, like weather epoch interruption is enabled, or a dedicated flag inConfig.This is what I initially tried to do on my own, but
wasmtime-internal-craneliftcrate scared me a bit, so I guess it is better to be done by someone who knows what to do.I believe there is no need to try to do the same for fuel consumption.
Alternatives
Alternative 1. Almost the same can be done by transforming WASM. Same loop, but timeout is limited to, let's say, 10 ms. Not sure when to break that loop. It comes with huge performance penalty of program that spawn lots of stale threads.
Alternative 2. Notify all waits on all shared memories without doing any changes, probably multiple times, so memory.atomic.wait* instructions would simply return 0 (Ok). May break some logic in guest code, so it is suitable only for terminating.
Destructor17 edited issue #12809:
Feature
Currently epoch interruption has no effect on memory.atomic.wait* instructions since they just block and give no opportunity for epoch checking to occur. It would be nice to make threads work with epoch interruption.
Benefit
Being able to interrupt threads that stale on waiting for something to happen on another thread.
I'm personally interested in being able to do the following with programs targeting
wasip1-threads:
- Terminating all threads on some event (a trap, proc_exit, etc.)
- Pausing all threads for debug purposes
Implementation
I believe this problem can be solved by doing the following:
- Extending
wasmtime::runtime::vm::WaitResultwith an new case. (e.g.Interrupted = 3)- Adding a function to notify all shared memories in engine to return that new case. Maybe even call it on each
Engine::increment_epoch- Wrapping each call to memory_atomic_wait* builtin function with a loop. That loop would check epoch and continue if returned value is
Interrupted, and break otherwise. It may worth it to gate this transformation behind some condition, like weather epoch interruption is enabled, or a dedicated flag inConfig.This is what I initially tried to do on my own, but
wasmtime-internal-craneliftcrate scared me a bit, so I guess it is better to be done by someone who knows what to do.I believe there is no need to try to do the same for fuel consumption.
Alternatives
Alternative 1. Almost the same can be done by transforming WASM. Same loop, but timeout is limited to, let's say, 10 ms. Not sure when to break that loop. It comes with huge performance penalty for programs that spawn lots of stale threads.
Alternative 2. Notify all waits on all shared memories without doing any changes, probably multiple times, so memory.atomic.wait* instructions would simply return 0 (Ok). May break some logic in guest code, so it is suitable only for terminating.
Destructor17 edited issue #12809:
Feature
Currently epoch interruption has no effect on memory.atomic.wait* instructions since they just block and give no opportunity for epoch checking to occur. It would be nice to make threads work with epoch interruption.
Benefit
Being able to interrupt threads that stale on waiting for something to happen on another thread.
I'm personally interested in being able to do the following with programs targeting
wasip1-threads:
- Terminating all threads on some event (a trap, proc_exit, etc.)
- Pausing all threads for debug purposes
Implementation
I believe this problem can be solved by doing the following:
- Extending
wasmtime::runtime::vm::WaitResultwith an new case. (e.g.Interrupted = 3)- Adding a function to notify all shared memories in engine to return that new case. Maybe even call it on each
Engine::increment_epoch- Wrapping each call to memory_atomic_wait* builtin function with a loop. That loop would check epoch and continue if returned value is
Interrupted, and break otherwise. It may worth it to gate this transformation behind some condition, like weather epoch interruption is enabled, or a dedicated flag inConfig.This is what I initially tried to do on my own, but
wasmtime-internal-craneliftcrate scared me a bit, so I guess it is better to be done by someone who knows what to do.I believe there is no need to try to do the same for fuel consumption.
Alternatives
Alternative 1. Almost the same can be done by transforming WASM. Same loop, but timeout is limited to, let's say, 10 ms. Not sure when to break that loop. It comes with huge performance penalty for programs that spawn lots of stale threads.
Alternative 2. Notify all waits on all shared memories without doing any changes, probably multiple times, so memory.atomic.wait* instructions would simply return 0 (Ok). May break some logic in guest code, so it is suitable only for terminating.
Alternative 3. That loop, as well as checking for epoch, can be implemented on the host side, but I don't know how to access current epoch value from there.
cfallin commented on issue #12809:
Thanks for filing an issue for this!
I think your suggested approach is, at a high level, what we would prefer. We definitely do not want Alternative 2 because it violates Wasm semantics. Alternative 1, instrumentation-based, is heavyweight and generally not our preferred approach either.
The way I would probably go about this is to implement a mechanism on the
Engineto register a thread-to-be-parked that should be awoken on epoch change (with a handle that implementsDropto de-register). Then thethread::park-based implementation would need to have a reference toEngineplumbed through, and would do a scoped thing registering on theEnginejust for the duration of the wait. I am imagining something likelet mut park_handle = Engine.park_handle(); match park_handle.park_timeout(timeout) { EngineParkResult::Notified => ..., EngineParkResult::EpochChange => ... }replacing the
std::thread::park_timeoutcall here. That implies the existence of mutex-protected registration state insideEngine. As you say all of this should be gated on epoch-interruption actually being enabled; park-handles should be no-ops that directly pass through to thread parking otherwise. Then theEpochChangereturn needs to be plumbed upward to the async layer to do an epoch yield (see the normal epoch yield libcall), and that whole thing needs to be wrapped in a retry loop as you say so we don't return to the Wasm from the wait libcall until we get a true notify result.Others may have other thoughts too but that seems like a workable design to me. I think that this should be implementable without changing the compiled code at all -- wait operations are still single libcalls.
Destructor17 commented on issue #12809:
And one more thing: I'm believe timeout should be somehow altered in that retry loop. Otherwise, if guest code wants to wait for 1s, but epoch change occurs every 0.5s,
EngineParkResult::Notifiedwould never be returned. It makes sense to set a deadline instead, but I'm also unsure weather time spent by epoch handling should be accounted.
Destructor17 edited a comment on issue #12809:
And one more thing: I'm believe timeout should be somehow altered in that retry loop. Otherwise, if guest code wants to wait for 1s, but epoch change occurs every 0.5s, libcall would never return. It makes sense to set a deadline instead, but I'm also unsure weather time spent by epoch handling should be accounted.
cfallin commented on issue #12809:
And one more thing: I'm believe timeout should be somehow altered in that retry loop
I don't think that's necessary actually, with the design I outlined: the idea is that the
Enginewould also hold a list of threads currently parked on any memory wait, and would immediately unpark/wake-up all such threads on an epoch change.In other words, a timeout isn't needed as part of the wakeup path, and we really don't want that either, because of the additional latency implications.
alexcrichton added the wasm-proposal:threads label to Issue #12809.
alexcrichton commented on issue #12809:
Personally I'm a bit wary to push shared memory too too much further, especially with possibly-invasive features like this, without a champion for shared memory. Shared memory has quite a few open issues (#4245) and not much work going towards resolving these issues. This is one behavior that stems from the "spec tests pass" implementation we currently have where we are still relatively far from a production-quality implementation with all the expected knobs and such.
I mostly want to caution you @Destructor17 insofar that
wasm32-wasip1-threadsis effectively a dead target in terms of upstream development. Wasmtime can run those binaries but not in a way that's highly customizable to embedders (e.g. this issue). Supporting these use cases and adding more features would require a lot of work on your (or someone else's) part in terms of designing the feature, working with maintainers to ensure it's appropriate, implementing/landing/etc.
Destructor17 commented on issue #12809:
wasm32-wasip1-threadsis effectively a dead target in terms of upstream developmentSad to hear that.
wasm32-wasip1-threadsseem to be the only usable WASI target with threads support so far. I'm looking forward for WASIp3, but for now current state of shared memory support in Wasmtime is fine for my particular usecase. As about this missing feature - it can be easily implemented and kept off-tree and unpolished, as I don't know if other Wasmtime users would actually benefit from it.
Last updated: Apr 12 2026 at 23:10 UTC