I'm playing around with the command flock
, which obtains and releases locks on files. For example, if I run
flock /tmp/mylock true
then it immediately exits, presumably obtaining and then releasing the lock. If I run
flock /tmp/mylock sleep 100
then it delays 100 seconds, again obtaining and releasing the lock. And, if I run the following in two separate shells:
flock /tmp/mylock sleep 100
and
flock /tmp/mylock true
then the second command is blocked, because it can't obtain the lock while the first command runs. Once the sleep 100
completes and the lock is released, the second command runs and exits. All good.
Here's the problem. If, during that 100 second delay, I run the following in a third shell:
flock -u /tmp/mylock true
then what happens? The man page for flock says:
-u, --unlock
Drop a lock. This is usually not required, since a lock is
automatically dropped when the file is closed. However, it may
be required in special cases, for example if the enclosed com-
mand group may have forked a background process which should not
be holding the lock.
So, this should drop the lock, which should allow flock /tmp/mylock true
to run, right? (I would also guess that the flock /tmp/mylock sleep 100
would immediately exit, but that's speculation.)
What happens? Nothing. flock -u /tmp/mylock true
immediately exits, but flock /tmp/mylock true
continues to be blocked, and flock /tmp/mylock sleep 100
continues to exit.
What does flock -u /tmp/mylock <command>
actually do?
(All examples tested on Ubuntu 18.04.)
Here's an example with -u
working with file descriptor 9 open on a file mylock
, successfully unlocking 9 so that a backgrounded flock mylock
can proceed.
Note that flock 9
cannot also have a command as in that case the "9" is taken to be a filename, not an fd.
bash -s <<\! 9>mylock 2>&1 |
flock 9; echo gotlock1
flock 9; echo gotlock2
9>&- flock mylock bash -c 'echo start_sleep;sleep 8; echo end_sleep' &
sleep 2
flock -u 9; echo unlock; sleep .1
flock 9; echo gotlock3
!
awk '{t2=systime(); if(t1==0)t1=t2; printf "%2d %s\n",t2-t1,$0; t1=t2}'
The first line makes bash run the following lines after opening fd 9, but also pipes stdout and stderr through the awk script seen at the end. This is just to annotate the output with the timing of the lines. The result is:
0 gotlock1
0 gotlock2
2 unlock
0 start_sleep
8 end_sleep
0 gotlock3
This shows the first 2 flock 9
commands run immediately. Then a flock mylock
command is run in the background, after closing fd 9 just for this line. This command could have been run from a second window, for example. The output shows that it hangs, as we do not see start_sleep
. This means that the preceding flock 9
did actually get an exclusive lock.
The output then shows that after sleep 2
and flock -u 9
we get the unlock
echo, and only then does the background command get the lock and starts its sleep 8
.
The main script immediately does a flock 9
, but the output shows that this does not proceed until the background script ends with end_sleep
8 seconds later, and the main script outputs gotlock3
.
The lslocks
command sometimes shows 2 processes interested in the lock. The *
means a wait:
COMMAND PID TYPE SIZE MODE M START END PATH
flock 23671 FLOCK 0B WRITE* 0 0 0 /tmp/mylock
flock 23655 FLOCK 0B WRITE 0 0 0 /tmp/mylock
But it does not show the result of the first flock 9
on its own, presumably because there is no process with the lock, even though the file truly is locked, as we see when the background job cannot proceed.