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

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.