jenkins-bot has submitted this change. (
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1348534?usp=email )
Change subject: [api] Make API cache writes atomic and failure-safe
......................................................................
[api] Make API cache writes atomic and failure-safe
Serialize API cache entries into a unique temporary file using
tempfile.mkstemp before safely publishing via os.replace(). This
prevents cache readers from encountering corrupted partial writes.
It isolates disk cleanup from serialization errors, ensuring disk-level
exceptions (OSError) only trigger warnings rather than failing active
API requests. The cache maintenance script is also updated to gracefully
skip partial '.tmp' files.
Bug: T438266
Change-Id: If648db9deca999cc933cf9a662348f9a83303c3f
---
M pywikibot/data/api/_requests.py
M scripts/maintenance/cache.py
2 files changed, 25 insertions(+), 6 deletions(-)
Approvals:
jenkins-bot: Verified
Mahveotm: Looks good to me, approved
diff --git a/pywikibot/data/api/_requests.py b/pywikibot/data/api/_requests.py
index 4b080c0..2c13277 100644
--- a/pywikibot/data/api/_requests.py
+++ b/pywikibot/data/api/_requests.py
@@ -15,6 +15,7 @@
import pprint
import re
import sys
+import tempfile
import traceback
from collections.abc import Callable, MutableMapping
from contextlib import suppress
@@ -1396,12 +1397,25 @@
def _write_cache(self, data) -> None:
"""Write data to self._cachefile_path()."""
data = self._uniquedescriptionstr(), data, pywikibot.Timestamp.nowutc()
- path = self._cachefile_path()
- with suppress(OSError), path.open('wb') as f:
- pickle.dump(data, f, protocol=config.pickle_protocol)
- return
- # delete invalid cache entry
- path.unlink()
+ path = None
+ try:
+ path = self._cachefile_path()
+ fd, temp_path_str = tempfile.mkstemp(prefix=path.name + '-',
+ suffix='.tmp',
+ dir=path.parent)
+ tmp_path = Path(temp_path_str)
+ try:
+ with open(fd, 'wb') as f:
+ pickle.dump(data, f, protocol=config.pickle_protocol)
+ tmp_path.replace(path)
+ finally:
+ with suppress(OSError):
+ tmp_path.unlink()
+ except OSError as e:
+ if path:
+ pywikibot.warning(f'Could not write cache file {path}: {e}')
+ else:
+ pywikibot.warning(f'Could not write cache file: {e}')
def submit(self):
"""Submit cached request."""
diff --git a/scripts/maintenance/cache.py b/scripts/maintenance/cache.py
index e85bc75..6dfbf7e 100755
--- a/scripts/maintenance/cache.py
+++ b/scripts/maintenance/cache.py
@@ -257,6 +257,11 @@
for filepath in filenames:
filename = os.path.basename(filepath)
+
+ # Skip temporary files for normal parsing/operations
+ if filename.endswith('.tmp'):
+ continue
+
cache_dir = os.path.dirname(filepath)
if use_accesstime is not False:
stinfo = os.stat(filepath)
--
To view, visit
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1348534?usp=email
To unsubscribe, or for help writing mail filters, visit
https://gerrit.wikimedia.org/r/settings?usp=email
Gerrit-MessageType: merged
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: If648db9deca999cc933cf9a662348f9a83303c3f
Gerrit-Change-Number: 1348534
Gerrit-PatchSet: 5
Gerrit-Owner: Aruotes <[email protected]>
Gerrit-Reviewer: Mahveotm <[email protected]>
Gerrit-Reviewer: jenkins-bot
Gerrit-CC: Xqt <[email protected]>
_______________________________________________
Pywikibot-commits mailing list -- [email protected]
To unsubscribe send an email to [email protected]