Beefy Boxes and Bandwidth Generously Provided by pair Networks
Pathologically Eclectic Rubbish Lister
 
PerlMonks  

Is the signal handler supposed to work like this?

by dpmott (Scribe)
on Aug 30, 2008 at 00:26 UTC ( [id://707861]=perlquestion: print w/replies, xml ) Need Help??

dpmott has asked for the wisdom of the Perl Monks concerning the following question:

The short question is this: should spawning a thread change how signal handlers behave? See below for a working code snippet.

So, I was banging my head against the keyboard trying to remember how to read in a series of lines from STDIN with Win32 ActivePerl. After much googling, I realize that I've only ever gotten characters with Term::ReadKey, and that isn't even close to what I wanted. Apparently, nothing is.

All I wanted to do was this, and be able to type ctrl-D to signify that I was done:

my @lines = <STDIN>;
(Somebody let me know if I missed the obvious boat here; I couldn't find the way to do this.)

So, I decided to go with ctrl-C:

{ local($SIG{INT}) = 'IGNORE'; @lines = <STDIN>; }
Hmmm... that doesn't work either. It kills my script.

I soon came to realize that I'm using that nifty new Perl v5.8.8 with "safe" signals, and I suspect that this was getting in the way. Sadly, Perl::Unsafe::Signals isn't available via ppm for ActivePerl, so I didn't get to find out either way.

What I did discover, quite by accident was that spawning a thread changes the behavior of the signal handler:

#!perl -w use strict; use threads; my @lines; print "Enter CTRL-C to end input\n"; $SIG{INT} = 'IGNORE'; # making this local() causes ctrl-C to kill the +script @lines = <STDIN>; threads->create( sub{} )->join(); $SIG{INT} = 'DEFAULT'; # putting this after thread creation makes eve +rything happy chomp @lines; # kill newlines from STDIN entries print "Enter CTRL-C to exit\n"; local($") = ', '; while ( 1 ) { print "You entered: @lines\n"; sleep(1); }
So, I'm left wondering if that was supposed to work that way?

Replies are listed 'Best First'.
Re: Is the signal handler supposed to work like this?
by tilly (Archbishop) on Aug 30, 2008 at 02:39 UTC
    Mixing signal handling and threads is unwise. The problem is that you have no control over which thread will receive the signal. If 2 threads disagree on how to handle the signal, you get inconsistent behaviour.

    To get unsafe signal handling in Perl, you just need to set the environment variable PERL_SIGNALS to the value "unsafe" before you call Perl. Unsafe signal handling is important when you have long-running opcodes that might block the handling of a signal. A perfect example of such an opcode is calling out to a C library. Think a long-running database handler. If you are using safe signals, that query cannot be interrupted. Your problem doesn't "smell" like an unsafe signals issue, but now you know how to do it if you want to experiment.

Re: Is the signal handler supposed to work like this?
by betterworld (Curate) on Aug 30, 2008 at 00:38 UTC
    All I wanted to do was this, and be able to type ctrl-D to signify that I was done:
    my @lines = <STDIN>;
    (Somebody let me know if I missed the obvious boat here; I couldn't find the way to do this.)

    I read somewhere that Ctrl-D is Ctrl-Z on Windows, but I cannot confirm that. Maybe this is the reason that it did not work for you?

      Ctrl-Z works as you suggest in the code above, but it must be the first character on a line and must be followed by a newline (although other characters may intervene between the Ctrl-Z and the newline). A Ctrl-Z that is not the first character on a line shows up as a teeny, right-pointing arrow.
Re: Is the signal handler supposed to work like this?
by AnomalousMonk (Archbishop) on Aug 30, 2008 at 02:12 UTC
    I don't know about the Y part of your quasi-XY Problem, but the X part might be approached like this (among several possibilities):

    perl -wMstrict -le "my $lines = do { local $/ = qq(\cD); my $in = <STDIN>; chomp $in; $in }; print $lines " qwert asdf zx^D qwert asdf zx
    Note that you still have to enter a newline after the ^D, and also, oddly, you can enter other characters between the ^D and the newline, but they will not be captured by the script.

    C:\@Work\Perl\monks\zemane>perl -wMstrict -le "my $lines = do { local $/ = qq(\cD); my $in = <STDIN>; chomp $in; $in }; print $lines " the cow jumped over the moo^Dn whoops! the cow jumped over the moo
    Also note that this code captures the multi-line input as a single line, not as an array, but that's a detail, a mere detail!
Re: Is the signal handler supposed to work like this?
by SilasTheMonk (Chaplain) on Aug 30, 2008 at 00:44 UTC

    Your ctrl-D technique of course works on UNIX but windows often does not follow UNIX. I cannot think of any reason why windows should follow UNIX on this point. A quick search reveals many people with the same issue.

    What problem are you trying to solve? Could you treat a certain string of characters as terminating the input?

Re: Is the signal handler supposed to work like this?
by jbert (Priest) on Aug 30, 2008 at 12:37 UTC
    Hmm...your "ignoring SIGINT" approach works for me on Windows:
    #!/usr/bin/perl use warnings; use strict; my @lines; { $SIG{INT} = 'IGNORE'; @lines = <STDIN>; } print "You entered: " . join("", @lines)
    perl 5.10.0 MSWin32-x86-multi-thread (strawberry perl).
      Right... but now ctrl-C is ignored for the entire life of the script. And, if the signal handler is reset back to default later in the script, it doesn't keep the script from dying as a result of the ctrl-C.
Re: Is the signal handler supposed to work like this?
by cdarke (Prior) on Aug 30, 2008 at 17:22 UTC
    As other have said, CTRL+Z is End-of-Data on Windows consoles, but then you must press <RETURN>, unlike UNIX.

    (CTRL+D is only the default EOD on UNIX, it can be changed using stty(1), resulting in confusion and great fun for everyone).

    Signal handling is not native to Windows, and does not sit well on that platform. Personally if I want to trap a console interrupt I use the Win32 API SetConsoleCtrlHandler.
Re: Is the signal handler supposed to work like this?
by dpmott (Scribe) on Sep 05, 2008 at 03:05 UTC
    Thank you all for your input.

    Many stated that ctrl-Z + newline would get the job done. I was unaware of this, and it does indeed work, so thanks for this tip.

    My original goal was to get a single control character to break input without killing the script. In pursuit of that obscure goal, here's what I've since discovered:

    1. Accessing SetConsoleCtrlHandler via Win32::API sounds promising, but causes an unhandled exception when a Ctrl-C is generated. Did I do something wrong?
    #!perl use strict; use Win32::API; use Win32::API::Callback; use constant CTRL_C_EVENT => 0; use constant CTRL_BREAK_EVENT => 1; use constant CTRL_CLOSE_EVENT => 2; use constant CTRL_LOGOFF_EVENT => 5; use constant CTRL_SHUTDOWN_EVENT => 6; ###################################################################### +# # BOOL WINAPI HandlerRoutine( __in DWORD dwCtrlType ); my $cbfn = Win32::API::Callback->new( sub { my $type = shift; return 0 +; }, 'L', 'L' ); ###################################################################### +# # BOOL WINAPI SetConsoleCtrlHandler( __in_opt PHANDLER_ROUTINE Handle +rRoutine, __in BOOL Add); my $fn1 = new Win32::API('kernel32', 'SetConsoleCtrlHandler', 'KL', 'L +'); die "Can't get function handle" unless ($fn1); # The following line of code complains that PHANDLER_ROUTINE is an unk +nown type. Boo. Hiss. #my $fn = new Win32::API('kernel32', 'BOOL SetConsoleCtrlHandler(PHAND +LER_ROUTINE HandlerRoutine, BOOL Add)'); #die "Can't get function handle" unless ($fn); my $fn2 = new Win32::API('kernel32', 'SetConsoleCtrlHandler', 'LL', 'L +'); die "Can't get function handle" unless ($fn2); ###################################################################### +# # BOOL WINAPI GenerateConsoleCtrlEvent(DWORD dwCtrlEvent, DWORD dwProc +essGroupId); my $fn3 = new Win32::API('kernel32', 'BOOL GenerateConsoleCtrlEvent(DW +ORD dwCtrlEvent, DWORD dwProcessGroupId)'); die "Can't get function handle" unless ($fn3); print "Registering the callback function...\n"; die "Bad function call return value" unless $fn1->Call($cbfn, 1); # Generate a CTRL-C event that can be captured print "Generating a CTRL-C event...\n"; $fn3->Call(CTRL_C_EVENT, 0); print "Resetting the callback handler...\n"; my $return = $fn2->Call(0, 0); print "Done!\n";

    2. I've decided, whether it was intended to be this way or not, that setting $SIG{INT} is a lot more useful for a spawned thread than it is for the main thread. Check this out:
    #!perl -w use strict; print "Enter CTRL-C to end input\n"; my $lines = get_lines(); print "Enter CTRL-C to exit\n"; while ( 1 ) { local($") = ', '; print "You entered: @$lines\n"; sleep(1); } sub get_lines { use threads; local($SIG{INT}) = 'IGNORE'; $lines = threads->create( sub{ my @rv = <STDIN>; chomp @rv; return +\@rv } )->join(); }
    Now, I have a three-line routine that will let me pipe data into it (like 'type file.txt | perl myscript.pl'), or get input from the command line (like 'perl myscript.pl'), and in the latter case the sensible ctrl-C works like the average UNIX person expects. (Yeah, it could be a two-line script if I move the 'use threads' line to the top of the file...)

    I have something that works, so I'm a happy camper.

    Now, back to my original question: are signal handlers supposed to work this way? Why couldn't I get it to work like this in the main thread, and why does it work like I expect only in a spawned thread?
Re: Is the signal handler supposed to work like this?
by dpmott (Scribe) on Sep 05, 2008 at 03:18 UTC
    Eureka!

    Look at this code -- it works without threads! So, threads aren't the magic here.

    #!perl -w use strict; print "Enter CTRL-C to end input\n"; $SIG{INT} = 'IGNORE'; my @rv = <STDIN>; eval q/$SIG{INT} = 'DEFAULT'/; chomp @rv; print "Enter CTRL-C to exit\n"; while ( 1 ) { local($") = ', '; print "You entered: @rv\n"; sleep(1); }
    Is another Perl interpreter instance being launched to handle that eval()? Or is the first instance looking for all of the places that twiddle %SIG, and evaluating them upfront? Perhaps there are only a handful of things (like spawning a thread or calling eval()) can get the Perl interpreter to push the state of %SIG down into the operating system?

    Thoughts on just what the mechanics are behind this behavior?

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://707861]
Approved by toolic
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others goofing around in the Monastery: (6)
As of 2024-04-23 15:40 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found