mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-18 12:59:12 +00:00
fix(core): unique snapshot temp name, honour on-demand requests, skip empty starlark
The preview snapshot wrote through a fixed "<snapshot>.tmp". /tmp is world-writable and sticky, and the display service runs as a different user from the tooling, so a leftover temp owned by anyone else became unopenable even by root -- fs.protected_regular refuses O_CREAT on a foreign file in a sticky directory. The preview and the health check's liveness proxy then froze until someone deleted the file by hand; on the test rig that meant 23 hours of a healthy display reporting "hardware: stale". Now uses tempfile.mkstemp with cleanup on failure, matching the hardware-status write a few hundred lines above. Closes #528. _poll_on_demand_requests read its mailbox with max_age=3600, and get() defaults the in-memory TTL to max_age -- so the first request was pinned in memory for an hour and every later poll returned that stale copy. No second on-demand request was honoured until the service restarted, while the API kept returning 200. get() already documents memory_ttl=0 for exactly this cross-process case. The consumed request is also now deleted: leaving it on disk meant a restart replayed the previous request, activated it, and ignored the one the caller had just made. Closes #530. starlark-apps returned None from display() when it has no app to show, which is the state of every install without Pixlet and of a fresh one before any app is added. The controller only skips on a boolean False, so that held a black panel for the full display_duration instead of rotating on. Closes #456 (core side). Verified on the rig: two consecutive on-demand requests with no restart between them are both activated, where the second was previously dropped in silence.
This commit is contained in:
+21
-3
@@ -1453,12 +1453,30 @@ class DisplayManager:
|
||||
if parent_dir and str(parent_dir) != '/tmp': # nosec B108 - guard to skip /tmp for permission ops
|
||||
ensure_directory_permissions(parent_dir, get_assets_dir_mode())
|
||||
self._snapshot_dir_prepared = True
|
||||
# Write atomically: temp then replace
|
||||
tmp_path = f"{self._snapshot_path}.tmp"
|
||||
self.image.save(tmp_path, format='PNG')
|
||||
# Write atomically: temp then replace. The temp name must be
|
||||
# unique, not "<snapshot>.tmp": /tmp is world-writable and sticky,
|
||||
# and this file is written by whichever user the display service
|
||||
# runs as while tests and tooling run as someone else. A leftover
|
||||
# fixed-name temp owned by another user is then unopenable even by
|
||||
# root (fs.protected_regular refuses O_CREAT on a foreign file in a
|
||||
# sticky dir), which froze the preview and the health check's
|
||||
# liveness proxy until somebody deleted it by hand. Same pattern as
|
||||
# the hardware-status write above.
|
||||
_fd, tmp_path = tempfile.mkstemp(
|
||||
dir=str(snapshot_path_obj.parent),
|
||||
prefix=f".{snapshot_path_obj.name}.", suffix=".tmp")
|
||||
try:
|
||||
with os.fdopen(_fd, "wb") as _f:
|
||||
self.image.save(_f, format='PNG')
|
||||
os.chmod(tmp_path, 0o644)
|
||||
os.replace(tmp_path, self._snapshot_path)
|
||||
except Exception:
|
||||
# Never leave the temp behind -- that is what made the failure
|
||||
# permanent rather than transient.
|
||||
try:
|
||||
os.unlink(tmp_path)
|
||||
except OSError:
|
||||
pass
|
||||
# Fallback to direct save if replace not supported
|
||||
self.image.save(self._snapshot_path, format='PNG')
|
||||
# Set proper file permissions after saving
|
||||
|
||||
Reference in New Issue
Block a user