in reply to Flock Feedback

First of all I would suggest moving the information on where those directories are into a configuration file. You will (presumably) have a number of CGI scripts and you don't want to have the same information included in every one of them. (Alternately you could have a makefile for your scripts that inserts that information in each of them.)

Secondly your flock makes all of the usual mistakes. When you close it you have no guarantee that when you turn around you will be next to get the file. What is likely to happen sooner or later is that you read, someone else is waiting to read, you close, it gets the lock and reads, closes, you write, it writes. Thereby missing a count. What is worse is if you manage to open for write (wiping out the file) before it reads.

If you must flock the file you are writing, the right way to do it is a non-destructive open for append or read, flock for writing, seek to the beginning, read, seek back, truncate, write, close (without unlocking). The simpler (but still safe) way is to have a sentinel (eg "counter.lock") that you open, flock, access the other file, then close. (Don't ever delete this!)

For a rather heavy version of the sentinel file approach see Simple Locking.

Replies are listed 'Best First'.
Re: Re (tilly) 1: Flock Feedback
by footpad (Abbot) on Feb 25, 2001 at 08:48 UTC
    First off, the config stuff is already externalized. I chose not to include that code because I didn't want to confuse the actual question. (Doing my homework and providing the simplest case. :} )

    I think I see your point, but am not sure I followed the solution. I think you said, instead of opening/flocking/closing the data file twice, do something like this:

    • Try to flock the sentinel.
    • If it succeeds, open, read, edit, and close the data file.
    • Release the sentinel.
    • If the original flock failed, sleep for a period of time and then retry (up to a preset number of times, failing if the retries run out).

    Is that right? (I'm just making sure I understand the basic logic before I dive into the code.)

    -f

    Update: Okay; I think I get it. Two follow ups:

    • In your example, where is FH pointing? The data file or the sentinel?
    • Kanji offered File::CounterFile. Any thoughts/feedback?
      Close. Your traditional flock will either be blocking or non-blocking. If you only set the LOCK_EX flag, it will block by default, therefore when you try to get the lock it will wait indefinitely.

      If this is not what you want, then you should set the LOCK_NB flag as well, and poll at a specified interval a specified number of times before giving up. (You can set an alarm, but Perl's signal handling is not very good so I would not recommend doing that.)

      The portable way to get those constants is to

      use Fcntl qw(:flock); # time passes foreach (1..10) { $is_locked = flock(FH, LOCK_EX | LOCK_NB); last if $is_locked; ### Or gets both flags sleep 1; } # Test $is_locked here
      See the longer example for the full semantics of how to do the open, etc. (There I am locking a sentinel...)

      UPDATE
      In the above FH is a filehandle that has already been opened. As for the other question, yeah that module will solve the problem. But everything that I said about locking will also hold when you want to start playing games with reading and writing other kinds of data files as well...

      UPDATE 2
      OK, if you want to do this explicitly for a sentinel, here is some untested code:

      use Carp; use Fcntl qw(:flock); sub open_sentinel { my $lock_file = shift; my $fh = do {local *FOO}; # Old trick open ($fh, "> $lock_file") or die "Cannot write $lock_file: $!"; my $is_locked; foreach (1..10) { $is_locked = flock($fh, LOCK_EX | LOCK_NB); last if $is_locked; select (undef, undef, undef, 0.01); } return $is_locked ? $fh : undef; }
      This will wipe out the file (hence it is only useful to use on a sentinel file). To get the lock just try to grab. The variable will only be defined if you got the lock, and the lock is freed when the variable is destroyed. (So you can enter a routine, ask for a lock, if you get it then proceed, if you don't then exit immediately.)

      The above example tries 10 times, one hundredth of a second apart.