A. The flock
function
With UNIX and Windows NT, there is no problem if two different programs try to read a file at the same time. However, if a program is writing a file then all other programs should stay out of the way. There are also times when you don't want two programs to read the same file, for example, if the file is about to be updated by one program, then the information is about to be out of date so the second program shouldn't be reading it.
The ``two different programs'' could be two different instances of the same program. For example, two different users could click ``submit'' on the same form and start up the same CGI program. Then there are two instances of the same CGI program running, each receiving different form data. This is fine unless the CGI program needs to write a file to save the data.
To solve this ``collision'' problem there is a ``locking'' system
in UNIX that is also accessible as a Perl function flock
.
When you ``lock'' a file it is like posting a sign saying ``OCCUPIED''.
It works like this:
To open myfile
for reading with a filehandle FH
and lock it,
use
open FH, "<myfile";
flock FH, 2;
Then read the file until you are through with it. Then
close FH;
There is a command flock FH, 8
to unlock, but closing the
file unlocks it anyway. The file also becomes unlocked if the
CGI program finishes.
To lock a file while writing works the same.
If the file is already locked for reading or writing when
flock FH, 2
is run, the program waits at that command until
the file is not locked any more.
B. A simple locking trick
What if you want to do something complicated with a file, perhaps reading it and then adding onto it (i.e, appending to it), while handling collisions?
The simplest way is to make a new unrelated ``lock file'' and
lock it while you're doing what you want. For example, if
your program is myscript.cgi
the new file could be myscript.cgi.lock
;
you lock it when you start and unlock it when you are done.
While it's locked, another instance of the same program will wait
patiently.
This suggests putting a locking subroutine in your file cgi_helper
(at the end, except before the final 1
for ``true''). It can go
like this:
sub lock
{
my $lock_file = "$0.lock";
unless (-e $lock_file)
{
open LOCK_FILE, ">$lock_file" or
&Error_Stop(404,
"can't open lock file $lock_file for writing");
flock LOCK_FILE, 2;
print LOCK_FILE "";
close LOCK_FILE;
}
open LOCK_FILE, "$lock_file";
flock LOCK_FILE, 2;
}
sub unlock
{
close LOCK_FILE;
}
Look at these subroutines to see what they do. They don't need
parameters (arguments).
Remember that $0
gives the name of the script file that is running
(the one that included the cgi_helper
file), so the third
line of lock
adds .lock
to that to make the name. That
way different scripts have differently named lock files.
If the lock file doesn't exist then it is created, with no contents
(although contents wouldn't matter). Finally, the lock
subroutine
opens the file for reading and locks it. No actual reading takes place!
In your main CGI script, then, you simply do this:
require 'cgi_helper';
...
&lock;
before doing things to other files that might make a
collision
...
&unlock;
when you're through with those files
Very easy! You don't need flock
otherwise in the main CGI program.