Reputation: 23541
I have a script based on Term::ReadLine and LWP::UserAgent
The logic is like this,
while (defined ($_ = $term->readline('console> ')))
{
next unless $_; chomp;
if ($_ eq 'exit')
{
last;
}
&run ($_);
}
sub run {
my $ua = LWP::UserAgent->new;
my $resp = $ua->get (...);
say $resp->content;
}
In run
it will do a LWP request. Now If I press CTRL + C, not only the LWP is terminated, the whole perl script is terminated as well.
I wanted to kill the LWP request only. Any ideas?
I can add a SIGINT handler, but I don't know what the handler should do
Upvotes: 2
Views: 399
Reputation: 66899
One way to stop code is to run it in a child process and kill
that child in the parent's signal handler when SIGINT
is received by the parent. The parent keeps running since the signal is handled.
use warnings;
use strict;
use feature 'say';
$SIG{INT} = \&sigint_handler; # or: $SIG{INT} = sub { ... };
say "Parent $$ start.";
my $pid = run_proc();
my $gone_pid = waitpid $pid, 0; # check status, in $?
say "Parent exiting";
sub run_proc
{
my $pid = fork // die "Can't fork: $!";
if ($pid == 0) { # child process
say "\tKid, sleep 5 (time for Ctrl-C)"; # run your job here
sleep 5;
say "\tKid exiting.";
exit;
}
return $pid;
}
sub sigint_handler {
if ($pid and kill 0, $pid) {
say "Got $_[0], send 'kill TERM' to child process $pid.";
my $no_signalled = kill 15, $pid;
}
else { die "Got $_[0]" } # or use exit
}
A good deal of the code is for diagnostic prints. Some comments follow
The kill
only sends a signal. It does not in any way ensure that the process terminates. Check this with kill $pid, 0
, which returns true if the process has not been reaped (even if it's a zombie). On my system TERM
is 15
, and even though this is very common please check.
The signal could come at a time when the child is not running. The handler first checks whether the $pid
is out there and if not it dies/exits, respecting SIGINT
. Change as appropriate.
After the fork
the parent drops past if ($pid == 0)
and returns the $pid
right away.
You can install $SIG{TERM}
in the child, where it can clean up if it needs to exit orderly.
The SIGINT
handler will run out of the child as well, so "Got $_[0] ..."
is printed twice. If this is a concern add a handler to the child to ignore the signal, $SIG{INT} = 'IGNORE';
. With this in place and with Ctrl-C hit while the child is running, the output is
Parent 9334 start. Kid, sleep 5 (time for Ctrl-C) ^CGot INT, send 'kill TERM' to child process 9335. Parent exiting
The status of the child once it exited can be checked via $?
, see system
and in perlvar.
Documentation: fork (and exec, system), %SIG in perlvar, waitpid, parts of perlipc, kill.
If the job done in the child needed to communicate with the parent then there would be more to do. However, the code snippet added to the question indicates that this is not the case.
Upvotes: 1
Reputation: 386396
Convert the signal into an exception.
local $SIG{INT} = sub { die "SIGINT\n" };
Generally, one would then wrap the code in an eval BLOCK
, but LWP::UserAgent catches these exceptions and returns an error response.
For example,
use LWP::UserAgent;
my $ua = LWP::UserAgent->new();
my $response = do {
local $SIG{INT} = sub { die "SIGINT\n" };
$ua->get("http://localhost/zzz.crx")
};
say $response->is_success ? "Successful" : "Unsuccessful";
say $response->code;
say $response->status_line;
Output if no SIGINT received:
Successful
200
200 OK
Output if SIGINT received:
Unsuccessful
500
500 SIGINT
Upvotes: 2
Reputation: 126742
You need to provide a callback in your call to $ua->request
. Issuing die
in that callback will terminate the transfer.
You then just need to set a flag variable in your Ctrl-C signal handler, and die
in your callback if that flag is set.
I'll write some code when I get back to a PC, and when you have shown what your run
subroutine does.
Here's some code that looks right, but I can't test it at present
Beware that run
is a dire identifier for any subroutine, especially one that starts a network transfer and prints the result
sub run {
my ($url) = @_;
my $die;
local $SIG{INT} = sub { $die = 1 };
my $ua = LWP::UserAgent->new;
my $resp = $ua->get(
$url,
':content_cb' => sub {
die "Interrupted LWP transfer" if $die;
my ($data, $resp, $proto) = @_;
print $data;
},
':read_size_hint' => 1024
);
print "\n"; # Emulate additional newline from `say`
}
Note that reducing :read_size_hint
will cause the callback to be called more frequently with smaller chunks of data. That will improve the response to Ctrl-C but reduce the efficiency of the transfer
Upvotes: 0