Reputation: 1997
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
Upvotes: 6
Views: 1758
Reputation: 4320
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:
LOCK_UN
operation on any of these duplicate file descriptorsIn 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.
Upvotes: 3
Reputation: 6038
I think the phrasing in the man page is confusing:
$ cat lock_assoc.py
#!/usr/bin/python3
import fcntl
f3 = open('test.ini','w')
fcntl.flock(f3, fcntl.LOCK_EX | fcntl.LOCK_NB)
fcntl.flock(f3, fcntl.LOCK_EX | fcntl.LOCK_NB)
f4 = open('test.ini','w')
fcntl.flock(f4, fcntl.LOCK_EX | fcntl.LOCK_NB)
$ strace -f -e flock ./lock_assoc.py
flock(3, LOCK_EX|LOCK_NB) = 0
flock(3, LOCK_EX|LOCK_NB) = 0
flock(4, LOCK_EX|LOCK_NB) = -1 EAGAIN (Resource temporarily unavailable)
Traceback (most recent call last):
File "./lock_assoc.py", line 8, in <module>
fcntl.flock(f4, fcntl.LOCK_EX | fcntl.LOCK_NB)
BlockingIOError: [Errno 11] Resource temporarily unavailable
+++ exited with 1 +++
When the man page says:
Locks created by flock() are associated with an open file description [...]
What it apparently means is that the lock owner is associated with the open file, whereas the lock itself is (presumably obviously) associated with the file inode.
That's why I can take a lock that's already exclusively locked, if I'm using the same open file (f3
above) - it's an idempotent operation on the data structure that represents the lock owner.
The other file descriptor (f4
) has another lock owner, which has the value "I'm not the owner", and when it tries to take ownership, it fails, because the inode knows the owner is f3
.
$ cat lock_fork.py
#!/usr/bin/python3
import fcntl
import time
import os
f3 = open('test.ini','w')
fcntl.flock(f3, fcntl.LOCK_EX | fcntl.LOCK_NB)
f4 = open('test.ini','w')
pid = os.fork()
if pid > 0:
time.sleep(1)
exit(0)
elif pid == 0:
time.sleep(3)
fcntl.flock(f3, fcntl.LOCK_EX | fcntl.LOCK_NB)
fcntl.flock(f4, fcntl.LOCK_EX | fcntl.LOCK_NB)
exit(0)
$ strace -f -e flock ./lock_fork.py
flock(3, LOCK_EX|LOCK_NB) = 0
strace: Process 146372 attached
[pid 146371] +++ exited with 0 +++
flock(3, LOCK_EX|LOCK_NB) = 0
flock(4, LOCK_EX|LOCK_NB) = -1 EAGAIN (Resource temporarily unavailable)
Traceback (most recent call last):
File "./lock_fork.py", line 18, in <module>
fcntl.flock(f4, fcntl.LOCK_EX | fcntl.LOCK_NB)
BlockingIOError: [Errno 11] Resource temporarily unavailable
+++ exited with 1 +++
Here we see that fork()
duplicates both f3
and f4
, and even through the parent dies, the lock owner data structures associated with f3
and f4
remain in the child. The child's f3
can still idempotently take the lock again, whereas f4
cannot.
Upvotes: 0
Reputation: 233
File locks are associated with the open file descriptors. This means that when you will duplicate your descriptor with dup() like system call (or when the child process will inherit the file descriptor from its parent), the locks are also inherited. For example
flock(fd, LOCK_EX);//get a lock
newfd = dup(oldfd);//duplicating the file descriptors
flock(newfd, LOCK_UN);//THis call will release the lock using the duplicated file descriptor.
I hope this info helps.
Upvotes: -1