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

Greetings,

I am trying to set an alarm to stop a script from sending the load on my machine to the moon. I am using Image::Magick::Thumbnail::PDF to create PNG thumbnails of the first page of a bunch of PDF docs.

ImageMagick uses Ghostscript to parse the PDFs and occasionally a PDF will cause one of them problems, causing my script to hang endlessly sending the load really high on the machine (eventually it gets killed actually). When this happens I want to skip that PDF and move on to the next.

This is what I have, but it is not working on start up. If I hit control-c, then it starts doing its thing... why is that? I want this to run as a cron.

foreach my $pdf (@pdfs) { my $png = "/path/to/thumbs/" . $pdf . ".png"; if (!stat($png)) { eval { local $SIG{ALRM} = sub { die "alarm\n" }; alarm(10); create_thumbnail($pdf,$png); alarm(0); print "Created: $pdf\n"; }; if ($@) { die unless $@ eq "alarm\n"; print " >> Timeout On: $pdf\n"; next; } } }

Thanks!

Replies are listed 'Best First'.
Re: Setting an alarm
by ikegami (Patriarch) on Mar 10, 2010 at 01:39 UTC

    Signals are checked after each Perl opcode is executed, and only then. Perl won't check until if a signal was received until the create_thumbnail XS functions call ends (or until it calls some Perl code, which it probably doesn't do). I'm not sure why Ctrl-C works. (Are you on Windows or unix?)

    One solution is to create the thumbnails in a child process, and kill the process if it hasn't finished in the desired amount of time. Bonus: It'll provide a cleaner shutdown when a problem occurs. Bonus: It'll allow you to parallelize the process if you so desire.

    You could also turn off safe signals.

      I am on UNIX.

      Forking kids is new to me. After looking around, I came up with this:

      my $pid; ... if (!stat($png)) { $SIG{'CHLD'} = "wait_for_child"; $pid = fork(); if ($pid) { "Creating thumbnail for: $pdf (pid $pid)\n"; } else { create_thumbnail($pdf,$png); exit(0); } } sub wait_for_child { print "Waiting on $pid..."; sleep(1); }

      I haven't actually run the code yet for two reasons. This will generate all of them at the same time (forking a child for each PDF). And I don't have anything to check how long they are taking.

      Can you add some pseudo code that would point me in the right direction?

      Thanks!

        The $SIG{CHLD} + sleep approach you took (instead of wait/waitpid) is interesting, since it saves you from using alarm. While an alarm would interrupt wait/waitpid, why use signals when they're not needed.

        use POSIX qw( _exit ); if (!stat($png)) { my $child_code; local $SIG{CHLD} = sub { wait(); # Reap child $child_code = $?; }; defined( my $pid = fork() ) or die("Can't fork: $!\n"); if (!$pid) { # Child if (eval { create_thumbnail($pdf,$png); 1 }) { _exit(0); } else { # Especially deadly version of die() print STDERR $@; _exit($! || $?>>8 || 255); } } print "Waiting on $pid...\n"; sleep($timeout); # Interrupted by SIGCHLD since we have a handler if (defined($child_code)) { die("Child failure\n") if $child_code; } else { $SIG{CHLD} = 'DEFAULT'; kill KILL => $pid; wait(); # Reap child die("Child timeout\n"); } }