Async signal handling in ada

Is there a way to make gnat play nice with asynchronous signal handling, i.e. signalfd(2)? From my experimentation so far, it seems that the gnat/ada runtime always spawns a separate signal handling thread (i.e. Interrupt_Manager), which is fine – but it always terminates the program at the first received event without attached handler, which is rather not fine.

I’m aware of pragma Interrupt_State, but it seems to exacerbate this issue, not help here: Interrupt_State(Int, SYSTEM) disallows attaching a handler, basically guaranteeing the abort-on-signal behaviour, while Interrupt_State(Int, USER) does allow attaching a handler but does not seem to mask the signal when no synchronous handler is attached.

In order to process the signal through signalfd it must not have been handled another way, so attaching a null handler in ada or setting it to SIG_IGN via sigaction is not an option. Is there any way to control the signal mask on the Interrupt_Manager thread?

Bump. I am also interested in async handling of events/data in Ada and I would like to know how people can do it.

I’m not very well versed in this, but would blocking the default handling at the kernel level help at all? Something like:

    sigset_t new_set, old_set;

    // 1. Initialize the set and add SIGINT
    sigemptyset(&new_set);
    sigaddset(&new_set, SIGINT);

    // 2. Block SIGINT, save the current mask in old_set
    if (sigprocmask(SIG_BLOCK, &new_set, &old_set) < 0) {
        perror("sigprocmask");
        return 1;
    }

Maybe that would keep the runtime from handling it? I haven’t tried anything like this mind you. It was just mentioined in the man file for signalfd(2): signalfd(2) - Linux manual page

I googled an example of how to use it.

The problem with [rt_]sigprocmask is that only affects the current thread; there is no way to set the signal mask for an already-running thread, which is why in C you set the signal mask before spawning any threads.

Luckily, I’ve found that what I need is the rather obvious GNAT.Signals.Block_Signal. Not sure why I couldn’t find it earlier, maybe I was looking specifially for signal masking rather than blocking. Anyway, this seems to work:

  function Create_Fd (Mask  : aliased kernel_sigset_t;
                      Flags : signalfd_flags_t) return C.int is
      Value : C.int;
   begin
      Value := signalfd (-1, Mask, Flags);
      if Value < 0 then
         raise Syscall_Error with "could not create signalfd: " & Errno_Message;
      end if;
      Iterate (Mask, GNAT.Signals.Block_Signal'Access);
      return Value;
   end Create_Fd;

(with Iterate being a custom procedural iterator that calls proc for every set bit in Mask, since Block_Signal only operates on one signal at a time).

It does seem that an explicit call read(2) is required to reset the pending signal flag. Converting the signalfd to a stream via C_Streams.fdopen() and then trying to read it via Ada.Direct_IO does return the signal data, however the program is now stuck in an endless loop between epoll/select indicating there is data on the file descriptor and the fd having no data to offer.

Also, pragma Interrupt_State hardly seems useful. The only signal for which it has any effect is SIGINT – all other signals can either be masked by default, or are reserved by the runtime and cannot be masked at all under pain of Program_Error.

We do this to block SIGPIPE when reading from sockets via our home-brew sockets package.

We have another tasks reading from a named pipe as well, which may cause SIGPIPE we’d like to ignore.


with Ada.Interrupts; use Ada.Interrupts;
with Ada.Interrupts.Names; use Ada.Interrupts.Names;

package Sockets.Sigpipe_Ignore is

   protected Signal_Handlers is
      procedure Ignore_Signal;
      pragma Interrupt_Handler(Ignore_Signal);
      pragma Attach_Handler(Ignore_Signal, Sigpipe);
   end Signal_Handlers;

end Sockets.Sigpipe_Ignore;

package body Sockets.Sigpipe_Ignore is

   protected body Signal_Handlers is

      procedure Ignore_Signal is
      begin
         null;
      end Ignore_Signal;

   end Signal_Handlers;

end Sockets.Sigpipe_Ignore;

This was on AIX, I don’t know if really needed on Linux, but we still have it in place.

I did not know a signal could carry user data - or I might be misunderstanding your use case