Reputation: 695
I have Perl script - simple server. Wait for connect in the loop, and write it to the new file on disc. The server should be stopped when ctrl + c
are pressed. Before exit, I want to close all files descriptor. It's not so easy, because terminal shows error.
Yes, I read this thread:
How can I check if a filehandle is open in Perl?
But it's not working for me.
There is my main loop:
my $my_socket = new IO::Socket::INET(
LocalHost => $local_host,
LocalPort => $local_port,
Proto => 'tcp',
Listen => 5,
Reuse => 1
);
die "Couldn't create: my_socket $!n " unless $my_socket;
print "Send files.. \n";
while(1) {
my $accepter = $my_socket->accept();
my $count = 0;
#print "$directory.$save_dir/$my_data";
$datetime = localtime();
$datetime =~ s/(?<=\w)\s(?=\w)/_/g;
open my $fh, '>', "$direc/$save_dir/$datetime"
or die "Couldn't open the file";
while(<$accepter>){
chomp;
#last if $count++ ==10;
#print($accepter);
say $fh $_;
}
$SIG{'INT'} = sub {
print "Caught One!\n";
close $fh;
}; #It gives me error. Close fd which is not opened.
}
#print "Received. End \n";
#close $fh;
close $my_socket;
Upvotes: 2
Views: 1959
Reputation: 66899
The code in the signal handler closes that filehandle -- and lets the loop continue. So the next time round that filehandle is indeed closed so you get warnings. (I'd expect issues first with printing to it on the next pass, if the problem is described well, but there may be reasons that that's avoided.)
The fix, in short -- set a flag in the signal handler, nothing else. Check for it at a suitable place in the code and if it is set then close the file and exit the loop by last
.† (Or perform some other action, as suitable for your code.)
There's more I'd like to comment about the signal handler though. The %SIG
is a very global creature. By setting $SIG{INT}
you've changed it for all of the code in the interpreter.
Why not use local $SIG{INT}
instead? Then it is changed only inside the scope in which it is defined; see local. And I'd pull that definition outside of all loops possible. (If you actually want it global place it right at the beginning so it's loud and clear and not hidden away.)
So something like
SOME_SCOPE:
{
my $got_signal;
local $SIG{INT} = sub {
#say "Caught: @_";
$got_signal = 1;
};
while (1) {
...
open my $fh, '>', ... or die $!;
while (<$accepter>) {
...
if ($got_signal) {
close $fh;
# $got_signal = 0; # if used again (reset it)
last;
}
say $fh $_;
}
...
}
};
This is still a sketch, even as it may work as it stands in a simpler case. I have to assume some things, in the first place that there is a lot more going on in your code. If a contained and complete runnable example can help let me know and I can add it.
† Can't do last in a signal handler's sub (it's not inside of a loop, even if it's defined there in the code). And in general, using last
in a sub is not a good idea, to say the least, as it would lead to confusing and opaque code. It also sports very specific behavior
last
cannot return a value from a block that typically returns a value, such aseval {}
,sub {}
, ordo {}
. It will perform its flow control behavior, which precludes any return value.
(it comes with a warning, too) In this case you don't need to return a value but (even if it worked) you absolutely would not want to do that out of a signal handler.
To note, even as it is linked in the question, that one can check whether a filehandle is open using Scalar::Util::openhandle
. That can come handy in this code.
Upvotes: 4
Reputation: 1832
If you want the server to truly exit when Ctrl-C is pressed, simply put an exit
statement in the interrupt handler. That will automatically close all the file handles as Perl exits.
If you want the read loop to terminate when Ctrl-C is pressed but the program will otherwise carry on, do this. However might I suggest Ctrl-\ as a better alternative which sends a SIGQUIT signal. Most people don't expect graceful exits from Ctrl-C; they expect crash stop now.
our $doloop = 1;
$SIG{QUIT} = sub { $doloop = 0; };
while ($doloop) { ... }
$SIG{QUIT} = "IGNORE";
cleanup;
exit;
If you need to check if the filehandle is open before trying to close it, just use -e. For other kinds of open tests see perlfunc -X.
close $fh if -e $fh;
HTH
Upvotes: 2