Semaphore Files
Probably the best strategy for safe file locking is to use semaphore files, which are files that will be locked outside of the data resource. The beauty of semaphores is we completely separate the data resource from the task of protecting it. If a program needs to access a resource it will have to obtain a lock on the semaphore file before it can touch the resource. Now, let's look at the example script again, now updated to use a semaphore file.
01: #!/usr/bin/perl -w 02: use strict; 03: use Fcntl qw(:flock); 04: my $COUNTER = 'count.dat'; 05: my $SEMAPHORE = $COUNTER . ".lock"; 06: print qq{Content-Type: text/html\n\n}; 07: open(LOCK, ">$SEMAPHORE") or die "Can't open $SEMAPHORE ($!)"; 08: flock(LOCK, LOCK_EX); 09: open(DATA, $COUNTER) or die "Can't open $COUNTER ($!)"; 10: my $count = <DATA>; 11: close DATA; 12: $count++; 13: open(DATA, "+<$COUNTER") or die "Can't open $COUNTER ($!)"; 14: print DATA $count; 15: close DATA; 16: close LOCK; 17: print qq{You are number $count!};
Line 5 defines a file that will be used as the semaphore. This can be named whatever makes sense for your application and needs. Here, we are only accessing a single file, so we use the file name, with an added .lock extension. If you need to protect many resources or a different type of data store, you should name it appropriately. You can notice that the locking of the resource itself has been removed, since it will not be accessed until the program obtains an exclusive lock on the semaphore file. You may also notice that we do not LOCK_UN the semaphore. When a filehandle is closed, or goes out of scope, the lock will automatically be removed. So, by not explicitly unlocking, we protect against a possible race condition occurring between the unlocking and closing of the semaphore. Let's take a look at the final flow to our example script.
- Open semaphore for reading
- Lock the semaphore file
- Open data for reading
- Read the data
- Close data file
- Open file for update
- Write updated data to file
- Close data file
- Close semaphore file
You may wonder why we open the semaphore file for reading. This is because an exclusive lock isn't always granted when a file is opened for just reading. We want to be sure we get the lock, so we open for writing. As this new flow shows, the data source itself is completely isolated, and should be safe if proper locking is done with other files accessing this resource. By using semaphore files you also have the flexibility to isolate resources which aren't text files. You can isolate a database, another script, an action, or whatever resource you need.