Can you see the problem with this code? It comes from Ansible, v18.104.22.168.
if not os.path.exists(value): os.makedirs(value, 0o700)
It’s quite straightforward. It checks if a directory path exists. If
it does not, then it creates the directory path, similar to
mkdir -p. What could be wrong?
The problem is that the check and the creation are not atomic. If two Ansible processes are started and the path does not exist, we have a race condition. In other words, someone can create the directory between the check for existence and the call to makedirs(). Then, makedirs() will fail and throw an exception. Ansible will exit. Your cluster won’t come up, etc.
OK, but how big is that window? You’d be surprised, because I actually hit the problem. Here’s the stack to prove it:
Traceback (most recent call last): File "/opt/python/bin/ansible-playbook", line 5, in <module> pkg_resources.run_script('ansible==22.214.171.124', 'ansible-playbook') File "/opt/python/lib/python2.6/site-packages/setuptools-0.6c11-py2.6.egg/pkg_resources.py", line 489, in run_script File "/opt/python/lib/python2.6/site-packages/setuptools-0.6c11-py2.6.egg/pkg_resources.py", line 1207, in run_script File "/opt/python/lib/python2.6/site-packages/ansible-126.96.36.199-py2.6.egg/EGG-INFO/scripts/ansible-playbook", line 44, in <module> import ansible.constants as C File "/opt/python/lib/python2.6/site-packages/ansible-188.8.131.52-py2.6.egg/ansible/constants.py", line 152, in <module> DEFAULT_LOCAL_TMP = get_config(p, DEFAULTS, 'local_tmp', 'ANSIBLE_LOCAL_TEMP', '$HOME/.ansible/tmp', istmppath=True) File "/opt/python/lib/python2.6/site-packages/ansible-184.108.40.206-py2.6.egg/ansible/constants.py", line 78, in get_config os.makedirs(value, 0o700) File "/opt/python/lib/python2.6/os.py", line 157, in makedirs mkdir(name, mode) OSError: [Errno 17] File exists: '/home/pgxl/.ansible/tmp'
Python differs from Unix
Python documentation mentions that os.makedirs() can fail if the leaf directory exists:
Raises an error exception if the leaf directory already exists or cannot be created.
This behavior is different from
which will never complain if any
of the components already exist!
$ mkdir -p /tmp $ mkdir /tmp mkdir: /tmp: File exists $
This bug is already fixed in the Ansible tree (there goes my chance for glory).
For the sake of completion, here is the correct code:
import os import errno if not os.path.exists(value): try: os.makedirs(value, 0o700) except OSError as e: if e.errno != errno.EEXIST: raise