catch hitting crtl-c twice

M

Michael Goerz

Hi,

is there a way to catch if an interrupt (ctrl-c) is called twice in a
certain time window? Catching the first one is done by

$SIG{INT} = \&refresh;

but now I want to detect if the user hits ctrl-c again while that
handler is running (there can be a 'sleep 1' inside the handler to wait
for this), so that the program can exit in that case. As I observed, the
event is not triggered again while the handler is running.

Any suggestions?

Thanks,
Michael Goerz
 
S

Sisyphus

Michael Goerz said:
Hi,

is there a way to catch if an interrupt (ctrl-c) is called twice in a
certain time window? Catching the first one is done by

$SIG{INT} = \&refresh;

but now I want to detect if the user hits ctrl-c again while that
handler is running (there can be a 'sleep 1' inside the handler to wait
for this), so that the program can exit in that case. As I observed, the
event is not triggered again while the handler is running.

Any suggestions?

A simplistic approach:

use warnings;
use strict;

$SIG{INT} = \&refresh;
my $global = 1;

while(1) {}

sub refresh {
print "Ctrl-C caught\n";
$global *= -1;
sleep(1);
if($global > 0) {
print "Another Ctrl-C detected\n";
exit(0);
}
else {
print "Continuing on\n";
$global *= -1;
}
}

__END__

Whenever I hit Ctrl-C, the program calls refresh() and then continues on -
unless I hit Ctrl-C a second time during the the sleep(1) period - in which
case the program exits.

(This is perl 5.8.8, Windows 2000.)

Cheers,
Rob
 
M

Michael Goerz

Sisyphus said:
A simplistic approach:

use warnings;
use strict;

$SIG{INT} = \&refresh;
my $global = 1;

while(1) {}

sub refresh {
print "Ctrl-C caught\n";
$global *= -1;
sleep(1);
if($global > 0) {
print "Another Ctrl-C detected\n";
exit(0);
}
else {
print "Continuing on\n";
$global *= -1;
}
}

__END__

Whenever I hit Ctrl-C, the program calls refresh() and then continues on -
unless I hit Ctrl-C a second time during the the sleep(1) period - in which
case the program exits.

(This is perl 5.8.8, Windows 2000.)

Cheers,
Rob
Yes, thanks, that works indeed! I tried something similar, which didn't
work for some reason...

Thanks a lot,
Michael
 
M

Michael Goerz

Michael said:
Yes, thanks, that works indeed! I tried something similar, which didn't
work for some reason...

Thanks a lot,
Michael
Hmmm... on second try, it *doesn't* work. I always end up in the else
block (running your script verbatim). Is this an inconsistency in Perl?
I'm running perl 5.8.7 on Suse Linux 10.0.

Confused,
Michael Goerz
 
S

Sisyphus

use warnings;
use strict;

$SIG{INT} = \&refresh;
my $global = 1;

while(1) {}

sub refresh {
print "Ctrl-C caught\n";
$global *= -1;
sleep(1);
if($global > 0) {
print "Another Ctrl-C detected\n";
exit(0);
}
else {
print "Continuing on\n";
$global *= -1;
}
}

__END__ ..
..
..
Hmmm... on second try, it *doesn't* work. I always end up in the else
block (running your script verbatim). Is this an inconsistency in Perl?
I'm running perl 5.8.7 on Suse Linux 10.0.

Probably a difference between operating systems.
The script works for me as intended on Windows 2000 (perl 5.8.8) but I find
the same behaviour as you on Mandrake-9.1 linux (also perl 5.8.8).

Seems that on Win32, the second instance of refresh() is run as soon as the
second Ctrl-C is hit. But on linux, the second instance of refresh() does
not get run until the first instance has finished running .... which is not
what we want if the script is going to perform as intended.

Perhaps on linux refresh() needs to fork() the process that modifies $global
and sleeps.

(Note - I've never used fork() so my suggestion may not even be sane, faik
:)

Cheers,
Rob
 
B

Ben Morrow

Quoth "Sisyphus" <[email protected]>:

[invoking a signal handler reentrantly]
Probably a difference between operating systems.
The script works for me as intended on Windows 2000 (perl 5.8.8)

[pressing ctrl-c while $SIG{INT} is still running reenters $SIG{INT}]
but I find
the same behaviour as you on Mandrake-9.1 linux (also perl 5.8.8).

[the second SIGINT is apparently not delivered until just after
$SIG{INT} returns]
Seems that on Win32, the second instance of refresh() is run as soon as the
second Ctrl-C is hit. But on linux, the second instance of refresh() does
not get run until the first instance has finished running .... which is not
what we want if the script is going to perform as intended.

General recommendation when using signal handlers is to do nothing in
the actual handler except set a global, which you then check in your
main loop. However, if you really want the behaviour you describe, you
can get it as follows:

use POSIX qw/sigaction SIGINT SA_NODEFER/;

