[comp.unix.i386] Locking from shell scripts

martin@mwtech.UUCP (Martin Weitzel) (06/05/90)

In article <475@van-bc.wimsey.bc.ca> sl@van-bc.wimsey.bc.ca (Stuart Lynne) writes:
[about locking from interface programs of the SysV spool system]
>
>This script shows how this could be done, except that there is a race 
>condition to create the lock file. 
>
>A C program would be a much better solution.
>
[script deleted, basically the existence of some file was used
as lock condition; because `test -f lockfile was done before
`echo ... >lockfile', there remained a small chance for two
simultaneous executions of the script both to "think" they
have won - or technically spoken, they succeeded locking the
resource]

As the poster allready pointed out, there is a race condition
with this method. Without changing much, this could be avoided
by `link'ing to the lockfile instead of creating it. Eg. write
to a temporary file first, then place the lock with `/etc/link';
this operation will replace the test for existence in the script.
(Note that for some newer "ln"-commands it is no error if the
destination file exists, though there's a "-f" option which
seems at first glance to give the choice to the user ... :-().

Linking (and unlinking) provide some "semaphor"-mechanism for
shell scripts, which works even for the priviledged processes.
(Note that the latter is *not* the case for the more common
technique of creating a file without access permissions.)
If your system supports FIFOs, there's another interresting
technique, which even avoids the usual trade off of "busy waiting"
vs. "quick response". (This is not so much a problem in the
spooler example, but may be in other applications.)

Locking with FIFOs works by creating a named pipe with some
daemon process, then write a single new-line character into
it and sleep forever. If you don't want to program in C, the
latter may be done by executing repeated `sleep 30000' from
a never ending loop. It is essential that the daemon keeps
the FIFO open for read and write, so that the character in it
is never discarded! All in all it's a bit tricky to get this
started, as you can open a FIFO from the shell only for read OR
for write and each request would wait for the other, but you can
get around this with:

	mknod LOCKFIFO p
	echo >LOCKFIFO &
	while sleep 30000
	do :; done <LOCKFIFO &

Some other process then "places a lock" by reading from the FIFO.
If the FIFO contains a new-line character, this can be done by
the shell's read command. (In fact, this is the only reason why
a new-line was written to the FIFO, otherwise every character
would have served similar well). Now there is no more character
in the FIFO, so that a third (and forth, fifth, ...) process which
will try to read is blocked. As this is done by the kernel, no
CPU-time is consumed in this situation. When the first process
leaves its critical region, it writes a new-line character back
into the FIFO, which eventually will be read by some other
process (a waiting one or one that starts later; remember: the
daemon constitutes a `possible consumer' for data in the FIFO,
so that the written character will never be lost).

The disadvantages of this technique are similar to using a lockfile,
especially the case that the process which placed the lock dies
without releasing it must be solved. But because the race condition
is avoided now, any other technique could be used. Note that the
`FIRST writes PID to file - SECOND issues kill PID to see if FIRST
is still alive' is one choice, though this technique has a (very)
small chance to go wrong. (Assume that one process dies, then the
PIDs wrap around and it happens that a very long running process
picks just that PID that is still in the lockfile).

If you want to make it "really water-proof", you can assign the
role of a "watchdog" to the daemon. For that purpose a process
that successfully placed the lock should write its PID into some
other FIFO which is read by the daemon. After this read returns,
the daemon goes into a loop and periodically checks the validity
of the PID (eg. with the `kill PID' approach). If this shows that
the locking process died without releasing the lock, the daemon
could easily write a new new-line into the FIFO.

Everyone's happy now? - Not me. What I want to know from the
readership is if anyone allready uses "FIFO-Locking", as I have
never found in mentioned in any literature (maybe there's a leak
of memory in my head :-), but at least IMHO). Do you have pointers
if someone else allready described it or did you yourself "invent"
it. Further: Has anybody allready added possible improvements, eg.
a "first come first served" scheme for competing processes (the
kernel gives *no* guarantee which of several blocked-on-read
processes will be unblocked when a character becomes available
in the FIFO.)
-- 
Martin Weitzel, email: martin@mwtech.UUCP, voice: 49-(0)6151-6 56 83