Search code examples
shelllockingflock

What does `flock -u` actually do?


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.)


Solution

  • 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.