mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-10 09:06:36 +00:00
fix(vegas): tell code the gate must not park in by module name, not path
The gate never parks a thread inside logging, threading, importlib or the cache, and it matched those as substrings of each frame's file path. On GitHub's runners Python lives under /opt/hostedtoolcache, so every stdlib frame said "cache" and the gate never parked anything -- three tests failed there and passed here. A virtualenv under ~/.cache would have done the same on a Pi. Match the frame's module name (f_globals['__name__']) instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+13
-13
@@ -67,15 +67,16 @@ STALE_SECONDS = 0.05
|
|||||||
#: Swaps needed before the refresh period is trusted enough to open a window.
|
#: Swaps needed before the refresh period is trusted enough to open a window.
|
||||||
MIN_SAMPLES = 8
|
MIN_SAMPLES = 8
|
||||||
|
|
||||||
#: Parking with any of these on the stack could hold a lock the render
|
#: Parking inside any of these modules could hold a lock the render thread
|
||||||
#: thread takes: logging handler locks, Condition and Event internals, the
|
#: takes: logging handler locks, Condition and Event internals, the module
|
||||||
#: module import locks, and the disk and memory cache locks.
|
#: import locks, and the disk and memory cache locks. Matched by module name,
|
||||||
_UNSAFE_PATHS = (
|
#: not file path: a path can say "cache" or "logging" for reasons of its own --
|
||||||
"logging",
|
#: a virtualenv under ~/.cache, or GitHub's /opt/hostedtoolcache, where every
|
||||||
"threading.py",
|
#: stdlib frame would otherwise count and the gate would never park anything.
|
||||||
"importlib",
|
_UNSAFE_MODULES = frozenset({
|
||||||
"cache",
|
"logging", "threading", "importlib", "src.cache_manager", "src.cache",
|
||||||
)
|
})
|
||||||
|
_UNSAFE_PREFIXES = ("logging.", "importlib.", "_frozen_importlib", "src.cache.")
|
||||||
|
|
||||||
|
|
||||||
def _unsafe(frame: Any, base: Any) -> bool:
|
def _unsafe(frame: Any, base: Any) -> bool:
|
||||||
@@ -85,10 +86,9 @@ def _unsafe(frame: Any, base: Any) -> bool:
|
|||||||
thread's own bootstrap in threading.py) holds nothing.
|
thread's own bootstrap in threading.py) holds nothing.
|
||||||
"""
|
"""
|
||||||
while frame is not None and frame is not base:
|
while frame is not None and frame is not base:
|
||||||
filename = frame.f_code.co_filename
|
name = frame.f_globals.get("__name__") or ""
|
||||||
for part in _UNSAFE_PATHS:
|
if name in _UNSAFE_MODULES or name.startswith(_UNSAFE_PREFIXES):
|
||||||
if part in filename:
|
return True
|
||||||
return True
|
|
||||||
frame = frame.f_back
|
frame = frame.f_back
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
+29
-14
@@ -141,27 +141,42 @@ class TestWhenToPark:
|
|||||||
done.set()
|
done.set()
|
||||||
other.join()
|
other.join()
|
||||||
|
|
||||||
@pytest.mark.parametrize("filename", [
|
@staticmethod
|
||||||
"/usr/lib/python3.11/logging/__init__.py",
|
def _frame_in(module):
|
||||||
"/usr/lib/python3.11/threading.py",
|
namespace = {"__name__": module}
|
||||||
"<frozen importlib._bootstrap>",
|
exec("import sys\ndef here():\n return sys._getframe()\n", namespace)
|
||||||
"/home/pi/LEDMatrix/src/cache/disk_cache.py",
|
return namespace["here"]()
|
||||||
"/home/pi/LEDMatrix/src/cache_manager.py",
|
|
||||||
|
@pytest.mark.parametrize("module", [
|
||||||
|
"logging", "logging.handlers", "threading", "importlib",
|
||||||
|
"_frozen_importlib", "_frozen_importlib_external",
|
||||||
|
"src.cache_manager", "src.cache.disk_cache",
|
||||||
])
|
])
|
||||||
def test_never_inside_code_that_takes_shared_locks(self, gate, filename):
|
def test_never_inside_code_that_takes_shared_locks(self, gate, module):
|
||||||
namespace = {}
|
frame = self._frame_in(module)
|
||||||
exec(compile("import sys\ndef here():\n return sys._getframe()\n",
|
|
||||||
filename, "exec"), namespace)
|
|
||||||
frame = namespace["here"]()
|
|
||||||
assert render_gate._unsafe(frame, None)
|
assert render_gate._unsafe(frame, None)
|
||||||
assert not gate._should_park(frame, gate._open_until + 0.0001)
|
assert not gate._should_park(frame, gate._open_until + 0.0001)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("module", [
|
||||||
|
"src.cache_helpers", "plugin_logging_ticker", "src.vegas_mode.render_pipeline",
|
||||||
|
])
|
||||||
|
def test_modules_that_only_sound_alike_are_fine(self, module):
|
||||||
|
assert not render_gate._unsafe(self._frame_in(module), None)
|
||||||
|
|
||||||
|
def test_where_python_is_installed_does_not_matter(self):
|
||||||
|
# GitHub's runners keep Python under /opt/hostedtoolcache; matching
|
||||||
|
# paths for "cache" made every stdlib frame unsafe there.
|
||||||
|
namespace = {"__name__": "json.decoder"}
|
||||||
|
exec(compile("import sys\ndef here():\n return sys._getframe()\n",
|
||||||
|
"/opt/hostedtoolcache/Python/3.11/lib/json/decoder.py", "exec"),
|
||||||
|
namespace)
|
||||||
|
assert not render_gate._unsafe(namespace["here"](), None)
|
||||||
|
|
||||||
def test_what_lies_below_the_yielding_block_does_not_count(self):
|
def test_what_lies_below_the_yielding_block_does_not_count(self):
|
||||||
# A thread's stack always starts in threading.py; only frames above
|
# A thread's stack always starts in threading.py; only frames above
|
||||||
# the one that entered yielding() matter.
|
# the one that entered yielding() matter.
|
||||||
namespace = {}
|
namespace = {"__name__": "threading"}
|
||||||
exec(compile("def bootstrap(fn):\n return fn()\n",
|
exec("def bootstrap(fn):\n return fn()\n", namespace)
|
||||||
"/usr/lib/python3.11/threading.py", "exec"), namespace)
|
|
||||||
|
|
||||||
def entered():
|
def entered():
|
||||||
base = sys._getframe()
|
base = sys._getframe()
|
||||||
|
|||||||
Reference in New Issue
Block a user