Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw
 
PerlMonks  

Re: Strawberry Perl and alarm() on Windows

by afoken (Chancellor)
on May 22, 2015 at 16:28 UTC ( [id://1127481]=note: print w/replies, xml ) Need Help??


in reply to Strawberry Perl and alarm() on Windows

Also works in Strawberry 5.14.2 on Win7-64.

alarm says: Emulated using timers that must be explicitly polled whenever Perl wants to dispatch "safe signals" and therefore cannot interrupt blocking system calls. (Win32). And sleep says: Emulated using synchronization functions such that it can be interrupted by alarm(), and limited to a maximum of 4294967 seconds, approximately 49 days. (Win32)

So, yes, mixing alarm and sleep works on Win32. Replace sleep with something that blocks for 5 seconds outside perl (i.e. in the system) and alarm will no longer work.

Alexander

--
Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)

Replies are listed 'Best First'.
Re^2: Strawberry Perl and alarm() on Windows
by SimonPratt (Friar) on May 22, 2015 at 16:52 UTC

    Uhh, am I missing something?

    use strict; use warnings; print scalar localtime, "\n"; eval { $SIG{__DIE__} = sub { alarm(0) }; $SIG{ALRM} = sub { die "timeout" }; alarm(2); system('sleep 5'); }; print $@ if $@; print scalar localtime, "\n";

    Output:

    Fri May 22 17:46:53 2015
    timeout at C:\Temp\b.plx line 8.
    Fri May 22 17:46:55 2015

    Alarm appears to work just fine even when calling out to system

      am I missing something?

      Yes. I did not write about the system function, but about the system (as in "operating system"). I cited alarm in perlport: "Emulated using timers that must be explicitly polled whenever Perl wants to dispatch "safe signals" and therefore cannot interrupt blocking system calls. (Win32)" (Emphasis mine) I wrote " Replace sleep with something that blocks for 5 seconds outside perl (i.e. in the system) and alarm will no longer work."

      Demo, using flock, a function using a system call that may block for a long time:

      #!/usr/bin/perl use strict; use warnings; use v5.10; use Fcntl qw( LOCK_EX LOCK_UN ); use autodie qw( open flock close ); sub main { # create a tempfile open my $h,'>>','tempfile.tmp'; close $h; # start a background process that locks the tempfile for 10 second +s if ($^O eq 'MSWin32') { system 1,$^X,$0,'locker'; } else { my $pid=fork() // die "Can't fork: $!"; unless ($pid) { exec $^X,$0,'locker' or die "Can't exec: $!"; } } sleep 1; # wait one second for the helper process; open $h,'>>','tempfile.tmp'; $@=''; my $start=time(); eval { local $SIG{'ALRM'}=sub { die "timeout" }; say 'main: alarm 5'; alarm(5); say 'main: flock LOCK_EX'; flock($h,LOCK_EX); say 'main: alarm 0'; alarm(0); }; my $err=$@ || 'successfully locked'; my $stop=time(); say "main: $err"; say 'main: ',$stop-$start,' seconds have passed'; close $h; # allow locker() to finish before returning to command prompt ($^O eq 'MSWin32') ? sleep 1 : wait; } sub locker { open my $h,'>>','tempfile.tmp'; say 'locker: flock LOCK_EX'; flock($h,LOCK_EX); say 'locker: locked'; say 'locker: sleep 10'; sleep 10; say 'locker: flock LOCK_UN'; flock($h,LOCK_UN); say 'locker: unlocked'; close $h; } @ARGV ? locker() : main();

      How the demo works:

      Without arguments, main() is invoked, main() creates a background process (using system(1,@args) on Windows, fork() and exec() on Linux), waits a second, then tries to lock a temp file with a classic timeout construct (eval, $SIG{'ALRM'}=sub { die }, alarm($timeout), system call, alarm(0)). Time for this is measured. The background process is the same script, but invoked with an argument, so that locker() is invoked instead of main(). Locker locks the temp file for 10 seconds, and because main() waits a second, it will succeed. In main(), the temp file is already locked, so flock() will block until locker() releases the lock OR the system call is interrupted by the ALRM signal caused by alarm().

      On Linux, signals just work. flock() in main() is interrupted by the ARLM signal, the signal handler in $SIG{'ALRM'} is invoked, that terminales the eval block using die() after 5 seconds.

      On Windows, signals are emulated. alarm($timeout) sets up a timer that must be polled. This is impossible during a blocking system call like flock(). So flock() blocks until it can aquire the lock after about 9 seconds (locker() waits 10 seconds after locking, main() waits 1 second before trying to lock, 9 seconds remain). $SIG{'ALRM'} is not invoked. alarm(0) disables the timer. The eval block terminates without an error.

      Output on Linux:

      >perl lockdemo.pl locker: flock LOCK_EX locker: locked locker: sleep 10 main: alarm 5 main: flock LOCK_EX main: timeout at lockdemo.pl line 30. main: 5 seconds have passed locker: flock LOCK_UN locker: unlocked >

      Output on Windows:

      H:\tmp>perl lockdemo.pl locker: flock LOCK_EX locker: locked locker: sleep 10 main: alarm 5 main: flock LOCK_EX locker: flock LOCK_UN main: alarm 0 main: successfully locked main: 10 seconds have passed locker: unlocked H:\tmp>

      So why did alarm($timeout) "work" with system "sleep 10"?

      system @args (unlike system 1,@args) waits for the sub-process to terminate in perl. (flock() waits in the operating system!) During that time, perl can poll the timer and thus can emulate the ARLM signal.

      Alexander

      --
      Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)
        On Windows, signals are emulated. alarm($timeout) sets up a timer that must be polled.

        That's not a very good description of what actually happens.

        On Windows, alarm is simulated using the SetTimer() system API, which send a message to the process' message queue when the time expires.

        From perl5.18/Win32.c:

        DllExport unsigned int win32_alarm(unsigned int sec) { /* * the 'obvious' implentation is SetTimer() with a callback * which does whatever receiving SIGALRM would do * we cannot use SIGALRM even via raise() as it is not * one of the supported codes in <signal.h> */ dTHX; if (w32_message_hwnd == INVALID_HANDLE_VALUE) w32_message_hwnd = win32_create_message_window(); if (sec) { if (w32_message_hwnd == NULL) w32_timerid = SetTimer(NULL, w32_timerid, sec*1000, NULL); else { w32_timerid = 1; SetTimer(w32_message_hwnd, w32_timerid, sec*1000, NULL); } } else { if (w32_timerid) { KillTimer(w32_message_hwnd, w32_timerid); w32_timerid = 0; } } return 0; }

        And that message is received when the run loop checks the message queue:

        static LRESULT win32_process_message(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara +m) { /* BEWARE. The context retrieved using dTHX; is the context of the * 'parent' thread during the CreateWindow() phase - i.e. for all +messages * up to and including WM_CREATE. If it ever happens that you nee +d the * 'child' context before this, then it needs to be passed into * win32_create_message_window(), and passed to the WM_NCCREATE ha +ndler * from the lparam of CreateWindow(). It could then be stored/ret +rieved * using [GS]etWindowLongPtr(... GWLP_USERDATA ...), possibly elim +inating * the dTHX calls here. */ /* XXX For now it is assumed that the overhead of the dTHX; for wh +at * are relativley infrequent code-paths, is better than the added * complexity of getting the correct context passed into * win32_create_message_window() */ dTHX; switch(msg) { #ifdef USE_ITHREADS case WM_USER_MESSAGE: { long child = find_pseudo_pid(aTHX_ (int)wParam); if (child >= 0) { w32_pseudo_child_message_hwnds[child] = (HWND)lParam; return 1; } break; } #endif case WM_USER_KILL: { /* We use WM_USER_KILL to fake kill() with other signals * +/ int sig = (int)wParam; if (do_raise(aTHX_ sig)) sig_terminate(aTHX_ sig); return 1; } case WM_TIMER: { /* alarm() is a one-shot but SetTimer() repeats so kill it + */ if (w32_timerid && w32_timerid==(UINT)wParam) { KillTimer(w32_message_hwnd, w32_timerid); w32_timerid=0; /* Now fake a call to signal handler */ if (do_raise(aTHX_ 14)) sig_terminate(aTHX_ 14); return 1; } break; } default: break; } /* switch */ /* Above or other stuff may have set a signal flag, and we may not + have * been called from win32_async_check() (e.g. some other GUI's mes +sage * loop. BUT DON'T dispatch signals here: If someone has set a SI +GALRM * handler that die's, and the message loop that calls here is wra +pped * in an eval, then you may well end up with orphaned windows - si +gnals * are dispatched by win32_async_check() */ return 0; }

        And, at least since the advent of SAFE_SIGNALS in 5.8.1, *nix perls, also only process signals, between opcodes, by "polling" (from the run loop) to see if any have been received during the last opcode. Ie. in exactly the same way as Windows perls do.

        The reason your rather over-elaborate demo "works" on *nix and not under windows, is because under *nix, signals are also delivered (by the OS) to child processes, whereas they are obviously not on Windows. Thus, the locker child process also receives the alrm signal which interrupts the 10 second sleep, thus the lock it holds gets removed, and the main() process can therefore acquire its lock before the alarm is triggered.

        This is clearly shown by the output you posted:

        >perl lockdemo.pl locker: flock LOCK_EX locker: locked locker: sleep 10 main: alarm 5 main: flock LOCK_EX ### The lock is acquired +here main: timeout at lockdemo.pl line 30. ### before the timeout oc +curs. main: 5 seconds have passed locker: flock LOCK_UN locker: unlocked

        Perl's "Safe Signals" (unless disabled) are also implemented on *nix perls, and are only acted upon between opcodes, just as they are on Windows perls. Your demo is deceptive (I'm not suggesting deliberately so), because the alarm is not interrupting the flock opcode in the main() process, but the sleep in the child process. The lock is only acquired early in the main process because the child process relinquished its lock early.

        That doesn't happen in the Windows example, because the OS doesn't do signals, thus doesn't deliver the signal to the child process.

        I agree that the emulated signals on Windows are less complete than the real ones on *nix; but then the reason Perl has "Safe signals" in the first place is because the entire concept of using asynchronous interrupts as a flow-control mechanism is terminally brain-dead.


        With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
        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". I'm with torvalds on this
        In the absence of evidence, opinion is indistinguishable from prejudice. Agile (and TDD) debunked

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others wandering the Monastery: (4)
As of 2024-04-25 18:52 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found