sigaction
SIGINT,
POSIX::SigAction->new(
sub {
warn "start INT handler";
sleep 2;
warn "end INT handler";
},
POSIX::SigSet->new,
SA_NODEFER,
),
or die "sigaction for SIGINT failed: $!";

Note that this defeats the 5.8 'safe signals' mechanism (for this signal
only), which means there is a chance of a segfault if the signal arrives
while perl is in the middle of something non-reentrant. You can
reinstate it with the ->safe method on the POSIX::SigAction object, but
then you lose the reentrancy again. See sigaction and POSIX::SigAction
in the POSIX.pm manpage.
Perhaps on linux refresh() needs to fork() the process that modifies $global
and sleeps.

(Note - I've never used fork() so my suggestion may not even be sane, faik
:)

Er, no. For a start, a forked child cannot affect $global in the parent
(that's kinda the point :) ).

Ben
 
C

Ch Lamprecht

Michael said:
Hi,

is there a way to catch if an interrupt (ctrl-c) is called twice in a
certain time window? Catching the first one is done by

$SIG{INT} = \&refresh;

but now I want to detect if the user hits ctrl-c again while that
handler is running (there can be a 'sleep 1' inside the handler to wait
for this), so that the program can exit in that case. As I observed, the
event is not triggered again while the handler is running.

Any suggestions?

Thanks,
Michael Goerz


How about this:

use warnings;
use strict;

$SIG{INT} = \&refresh;
my $last_time= 0 ;

while(1) {}

sub refresh {
print "Ctrl-C caught\n";
if (time() - $last_time< 2){
print "Another Ctrl-C detected\n";
exit(0);
}
else {
print "Continuing on\n";
$last_time = time();
}
}

__END__

Christoph
 
C

Charles DeRykus

Ben said:
Quoth "Sisyphus" <[email protected]>:

[invoking a signal handler reentrantly]
Probably a difference between operating systems.
The script works for me as intended on Windows 2000 (perl 5.8.8)

[pressing ctrl-c while $SIG{INT} is still running reenters $SIG{INT}]
but I find
the same behaviour as you on Mandrake-9.1 linux (also perl 5.8.8).

[the second SIGINT is apparently not delivered until just after
$SIG{INT} returns]
Seems that on Win32, the second instance of refresh() is run as soon as the
second Ctrl-C is hit. But on linux, the second instance of refresh() does
not get run until the first instance has finished running .... which is not
what we want if the script is going to perform as intended.

General recommendation when using signal handlers is to do nothing in
the actual handler except set a global, which you then check in your
main loop. However, if you really want the behaviour you describe, you
can get it as follows:

use POSIX qw/sigaction SIGINT SA_NODEFER/;

sigaction
SIGINT,
POSIX::SigAction->new(
sub {
warn "start INT handler";
sleep 2;
warn "end INT handler";
},
POSIX::SigSet->new,
SA_NODEFER,
),
or die "sigaction for SIGINT failed: $!";

Note that this defeats the 5.8 'safe signals' mechanism (for this signal
only), which means there is a chance of a segfault if the signal arrives
while perl is in the middle of something non-reentrant. You can
reinstate it with the ->safe method on the POSIX::SigAction object, but
then you lose the reentrancy again. See sigaction and POSIX::SigAction
in the POSIX.pm manpage.

Interesting, I thought the 'safe' signal semantics of 5.8+ would enable
you to get away with I/O and other potentially non-reentrant calls as
documented in perlipc IIUC:

... Then at strategic "safe" points in the Perl interpreter (e.g.
when it is about to execute a new opcode) the flags are checked
and the Perl level handler from %SIG is executed. The "deferred"
scheme allows much more flexibility in the coding of signal
handlers as we know Perl interpreter is in a safe state, and that
we are not in a system library function when the handler is called...

But here, it looks as though 'safe' means deferring receipt until the
handler returns entirely (except for Win32).
 
X

xhoster

Charles DeRykus said:
Interesting, I thought the 'safe' signal semantics of 5.8+ would enable
you to get away with I/O and other potentially non-reentrant calls as
documented in perlipc IIUC:

... Then at strategic "safe" points in the Perl interpreter (e.g.
when it is about to execute a new opcode) the flags are checked
and the Perl level handler from %SIG is executed. The "deferred"
scheme allows much more flexibility in the coding of signal
handlers as we know Perl interpreter is in a safe state, and that
we are not in a system library function when the handler is called...

But here, it looks as though 'safe' means deferring receipt until the
handler returns entirely (except for Win32).

I think there are two different aspects of "safety" here. At the perl
level, you don't want to interrupt perl ops and cause bad things to inside
the black box (e.g. seg faults) . At the Perl level, you don't want to
interrupt signal handlers with other signal handlers, which can cause bad
things to happen outside the black box.

Xho
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,007
Latest member
obedient dusk

Latest Threads

Top