Spaces:
Running
Running
File size: 3,308 Bytes
e16348e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | """Per-material table opening; all table decoding/probing stays upstream.
The router resolves through a source's _find(), not its _open_*() methods.
Consequently per-material locking must live on the tablebase which actually
owns the opened files. This module holds one small copy of upstream's
double-checked open contract and the block-cache accessor shared by the
router and the remote sources.
"""
from __future__ import annotations
import threading
from typing import Any
import weakref
class PerMaterialOpening:
"""Mixin for chesstb.Tablebase with independent first opens per material.
Upstream probe() registers readers around the entire recursive walk;
its close() drains them before closing the caches. No probing logic or
lifecycle override is needed here. As upstream, direct calls to private
_open_* methods must not race close().
"""
def __init__(self, *args: Any, **kwargs: Any) -> None:
self._material_locks: weakref.WeakValueDictionary = weakref.WeakValueDictionary()
self._material_locks_meta_lock = threading.Lock()
super().__init__(*args, **kwargs)
def _material_lock(self, kind: str, cache_key: Any) -> Any:
# A caller's context manager holds a strong reference while waiting
# and while owning the lock. Unused lock objects can then disappear,
# without deleting a lock another thread may still be waiting on.
with self._material_locks_meta_lock:
key = (kind, cache_key)
lock = self._material_locks.get(key)
if lock is None:
lock = threading.Lock()
self._material_locks[key] = lock
return lock
def _table_open_lock(self, kind: str, cache_key: Any) -> Any:
return self._material_lock(kind, cache_key)
def _open_any(self, kind: str, cache: dict, file_cls: Any, cfg: Any) -> Any:
key = cfg.cache_key
try:
return cache[key]
except KeyError:
pass
with self._table_open_lock(kind, key):
try:
return cache[key]
except KeyError:
pass
path = self._find(kind, cfg.name(), file_cls.EXT)
table = file_cls(cfg, path, self._block_cache) if path is not None else None
# Cache genuine absence, but never cache a transport/parse error.
cache[key] = table
return table
def _open_wdl(self, cfg: Any) -> Any:
return self._open_any("wdl", self._wdl_cache, self.WDL_FILE, cfg)
def _open_dtz(self, cfg: Any) -> Any:
return self._open_any("dtz", self._dtz_cache, self.DTZ_FILE, cfg)
def _open_dtc(self, cfg: Any) -> Any:
return self._open_any("dtc", self._dtc_cache, self.DTC_FILE, cfg)
def _open_dtm(self, cfg: Any) -> Any:
return self._open_any("dtm", self._dtm_cache, self.DTM_FILE, cfg)
def _open_dtm50(self, cfg: Any) -> Any:
return self._open_any("dtm50", self._dtm50_cache, self.DTM50_FILE, cfg)
def block_cache_usage(tb: Any) -> tuple[int, int]:
"""(decoded blocks, resident bytes) of an upstream tablebase's block cache.
Reads upstream's private _block_cache, _lru, and cur_bytes.
"""
cache = tb._block_cache
return len(cache._lru), cache.cur_bytes
|