Re (tilly) 1: Flock Feedback
by tilly (Archbishop) on Feb 25, 2001 at 03:05 UTC
|
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. | [reply] |
|
|
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?
| [reply] |
|
|
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. | [reply] [d/l] [select] |
Re: Flock Feedback
by footpad (Abbot) on Feb 26, 2001 at 09:37 UTC
|
At the risk of belaboring the points, here's v0.02:
sub getNewCounter
# ---------------------------------------------------------------
# Reads and incremements the counter value. Implementation based
# on wisdom gained from perlmonks.org and the Perl Cookbook.
# ---------------------------------------------------------------
{
my( $datafile ) = @_;
my( $lockfile );
# Determine the name of the lock file
$_ = $datafile;
s/\.dat/\.lck/;
$lockfile = $_;
# The idea here is to wait up to ten seconds to gain access to
# the lock file. If that fails, well, try again later.
my( $retries ) = 10;
open( LOCK, "$lockfile" ) or die "Cannot open lock file for readin
+g. $!";
while ( $retries )
{
if ( flock( LOCK, LOCK_EX | LOCK_NB ) ) # if Fcntl isn't availa
+ble (it should be), try 2 | 4.
{
last;
}
else
{
$retries--;
sleep( 1 ) ;
}
}
# How did we get out of that loop? If we failed to flock, then fle
+e
unless ( $retries )
{ die "Cannot lock counter for reading. $!"; }
# Okay, we're assuming here...
open( FILE, "+< $datafile" ) or die "Can't open data file for readi
+ng. $!";
my( $countval ) = <FILE>;
seek( FILE, 0, 0 ) or die "Can't reset data file for writ
+ing. $!";
truncate( FILE, 0 ) or die "Can't clear data file for writ
+ing. $!";
print FILE ++$countval;
close( FILE ) or die "Cannot close counter after wri
+ting. $!";
close( LOCK ) or die "Cannot close lock file. $!";
return( $countval );
}
Yes, it assumes that the sentinel file already exists.
Better? (I ask because I may not be able to get the SecuroTroopers to install File::CounterFile.)
--f
P.S. Thanks to jptxs for picking up on the other question. Also, tilly's newest sub looks interesting and that will go into 0.03, should anything else?
Update: Thanks, tilly for your extreme patience in this. Point by Point:
- .dat is fine for this script, but good advice for future ones.
- Added LOCK_NB, per advice; see above.
- I was under the impression that one second was the finest granularity available for sleep()?
- You mean, like the 0.01 did?
- *blush*
- /me hies himself to a Monastery library...
Update #2: Quickly fixed the || typo and the magic numbers before running off to write v0.03. | [reply] [d/l] |
|
|
if ( flock( LOCK, 2 || 4 ) )
Down with magic numbers! This code is unnecessarily platform dependent. Use the Fcntl module to get the appropriate constants (as shown in the documentation for flock). (BTW, I'm assuming that the logical instead of bitwise or is just a typo.)
use Fcntl ':flock';
# ...
if ( flock( LOCK, LOCK_EX | LOCK_NB ) )
# ...
P.S. Regarding sleeping... How can I sleep() or alarm() for under a second? | [reply] [d/l] [select] |
|
|
| [reply] |
|
|
| [reply] [d/l] |
|
|
my $filename = '/path/to/file';
# Increment the counter (atomically, so no locking required)
open(FILE, ">> $filename") or warn "Missed a count";
print FILE '.' # Ouput one (arbitrary) byte
close FILE;
# What is the counter set to?
my $countval = -s $filename || 0;
The disadvantage of this technique is that it uses one byte of disk space for each count.
| [reply] [d/l] |
|
|
Not only does this use one byte of disk space per count, which is ridiculously wasteful, but you still have a race condition if two processes try to write to the file and then check its size at the same time. Even with this approach you still would need locking.
| [reply] |
|
|
(jptxs)Re: Flock Feedback
by jptxs (Curate) on Feb 25, 2001 at 07:03 UTC
|
I read this as "let me know any special issues with writing on BSD and deploying on Sun." I do this all the time, many different versions of both OS's and I have yet to have a problem. I have had plenty of the problems tilly points out, but that's another story =)
"A man's maturity -- consists in having found again the
seriousness one had as a child, at play." --Nietzsche
| [reply] [d/l] |