signal_handler.pony

use @pony_asio_event_create[AsioEventID](
  owner: AsioEventNotify,
  fd: U32,
  flags: U32,
  nsec: U64,
  noisy: Bool)
use @pony_asio_event_unsubscribe[None](event: AsioEventID)
use @pony_asio_event_destroy[None](event: AsioEventID)

actor SignalHandler is AsioEventNotify
  """
  Listen for a specific signal.

  Multiple SignalHandlers can be registered for the same signal — up to 16
  per signal number. All registered handlers will be notified when the
  signal is received, in no particular order. If a handler cannot be
  registered (the 16-subscriber limit is reached, or the runtime fails to
  register with the operating system), its notify's `registration_failed`
  is called with the reason and the handler is automatically disposed;
  `apply` will not have run.

  If the wait parameter is true, the program will not terminate until
  the SignalHandler's dispose method is called or the SignalNotify
  returns false after handling the signal. Disposing a SignalHandler
  unsubscribes it from the signal and is required to allow the runtime
  to garbage collect the handler.
  """
  let _notify: SignalNotify
  let _sig: U32
  var _event: AsioEventID

  new create(auth: SignalAuth, notify: SignalNotify iso, sig: HandleableSignal,
    wait: Bool = false)
  =>
    """
    Create a signal handler for a validated signal number.
    """
    _notify = consume notify
    _sig = sig()
    _event =
      @pony_asio_event_create(this, 0, AsioEvent.signal(), _sig.u64(), wait)

  be raise(auth: SignalAuth) =>
    """
    Raise the signal, process-wide: every currently subscribed handler for
    this signal is notified, not just this one. If no handler is subscribed
    when the signal is delivered — every handler was disposed, or none ever
    registered — the signal's current disposition applies: the operating
    system default for most signals, which ends the process for a terminating
    one, but SIG_IGN for a signal the runtime keeps ignored such as SIGPIPE,
    which does nothing.
    """
    SignalRaise(auth, _sig)

  be dispose(auth: SignalAuth) =>
    """
    Dispose of the signal handler, unsubscribing from the signal.
    """
    _dispose()

  be _event_notify(event: AsioEventID, flags: U32, arg: U32) =>
    """
    Called when the signal is received, when registration fails (the
    event arrives errored), or when the AsioEventID can be destroyed.
    """
    if AsioEvent.disposable(flags) then
      // The runtime sends this only after it has finished the
      // unregistration (the backends commit the teardown before the
      // disposal notification), so this is the moment `disposed` means:
      // the handler no longer affects signal delivery.
      @pony_asio_event_destroy(event)
      _notify.disposed()
    elseif (event is _event) and AsioEvent.errored(flags) then
      // The runtime reports why registration failed in the event's arg:
      // 1 = subscriber limit, anything else = the OS refused. The ASIO
      // backends define these codes (SIGNAL_ERR_* in epoll.c, kqueue.c, and
      // sock_notify.c); they and this mapping must stay in step.
      _notify.registration_failed(
        if arg == 1 then SignalSubscriberLimit
        else SignalRegistrationRefused end)
      _dispose()
    elseif event is _event then
      if not _notify(arg) then
        _dispose()
      end
    end

  fun ref _dispose() =>
    """
    Dispose of the AsioEventID. The notify's `disposed` is not called here:
    it fires when the runtime confirms the unregistration, in the
    disposable arm of `_event_notify`.
    """
    if not _event.is_null() then
      @pony_asio_event_unsubscribe(_event)
      _event = AsioEvent.none()
    end