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