Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

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.

like image 220
Bambam Avatar asked Jun 20 '15 16:06

Bambam


People also ask

How do I compile in GNU Prolog?

The GNU Prolog compiler is a command-line compiler similar in spirit to a Unix C compiler like gcc. To invoke the compiler use the gplc command as follows: % gplc [OPTION]… FILE…


2 Answers

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.

like image 98
Bambam Avatar answered Oct 22 '22 01:10

Bambam


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.

like image 23
fferri Avatar answered Oct 22 '22 02:10

fferri