Hi, I'm not quite sure if this is the best place to ask, but I've been thinking about the behavior for computations after task.return. Notably:
subtask.cancel would resolve immediately if a subtask has returned. If that's the case, how would I know if the task is actually cancelled or not? Or maybe subtask.cancel doesn't trully blocks until the end, but only blocks until task.cancel?task.cancel can't be called after task.return. So does that mean it would block the subtask.cancel a bit longer than expected? (e.g. if the cancellation woken some other coroutines and the event loop is busying with those stuff unable to return)task.return always allowed or is it defined per implementation? For example, I find it a bit weird if computation continues when run: async func() -> result has resolved, and it seems that the current wasmtime doesn't allow it (not quite sure how it is implemented now though)Once a subtask has either returned a value (e.g. by calling task.return) or confirmed cancellation (by calling task.cancel), the supertask which created it will receive either a RETURNED or a RETURN_CANCELLED event, respectively. After that, the supertask will receive no further events concerning that subtask, even if the subtask continues running. However, the supertask may still be able to communicate with the subtask by way of any streams or futures passed as parameters or returned as results.
There's no way to cancel a subtask for which a RETURNED or RETURN_CANCELLED event has been received, but you could do something equivalent at the WIT level by adding a cancel: future parameter to the function that creates the subtask. In that case, the supertask would have the write end of the future, while the subtask would have the read end, and when the supertask writes an item to that future, the subtask could interpret that as a request for it to exit. The function could also return a future if the supertask wanted confirmation that the subtask has finished exiting.
Zihang Ye said:
- If I understand correctly,
subtask.cancelwould resolve immediately if a subtask has returned. If that's the case, how would I know if the task is actually cancelled or not? Or maybesubtask.canceldoesn't trully blocks until the end, but only blocks untiltask.cancel?
The way to interpret subtask.cancel is: "I don't care about the return value any more, so stop whatever you're doing to compute that value". If the value has already been returned, it's too late to say that. There's no built-in mechanism to cancel post-return computation, so you have arrange that sort of thing at the WIT level as I described above.
Note that even a cancelled task (i.e. a task that has called task.cancel) can continue running, and there's nothing the supertask can do to prevent that.
Zihang Ye said:
- On the other side,
task.cancelcan't be called aftertask.return. So does that mean it would block thesubtask.cancela bit longer than expected? (e.g. if the cancellation woken some other coroutines and the event loop is busying with those stuff unable to return)
subtask.cancel can be called synchronously or asynchronously. If it's called synchronously, it will block until the subtask either calls task.return or task.cancel. If it's called asynchronously, the supertask can do other things while it is waiting for the subtask to call task.return or task.cancel.
Zihang Ye said:
- Is the computations after
task.returnalways allowed or is it defined per implementation?
Yes, computations after task.return are always allowed (and also after task.cancel), and are invisible to the supertask except indirectly via any streams or futures passed as parameters or results.
Zihang Ye said:
- For example, I find it a bit weird if computation continues when
run: async func() -> resulthas resolved, and it seems that the current wasmtime doesn't allow it (not quite sure how it is implemented now though)
That's a core part of the design, and it's what enables functions like foo: async func() -> stream<u8> to work. The only way the subtask can write to the stream it returns from that function is by writing to it after returning, since streams are unbuffered.
Can you elaborate on what you mean by "it seems that the current wasmtime doesn't allow it"? What, specifically, is Wasmtime not allowing?
Wow, thanks for your detailed explanation.
Can you elaborate on what you mean by "it seems that the current wasmtime doesn't allow it"? What, specifically, is Wasmtime not allowing?
I'm working on the codegen for wasip3 for MoonBit, and here's a prototype to let me know what to generate: https://github.com/peter-jerry-ye/async-wasi-cli/blob/80b30e1ca16cbf2115f00401b8c656ec0d45b0fd/gen/interface/wasi/cli/run/stub.mbt
So what it does is that it will spawn two coroutines, the first one prints Hello Wow and then reach the task.return. The second would sleep for some time and prints Hello World. And the result is that only Hello Wow is printed.
If you would like to try it out, you would need MoonBit toolchain, and wasm-tools, and just. The command is just run.
I tried to reproduce this example with Rust but I'm not that familiar with Rust, especially async Rust, so still wip.
Good catch; you're right that wasmtime run is not allowing post-return I/O (including monotonic-clock#wait-for) to complete. This PR fixes that.
See also https://github.com/bytecodealliance/wasmtime/issues/12187
Last updated: Jan 09 2026 at 13:15 UTC