Stream: wasi

Topic: Questions on computations after `task.return`


view this post on Zulip Zihang Ye (Dec 18 2025 at 04:03):

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:

  1. If I understand correctly, 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?
  2. On the other side, 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)
  3. Is the computations after 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)

view this post on Zulip Joel Dice (Dec 18 2025 at 15:11):

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.

view this post on Zulip Joel Dice (Dec 18 2025 at 15:16):

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.

view this post on Zulip Joel Dice (Dec 18 2025 at 15:20):

Zihang Ye said:

  1. If I understand correctly, 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?

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.

view this post on Zulip Joel Dice (Dec 18 2025 at 15:23):

Zihang Ye said:

  1. On the other side, 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)

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.

view this post on Zulip Joel Dice (Dec 18 2025 at 15:28):

Zihang Ye said:

  1. Is the computations after task.return always 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:

  1. 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)

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?

view this post on Zulip Zihang Ye (Dec 19 2025 at 02:19):

Wow, thanks for your detailed explanation.

view this post on Zulip Zihang Ye (Dec 19 2025 at 09:55):

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.

Contribute to peter-jerry-ye/async-wasi-cli development by creating an account on GitHub.

view this post on Zulip Joel Dice (Dec 19 2025 at 15:53):

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.

Previously, wasmtime run only ran wasi:cli/run@0.3.x tasks until they returned a result, then exited. That meant any post-return computation done by the task might not have a chance to run. This c...

view this post on Zulip Joel Dice (Dec 19 2025 at 16:20):

See also https://github.com/bytecodealliance/wasmtime/issues/12187

Currently, wasmtime run's invoke_component function uses Func::call_async to invoke the function. However, that will only run the guest task until it produces a result, then print that result an ex...

Last updated: Jan 09 2026 at 13:15 UTC