summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian Coca <bcoca@users.noreply.github.com>2022-07-07 17:49:34 -0400
committerGitHub <noreply@github.com>2022-07-07 16:49:34 -0500
commit357ca6394e713a8a4b477bdf2b7b871f38c51364 (patch)
tree9598d4a6cf5f62ff156945b641c7bc4e9b55c80f
parent3ef4609bca867e2751a470e1c4c49d8cc3005248 (diff)
downloadansible-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.yml2
-rw-r--r--lib/ansible/plugins/cache/__init__.py20
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):