2

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;
vkk05
  • 3,137
  • 11
  • 25
The Trainer
  • 633
  • 1
  • 3
  • 19

2 Answers2

4

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 as eval {}, sub {}, or do {}. 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.

zdim
  • 64,580
  • 5
  • 52
  • 81
  • You "can" do flow control in a sub, but please, for the love of Larry, don't do that!!! – lordadmira Nov 20 '20 at 06:07
  • @lordadmira Um ... what do you mean? Doing `last` out of a sub draws a warning (try it) -- which practically means that one shouldn't do that, and in this venue I'll call that "can't do". And one shouldn't use that (even if it were OK and did't warn), what I also say, in the same breath. Are you re-affirming that with your comment? :) – zdim Nov 20 '20 at 06:31
  • Oh there's a bright wide line between "can" and "should". :) – lordadmira Nov 20 '20 at 19:05
  • @lordadmira btw, with a signal handler you actually _cannot_ (try it), which was the intent of the original statement. I've added more now... – zdim Nov 20 '20 at 20:11
  • Well that does make sense atleast. I would have made all flow control illegal in a sub. – lordadmira Nov 20 '20 at 20:19
2

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

lordadmira
  • 1,807
  • 5
  • 14