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

2

22

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.

Herl answered 20/6, 2015 at 16:44 Comment(10)
Hi - still hoping for some help on this... but in the meantime I can comment that the reason for the requirement is a parallel-processing experimental approach where the Prolog executable can be interrupted by another process, and at that point if coughs up a 'state' value that represents its progress in the current search such that the processing can be split up from that point and assigned to other processors. There's plent of work in this area but my approach rather depends on this interrupt capability which was there in wamcc.Herl
@Bamb: sounds much like Condor. How large is that state? As a bottom-feeder, I wrote out a state regularly.Concertante
I've just worked out where a 'bounty' comes from... thanks false.Herl
You missed my question above?Concertante
Maybe you can describe the precise state you have? My experience is rather not to mess with signals, for I happened to be just on the bleeding edge. There are lots of techniques to avoid this. Extra question?Concertante
@false: I am actually using Condor for the Prolog worker processes although the prolog is spawned as a child of a more persistent C process that participates in a botnet (the original Skynet, not including the mythical movie version). But the 'state' is a fancy concept - it is actually a short sequence of integers representing a path through the (pure) Prolog search tree, which can be used to reconstruct the actual state...Herl
My key requirement is to be able to interrupt the prolog program and have it print something I can use. Eg it would be simple if I could catch(myprog, X, write(abc)) where the catch gets triggered by a signal...Herl
Looks very much like the config I had - except that my processes only ran at night. Interrupts challenge a Prolog system - you will have 0 chance to reproduce them. In many situations you can compress search trees using call_nth/2 down to a couple of integers. Thus writing is something like 50 bytes...Concertante
Thanks false.. I checked out call_nth and (nested) it is similar to what I'm doing.Herl
Not sure you got it :-).Concertante
H
12

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.

Herl answered 29/6, 2015 at 8:30 Comment(4)
When will a process split? How often does this occur? In my experience it is rather (foreseen) downtime which is a problem with massively parallel tasks.Concertante
"When will a process split? How often does this occur?" - linkHerl
I get currently for the link "an error has occured java.lang.NullPointerException: "Concertante
Thanks! The archive certainly works sometimes within 500 years.Concertante
B
10

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.

Beason answered 24/6, 2015 at 0:58 Comment(1)
Thanks mescalinum... v much appreciated. Shame as I really need to interrupt my compiled prolog programs. My next step will be to see if I can blend in some C to catch a signal and trigger a Throw...Herl

© 2022 - 2024 — McMap. All rights reserved.