@@ -326,17 +326,26 @@ def _linker_backend_and_version() -> tuple[str, str]:
326326 """Return ``(backend, version)`` for the linker used on PTX inputs.
327327
328328 Raises any underlying probe exception. ``make_program_cache_key`` catches
329- and mixes the exception's class and message into the digest, so the same
330- probe failure produces the same key across processes -- the cache stays
329+ and mixes the exception's class name into the digest, so the same probe
330+ failure produces the same key across processes -- the cache stays
331331 persistent in broken environments, while never sharing a key with a
332332 working probe (``_probe_failed`` label vs. ``driver``/``nvrtc``/...).
333+
334+ nvJitLink version lookup goes through ``sys.modules`` first so we hit the
335+ same module ``_decide_nvjitlink_or_driver()`` already loaded. That keeps
336+ fingerprinting aligned with whichever ``cuda.bindings.nvjitlink`` import
337+ path the linker actually uses.
333338 """
339+ import sys
340+
334341 from cuda .core ._linker import _decide_nvjitlink_or_driver
335342
336343 use_driver = _decide_nvjitlink_or_driver ()
337344 if use_driver :
338345 return ("driver" , str (_driver_version ()))
339- from cuda .bindings import nvjitlink
346+ nvjitlink = sys .modules .get ("cuda.bindings.nvjitlink" )
347+ if nvjitlink is None :
348+ from cuda .bindings import nvjitlink
340349
341350 return ("nvJitLink" , str (nvjitlink .version ()))
342351
@@ -1300,7 +1309,10 @@ def _enforce_size_cap(self) -> None:
13001309 st = path .stat ()
13011310 except FileNotFoundError :
13021311 continue
1303- entries .append ((st .st_mtime , st .st_size , path ))
1312+ # Carry the full stat so eviction can guard against a concurrent
1313+ # os.replace that swapped a fresh entry into this path between
1314+ # snapshot and unlink.
1315+ entries .append ((st .st_mtime , st .st_size , path , st ))
13041316 total += st .st_size
13051317 if self ._tmp .exists ():
13061318 for tmp in self ._tmp .iterdir ():
@@ -1312,10 +1324,24 @@ def _enforce_size_cap(self) -> None:
13121324 continue
13131325 if total <= self ._max_size_bytes :
13141326 return
1315- entries .sort () # oldest mtime first
1316- for _mtime , size , path in entries :
1327+ entries .sort (key = lambda e : e [ 0 ] ) # oldest mtime first
1328+ for _mtime , size , path , st_before in entries :
13171329 if total <= self ._max_size_bytes :
13181330 return
1331+ # _prune_if_stat_unchanged refuses if a writer replaced the file
1332+ # between snapshot and now, so eviction can't silently delete a
1333+ # freshly-committed entry from another process.
1334+ try :
1335+ stat_now = path .stat ()
1336+ except FileNotFoundError :
1337+ total -= size
1338+ continue
1339+ if (stat_now .st_ino , stat_now .st_size , stat_now .st_mtime_ns ) != (
1340+ st_before .st_ino ,
1341+ st_before .st_size ,
1342+ st_before .st_mtime_ns ,
1343+ ):
1344+ continue
13191345 with contextlib .suppress (FileNotFoundError ):
13201346 path .unlink ()
13211347 total -= size
0 commit comments