I am implementing a mutual exclusion mechanism based on a file lock. Other instances of my script know that they are not supposed to run when they come accross a specific file, which is locked.
In order to achieve this, I have created and locked the file using fcntl.flock
. When I release the lock I also want to clean up the file, that it doesn't sit there indicating an old pid when no process is actually running.
My question is, when and how should I clean up the file, especially at what point can I delete it. Basically I see two options:
From my understanding, each one exposes my application to slightly different race conditions. What is best practice, what have I missed?
Here's an (overly simplified) Example:
import fcntl
import os
import sys
import time
# open file for read/write, create if necessary
with open('my_lock_file.pid', 'a+') as f:
# acquire lock, raises exception if lock is hold by another process
try:
fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
except IOError:
print 'other process running:', f.readline()
sys.exit()
try:
# do something
f.write('%d\n' % os.getpid())
f.flush()
# more stuff here ...
time.sleep(5)
finally:
# clean up here?
# release lock
fcntl.flock(f, fcntl.LOCK_UN)
# clean up here?
# clean up here?
File locking is a feature that prevents a file from being edited.
I found this related question to give some suggestions about how to handle this case:
It also made me aware of another possible race condition, which occurs when another process deletes the file just after it's been opened by the current process. This would cause the current process to lock a file that is no longer to be found on the filesystem and thus fail to block the next process that would create it anew.
There I found the suggestion to make use of the open flag O_EXCL
for atomic exclusive file creation, which is exposed via the os.open()
function for low-level file operations.
I then implemented the following example accordingly:
import os
import sys
import time
# acquire: open file for write, create if possible, exclusive access guaranteed
fname = 'my_lock_file.pid'
try:
fd = os.open(fname, os.O_CREAT|os.O_WRONLY|os.O_EXCL)
except OSError:
# failed to open, another process is running
with open(fname) as f:
print "other process running:", f.readline()
sys.exit()
try:
os.write(fd, '%d\n' % os.getpid())
os.fsync(fd)
# do something
time.sleep(5)
finally:
os.close(fd)
# release: delete file
os.remove(fname)
After implementing this, I found out that this is exactly the same approach the lockfile
module uses for its pid files.
For reference:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With