probe.py 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. from __future__ import annotations
  2. import threading
  3. class _HTTP2ProbeCache:
  4. __slots__ = (
  5. "_lock",
  6. "_cache_locks",
  7. "_cache_values",
  8. )
  9. def __init__(self) -> None:
  10. self._lock = threading.Lock()
  11. self._cache_locks: dict[tuple[str, int], threading.RLock] = {}
  12. self._cache_values: dict[tuple[str, int], bool | None] = {}
  13. def acquire_and_get(self, host: str, port: int) -> bool | None:
  14. # By the end of this block we know that
  15. # _cache_[values,locks] is available.
  16. value = None
  17. with self._lock:
  18. key = (host, port)
  19. try:
  20. value = self._cache_values[key]
  21. # If it's a known value we return right away.
  22. if value is not None:
  23. return value
  24. except KeyError:
  25. self._cache_locks[key] = threading.RLock()
  26. self._cache_values[key] = None
  27. # If the value is unknown, we acquire the lock to signal
  28. # to the requesting thread that the probe is in progress
  29. # or that the current thread needs to return their findings.
  30. key_lock = self._cache_locks[key]
  31. key_lock.acquire()
  32. try:
  33. # If the by the time we get the lock the value has been
  34. # updated we want to return the updated value.
  35. value = self._cache_values[key]
  36. # In case an exception like KeyboardInterrupt is raised here.
  37. except BaseException as e: # Defensive:
  38. assert not isinstance(e, KeyError) # KeyError shouldn't be possible.
  39. key_lock.release()
  40. raise
  41. return value
  42. def set_and_release(
  43. self, host: str, port: int, supports_http2: bool | None
  44. ) -> None:
  45. key = (host, port)
  46. key_lock = self._cache_locks[key]
  47. with key_lock: # Uses an RLock, so can be locked again from same thread.
  48. if supports_http2 is None and self._cache_values[key] is not None:
  49. raise ValueError(
  50. "Cannot reset HTTP/2 support for origin after value has been set."
  51. ) # Defensive: not expected in normal usage
  52. self._cache_values[key] = supports_http2
  53. key_lock.release()
  54. def _values(self) -> dict[tuple[str, int], bool | None]:
  55. """This function is for testing purposes only. Gets the current state of the probe cache"""
  56. with self._lock:
  57. return {k: v for k, v in self._cache_values.items()}
  58. def _reset(self) -> None:
  59. """This function is for testing purposes only. Reset the cache values"""
  60. with self._lock:
  61. self._cache_locks = {}
  62. self._cache_values = {}
  63. _HTTP2_PROBE_CACHE = _HTTP2ProbeCache()
  64. set_and_release = _HTTP2_PROBE_CACHE.set_and_release
  65. acquire_and_get = _HTTP2_PROBE_CACHE.acquire_and_get
  66. _values = _HTTP2_PROBE_CACHE._values
  67. _reset = _HTTP2_PROBE_CACHE._reset
  68. __all__ = [
  69. "set_and_release",
  70. "acquire_and_get",
  71. ]