Beefy Boxes and Bandwidth Generously Provided by pair Networks
Your skill will accomplish
what the force of many cannot
 
PerlMonks  

How do I make the garbage collector throw an exception when it fails to auto-close a filehandle?

by Aristotle (Chancellor)
on Jan 12, 2007 at 16:53 UTC ( #594416=perlquestion: print w/ replies, xml ) Need Help??
Aristotle has asked for the wisdom of the Perl Monks concerning the following question:

My basic scenario is like so:

sub something { # ... open my $fh, '>', $_[0] or die "Couldn't open $_[0] for writing\n"; # ... }

Note that Iím not explicitly closing the filehandle. When execution leaves the subroutine (or loop or naked block or whichever kind of scope it is), the filehandle goes falls into limbo and is picked up by the garbage collector, which automatically closes the handle before disposing of the glob.

The problem is that close can actually fail (the canonical case is when there are write buffers left to flush but the disk has filled up). So therefore, you should always check the return value of close, at least on files you have opened for writing (whereas for files you have only opened for reading, there is very little point in caring). But when the garbage collector disposes of a filehandle, it ignores that return value. So I end up having to litter my code with explicit close calls anyway, just when I thought I could let the scope structure of the code implicitly define the scope of open files.

So I wonder if there is a way to get perl to throw an exception when it tries to close a filehandle in the garbage collector and gets an error. Most of the time, thatís what I want anyway, since there isnít much in the way of automatic recovery that can be done on failure to close a file, and the condition is usually indicative of some serious administrative issue, so the only sensible response is to die screaming bloody murder at the user.

In the simplest case, use Fatal qw( :void close ); would work for this, but I strongly doubt it, and in any case I canít think of a good way to purposefully trigger a close failure in order to test it.

(Note: in the chatterbox it was suggested to check $! just after the block, after clearing it just before the end of the block, but thatís really pretty pointless. For one thing, itís imprecise: there might be several filehandles or objects or tied variables going out of scope, any of which may set $!. Worse, though, it trades one line of manual cleanup code that can be placed within the block for several lines that need to be strewn across several scopes Ė so I wouldnít gain anything even if it worked in the first place.)

Makeshifts last the longest.

Comment on How do I make the garbage collector throw an exception when it fails to auto-close a filehandle?
Download Code
Re: How do I make the garbage collector throw an exception when it fails to auto-close a filehandle?
by Melly (Hermit) on Jan 12, 2007 at 17:20 UTC

    Hmm, could you use ulimit -f 1 to impose a very small file-size limit, and then try? (oh, and check the previous limit beforehand, and restore it afterwards...)

    <update>aargh, sorry, no - that will check for file-write errors, not closing (afaik)</update>

    map{$a=1-$_/10;map{$d=$a;$e=$b=$_/20-2;map{($d,$e)=(2*$d*$e+$a,$e**2 -$d**2+$b);$c=$d**2+$e**2>4?$d=8:_}1..50;print$c}0..59;print$/}0..20
    Tom Melly, pm@tomandlu.co.uk
Re: How do I make the garbage collector throw an exception when it fails to auto-close a filehandle?
by Util (Priest) on Jan 12, 2007 at 17:56 UTC
    Untested idea:
    1. Create a subclass of IO::Handle or IO::File with a custom DESTROY subroutine that closes (if open) with error handling.
    2. Use the subclass instead of basic filehandles.

      That was my next thought about attacking the problem. Iíd prefer being able to enable my desired behaviour with some sort of pragma somewhere and then not have to do anything special in the affected code, though. If I have to do any extra work at all (and if itís just remembering to instantiate a particular IO class instead of just calling open), Iíd rather stick to doing dumb and simple extra work than adding insufficiently magic complexity to the code.

      Makeshifts last the longest.

        The following triggers IO::Handle::DESTROY. It does not trigger IO::File::DESTROY even though this is a file handle.

        use IO::Handle; # or IO::File *IO::Handle::DESTROY { print "here\n" } { open my $fh, '<', 'a-file' or die; }

        ⠤⠤ ⠙⠊⠕⠞⠁⠇⠑⠧⠊

Re: How do I make the garbage collector throw an exception when it fails to auto-close a filehandle?
by ikegami (Pope) on Jan 12, 2007 at 18:33 UTC

    When a function has special cleanup needs, use tilly's ReleaseAction.

    use ReleaseAction qw( on_release ); sub something { ... open my $fh, ... my $closer = on_release { if (defined($fh) && defined(fileno($fh))) { close($fh) or die(...); } }; ... }

    $closer holds a reference to $fh, so $closer will get destroyed before $fh. (Not necessarily true if they survive until global destruction.)

      Yah, Iím aware of the concept. I would use it (more readily than a special IO class, possibly, although my opinion on that changes every other day), but for a single close $fh or die "Couldn't close: $!" statement it just doesnít seem worthwhile. Itís maddening Ė itís a simple single statement whose absence is not worth complicating the code for, but itís still annoying to have to write it, and thereís so little missing to make it unnecessaryÖ

      Makeshifts last the longest.

        Nevermind. die in destrutor. Doesn't work.

        use strict; use warnings; use ReleaseAction qw( on_release ); $| = 1; sub something { my $closer = on_release { print "on_release enter\n"; die("test"); print "on_release exit\n"; }; } print "pre\n"; something(); print "post\n";

        outputs

        pre on_release enter (in cleanup) test at script.pl line 11. post

        Tested on 5.6.1 & 5.8.8

Re: How do I make the garbage collector throw an exception when it fails to auto-close a filehandle?
by geekphilosopher (Friar) on Jan 12, 2007 at 23:17 UTC
Re: How do I make the garbage collector throw an exception when it fails to auto-close a filehandle?
by gaal (Parson) on Jan 13, 2007 at 17:25 UTC

      Thanks for that link! Some interesting stuff in your thread which wasnít discussed here in mine.

      Seems like clobbering IO::Handle::DESTROY is the only way to do this, but that itís at least reliable. Maybe I should put IO::Handle::CheckedAutoclose on the CPAN or something. :-)

      Makeshifts last the longest.

        I don't like the extra scope and the fact that it's local at best. If you do put it on CPAN, make a note to say it isn't thread safe.
Re: How do I make the garbage collector throw an exception when it fails to auto-close a filehandle?
by polettix (Vicar) on Jan 13, 2007 at 20:15 UTC
    In Linux:
    PolettiX:~# dd if=/dev/zero of=testfile bs=1024 count=1024 1024+0 records in 1024+0 records out 1048576 bytes transferred in 0.019164 seconds (54715967 bytes/sec) PolettiX:~# PolettiX:~# PolettiX:~# mkfs.ext2 testfile mke2fs 1.37 (21-Mar-2005) testfile is not a block special device. Proceed anyway? (y,n) y Filesystem label= OS type: Linux Block size=1024 (log=0) Fragment size=1024 (log=0) 128 inodes, 1024 blocks 51 blocks (4.98%) reserved for the super user First data block=1 1 block group 8192 blocks per group, 8192 fragments per group 128 inodes per group Writing inode tables: done Writing superblocks and filesystem accounting information: done This filesystem will be automatically checked every 27 mounts or 180 days, whichever comes first. Use tune2fs -c or -i to override. PolettiX:~# PolettiX:~# PolettiX:~# mount testfile -o loop /mnt

    Now you have a filesystem that will be easy to fill up:

    #!/usr/bin/perl use strict; use warnings; use Fatal qw( open close ); chdir '/mnt'; { open my $fh, '>', 'prova.dat'; print {$fh} "ciao\n" for 1 .. 1000000; # close $fh; } print {*STDERR} "should be closed here!\n";

    In my system, the final string on STDERR gets printed when the close above is commented; uncommenting the close triggers the fatal error:

    Can't close(GLOB(0x814cd4c)): No space left on device at (eval 2) line + 3 main::__ANON__('GLOB(0x814cd4c)') called at ./prova.pl line 13

    Flavio
    perl -ple'$_=reverse' <<<ti.xittelop@oivalf

    Don't fool yourself.

      In my system, the final string on STDERR gets printed when the close above is commented; uncommenting the close triggers the fatal error:

      And thatís exactly the point: when you have an explicit close, you can make it throw an exception; when you omit the close, the error is silent. But I want to omit the close and still get an exception. Fatal wonít help me there.

      Thanks for the pointer about the loop device though! All I can say in retrospect is, díuh. However, Linux has an easier way, mentioned by Zaxo in the old thread from gaal: thereís a /dev/full device where writing always fails with ENOSPC.

      Makeshifts last the longest.

        Oh, I misunderstood that you didn't know for sure about Fatal's behaviour:
        In the simplest case, use Fatal qw( :void close ); would work for this, but I strongly doubt it, and in any case I canít think of a good way to purposefully trigger a close failure in order to test it.
        And yes... the loop device rocks :)

        Flavio
        perl -ple'$_=reverse' <<<ti.xittelop@oivalf

        Don't fool yourself.

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others cooling their heels in the Monastery: (8)
As of 2014-12-28 01:21 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    Is guessing a good strategy for surviving in the IT business?





    Results (177 votes), past polls