From d81bc6e254cac06cd4ec4f07099597453b592f17 Mon Sep 17 00:00:00 2001 From: Pavan-Rana Date: Mon, 23 Mar 2026 10:30:33 +0000 Subject: [PATCH 1/4] fix(cache): handle FileNotFoundError for stale cache entries during initialisation Signed-off-by: Pavan-Rana --- sqlmesh/utils/cache.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sqlmesh/utils/cache.py b/sqlmesh/utils/cache.py index e72c34f632..3e25d57496 100644 --- a/sqlmesh/utils/cache.py +++ b/sqlmesh/utils/cache.py @@ -63,8 +63,12 @@ def __init__(self, path: Path, prefix: t.Optional[str] = None): # the file.stat() call below will fail on windows if the :file name is longer than 260 chars file = fix_windows_path(file) - if not file.stem.startswith(self._cache_version) or file.stat().st_atime < threshold: - file.unlink(missing_ok=True) + try: + stat_result = file.stat() + if not file.stem.startswith(self._cache_version) or stat_result.st_atime < threshold: + file.unlink(missing_ok=True) + except FileNotFoundError: + continue def get_or_load(self, name: str, entry_id: str = "", *, loader: t.Callable[[], T]) -> T: """Returns an existing cached entry or loads and caches a new one. From 77cf859bb8254433cfb59cb2eb000a7b138559bf Mon Sep 17 00:00:00 2001 From: Pavan-Rana Date: Mon, 23 Mar 2026 10:55:43 +0000 Subject: [PATCH 2/4] fix(cache): add comment to FileNotFoundError except block Signed-off-by: Pavan-Rana --- sqlmesh/utils/cache.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sqlmesh/utils/cache.py b/sqlmesh/utils/cache.py index 3e25d57496..59ffb91161 100644 --- a/sqlmesh/utils/cache.py +++ b/sqlmesh/utils/cache.py @@ -68,6 +68,7 @@ def __init__(self, path: Path, prefix: t.Optional[str] = None): if not file.stem.startswith(self._cache_version) or stat_result.st_atime < threshold: file.unlink(missing_ok=True) except FileNotFoundError: + # File was deleted between glob() and stat() — skip stale cache entries gracefully continue def get_or_load(self, name: str, entry_id: str = "", *, loader: t.Callable[[], T]) -> T: From 2f0e6ea77dcb17dfc289ed928cf13defa85b741d Mon Sep 17 00:00:00 2001 From: Pavan-Rana Date: Mon, 23 Mar 2026 14:17:52 +0000 Subject: [PATCH 3/4] fix(cache): add test for FileCache stale file race condition in __init__ Signed-off-by: Pavan-Rana --- tests/utils/test_cache.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/utils/test_cache.py b/tests/utils/test_cache.py index ed19765b8a..cc7d9ee369 100644 --- a/tests/utils/test_cache.py +++ b/tests/utils/test_cache.py @@ -1,3 +1,5 @@ +import os +import time import typing as t from pathlib import Path @@ -131,3 +133,22 @@ def test_optimized_query_cache_macro_def_change(tmp_path: Path, mocker: MockerFi new_model.render_query_or_raise().sql() == 'SELECT "_0"."a" AS "a" FROM (SELECT 1 AS "a") AS "_0" WHERE "_0"."a" = 2' ) + + +def test_file_cache_init_handles_stale_file(tmp_path: Path, mocker: MockerFixture) -> None: + cache: FileCache[_TestEntry] = FileCache(tmp_path) + + stale_file = tmp_path / f"{cache._cache_version}__fake_deleted_model_9999999999" + stale_file.touch() + + original_stat = Path.stat + + def flaky_stat(self, **kwargs): + if self.name == stale_file.name: + raise FileNotFoundError(f"Simulated stale file: {self}") + return original_stat(self, **kwargs) + + mocker.patch.object(Path, "stat", flaky_stat) + + FileCache(tmp_path) + \ No newline at end of file From 6dbb7ed1864cfa722a77318f5d5adae7de7286e4 Mon Sep 17 00:00:00 2001 From: Pavan-Rana Date: Mon, 1 Jun 2026 20:47:17 +0100 Subject: [PATCH 4/4] style: apply ruff fixes and formatting Signed-off-by: Pavan-Rana --- sqlmesh/utils/cache.py | 5 ++++- tests/utils/test_cache.py | 3 --- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sqlmesh/utils/cache.py b/sqlmesh/utils/cache.py index 59ffb91161..e1ff59a4a7 100644 --- a/sqlmesh/utils/cache.py +++ b/sqlmesh/utils/cache.py @@ -65,7 +65,10 @@ def __init__(self, path: Path, prefix: t.Optional[str] = None): try: stat_result = file.stat() - if not file.stem.startswith(self._cache_version) or stat_result.st_atime < threshold: + if ( + not file.stem.startswith(self._cache_version) + or stat_result.st_atime < threshold + ): file.unlink(missing_ok=True) except FileNotFoundError: # File was deleted between glob() and stat() — skip stale cache entries gracefully diff --git a/tests/utils/test_cache.py b/tests/utils/test_cache.py index cc7d9ee369..e6e041e30a 100644 --- a/tests/utils/test_cache.py +++ b/tests/utils/test_cache.py @@ -1,5 +1,3 @@ -import os -import time import typing as t from pathlib import Path @@ -151,4 +149,3 @@ def flaky_stat(self, **kwargs): mocker.patch.object(Path, "stat", flaky_stat) FileCache(tmp_path) - \ No newline at end of file