Class: TTY::Command::Window::TrapManager
- Inherits:
-
Object
- Object
- TTY::Command::Window::TrapManager
- Defined in:
- lib/tty/command/window/trap_manager.rb
Overview
Process-global owner of the signals this gem cares about
(+WINCH+, INT, TERM, HUP).
Ruby's Signal.trap is a process singleton: two coordinators
installing their own trap for the same signal will silently
clobber each other and lose the host application's previous
handler. TrapManager mediates that by installing exactly one
trap per signal (the first time anyone subscribes) and
multiplexing to a subscriber list.
Ownership contract. Once a signal has been installed by TrapManager it remains installed for the life of the process (only TrapManager.reset!, which is test-only, uninstalls). This avoids clobbering a host-application handler that a caller installed after our first subscribe (follow-up review P1-6): restoring the snapshot we captured at first-install time would silently overwrite the host's newer handler.
Thread-safe. Trap-safe: the trap block reads a reference to the subscriber array under MRI's GVL and iterates a local snapshot; unsubscribe swaps the array reference so an in-flight trap keeps iterating the array it saw.
Class Method Summary collapse
-
.reset! ⇒ Object
Uninstall every subscriber and restore original handlers.
-
.subscribe(signal, callable) ⇒ #call
Register
callableas a handler forsignal. - .subscribed?(signal) ⇒ Boolean
-
.unsubscribe(signal, callable) ⇒ void
Remove a previously subscribed handler.
Class Method Details
.reset! ⇒ Object
Uninstall every subscriber and restore original handlers. Intended for tests and emergency teardown.
74 75 76 77 78 79 80 |
# File 'lib/tty/command/window/trap_manager.rb', line 74 def reset! @mutex.synchronize do # Materialize keys before iteration because uninstall mutates @originals. @subscribers.keys.to_a.each { |key| uninstall(key) } @subscribers.clear end end |
.subscribe(signal, callable) ⇒ #call
Register callable as a handler for signal.
39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/tty/command/window/trap_manager.rb', line 39 def subscribe(signal, callable) key = signal.to_s @mutex.synchronize do # Populate the subscriber list BEFORE installing the trap # so a signal arriving between Signal.trap returning and # the append cannot observe an empty list (follow-up # review P1-7). list = @subscribers[key] || [] @subscribers[key] = list + [callable] install_once(key) unless @originals.key?(key) end callable end |
.subscribed?(signal) ⇒ Boolean
83 84 85 |
# File 'lib/tty/command/window/trap_manager.rb', line 83 def subscribed?(signal) @mutex.synchronize { !(@subscribers[signal.to_s] || []).empty? } end |
.unsubscribe(signal, callable) ⇒ void
This method returns an undefined value.
Remove a previously subscribed handler.
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/tty/command/window/trap_manager.rb', line 56 def unsubscribe(signal, callable) key = signal.to_s @mutex.synchronize do list = @subscribers[key] next if list.nil? new_list = list.reject { |c| c.equal?(callable) } # Keep the installed trap even when the subscriber list # empties: uninstalling would restore the snapshot we took # at first-install time and clobber a host handler # installed after that snapshot (follow-up review P1-6). # reset! (test-only) is the one path that uninstalls. @subscribers[key] = new_list end end |