In GNU-Prolog, can I 'catch' a linux signal?

452 views Asked by At

Is there a way to 'trap' (e.g. 'catch') an operating system signal within GNU Prolog? (I'm using Ubuntu/Linux, latest gprolog).

I think a long time ago I used this approach in WAMCC, before that morphed into GNU Prolog:

:- catch(Long_Running_Goal,signal(2),write('program interrupted'))

But if I test this using a (repeat,fail) infinite loop with, for example

:- catch((repeat,fail),X,write(X)).

In the interpreter Ctrl-C still takes me to the trace/debugger, and the compiled program just quits if I interrupt it with kill -1, kill -2 etc.

I've tried compiling the program with --no-top-level in case the default toplevel somehow captures the signal, but that made no difference.

SWI-Prolog seems to have a suitable built-in predicate on_signal which serves the purpose but I'm looking for a solution with gprolog if that's possible.

2

There are 2 answers

4
Bambam On BEST ANSWER

Thanks to mescalinum who confirmed signal handling is not available by default in GNU Prolog.

But GNU Prolog has excellent support for user routines in C, and I've been able to write a small amount of C code which catches the Linux signal and triggers (if required) a Prolog exception (note mine is Ubuntu 14.04/GNU Prolog 1.3.0 so C type for init_signal function is Bool from gprolog.h - this changed in gprolog.h 1.3.1 onwards to PlBool - see 1.3.0 vs most recent manuals):

C code "signal.c":

#include <stdio.h>
#include <signal.h>
#include <gprolog.h>

/* signal handler */
void sig_handler(int signo)
{
  if (signo == SIGHUP)
  {
    printf("received SIGHUP\n");
    /* throw Prolog exception */
    Pl_Err_Instantiation();
  }
}

/* GNU Prolog  goal that registers the signal handler */
/* declared with :- foreign(init_signal).             */
Bool init_signal()
{
  if (signal(SIGHUP, sig_handler) == SIG_ERR)
  {
        printf("\ncan't catch SIGHUP\n");
  }
  printf("%s","SIGHUP handler registered\n");
  return TRUE;                  /* succeed */
}

Test usage in Prolog "test.pl" - the "long-running" query in this example is o_query, used in a 'catch' relation and can be interrupted with SIGHUP:

:- foreign(init_signal).

:- initialization(main).

main :- write('Prolog signal test program started'),
        nl,
        init_signal,
        catch(o_query,X,write('Prolog exception thrown')),
        nl,
        halt.

o_query :- repeat,
           sleep(1),
           fail.

Compile with gplc test.pl signal.c

Now if the program is run with ./test it can be interrupted from another terminal with kill -1 <test process id>

Bambam@desktop:~/prolog/signal$ ./test
Prolog signal test program started
SIGHUP handler registered
received SIGHUP
Prolog exception thrown
Bambam@desktop:~/prolog/signal$

For my purposes, I can usefully handle the incoming exception while I'm in the C signal handler, but reflecting it back to a Prolog 'throw' (in this case with a 'instantiation error') keeps the code tidily within Prolog.

The reason I want to be able to send (and catch) a signal to the executing GNU Prolog process is because my system is a high-performance parallel processing Prolog environment which can trigger any long-running Prolog process to dynamically 'split' itself into multiple parts which then execute on other machines. But you fundamentally cannot (with my method) predict the exact distribution of work and in due course other processors will be interrupted (i.e. sent a signal) to further split the workload.

1
fferri On

After looking at current gprolog source code where signal() is used:

  • src/BipsPl/os_interf_c.c: signal(SIGPIPE, SIG_IGN);
  • src/EnginePl/LINUX_SIGSEGV.c: signal(SIGSEGV, (void (*)()) SIGSEGV_Handler);
  • src/EnginePl/PPC_SIGSEGV.c: signal(SIGSEGV, (void (*)()) SIGSEGV_Handler);
  • src/EnginePl/SOLARIS_SIGSEGV.c: signal(SIGSEGV, (void (*)()) SIGSEGV_Handler);
  • src/EnginePl/stacks_sigsegv.c: signal(SIGSEGV, (void (*)(int)) SIGSEGV_Handler);
  • src/EnginePl/WIN32_all_SIGSEGV.c: signal(SIGSEGV, (void (*)(int)) SIGSEGV_Handler);
  • src/Linedit/ctrl_c.c: signal(sig, Wrapper_Handler);
  • src/Linedit/ctrl_c.c: signal(SIGINT, Wrapper_Handler);

we can see the only use of signals is:

  • to handle SIGINT (generated by pressing CTRL+C) in the REPL
  • to handle SIGSEGV
  • to ignore SIGPIPE

So it is not possible, unless you are willing to modify the source code.

Also, I could not find any mention of signals in the git commit messages.