http://www.perlmonks.org?node_id=881987

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

I need your esteemed help on some wierd behaviour of alarms in ActivePerl. I am using v5.12.2 on Windows 2008 SP1 32bit.

I want to read from a long-running co-process until I am tired, then kill the co-process. I boiled down my code to the following snippet, replacing the real co-process with a dummy command:

#! /usr/bin/perl -w use strict; # Starting a co-process to read from. my $procid = open(READ, '-|', 'perl -e "$|=1; for ($i=0;$i<10;$i++) {p +rint \"Line $i\n\"; sleep 1;}"'); my $timeout = 0; $SIG{ALRM} = sub { $timeout = 1; }; alarm(3); # Reading from co-process. while (!$timeout) { my $line = <READ>; last unless defined $line; print $line; # for (my $i=0;$i<5000;$i++) {} } kill('INT', $procid) if $timeout;

I could not make the alarm work. I even tried unsafe signals. However, putting some tedious calculations somewhere inside the loop, will strangely solve the problem. Must this be considered as a bug?

I also experimented with threads and semaphores and finally found a work-around. But that would be an awful bunch of code for something quite obvious. Is there a state-of-the-art solution to this?

----------

Thank you, Monks, for all your helpful answers. I will have to study and try for a while to understand all the implications of your proposals. The next thing, I will probably do, is using Win32::Job and/or Win32::Process. These sound quite reasonable and easy to interface, although, alas, they will impair portability of my code.

Replies are listed 'Best First'.
Re: Alarms with ActivePerl do not work properly (small modification)
by BrowserUk (Patriarch) on Jan 12, 2011 at 23:27 UTC

    The problem is that the signal emulation won't interrupt a blocking read. That's because the read runs in the kernel space, but the emulation runs in user space. It's a bug in the emulation, but not one that is easily corrected.

    The following small mod makes it work well enough for most purposes:

    #! /usr/bin/perl -w use strict; # Starting a co-process to read from. my $procid = open(READ, '-|', 'perl -e "$|=1; for ($i=0;$i<10;$i++) {print \"Line $i\n\"; sleep +1;}"' ); my $timeout = 0; $SIG{ALRM} = sub { $timeout = 1; }; alarm(3); # Reading from co-process. while (!$timeout) { my $line = <READ>; last unless defined $line; print $line; sleep 1; } kill('INT', $procid) if $timeout;

    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
      The downside to your modification is that if lines come 3 per second, you still only read one per second. And if lines come one every 2 seconds, your alarm has even odds of not doing anything.

      I also think that the emulation can be fixed. Just use a select/sysread loop, which gives you lots of opportunities to be interrupted. Yes, it is a busy wait. But if you only do it when there is an outstanding alarm, I think it is a reasonable tradeoff.

      In fact it is even possible to make that fix in pure Perl. All you have to do is tie the filehandle to an implementation that has a READLINE that does the select/sysread loop and returns when it completes a line of text. (Don't forget to strip out "\r".)

        The downside to your modification is that if lines come 3 per second, you still only read one per second. And if lines come one every 2 seconds, your alarm has even odds of not doing anything.

        The sleep can be varied to suit. It would be better to invoke the sleep before the read.

        In essence, the trick is to ensure that the read doesn't block. I've posted several other variations on the theme here over the years. Some more thorough than others.

        I also think that the emulation can be fixed. Just use a select/sysread loop, ...

        select doesn't work on filehandles on Win32.

        In fact it is even possible to make that fix in pure Perl.

        I guess that "fact" is tempered by the above.

      The problem is that the signal emulation won't interrupt a blocking read.

      Did I test it wrong, cause it wasn't triggering for me after the read returned either.

        Did I test it wrong,

        No, your test is correct, though the interpretation may be a little off.

        In this case, it isn't that the IO is causing the signal to be deferred. The alarm 'signal' is being simulated by a one-shot timer, which as the name applies happens once. If the code is not in an interuptable state when it does, the timer is effectively discarded.

        That could be fixed by using an Overlapped-IO Read and entering an interuptable wait and cancelling the pending IO if the timer goes off.

        Indeed, ubiquitous use of Overlapped IO would allow the whole select/signals emulation to be done in a far more POSIX-compliant fashion, but that would be a major re-write of the perl/win32 subsystems and I can't see anyone taking that task on.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Alarms with ActivePerl do not work properly
by tokpela (Chaplain) on Jan 12, 2011 at 22:37 UTC

    An alternative method to monitor your process could be to execute using Win32::Job and set a timeout using run($timeout).

      Yes, FWIW a few years back I found Win32::Job the least painful way to solve my problem of running a command for a specified period of time, then killing it. I originally did a sigalarm-based Unix version (in Timing and timing out Unix commands); the final Win32::Job-based Windows version can be found in Re: Timing Windows commands. Completely different code for each platform, which is ugly. However, I've found nothing but pain with signals on Windows and always avoid them on that platform, in both Perl and C. Note that the native Win32 API has no concept of signals.

Re: Alarms with ActivePerl do not work properly
by cdarke (Prior) on Jan 13, 2011 at 08:41 UTC
      alarm does not work on the Activestate implementation of Perl on Windows,

      Really? Explain:

      perl -E"eval{ $SIG{ALRM}=sub{die}; say time; alarm(10); sleep 20; };sa +y time" 1294909100 1294909110

      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.

        alarm "works" since (I think) 5.8. On Windows, it's simulated using a timer message (WM_TIMER), but I think that "unsafe signals" do not look at whether that timer message has been received. So an alarm call can only interrupt the Perl interpreter when it is in the runloop, and not when it spends time in XS (or a system call, or a database operation).

        The above is from memory - a closer look at win32_alarm in win32.c will likely be enlightening. It seems that alarm() could be made to "work" with unsafe signals (as far as unsafe signals can ever be called to "work") by having the WM_TIMER message trigger a callback, just like SIG_ALRM would under some unixish OS.

Re: Alarms with ActivePerl do not work properly
by tilly (Archbishop) on Jan 12, 2011 at 21:34 UTC
    Clearly it is a bug. The equivalent code works just fine on other operating systems.

    Can you reproduce it with Strawberry Perl? If so, then it is a core Perl bug and should be reported to p5p. If not then it is an Activestate bug.

Re: Alarms with ActivePerl do not work properly
by ikegami (Patriarch) on Jan 12, 2011 at 21:54 UTC
    Given that tilly says this works on some other OS, this is surely one of the places where Perl's emulation of unix signals is incomplete.
Re: Alarms with ActivePerl do not work properly
by philipbailey (Curate) on Jan 12, 2011 at 22:47 UTC

    I used to find in an earlier version of ActiveState Perl (5.6.x) that the standard $SIG{ALRM} method did not work. I used Win32::Process to spawn and time out processes. I'm pretty sure that you can use $SIG{ALRM} on Cygwin's Perl. I hope the OP will let us know whether Strawberry Perl works or not (not optimistic).