Search code examples
linuxlockingfcntl

file write lock and child process


If a process give a file a write lock and then it spawn a child process, is lock inherited by the child process? If yes, then there is 2 process have the write lock, I learned that there is only 1 process can have a write lock, some truth? here is a test python code

#!/usr/bin/python

import fcntl
import time
import os

fp = open('test.ini','w')
fcntl.flock(fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
pid = os.fork()

if pid > 0:
    time.sleep(10)
    exit(0)
if pid == 0:
    time.sleep(100)
    exit(0)

when the parent exist, i tried to get the lock of file test.ini, but failed , so I guess the child has the lock


Solution

  • So, as you've noted in the man page for flock(2), the relationship between the lock and the file is as follows:

    Locks created by flock() are associated with an open file description (see open(2)). This means that duplicate file descriptors (created by, for example, fork(2) or dup(2)) refer to the same lock, and this lock may be modified or released using any of these file descriptors. Furthermore, the lock is released either by an explicit LOCK_UN operation on any of these duplicate file descriptors, or when all such file descriptors have been closed.

    To be clear, it notes two cases when the lock is released:

    • by an explicit LOCK_UN operation on any of these duplicate file descriptors
    • when all such file descriptors have been closed

    In the code provided in the question, there is no explicit unlock in either the parent or child execution, so the first condition won't be met. Similarly, as the second condition requires that all such file descriptors have been closed, this won't be met by the earlier termination of the parent process; only when the child process terminates later.

    You can satisfy yourself that this holds by adding an explicit unlock:

    fcntl.flock(fp, fcntl.LOCK_UN)
    

    in the parent code path before the exit, and then test taking the lock from a separate process before the child exits. Such modified code can be found below:

    #!/usr/bin/python
    
    import fcntl
    import time
    import os
    
    fp = open('test.ini','w')
    fcntl.flock(fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
    pid = os.fork()
    
    if pid > 0:
        time.sleep(10)
        fcntl.flock(fp, fcntl.LOCK_UN)
        exit(0)
    if pid == 0:
        time.sleep(100)
        exit(0)
    

    You can also read /proc/locks or use lslocks (a parser of same) to show the currently held file locks in the system. The contents of the former look like:

    1: FLOCK  ADVISORY  WRITE 358 00:15:628 0 EOF
    2: FLOCK  ADVISORY  WRITE 296 00:15:608 0 EOF
    3: FLOCK  ADVISORY  WRITE 291 00:15:599 0 EOF
    4: FLOCK  ADVISORY  WRITE 25874 b3:02:256617 0 EOF
    

    and the output of the latter:

    COMMAND           PID  TYPE SIZE MODE  M START END PATH
    (unknown)       25874 FLOCK      WRITE 0     0   0 /...
    cron              291 FLOCK      WRITE 0     0   0 /run...
    (unknown)         296 FLOCK      WRITE 0     0   0 /run...
    (unknown)         358 FLOCK      WRITE 0     0   0 /run...
    

    When filtering this output by PID the one to use is the parent PID, unless the mode has been changed in the child.