diff --git a/changelogs/fragments/add_file_lock_feature.yaml b/changelogs/fragments/add_file_lock_feature.yaml
new file mode 100644
index 00000000000..0aa8245853b
--- /dev/null
+++ b/changelogs/fragments/add_file_lock_feature.yaml
@@ -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)
diff --git a/lib/ansible/module_utils/common/file.py b/lib/ansible/module_utils/common/file.py
new file mode 100644
index 00000000000..e9b3b48cc55
--- /dev/null
+++ b/lib/ansible/module_utils/common/file.py
@@ -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