Skip to content

While RunAsync is running, Ctrl+C and SIGTERM can never end a hung app: every signal is cancelled, however many are sent #183

Description

@matt-edmondson

What's wrong

UIApplication.RunAsync registers an interrupt handler for the whole run (TUI/Services/UIApplication.cs:145) and disposes it only in finally (:176). ConsoleInterruptSource.OnSignal (TUI/Services/ConsoleInterruptSource.cs:40-46) cancels the runtime's default termination on every signal: Ctrl+C sets e.Cancel = true, and SIGTERM sets context.Cancel = true. It then only calls Shutdown(), which cancels a token. That token is observed only by the input loop, between keys.

So if the app's own code hangs, nothing will ever act on that token. That covers an OnHandleInput or a render that deadlocks or loops, and a synchronous wait inside a handler. Every further Ctrl+C or kill <pid> is cancelled as well. The only ways out are kill -9 or closing the terminal. Before #110/#117, the runtime's default behaviour ended such a process.

Reproduction (main 95d9508, real process and real signals)

The root is a StackPanel subclass whose OnHandleInput does Thread.Sleep(Timeout.Infinite). The provider's ReadInputAsync returns a key immediately. With await app.RunAsync() running, send three signals of each kind, 1 s apart:

SIGINT  x3: process STILL ALIVE   (needed kill -9)
SIGTERM x3: process STILL ALIVE   (needed kill -9)

Control: the same hanging handler called outside RunAsync exits after a single SIGINT or SIGTERM.

Why it matters

A bug in application code becomes a process that can't be interrupted. It also defeats supervisors that expect SIGTERM to end the process, such as systemd, docker stop and CI timeouts. Those only escalate to SIGKILL after a grace period.

Suggested fix / acceptance criteria

  • Cancel only the first signal of a run and turn it into a graceful Shutdown().
  • If a second Ctrl+C or SIGTERM arrives while shutdown is still pending, don't cancel it, so the runtime terminates the process. Alternatively, restore the terminal and call Environment.Exit.
  • Optionally, force the exit after a grace timeout that starts at the first signal.
  • Add a unit test through OnSignal / FakeInterruptSource: the first signal cancels and calls onInterrupt, and a second signal is not cancelled.

Activity

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

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions