diff options
author | Brian Coca <bcoca@users.noreply.github.com> | 2022-07-07 17:49:34 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-07-07 16:49:34 -0500 |
commit | 357ca6394e713a8a4b477bdf2b7b871f38c51364 (patch) | |
tree | 9598d4a6cf5f62ff156945b641c7bc4e9b55c80f | |
parent | 3ef4609bca867e2751a470e1c4c49d8cc3005248 (diff) | |
download | ansible-357ca6394e713a8a4b477bdf2b7b871f38c51364.tar.gz |
Ensure atomic writes for cache file (#78208) (#78217)
* Ensure atomic writes for cache file
helps avoid errors in highly concurrent environments
(cherry picked from commit f6419a53f6e954e5fae8cd3102619dadb6938272)
-rw-r--r-- | changelogs/fragments/atomic_cache_files.yml | 2 | ||||
-rw-r--r-- | lib/ansible/plugins/cache/__init__.py | 20 |
2 files changed, 18 insertions, 4 deletions
diff --git a/changelogs/fragments/atomic_cache_files.yml b/changelogs/fragments/atomic_cache_files.yml new file mode 100644 index 0000000000..29d30e0e59 --- /dev/null +++ b/changelogs/fragments/atomic_cache_files.yml @@ -0,0 +1,2 @@ +bugfixes: + - file backed cache plugins now handle concurrent access by making atomic updates to the files. diff --git a/lib/ansible/plugins/cache/__init__.py b/lib/ansible/plugins/cache/__init__.py index 50acefa225..aa5cee4af4 100644 --- a/lib/ansible/plugins/cache/__init__.py +++ b/lib/ansible/plugins/cache/__init__.py @@ -19,9 +19,10 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type import copy +import errno import os +import tempfile import time -import errno from abc import abstractmethod from collections.abc import MutableMapping @@ -162,10 +163,21 @@ class BaseFileCacheModule(BaseCacheModule): self._cache[key] = value cachefile = self._get_cache_file_name(key) + tmpfile_handle, tmpfile_path = tempfile.mkstemp(dir=os.path.dirname(cachefile)) try: - self._dump(value, cachefile) - except (OSError, IOError) as e: - display.warning("error in '%s' cache plugin while trying to write to %s : %s" % (self.plugin_name, cachefile, to_bytes(e))) + try: + self._dump(value, tmpfile_path) + except (OSError, IOError) as e: + display.warning("error in '%s' cache plugin while trying to write to '%s' : %s" % (self.plugin_name, tmpfile_path, to_bytes(e))) + try: + os.rename(tmpfile_path, cachefile) + except (OSError, IOError) as e: + display.warning("error in '%s' cache plugin while trying to move '%s' to '%s' : %s" % (self.plugin_name, tmpfile_path, cachefile, to_bytes(e))) + finally: + try: + os.unlink(tmpfile_path) + except OSError: + pass def has_expired(self, key): |