From 98f7190f18c68f0e2bd6133b34ea171cf1a509b5 Mon Sep 17 00:00:00 2001 From: disqualifier Date: Mon, 6 Jul 2026 00:18:16 -0400 Subject: [PATCH] fix: _save no longer resolves the store's realpath twice per call _save() already resolves target = realpath(self.file) for the write path; _sweep_stale_tmp then independently recomputed the same realpath to derive its match basename. _sweep_stale_tmp now takes that resolved basename as a parameter instead of re-resolving it, cutting one syscall per save. the sweep itself still runs on every save (unchanged) - gating it to first-save-per- instance would change when a crash-orphaned tmp from before this process started actually gets swept, so that part is left alone. Signed-off-by: disqualifier --- src/aiokv/aiokv.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/aiokv/aiokv.py b/src/aiokv/aiokv.py index 2e0ce4f..2a8ad04 100644 --- a/src/aiokv/aiokv.py +++ b/src/aiokv/aiokv.py @@ -121,7 +121,7 @@ class AioKV: target = await asyncio.to_thread(os.path.realpath, self.file) directory = os.path.dirname(target) or "." await asyncio.to_thread(os.makedirs, directory, exist_ok=True) - await self._sweep_stale_tmp(directory) + await self._sweep_stale_tmp(directory, os.path.basename(target)) payload = await asyncio.to_thread(json.dumps, cache, allow_nan=False) tmp = f"{target}.{os.getpid()}.tmp" @@ -137,14 +137,15 @@ class AioKV: log.exception("aiokv: failed to clean up temp file %s", tmp) raise - async def _sweep_stale_tmp(self, directory: str) -> None: + async def _sweep_stale_tmp(self, directory: str, base: str) -> None: """remove orphaned ..tmp files left by a hard crash of a different, dead process matches only the exact shape _save() creates (..tmp), via a re.escape'd regex over a directory listing rather than a raw glob - a glob pattern both crosses unrelated dots (matching non-aiokv neighbors like foo.backup.999.tmp) and - mistreats glob metacharacters in the store's own filename (e.g. state[prod].json)""" - base = os.path.basename(await asyncio.to_thread(os.path.realpath, self.file)) + mistreats glob metacharacters in the store's own filename (e.g. state[prod].json). + `base` is the realpath'd basename _save() already resolved - passed in rather than + re-resolved here so a save does one realpath call, not two.""" candidate = re.compile(rf"^{re.escape(base)}\.(\d+)\.tmp$") names = await asyncio.to_thread(os.listdir, directory) for name in names: