Skip to content

A tray run whose UI thread or OnStop hangs can only be stopped with SIGKILL: every repeated Ctrl+C, SIGTERM and SIGHUP is cancelled and does nothing #63

Closed

Description

@matt-edmondson

What's wrong

Once a tray host exists, ShutdownSignals.RequestShutdown returns true for every signal, which cancels the signal's default handling. It ignores what the host's own RequestShutdown returned (TrayApp/Tray/ShutdownSignals.cs L63-L72):

_ = running.RequestShutdown();
return true;

TrayApplication.RequestShutdown posts Shutdown to the UI thread only the first time (TrayApplication.cs L255-L262). The test ARepeatedSignal_IsStillTakenButPostsNoSecondShutdown locks this behaviour in.

So if the posted shutdown can't complete, nothing short of SIGKILL ends the process. The shutdown can stall in two ways:

  • The UI thread is blocked in tool code. For example, a toggle setter or command waiting on D-Bus, a hung network mount, or a lock.
  • The tool's OnStop hangs. OnStop runs on the UI thread, from OnExit → Dispose.

Every normal way to stop the process is swallowed:

  • Ctrl+C in the launching terminal
  • kill
  • systemctl stop (systemd waits out its stop timeout, then sends SIGKILL)
  • closing the terminal

A terminal user has to go and find the PID.

ConsoleRunner has a smaller version of the same gap. Its SIGTERM registration (ConsoleRunner.cs L66) is still active while OnStop runs in the finally, so a SIGTERM sent during a hung OnStop is also cancelled.

Repro (ran)

A process holds a ShutdownSignals over a real TrayApplication whose dispatcher is never pumped, which stands in for a busy UI thread. It then receives TERM, TERM, INT, INT and HUP, 0.5 s apart:

pid 2974 ready
still alive after TERM,TERM,INT,INT,HUP
... Killed (exit=137, only kill -9 worked)

Why it matters

The #21 fix exists so that a signal ends the run through OnStop and the preference flush. It shouldn't also take away the user's last way to stop a wedged tool. The usual CLI convention is that a second Ctrl+C or SIGTERM forces the exit. Before #52/#55, Ctrl+C still killed a stuck tray, so this is arguably a regression.

Suggested fix

  • In ShutdownSignals.RequestShutdown, return running.RequestShutdown(). Then only the first signal is cancelled, and a repeated signal gets its default handling and ends the process.
  • If that's too abrupt, a softer option is a grace period: cancel repeats for N seconds after the first signal, then let them through.
  • In ConsoleRunner, apply the same rule: either drop the SIGTERM registration before OnStop runs, or let repeated signals through.

Acceptance criteria

  • After the first signal has posted a shutdown, a second Ctrl+C, SIGTERM or SIGHUP is not cancelled.
  • ARepeatedSignal_IsStillTakenButPostsNoSecondShutdown is updated to reflect this.
  • scripts/check-tray-signals.sh still passes: a single signal still ends the run through OnStop with exit code 0.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

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