Module: KairosMcp::Daemon::SignalHandler

Defined in:
lib/kairos_mcp/daemon/signal_handler.rb

Overview

Signal handling for the daemon (design v0.2 §3.1).

Signal → action mapping:

SIGTERM  → graceful shutdown (sets @shutdown_requested)
SIGINT   → graceful shutdown (same as TERM, for `Ctrl-C` during foreground runs)
SIGHUP   → reload config      (enqueues :reload command)
SIGUSR1  → status dump        (enqueues :status_dump command)

Signal handlers run in the VM’s signal-handling context, where only a narrow subset of operations is safe. We therefore do as little work as possible inside the trap block — just flip a flag or push onto the thread-safe CommandMailbox — and let the main event loop do the work.

Constant Summary collapse

SUPPORTED_SIGNALS =
%w[TERM INT HUP USR1].freeze

Class Method Summary collapse

Class Method Details

.handle(daemon, sig) ⇒ Object

Dispatch a signal to the daemon. Kept public for tests that want to simulate signal delivery without actually raising signals.

CF-2 fix: only set atomic flags in signal context. The event loop translates flags into mailbox commands on the next tick.



62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/kairos_mcp/daemon/signal_handler.rb', line 62

def self.handle(daemon, sig)
  case sig
  when 'TERM', 'INT'
    daemon.request_shutdown!(sig)
  when 'HUP'
    daemon.request_reload!
  when 'USR1'
    daemon.request_status_dump!
  end
rescue StandardError
  # Signal handlers must never raise — swallow everything.
end

.install(daemon) ⇒ Array<String>

Install handlers on the given daemon instance.

The daemon must respond to:

- #request_shutdown!(signal)
- #mailbox  (returns a CommandMailbox)
- #logger   (returns a KairosMcp::Logger or duck-equivalent)

Parameters:

  • daemon (Daemon)

    the daemon to wire signals into

Returns:

  • (Array<String>)

    list of signals successfully installed



29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/kairos_mcp/daemon/signal_handler.rb', line 29

def self.install(daemon)
  installed = []
  SUPPORTED_SIGNALS.each do |sig|
    next unless signal_supported?(sig)

    begin
      Signal.trap(sig) { handle(daemon, sig) }
      installed << sig
    rescue ArgumentError, Errno::EINVAL
      # Platform doesn't support this signal — skip silently.
    end
  end
  installed
end

.signal_supported?(sig) ⇒ Boolean

Returns:

  • (Boolean)


75
76
77
# File 'lib/kairos_mcp/daemon/signal_handler.rb', line 75

def self.signal_supported?(sig)
  Signal.list.key?(sig)
end

.uninstallObject

Remove any handlers we installed (mainly for tests).



45
46
47
48
49
50
51
52
53
54
55
# File 'lib/kairos_mcp/daemon/signal_handler.rb', line 45

def self.uninstall
  SUPPORTED_SIGNALS.each do |sig|
    next unless signal_supported?(sig)

    begin
      Signal.trap(sig, 'DEFAULT')
    rescue ArgumentError, Errno::EINVAL
      # ignore
    end
  end
end