| 16 | self._cache_values: dict[tuple[str, int], bool | None] = {} |
| 17 | |
| 18 | def acquire_and_get(self, host: str, port: int) -> bool | None: |
| 19 | # By the end of this block we know that |
| 20 | # _cache_[values,locks] is available. |
| 21 | value = None |
| 22 | with self._lock: |
| 23 | key = (host, port) |
| 24 | try: |
| 25 | value = self._cache_values[key] |
| 26 | # If it's a known value we return right away. |
| 27 | if value is not None: |
| 28 | return value |
| 29 | except KeyError: |
| 30 | self._cache_locks[key] = threading.RLock() |
| 31 | self._cache_values[key] = None |
| 32 | |
| 33 | # If the value is unknown, we acquire the lock to signal |
| 34 | # to the requesting thread that the probe is in progress |
| 35 | # or that the current thread needs to return their findings. |
| 36 | key_lock = self._cache_locks[key] |
| 37 | key_lock.acquire() |
| 38 | try: |
| 39 | # If the by the time we get the lock the value has been |
| 40 | # updated we want to return the updated value. |
| 41 | value = self._cache_values[key] |
| 42 | |
| 43 | # In case an exception like KeyboardInterrupt is raised here. |
| 44 | except BaseException as e: # Defensive: |
| 45 | assert not isinstance(e, KeyError) # KeyError shouldn't be possible. |
| 46 | key_lock.release() |
| 47 | raise |
| 48 | |
| 49 | return value |
| 50 | |
| 51 | def set_and_release( |
| 52 | self, host: str, port: int, supports_http2: bool | None |