rzward has asked for the wisdom of the Perl Monks concerning the following question:

Hello,

I have a CGI script that reads from and writes to a text file on the web server. Before reading or writing, the script uses flock to lock another file. The script is distributed with a program I sell and has been working on many different Unix, Linux and MS Windows web servers for years.

However, one customer discovered that on his Linux web server, the text file is consistently getting corrupted. I sent him a modified version of the script that tests the flock call and it seems flock always returns TRUE right away, even when the file it is trying to lock should still be locked by another user.

Below are two subroutines: LockFile and UnlockFile. LockFile creates a temporary file when it is asked to lock the file exclusively. UnlockFile deletes the temporary file.

For the purposes of this discussion, I simplified the functions a little. The script with the modified LockFile and UnlockFile subroutines has been tested on Linux and MS Windows web servers. On the customer's Linux web server, the LockFile subroutine consistently dies with the message "Acquired an exclusive lock for $flockPath but cannot create $fexistPath." It does not do this on any other web server that I have tested.

I thought there may be a problem with sysopen on the customers' web server. So, I added an alternative method to flock that used sysopen instead and it worked.

Even if the CGI script is run using the same process for each website visitor on the customer's web server, shouldn't flock wait until the lock is released before returning TRUE?

sub LockFile { local $path = $_[0] ; local $mode = $_[1] ; local $flockPath = $path . '.lck' ; local $flockFH = $flockPath ; open ($flockFH, ">$flockPath") or die ("Failed to open $flockPath") +; if (flock ($flockFH, $mode)) { use Fcntl ; local $fexistPath = $path . '.lck.tmp' ; if ($mode & LOCK_EX) { local $fexistFH = $fexistPath ; sysopen ($fexistFH, $fexistPath, O_WRONLY | O_CREAT | O_EXCL) or die ("Acquired an exclusive lock for $flockPath but cannot +create $fexistPath.") ; close $fexistFH ; } elsif (-e $fexistPath) { die ("Acquired a shared lock for $flockPath but found that $fexi +stPath exists.") ; } } else { die ("System Busy") ; } return ($flockFH, $path, $mode) ; } sub UnlockFile { use Fcntl ; local $fh = $_[0] ; local $path = $_[1] ; local $mode = $_[2] ; if ($mode & LOCK_EX) { local $fexistPath = $path . '.lck.tmp' ; unlink ($fexistPath) > 0 or die ("Failed to delete the lock file $ +fexistPath.") ; } close ($fh) ; }

For example, the subroutines would be called like:

local ($lockFH, $lockPath, $lockMode) = LockFile ("test.txt", 2); # Wait 10 seconds before calling UnlockFile UnlockFile ($lockFH, $lockPath, $lockMode);

Thank you in advance for your help.

  • Comment on In a CGI script can flock return TRUE right away even if the file has already been locked?
  • Select or Download Code

Replies are listed 'Best First'.
Re: In a CGI script can flock return TRUE right away even if the file has already been locked?
by merlyn (Sage) on Feb 20, 2006 at 15:35 UTC
    I believe if the disk is NFS-mounted (or maybe even SMB-mounted), your scheme will fail, because the creation of the file can begin on two separate mounted machines, both apparently succeeding, and both apparently flocking their own view of the world.

    If that isn't the case, I'm curious. It seems you're going through a lot more work than a simple flock(). Why did you add so much chicanery?

    -- Randal L. Schwartz, Perl hacker
    Be sure to read my standard disclaimer if this is a reply.

      Thank you for your comments.

      I suspected that flock was somehow not behaving the way I expected when the problem was reported. I added the call to sysopen to test whether or not flock was returning TRUE when it should instead be waiting. The non-testing version of the LockFile subroutine doesn't do this.

      The idea is that if flock returns TRUE and the temporary file exists, then flock must have been called prevously with LOCK_EX and UnlockFile must not yet have been called. I couldn't think of a better way of testing to see if flock was really working on the web server.

      So, if I understand what you are saying, on NFS and/or maybe SMB-mounted disks it's possible that even though the pathname given to the open command is the same, each time, flock may very well treat the file as though it were at a different location and therefore grant the exclusive lock?

      When you say NFS or SMB-mounted disks, are you talking about a group of machines acting as resources to a web server?

      Thank you for your help.