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.