I have a long running script that logs it's process to a file and an FCGI that reads said file every 200ms when prompted to by ajax calls and it seems to mostly work but occasionally fails. I believe I've recreated the problem with the following 2 SSCCE's
one.pl
use Fcntl qw (:DEFAULT :flock :seek);
my $file = '/dev/shm/tmpcount.txt';
for (1 .. 180) {
print "$_\n";
&filewrite($file,$_);
sleep 1;
}
sub filewrite {
my ($file,$enc) = @_;
open(my $fh,">", $file) || die "Can't open $file for reading: $!";
my $mode = LOCK_EX;
flock($fh,$mode) or die "Couldn't lock $file for write";
print $fh $enc;
close $fh;
}
two.pl
use Fcntl qw (:DEFAULT :flock :seek);
my $file = '/dev/shm/tmpcount.txt';
my $c;
do {
if (-e $file) {
$c = &fileread($file);
die "C is empty" unless ($c);
print "count is $c \n";
} else { print "File not found\n"; }
} until ($c == 180);
sub fileread {
+ # Read using perl IO
my $file = shift;
open(my $fh, "<", $file) || die "Can't open $file for reading: $!";
my $mode = LOCK_SH;
flock($fh,$mode) or die "couldn't lock";
my $string = do { local $/; <$fh> };
close $fh;
return $string;
}
When I use warn instead of die the script appears to work, as it is it will invariably complain that $c is empty at some point. What I think is happening is one.pl opens the file and truncates it but before it gets to calling flock two.pl swoops in and reads an empty file. I suppose the obvious solution is semaphores but as I'm trying to develop a "safe" file handling library I'm wondering if there's another way to go about it. My first thought was an atomic open and lock with sysopen but apparently linux can't do that. I'm wondering if opening the file readwrite and truncating after flock would solve this or would possible race conditions remain? I'm aware if multiple processes are doing read-mutate-write then a semaphore lock is the only way to go but I'm curious if 1 writing and 1 reading process can work without them.