Locking a File (Perl Cookbook, 2nd Edition)

## 7.18. Locking a File

### 7.18.1. Problem

Many processes need to update the same file simultaneously.

### 7.18.2. Solution

Have all processes honor advisory locking by using `flock`:

>     use Fcntl qw(:flock);               # for the LOCK_* constants
>     open(FH, "+<", $path)               or die "can't open $path: $!";
>     flock(FH, LOCK_EX)                  or die "can't flock $path: $!";
>     # update file, then...
>     close(FH)                           or die "can't close $path: $!";

### 7.18.3. Discussion

Operating systems vary greatly in the type and reliability of locking techniques available. Perl tries hard to give you something that works, even if your operating system uses its own underlying technique. The `flock` function takes two arguments: a filehandle and a number representing what to do with the lock on that filehandle. The numbers are normally represented by names, such as `LOCK_EX`, which you can get from the Fcntl or IO::File modules.

Locks come in two varieties: shared (`LOCK_SH`) and exclusive (`LOCK_EX`). Despite what you might infer by "exclusive," processes aren't required to obey locks on files. Another way of saying this is that `flock` implements advisory locking. It allows processes to let the operating system suspend would-be writers of a file until any readers are finished with it.

Flocking files is like putting up a stoplight at an intersection. It works only if people pay attention to whether the light is red or green—or yellow for a shared lock. The red light doesn't stop traffic; it merely signals that traffic should stop. A desperate, ignorant, or rude person will still go flying through the intersection no matter what the light says. Likewise, `flock` only blocks out other flockers—not all processes trying to do I/O. Unless everyone is polite, accidents can (and will) happen.

The polite process customarily indicates its intent to read from the file by requesting a `LOCK_SH`. Many processes can have simultaneous shared locks on the file because they (presumably) won't be changing the data. If a process intends to write to the file, it should request an exclusive lock via `LOCK_EX`. The operating system then suspends the requesting process until all other processes have released their locks, at which point it grants the lock to the suspended process and unblocks it. You are guaranteed that no other process will be able to successfully run `flock(FH`, `LOCK_EX)` on the same file while you hold the lock.

(This is almost—but not quite—like saying there can be only one exclusive lock on the file. Forked children inherit not only their parents' open files, but, on some systems, also any locks held. That means if you hold an exclusive lock and `fork` without `exec` ing, your child might also have that same exclusive lock on the file!)

The `flock` function is therefore by default a blocking operation. You can also acquire a lock without wedging your process by using the `LOCK_NB` flag when you request a lock. This lets you warn the user that there may be a wait until other processes with locks are done:

>     unless (flock(FH, LOCK_EX|LOCK_NB)) {
>         warn "can't immediately write-lock the file ($!), blocking ...";
>         unless (flock(FH, LOCK_EX)) {
>             die "can't get write-lock on numfile: $!";
>         }
>     }

If you use `LOCK_NB` and are refused a `LOCK_SH`, then you know that someone else has a `LOCK_EX` and is updating the file. If you are refused a `LOCK_EX`, then someone holds either a `LOCK_SH` or a `LOCK_EX`, so you shouldn't try to update the file.

Locks dissolve when the file is closed, which may not be until your process exits. If you lock or unlock the file, Perl automatically flushes its buffers for you.

Here's how you increment a number in a file, using `flock`:

>     use Fcntl qw(:DEFAULT :flock);

>     sysopen(FH, "numfile", O_RDWR|O_CREAT)
>                                         or die "can't open numfile: $!";
>     flock(FH, LOCK_EX)                  or die "can't write-lock numfile: $!";
>     # Now we have acquired the lock, it's safe for I/O
>     $num = <FH> || 0;                   # DO NOT USE "or" THERE!!
>     seek(FH, 0, 0)                      or die "can't rewind numfile : $!";
>     truncate(FH, 0)                     or die "can't truncate numfile: $!";
>     print FH $num+1, "\n"               or die "can't write numfile: $!";
>     close(FH)                           or die "can't close numfile: $!";

Closing the filehandle flushes the buffers and unlocks the file. The `truncate` function is discussed in Chapter 8.

File locking is not as easy as you might think—or wish. Because locks are advisory, if one process uses locking and another doesn't, all bets are off. Never use the existence of a file as a locking indication because there exists a race condition between the test for the existence of the file and its creation. Furthermore, because file locking is an intrinsically stateful concept, it doesn't get along well with the stateless model embraced by network filesystems such as NFS. Although some vendors claim that `fcntl` addresses such matters, practical experience suggests otherwise. The CPAN module File::NFSLock uses a clever scheme to obtain and release locks on files over NFS, which is different from the `flock` system.

Don't confuse Perl's `flock` with the SysV function `lockf`. Unlike `lockf`, `flock` locks entire files at once. Perl doesn't support `lockf` directly, although the CPAN module File::Lock does offer its functionality if your operating system has `lockf`. The only way in pure Perl to lock a portion of a file is to use the `fnctl` function, as demonstrated in the lockarea program at the end of this chapter.

### 7.18.4. See Also

The `flock` and `fcntl` functions in perlfunc(1) and in Chapter 29 of Programming Perl; the documentation for the standard Fcntl and DB_File modules (also in Chapter 32 of Programming Perl); the documentation for the CPAN modules File::Lock and File::NFSLock; Recipe 7.24; Recipe 7.25

Copyright © 2003 O'Reilly & Associates. All rights reserved.