Question

I need to create a file that is locked against reads, at the point of creation, so that other processes that may go looking for this file do not start reading it, before it has been completely written.

I know that I can create and then lock it, but I'm concerned that this leaves me open to a race condition.

Or am I worrying about nothing here? If I have a file open for writing and then open it for reading with another process, will the reading process never see an EOF until the writing process closes the file?

Was it helpful?

Solution

There's is a race condition with > and >>, but it can be circumvented using +<.

# >
open(my $fh, '+<', $qfn) or die $!;
flock($fh, LOCK_EX) or die $!;
truncate($fh, 0) or die $!;
...

# >>
open(my $fh, '+<', $qfn) or die $!;
flock($fh, LOCK_EX) or die $!;
seek($fh, 0, SEEK_END) or die $!;
...

There is also a race condition in the scenario you describe.

Writer                       Reader
=========================    =========================
- opens file
                             - opens file
                             - locks file
                             - obtains lock on file
- locks file [blocks]        - reads the file [empty]
                             - closes and unlocks file
- obtains lock on file
- writes to file
- writes to file
- closes and unlocks file

A common strategy to avoid this problem is to have the writer

  1. create the file in a temporary directory, and then
  2. rename the file into the directory the reader monitors when the file is complete.

rename is an atomic action, so the file will appear fully formed in the directory the reader monitors. This requires the cooperation of the writer, but the best solutions will.

OTHER TIPS

Use umask(0777) before creating the file.

The file's entry in the file system will be completely inaccessible[*] (i.e. permissions ----------), even though the file handle you have still permits writes.

Then chmod() the file once you're done:

my $file = 'foo.txt';
my $umask = umask(0777);    # change the umask
open(OUT, '>', $file);      # create the file 
umask($umask);              # reset the umask
print OUT "testing\n";      # put stuff in your file
close(OUT);                 # finished with that...
chmod(0644, $file);         # change the permissions

NB: this is not actually "locking" in the strict sense, where the operating system actively prevents access to files. This is a file-system level "hack" - if you can't actually open the file then it's sort-of-locked.

[*] except to root processe.

(FWIW, reading a half-written file will result in an EOF condition.)

Either this is supported on your operating system, or it isn’t. If if it, it’s easy and simple.

use Fcntl   qw( O_CREAT   O_EXCL   O_WRONLY   O_EXLOCK  );

$creat_flags = (O_CREAT | O_EXCL | O_WRONLY | O_EXLOCK );

sysopen(SOMEHANDLE, $somepath, $creat_flags, 0666)
    || die "$0: couldn't sysopen $somepath with flags $creat_flags: $!";
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top