Skip to content

Map Ctrl+C to cancellation of the main function #858

Description

@badeend

Currently, WASI does not describe any way for a CLI guest to receive the Ctrl+C signal (SIGINT on Unix). This signal is commonly used to request graceful shutdown, giving the application some time to e.g.:

  • clean up resources,
  • flush buffers to disk,
  • stop listening for new network requests, while letting the existing ones run to completion,
  • etc..

The component model has built-in support for async cancellation, and I think that in the CLI world Ctrl+C maps neatly to cancellation of the main (run) function.

I propose we update the documentation of the run function to say something along the lines of:

If a host wishes to propagate Ctrl+C into the guest, then cancellation of the run task is the canonical way to do so.

WDYT?


Related: #166

Activity

  1. added theissue type on Dec 27, 2025
  2. yoshuawuyts commented on Dec 28, 2025

    @yoshuawuyts
    Member

    Conceptually I like this a fair bit! Ctrl+C is the most commonly used Unix signal, and it seems like it should unambiguously map to cancellation of the main function.

    There are additional questions about how/whether we could handle other signals in wasi:cli. But I don't think we need to answer those now to support this particular behavior.

  3. badeend commented on Dec 28, 2025

    @badeend
    MemberAuthor

    There are additional questions about how/whether we could handle other signals in wasi:cli.

    The referenced issue above already goes into some detail on that topic with conclusions like:

    (DG): Full POSIX-style signal handling would likely be a massive undertaking, involving complex security, portability, and VM implementation considerations, and probably core wasm semantics changes (outside the scope of WASI).
    (NF): POSIX signals, in their full generality, are infamously tricky, footgun-y, and generally not a paragon of API design.

    So, I wanted to stay as far away from this as possible. 😃 Ctrl+C / SIGINT is by far the most commonly used one, and is also one of the few signals (if not the only) that has a 1:1 counterpart on Windows. So I'd say: lets try just this one first.

  4. yoshuawuyts commented on Dec 28, 2025

    @yoshuawuyts
    Member

    Oh I hadn't clicked through on the other link; that's good to know! But it sounds like we're on the same page about this!

  5. lann commented on Dec 29, 2025

    @lann
    Contributor

    I'm broadly in favor of adding some kind of documentation about this. Some questions/thoughts:

    • "Graceful shutdown" won't be meaningful for all guests; what (if any) default behavior should be prescribed for cancellation? (Transitively cancel subtasks? Call exit_with_code(130)?)
    • Async cancellation is cooperative and could take an indeterminate amount of time. Should we say anything about this situation? It's somewhat common for systems to treat a second Ctrl+C as a less-graceful shutdown signal, for example.
    • We should be careful to distinguish this as not being simply a "propagation" of SIGINT/CTRL_C_EVENT here. Async cancellation is fundamentally less powerful than signals but close enough in simple/happy-path cases to cause confusion.
  6. badeend commented on Dec 29, 2025

    @badeend
    MemberAuthor

    what (if any) default behavior should be prescribed for cancellation?

    For the guest to wrap up ASAP.

    Transitively cancel subtasks? Call exit_with_code(130)?

    Both of those are indeed examples of how the guest could implement cancellation. Ultimately, neither the host nor the spec need to care about how the guest decides to wrap up.

    Async cancellation is cooperative and could take an indeterminate amount of time. Should we say anything about this situation?

    SIGINT is cooperative too, so I don't think we'll be introducing any new unexpected behavior on that front. But I agree that we should document that hosts are free to kill the process on a second Ctrl+C.

    Async cancellation is fundamentally less powerful than signals

    In what sense exactly? Assuming we're talking only about SIGINT and not signals in its full generality, I think they're equivalent.

    close enough in simple/happy-path cases to cause confusion.

    What confusion do you foresee?

  7. lann commented on Dec 30, 2025

    @lann
    Contributor

    Ultimately, neither the host nor the spec need to care about how the guest decides to wrap up.

    This is probably true when the host is the caller but may not be true for inter-component calls.
    The quickest way for a callee to wrap up would be to trap which is probably not what a component caller would want from cancellation / graceful shutdown.

    close enough in simple/happy-path cases to cause confusion.

    What confusion do you foresee?

    I suppose this will depend on how async cancellation is expected to behave more generally, but where SIGINT's default behavior is preemptive termination, it seems like async cancellation's default behavior may be less reliable. My expectation for SIGINT is that it will either terminate immediately or it will trigger some documented graceful shutdown / cleanup process, but with component composition the cancellation behavior may not be entirely under the run implementer's control.

  8. alexcrichton commented on Feb 10, 2026

    @alexcrichton
    Contributor

    While modeling ctrl-c as an async cancel of the run function, I'm not sure that'll work out in practice w.r.t. how toolchains are going to interact with a main function.

    For @lann's concerns our historical thinking is that async cancellation is generally a cooperative system where each component is responsible for propagating the signal to callees. In Rust for example when an async export is cancelled that'll drop the future representing the export which has the effect of cancelling anything that was in-progress there as well. This sort of maps to signals and sort of not. The default behavior of signals to kill everything doesn't map well, but the behavior of "whoever caught the signal has to forward that to others" is modeled here as well. For example if I install a SIGINT handler there's no guarantee that I actually respect it and properly shut down the libraries I'm using. Regardless, though, I wouldn't personally see this cooperative nature as a blocker for modeling ctrl-c as such.

    My main concern is that a lot of programs are likely going to be using wasi-libc or some other sync-looking main function signature in guest languages (e.g. performing a sync-lift into an async type of run: async func() -> ...). In doing so ctrl-c is only delivered to blocking calls flagged as cancellable, and all wasi-libc could really do is surface that as a return value of poll for example. That's generally not how SIGINT works in native platforms at least.

    Integrating ctrl-c into a sync-looking main would probably require something along the lines of:

    1. A wasi-libc "sigaction" which is a which is a glorified callback registration for ctrl-c
    2. All calls to blocking functions anywhere in wasi-libc would be flagged as cancellable
    3. Upon seeing anything cancelled wasi-libc would invoke the ctrl-c callback and hope it does something reasonable. Probably EINTR then returned and pray for the best.

    Well, ok, maybe this isn't as bad as I originally thought? Supporting ctrl-c in wasi-libc, regardless though, is I think going to be a quite involved piece of implementation

  9. lukewagner commented on Feb 11, 2026

    @lukewagner
    Member

    Once we've added a proper callback type to WIT, then agreed that adding some new sigaction-like import that allows guest code to register a callback would make sense and would avoid the tricky plumbing questions w.r.t run cancellation. In fact, once we have shared functions in core wasm, if the callback is lifted from a shared function, then semantically it'd be fine to call it immediately when the host receives the signal as-if the callback were called from some "other" thread which would make it a pretty effective approximation of externally-originating signals (like SIGINT and SIGIO).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P-cliProposal: wasi-cli

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions