Combining SIGINT and Try/Catch

BlitzMax Forums/BlitzMax Programming/Combining SIGINT and Try/Catch

LordChaos(Posted 2013) [#1]
Hi! I need a functionality for my program to abort a potentially endless computation with the SIGINT signal (Control-C in most terminals).

I thought that it might be a good idea to just throw an exception when I receive the SIGINT signal. Surprisingly, this only works just once under OS X. Ideally, the code below would count the variable i up whenever SIGINT is received.

Any ideas?

Extern
	Function signal:Byte Ptr(signum:Int, handler:Byte Ptr)
End Extern

Local SIGINT:Int = 2

signal(SIGINT, abort)

Function abort()
	Throw "abort"
End Function

Local i:Int = 1

Repeat
	Try
		Print i
		Repeat
		Forever
	Catch ex:String
		Print ex
	End Try
	i :+ 1
Forever



Yasha(Posted 2013) [#2]
From here: http://en.cppreference.com/w/c/program/signal

"When signal handler is set to a function and a signal occurs, it is implementation defined whether signal(sig, SIG_DFL) will be executed immediately before the start of signal handler."

So perhaps you need to reapply the handler at the end of abort() to ensure it remains set?


LordChaos(Posted 2013) [#3]
Nope. Sorry, I should have mentioned this. It is particularly not working when used with Try/Catch. :(


Yasha(Posted 2013) [#4]
I guess it also depends on how Throw works in BlitzMax. If it doesn't use longjmp, but instead does some low-level machine-specific jump trickery, then the signal handler isn't valid and supposedly isn't allowed to return control to the main program. So it might not be possible to mix these two jump techniques.

Perhaps axe the entire Try/Catch construct and instead have the handler set a global exit flag for Repeat to detect?


LordChaos(Posted 2013) [#5]
Yep, it must have to do something with the Try/Catch implementation.

Problem is, I really can't use an exit flag as the code in the Try/Catch-Block is not mine. I use TinyScheme (a Scheme interpreter). Last option would be tinkering in TinyScheme's VM source code, but I really do not want to do that, actually...

Any alternative ideas are welcome!


Yasha(Posted 2013) [#6]
Is all of the (real) code in the Try section guaranteed to belong to TinyScheme (i.e. either VM code written in C or script code written in Scheme)?

'Cause if so, it might be safe to replace the Try block with a manual exception implemented using setjmp/longjmp. Normally this would be a terrible idea, as it would most likely completely corrupt the BlitzMax GC (uses refcounting so objects would go unreleased), but if there's no BlitzMax code in the block - or at least, no BlitzMax code that handles any kind of managed objects - it might well be safe to do (you'd probably need to wrap the commands up in a WithHandler function since taking the address of setjmp is illegal, but that's easily done). longjmp-ing from the signal handler is permitted and portable.


LordChaos(Posted 2013) [#7]
This seems to me like a reasonable option. I will have to figure out though if I can manage freeing that code from objects. Can you specify what the deal is with the WithHandler function? I would just use setjmp/longjmp like I would in C, what's the problem there?


Yasha(Posted 2013) [#8]
The short version is that setjmp isn't a function, it's a special unique C macro that the compiler has to be aware of to implement properly (don't quote me on this but I think it might not even be possible for it to be a normal function). It's only allowed to appear in a limited set of specific positions (see the standard for details, but basically it has to be the controlling expression of an if block), and you most definitely aren't allowed to take its address. Exporting it to a BlitzMax Extern function (even if it was possible, which it might not be - haven't checked) would possibly break BlitzMax code, as the compiler isn't aware of longjmp's ability to mess with registers.

The WithHandler arrangement puts it in a permitted C context where it shouldn't interfere with the calling code.

longjmp should actually be fine to export if you like, it's only setjmp which is special.


LordChaos(Posted 2013) [#9]
Thanks a lot so far, this information is really helping a lot!! This is my first attempt, obviously I'm doing something wrong as it crashes when the program receives the interrupt and I don't know why:

Import "inter.c"

Extern
	Function install_sigint_handler()
	Function inter_reset:Int()
End Extern

install_sigint_handler()

Repeat
	If inter_reset() = 0 Then
		Print i
		Repeat
		Forever
	Else
		Print "Interrupted."
	End If
	i :+ 1
Forever


inter.c
#include <signal.h>
#include <setjmp.h>

sigjmp_buf jmpbuf;

int inter_reset() {
	return sigsetjmp(jmpbuf,1);
}

void handler(int sig) {
	siglongjmp(jmpbuf,1);
}
     
void install_sigint_handler() {
     
	signal(SIGINT, handler);

}


I thought, encapsulating the jump in C functions would maybe prevent the registers from getting messed up as the call should preserve them. Turns out this is not the case or something goes wrong elsewhere.


Yasha(Posted 2013) [#10]
Did you look at the code I linked to in post #6? It contains a working version of the suggestion for Blitz3D, should be easy to modify for BlitzMax.

Your code is likely crashing because although you've wrapped setjmp itself, you don't continue within the same function (contrast mine where the "next action" is also passed into the function and called from within it). This means that after the setjmp... you return immediately, invalidating the jump buffer. Something else then gets called over the same stack space so when you jump to that stack position later, it contains junk data.

If you return after a setjmp call, it's no longer valid (certainly no longer safe), so anything you do that uses the jmp_buf must happen at the same stack level or higher.

(Also note: a setjmp - and sigsetjmp - call is not technically allowed to be the argument to return for this very reason: there's no possible legal use of it in that position.)


LordChaos(Posted 2013) [#11]
Thanks a million! I find those semantics really really tricky, but I got now thanks to your elaboration, I think.

Also, I'm really surprised that other people here use TinyScheme as well. I think it's a brilliant piece of software. :)

Import "inter.c"

Extern
	Function install_sigint_handler()
	Function inter_reset:Int(f:Byte Ptr)
End Extern

install_sigint_handler()

Global i:Int
Function loop()
	Print i
	Repeat
	Forever
End Function

Repeat
	inter_reset(loop)
	i :+ 1
Forever



inter.c
#include <signal.h>
#include <setjmp.h>


typedef void (*fun)();


sigjmp_buf jmpbuf;


int inter_reset(fun func) {
	if(!sigsetjmp(jmpbuf,1))
		func();
	else
		printf("Interrupted!");
}

void handler(int sig) {
	siglongjmp(jmpbuf,1);
}
     
void install_sigint_handler() {
     
	signal(SIGINT, handler);

}



Yasha(Posted 2013) [#12]
Heh, TinyScheme's a weird one. I can't honestly say I like anything about the interpreter design - it's really slow, not very elegant, not even very tiny - but it sure is pragmatic! It's the only Scheme interpreter I've ever actually deployed in a commercial project, 'cause the more elegant and powerful ones always seem to end up tripping over their own feet sooner or later (note, interpreter: for compiled code Gambit is pretty much king).