Simple file locking feature (#42024)

* class for file locking feature
This commit is contained in:
Andreas Calminder 2018-07-10 23:13:27 +02:00 committed by Toshio Kuratomi
parent 2d6bb2500e
commit 577e66660d
2 changed files with 119 additions and 0 deletions

View file

@ -0,0 +1,5 @@
---
minor_changes:
- File locking feature added, making it possible to gain exclusive access
to given file through module_utils.common.file.FileLock
(https://github.com/ansible/ansible/issues/29962)

View file

@ -0,0 +1,114 @@
# Copyright (c) 2018, Ansible Project
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
import errno
import os
import stat
import re
import pwd
import grp
import time
import shutil
import tempfile
import traceback
import fcntl
import sys
from contextlib import contextmanager
from ansible.module_utils._text import to_bytes, to_native, to_text
from ansible.module_utils.six import b, binary_type
try:
import selinux
HAVE_SELINUX = True
except ImportError:
HAVE_SELINUX = False
class LockTimeout(Exception):
pass
class FileLock():
'''
Currently FileLock is implemented via fcntl.flock on a lock file, however this
behaviour may change in the future. Avoid mixing lock types fcntl.flock,
fcntl.lockf and module_utils.common.file.FileLock as it will certainly cause
unwanted and/or unexpected behaviour
'''
def __init__(self):
self.lockfd = None
@contextmanager
def lock_file(self, path, lock_timeout=None):
'''
Context for lock acquisition
'''
try:
self.set_lock(path, lock_timeout)
yield
finally:
self.unlock()
def set_lock(self, path, lock_timeout=None):
'''
Create a lock file based on path with flock to prevent other processes
using given path
:kw path: Path (file) to lock
:kw lock_timeout:
Wait n seconds for lock acquisition, fail if timeout is reached.
0 = Do not wait, fail if lock cannot be acquired immediately,
Default is None, wait indefinitely until lock is released.
:returns: True
'''
tmp_dir = tempfile.gettempdir()
lock_path = os.path.join(tmp_dir, 'ansible-{0}.lock'.format(os.path.basename(path)))
l_wait = 0.1
r_exception = IOError
if sys.version_info[0] == 3:
r_exception = BlockingIOError
self.lockfd = open(lock_path, 'w')
if lock_timeout <= 0:
fcntl.flock(self.lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB)
os.chmod(lock_path, stat.S_IWRITE | stat.S_IREAD)
return True
if lock_timeout:
e_secs = 0
while e_secs < lock_timeout:
try:
fcntl.flock(self.lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB)
os.chmod(lock_path, stat.S_IWRITE | stat.S_IREAD)
return True
except r_exception:
time.sleep(l_wait)
e_secs += l_wait
continue
self.lockfd.close()
raise LockTimeout('{0} sec'.format(lock_timeout))
fcntl.flock(self.lockfd, fcntl.LOCK_EX)
os.chmod(lock_path, stat.S_IWRITE | stat.S_IREAD)
return True
def unlock(self):
'''
Unlock the file descriptor locked by set_lock
:returns: True
'''
if not self.lockfd:
return True
try:
fcntl.flock(self.lockfd, fcntl.LOCK_UN)
self.lockfd.close()
except ValueError: # file wasn't opened, let context manager fail gracefully
pass
return True