I use this small one liner to update a counter in a file:
perl -pi -le '++$_' .counter NR=$(cat .counter)Many instances can update the counter and it must be really an atomic update.
First question: is the -i option doing some exclusive locking on the file? I don't think so, because from the description I read that perl actually opens the existing file, unlinks, and writes a new file. So there is a tiny window that the file ".counter" does not exist.
Getting the number from the file in the bash also happens after perl is done, so there is another race condiditon, which I initially tried to solve as: (ugh)
NR=$(perl -pi -le '++$_; print STDERR' .counter 2>&1)
I also have the problem with the first creation of the file. I use the magic increment on strings to generate numbers of fixed with, so the initial value must be "000". Testing for existance, creating the file and then using it, is another race condition, so I wrap the whole block in the bash script with:
# ... longer bash script here, and then coming to:
if lockfile -1 -r1 .counter.lock
then
trap 'rm -f .counter.lock' 0 1 2 3 13 15
test -s .counter || echo 000 >.counter
perl -pi -le '++$_' .counter
else
echo 'error: could not lock .counter" >&2
exit 2
fi
NR=$(cat .counter)
#... continuing bash script
Becoming complicated (and "lockfile" is from the "procmail" package, not really standard). So I was rewriting the whole bash thing into perl. Due to other features that I need in the rest of the program, I use Path::Tiny, and then it comes to this:
use Path::Tiny;
# ... some perl script up to:
my $FH;
my $nr;
if (-e ".counter") {
$FH = path(".counter")->openrw_raw({locked=>1}) or die("open .counter: $!\n")
{ local $/; $nr = <$FH>; }
die(".counter contains garbage\n") unless $nr =~/^\d+\n$/;
chomp $nr;
truncate($FH,0) # not really needed
seek($FH,0,0);
} else {
$FH = path(".counter)->openw_raw({locked=>1}) or die("open .counter: $!\n");
$nr = "000";
}
$nr++;
print $FH $nr, "\n";
close($FH) or die("close .counter: $!\n");
# ... and it continues using the value of $nr
This one still has a race condition between the test for existance and the creation of the new file.
Path::Tiny does open with lock exclusive, but then the perl "openw" would fail, while the "lockfile" bash construct would at least wait up to 1 second and retries (if needed more retries than I specified now in the bash version).
Any idea how to solve that? Why is this becoming so complicated? I have a feeling that this could be much more elegant.
In reply to Atomic update counter in a file by pbijnens
| For: | Use: | ||
| & | & | ||
| < | < | ||
| > | > | ||
| [ | [ | ||
| ] | ] |