Beefy Boxes and Bandwidth Generously Provided by pair Networks
Pathologically Eclectic Rubbish Lister
 
PerlMonks  

Catching errors in closing lexical filehandles

by gaal (Parson)
on Sep 27, 2004 at 05:18 UTC ( [id://394068]=perlquestion: print w/replies, xml ) Need Help??

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

Stylistically, I like this construct of using lexicals for filehandles:1
{ open my $fh, $filename or die "open: $!"; # do somthing with $fh }
I like the economy of declaring the variable just as I'm using it; the fact that it's lexically scoped; how this provides a clean alternative to (global) filehandles.

But what about close? I mean, I know *when* it happens: when $fh goes out of scope. That's the point if this construct. But how does it happen, in terms of error catching? I almost always want to handle errors on close. How do I express that using this syntax? (Where do I hook up my "or die"?)

If choosing this syntax means silent failures in close, it becomes less useful. Maybe there's some declarative way of installing error handlers, a la BASIC ON ERROR or Perl %SIG? Even this starts to be less elegant that I'd hope for, but I can't see another way of expressing it.

Update: Clearly, I can close $fh explicitly when I'm done. But one of the features of this construct is that I can leave the scope in several ways and still have the file close automatically for me.

1 I believe this is relatively new syntax, 5.8-ish or so. Update: turns out it's from 5.6; thanks ikegami.

Replies are listed 'Best First'.
Re: Catching errors in closing lexical filehandles
by Zaxo (Archbishop) on Sep 27, 2004 at 05:43 UTC

    As far as I know, you can't [Added: There is a way, see my other reply below, You *Can* Catch errors in closing lexical filehandles]. Observe,

    # use Fatal qw/open print close/; use Fatal qw/open close/; my $result = eval { open my $fh, '>', '/dev/full'; print $fh "Foo\n" or die $!; }; print $result ? $result : $@, $/;
    prints '1', the result of printing to the filehandle.

    Placing an explicit close after the print statement gives something like,

    Can't close(GLOB(0x804b548)): No space left on device at (eval 2) line + 3 main::__ANON__('GLOB(0x804b548)') called at -e line 1 eval {...} called at -e line 1
    That uses the handy /dev/full device of linux, for which writes always fail with ENOSPC. The failure would have occurred on print if we had set $fh to autoflush or had printed more than a buffersworth of text.

    For careful error handling, you should close your lexical handles as if they didn't know how to do it for themselves.

    Update: gaal++ points out an error. Corrected.

    After Compline,
    Zaxo

      Hey, I didn't know about Fatal, cool. Unfortunately it seems broken on my system:
      Cannot make a non-overridable builtin fatal at /usr/share/perl/5.8/Fat +al.pm line 108. BEGIN failed--compilation aborted at f.pl line 1.
      I wonder, does this do anything different?
      use Fatal qw/open close/; my $result = do { open my $fh, '>', '/dev/full'; print $fh "Foo\n"; }; print $result ? $result : $@, $/;
      Also it's probably worth trying to put in an extra nonces value at the end of the block, like "37", just to disambiguate the value of print from some mysterious other factor.

        Arrgh! I keep falling afoul of print not being overridable! Leave it off the import list. I'll correct the code above.

        After Compline,
        Zaxo

Re: Catching errors in closing lexical filehandles
by graff (Chancellor) on Sep 27, 2004 at 05:32 UTC
    I don't see anything wrong with doing it this way:
    { open my $fh, $filename or die "open: $!"; # do somthing with $fh close $fh or die "close: $!" }
    Do you?

    (update: BTW, according to perldoc, close returns true "only if IO buffers are successfully flushed". The relevance for closing an output file handle is obvious -- disks do fill up on occasion, and depending on your OS, there may be other things that get in the way of finishing output. But it's hard for me to imagine a case where this is important for an input file handle. Maybe if you're doing tricky stuff with input from sockets or processes, yeah, but in the typical case of a disk file or STDIN, the return from close shouldn't matter.)

    (Another update, in response to your update: in the case of having multiple ways to exit the block, I usually make a habit of doing an explicit close on any sort of "premature return" condition involving an output file handle. Maybe that's an unnecessary compulsion on my part, but it just seems clean and coherent, like maintaining proper indentation.)

      As you must have gathered by my other responses by now, I don't think there's anything *wrong* in explicitly closing the file handle, except that I'd rather not have to do it :)

      The way I see it, I have a lexically scoped resource and when it goes out of scope, I want perl to free it for me. Of course on a low level that's exactly what it does, but the added DWIMmery of also doing something with possible errors here is just the kind of convenience I like having in Perl.

      Or from a slightly different tack: if this were a c function allocating memory on the heap, with the memory only being used inside the function, obviously you'd need to free() it before returning, no matter what. You don't undef your lexicals before you exit a Perl scope, do you? I think this sort of requirement in c is what led to the stylistic recommendation of having only one exit point for functions, because cleanup tends to be simpler, at least in theory. Once you don't have that pressure, the impetus to return in only one place lessens, and (to my mind, at least) exploiting scoping to do your cleanup for you becomes more attractive.

        Yes, what you say about low-level C-like behavior vs. DWIM Perl-like behavior is sensible. But you have to bear in mind that the "default, typical usage" model in Perl is to proceed as if the specifics of various error conditions don't usually matter and you should be able to carry on regardless -- i.e. in the "generic" case you ignore error conditions, and only attend to them (with an extra line or few of code) when you feel a specific need to do so.
•Re: Catching errors in closing lexical filehandles
by merlyn (Sage) on Sep 27, 2004 at 11:57 UTC
    It's actually rather simple.
    sub IO::Handle::DESTROY { my $deadbody = shift; not defined fileno $deadbody or close $deadbody or die "Cannot close $deadbody: $!"; }
    Of course, this is a global change, but that's life. You can make it regional by putting it inside a local setting:
    { local *IO::Handle::DESTROY = sub { ... }; { open my $fh, "..."; ... } # will trigger here }
    The nested parens here ensure that the handle is destroyed before the local sub assignment goes out of scope.

    -- Randal L. Schwartz, Perl hacker
    Be sure to read my standard disclaimer if this is a reply.

      Interesting: when I print ref $fh, I get GLOB, not IO::Handle. Is there some special magic at work here?
Re: Catching errors in closing lexical filehandles
by davido (Cardinal) on Sep 27, 2004 at 06:10 UTC

    Lexical filehandles have advantages beyond the fact that they close automagically when they fall out of scope. The truth of the matter is that in most cases it's fine to let input filehandles auto-close as they fall from scope, but probably a very good idea to explicity close filehandles that relate to output filehandles.

    Other reasons for using lexical filehandles include many of the same reasons that you would use lexical variables instead of package globals, plus it just looks cleaner passing lexical filehandlefs into function parameter lists than when typeglobs are used, IMHO. And it's nice to let filehandles fall under the watchful eye of strictures just like any other lexical variable; that's there to help write less buggy code.

    So use lexicals when appropriate not just because they auto-close, but for all the other benefits that lexical variables offer too.


    Dave

      I used to use IO::File for the cleanliness back when "open my $fh" didn't work. Or when I didn't feel like the overhead sometimes I used gensym (remember that?). I'm aware of all those advantages and mentioned most of them in the original post; and I still like the idea of automatic close :)

      Can you explain why explicitly closing output filehandles is especially important? Modulo close failures, in our case the close *will* happen, together with associated flushing.

Re: Catching errors in closing lexical filehandles
by FoxtrotUniform (Prior) on Sep 27, 2004 at 05:33 UTC
    But what about close? I mean, I know *when* it happens: when $fh goes out of scope. That's the point if this construct. But how does it happen, in terms of error catching? I almost always want to handle errors on close. How do I express that using this syntax? (Where do I hook up my "or die"?)

    Honestly, I'd do it explicitly. Depending on scoping rules for resource management isn't exactly the most obvious WTDI, although the bare block does make the point pretty well. Putting in an explicit close $fh or die "close: $!"; lets everyone reading your code know that, yes, you have thought about it, and dealt with it as you saw fit -- that you didn't forget to do something clever if close failed.

    (On the other hand, I've never actually had close fail. Not that that's an excuse for not checking a system call's return value, of course. :-)

    --
    F o x t r o t U n i f o r m
    Found a typo in this node? /msg me
    % man 3 strfry

      On the other hand, I've never actually had close fail.
      It's more likely to happen when you're dealing with special files, e.g. pipes and sockets.
Re: Catching errors in closing lexical filehandles
by eyepopslikeamosquito (Archbishop) on Sep 27, 2004 at 09:06 UTC

    Planning ahead for Perl 6, and after reading this thread, it seems best to get used to calling close() explicitly. AFAICT, in Perl 6, by default, the file handle will not get closed until a Dead Object Detection (DOD) run is triggered, which is not guaranteed to happen on scope exit.

    This is because Parrot will replace Perl 5's simple reference counting with full garbage collection. Though languages (such as Perl 5) that run on Parrot can implement traditional Perl 5-style reference counting/timely destruction semantics, it seems likely that doing so will incur a performance penalty (triggering a DOD run on scope exit, for instance).

      Good stuff, thanks for the link.

      I for one wish Perl6 will have some way of requesting timely destroyed objects. :)

        Would it be enough that all items who wish to be notified of the destruction are notified in a timely fashion? This would be vs. at the time of the actual freeing of the memory, which the DOD run is needed for.

        Being right, does not endow the right to be rude; politeness costs nothing.
        Being unknowing, is not the same as being stupid.
        Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
        Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

        I shouldn't have to say this, but any code, unless otherwise stated, is untested

Re: Catching errors in closing lexical filehandles
by TedPride (Priest) on Sep 27, 2004 at 07:03 UTC
    The advantage of using lexicals is that you can use the same function to open multiple files at the same time without overwriting the handle. However, the handle must be kept in scope between open and close, so the function has to assign the handle value to something:
    $ptr = &lock_file($path); # load $path with a regular open and do whatever operations on data... &unlock_file($ptr); sub lock_file { # Locks file by creating filepath.lock and flocking it. # Usage: $ptr = &lock_file($path); my $handle; my $path = (shift) . '.lock'; open($handle, ">$path"); flock($handle, 2); return [$handle, $path]; } sub unlock_file { # Unlocks file locked with &lock_file. # Usage: &unlock_file($ptr); my $inp = shift; my ($handle, $path) = @$inp; flock($handle, 8); close($handle); unlink($path); }
    Using these functions, I can have multiple files locked simultaneously (on a system supporting flock), but I have to keep the handles stored between &lock_file and &unlock_file.

    EDIT: To answer your question, you do your error checking on open, not close. Close will never have errors unless you drop the handle value from scope. I like to do:

    if (!open($handle, $path)) { &error('myerror'); }

    Having your code die isn't as good, because if you decide you want to output an error page instead of having the program quit, you have to go back in and change your code in many different places. Better to have an error sub.

      Allowing multiple opens form the same code is a great benefit, perhaps even the greatest, but who said it's the only one? davido mentions a few others, all of which IMHO are valid.

      Regarding "you do your error checking on open, not close", this is not quite correct. Close can have errors, e.g. when the file is a pipe that has been closed on the other side.

      And regarding die, in the first instance, I was using it as a token for "any appropriate error handling I might choose to apply here". In the second instance, you can (and I often do) achieve protection from termination by catching the die with an eval somewhere up the call train.

        Yes, but the point of closing a file is to have it closed, and does it really matter whether it closes from the server side or the client side? Perhaps I should have said no important errors rather than no errors...
Re: Catching errors in closing lexical filehandles
by jweed (Chaplain) on Sep 27, 2004 at 05:37 UTC
You *Can* Catch errors in closing lexical filehandles
by Zaxo (Archbishop) on Sep 27, 2004 at 15:17 UTC

    I have no idea why eval drops this instead of converting to $@, but you can check $! after a block exit.

    $ perl -e'eval{open my $fh, ">", "/dev/full"; print $fh "Foo\n";}; pri +nt $!' No space left on device
    It would be wise to clear $! beforehand so you don't see leftovers.
    use Fatal qw/open close/; { $! = undef; open my $fh, '>', '/dev/full'; print $fh 'short text' or die $!; } die $! if $!;

    After Compline,
    Zaxo

      The rule:
      Never use $! unless you've recently checked for an O/S request failure.
      This is akin to the rule:
      Never use $1 (and friends) unless you've recently checked for a match success.
      because both share the same principle: these variables are set, but never reset. So you might be looking at stale results.

      -- Randal L. Schwartz, Perl hacker
      Be sure to read my standard disclaimer if this is a reply.

        As of Perl 5.7.0 this behaviour has changed for $1 and friends. From perl570delta:

        The regular expression captured submatches ($1, $2, ...) are now more consistently unset if the match fails, instead of leaving false data lying around in them.

        ihb

        Read argumentation in its context!

        Exactly, that's why I mentioned stale values of $! and set it to undef in the scriptified version of the second paragraph.

        It would have been better to do that right before exiting the scope of $fh. EAGAIN is sometimes possible during normal I/O.

        After Compline,
        Zaxo

      eval would only "drop" something and populat $@ if something went wrong - that is, a "die" or a fatal error. But in the code you give, nothing goes wrong. Sure, print() and the implicite close() might return false values, but that doesn't mean the code dies. Hence, $@ will be undefined.

      Beside that, I wouldn't use this construct. Sure, it might be safe in this simple case, but in general, you might have more code in the block. Which means that upon termination of the block, end-of-scope actions will be run. Lexical variables will go out of scope, DESTROY blocks might be run, and local variables will get their old values back. This might cause all kinds of library calls to happen, each of which might set $! (even if they succeed!)

Re: Catching errors in closing lexical filehandles
by diotalevi (Canon) on Sep 28, 2004 at 11:22 UTC
Re: Catching errors in closing lexical filehandles
by ikegami (Patriarch) on Sep 27, 2004 at 14:32 UTC
    As an aside, that snippet works in 5.6.1, and as far as I know, worked with much earlier versions too. What was introduced in in 5.8.0 was the ability to use a reference for the filename as an alternative to IO::Scalar.
      On 5.005_03, you'd get a "Can't use an undefined value as filehandle reference" error at compilation. Hence the gensym.
        I see, thanks! good to know what works and doesn't work in which version. I'm so used to using FileHandle, which uses gensym.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://394068]
Approved by graff
Front-paged by FoxtrotUniform
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others wandering the Monastery: (5)
As of 2024-03-19 09:05 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found