How can I terminate a system command with alarm in Perl?

前端 未结 4 883
悲哀的现实
悲哀的现实 2020-12-03 12:23

I am running the below code snippet on Windows. The server starts listening continuously after reading from client. I want to terminate this command after a time period.

相关标签:
4条回答
  • 2020-12-03 12:50

    There are a few separate issues here.

    First, to keep the alarm from killing your script, you need to handle the ALRM signal. See the alarm documentation. You shouldn't need two scripts for this.

    Second, system doesn't capture output. You need one of the backtick variants or a pipe if you want to do that. There are answers for that on Stackoverflow already.

    Third, if alarm.pl puts anything in display.txt, you discard it in main.pl when you re-open the file in write mode. You only need to create the file in one place. When you get rid of the extra script, you won't have this problem.

    I recently had some problems with alarm and system, but switching to IPC::System::Simple fixed that.

    Good luck, :)

    0 讨论(0)
  • 2020-12-03 12:54

    I run into a similar problem that requires:

    • run a system command and get its output
    • time out the system command after x seconds
    • kill the system command process and all child processes

    After much reading about Perl IPC and manual fork & exec, I came out with this solution. It is implemented as a simulated 'backtick' subroutine.

    use Error qw(:try);
    
    $SIG{ALRM} = sub {
        my $sig_name = shift;
        die "Timeout by signal [$sig_name]\n";
    };
    
    # example
    my $command = "vmstat 1 1000000";
    my $output = backtick( 
                     command => $command, 
                     timeout => 60, 
                     verbose => 0 
                 );
    
    sub backtick {
    
        my %arg = (
            command => undef,
            timeout => 900,
            verbose => 1,
            @_,
        );
    
        my @output;
    
        defined( my $pid = open( KID, "-|" ) )
            or die "Can't fork: $!\n";
    
        if ($pid) {
    
            # parent
    
            # print "parent: child pid [$pid]\n" if $arg{verbose};
    
            try {
                alarm( $arg{timeout} );
                while (<KID>) {
                    chomp;
                    push @output, $_;
                }
    
                alarm(0);
            }
            catch Error with {
                my $err = shift;
                print $err->{-text} . "\n";
    
                print "Killing child process [$pid] ...\n" if $arg{verbose};
                kill -9, $pid;
                print "Killed\n" if $arg{verbose};
    
                alarm(0);
            }
            finally {};
        }
        else {
    
            # child
    
            # set the child process to be a group leader, so that
            # kill -9 will kill it and all its descendents
            setpgrp( 0, 0 );
    
            # print "child: pid [$pid]\n" if $arg{verbose};
            exec $arg{command};
            exit;
        }
    
        wantarray ? @output : join( "\n", @output );
    }
    
    0 讨论(0)
  • 2020-12-03 12:55

    What the hell was I thinking? You don't need a background process for this task. You just need to follow the example in the perldoc -f alarm function and wrap your time-sensitive code in an eval block.

    my $command = "adb shell cd /data/app; ./iperf -u -s -p 5001";
    my @output;
    eval {
        local $SIG{ALRM} = sub { die "Timeout\n" };
        alarm 30;
        @output = `$command`;
        alarm 0;
    };
    if ($@) {
        warn "$command timed out.\n";
    } else {
        print "$command successful. Output was:\n", @output;
    }
    

    Inside the eval block, you can capture your output the regular way (with backticks or qx() or readpipe). Though if the call times out, there won't be any output.

    If you don't need the output (or don't mind hacking some interprocess communication together), an almost idiot-proof alternative is to set the alarm and run the system call in a child process.

    $command = "adb shell cd /data/app; ./iperf -u -s -p 5001";
    if (($pid = fork()) == 0) {
        # child process
        $SIG{ALRM} = sub { die "Timeout\n" }; # handling SIGALRM in child is optional
        alarm 30;
        my $c = system($command);
        alarm 0;
        exit $c >> 8;  # if you want to capture the exit status
    }
    # parent
    waitpid $pid, 0;
    

    waitpid will return when either the child's system command is finished, or when the child's alarm goes off and kills the child. $? will hold the exit code of the system call, or something else (142 on my system) for an unhandled SIGALRM or 255 if your SIGALRM handler calls die.

    0 讨论(0)
  • 2020-12-03 12:57

    Might use "timeout -n " for wrapping your commands if thats already common on your system.

    0 讨论(0)
提交回复
热议问题