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

I have a script that creates a temporary file, writes to it, and renames it to another file. Sometimes the script is interrupted whilst it's running so the temporary file never gets renamed to the original, which I don't want to happen. I guess this isn't an uncommon problem, so can someone point me in the right direction of how to deal with this properly?

I'm guessing I'll have to call the script with a wrapper of some sort that checks to see if everything's done properly, but if there's a way to get the script to clean up after itself, that would definitely be a much neater (sic) method.

  • Comment on Temporary files and cleaning up after an interruption

Replies are listed 'Best First'.
(tye)Re: Temporary files and cleaning up after an interruption
by tye (Sage) on Jan 04, 2001 at 21:08 UTC

    I was thinking of typical solutions for this problem:

    • unlink() the file so it automatically gets freed when you close it [wouldn't work here since you can't create a new hard link from an open file handle, as nice as that would be in a lot of situations]
    • Mark the file as "delete on close" [I think this is only a Win32 feature]
    • Name the file such that the next run of the script can delete it [only works in some situations and this doesn't sound like one]
    • Catch the signals that might be "interrupting" the script and have the signal handler die and have an END block clean up [you can't catch all signals and having a signal handler means that there is a real (but fairly small) chance that your script will crash after it gets a caught signal]
    • Have a parent spawn a child to run the script and then have the parent wait for the child to finish and then clean up if the child didn't [but now the parent is the one that has to trap interrupts so we don't win much here]
    Then a (perhaps novel) approach came to me.

    You could spawn a child that deamonizes itself (so signals to the parent don't get sent to the child), ignores all the signals it can (so no need for signal handlers and the problems they cause in Perl), and then waits for the parent to exit.

    Ah, but how does a child wait for a parent to exit? Well, just have the parent flock() the temporary file before creating the child and pass a handle for the temporary file to the child and the child can block on flock() waiting for the parent to close the file. If the parent dies suddenly, the operating system will still close the file, which releases the lock and causes the child to wake up and delete the file.

    To prevent a race condition, I'd have the parent not even try to delete the file (otherwise a new invocation of the script might pick the same file name while a previous child is just getting around to deleting what it considers to be an old file). So in your case (of renaming the file), the parent should just create an extra link (though this makes the script less portable).

    If you have trouble coding this, then let me/us know.

            - tye (but my friends call me "Tye")
      Says tye:

      How does a child wait for a parent to exit? (use flock as an IPC mechanism)
      That's nice. Thanks for the idea.

      Here's an alternative mechanism. I think this might be more commonly used:

      #!/usr/bin/perl pipe R, W or die; die unless defined my $pid = fork; if ($pid) { # parent close R; print "parent: I'm going to do some work now\n"; for (1 .. rand 12) { print "Parent is working...\n"; sleep 1; } print "parent: exiting\n"; } else { # child close W; print "child: waiting for parent to exit\n"; <R>; print "child: parent exited; exiting\n"; }
      I used this technique extensively in my obfuscated contest entry.

        Says tye:

        How does a child wait for a parent to exit? (use flock as an IPC mechanism)

        Another related technique:

        # in the child: while (whatever...) { do_some_work(); if (getppid() == 1) { print "my parent is dead!"; clean_up(); exit; } }
Re: Temporary files and cleaning up after an interruption
by davorg (Chancellor) on Jan 04, 2001 at 17:05 UTC

    Sounds like you need to install a signal handler which would either prevent the process from being interrupted or cleans up the files when it is interrupted.

    Look for docs on the %SIG hash.

    --
    <http://www.dave.org.uk>

    "Perl makes the fun jobs fun
    and the boring jobs bearable" - me

Re: Temporary files and cleaning up after an interruption
by repson (Chaplain) on Jan 04, 2001 at 17:47 UTC
    /me thinks
    You may want an END block along these general lines.

    END { system('\bin\mv',TEMPFILENAME,FILENAME) }

    I'm not sure what type on interruption you are talking about or which ones will run the END block, but it may solve the problem.

Re: Temporary files and cleaning up after an interruption
by clemburg (Curate) on Jan 04, 2001 at 18:11 UTC

    Another solution might be to stick to a naming convention when generating the temporary files, and make another script run every X time units that deletes all files matching the naming convention that are older than Y time units.

    Christian Lemburg
    Brainbench MVP for Perl
    http://www.brainbench.com

      I was thinking of doing something like that, but there were a couple of reasons I decided against it.

      Firstly, it makes the temporary files (more) predictable, which makes it easier to exploit the script (I know if everything else is handled properly this shouldn't be a problem, but you can never be too paranoid careful).

      Also, the script is a fairly small utility and I'd like to make it as self-contained as possible. Having a cleanup script run via cron is a bit overkill for this.

      From what I've been reading about %SIG, it looks like the right solution. I'll post something if I figure out a nice solution.

Re: Temporary files and cleaning up after an interruption
by purp (Novice) on Jan 04, 2001 at 23:11 UTC

    How about something like this?

    #!/usr/bin/env perl use strict; # ...it's not just for teachers anymore. my ($tmpFile, $finalFile); sub tidyUp { # Time to go! if ($tmpFile && (-e $tmpFile)) { unless (-e $finalFile) { rename($tmpFile, $finalFile) or die ">>> Can't move $tmpFile -> +$finalFile ($!)\n";; } else { die ">>> Can't move $tmpFile -> $finalFile because $finalFile al +ready exists!\n"; } } else { die ">>> No temporary file to move.\n"; } } # Now let's catch a few well known disruptive signals -- the troublema +kers ;] $SIG{'INT'} = $SIG{'QUIT'} = $SIG{'ABRT'} = $SIG{'TERM'} = \&tidyUp; # Go on about your business ... open(tmpFile, $tmpFile);
    ...

    Utterly untested and written in a browser window to boot. If this code kills your dog, you've got my sympathy. ;]

    Cheers!

    --j