Files
firestar5683 e9dd458699 Replay
Provide route replay, log migration and recorded vehicle fixtures for regression checks.

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com>
Co-authored-by: Kacper Rączy <gfw.kra@gmail.com>
Co-authored-by: YassineYousfi <yyousfi1@binghamton.edu>
Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com>
2026-10-14 20:02:39 -05:00

149 lines
5.2 KiB
Python

"""One renderer-owned RGBA slot; device viewers only read it."""
from dataclasses import dataclass
import fcntl
import math
import mmap
import os
from pathlib import Path
import re
import stat
import struct
import time
MAGIC = 0x5350525046524D31
VERSION = 1
HEADER_SIZE = 4096
MAX_WIDTH, MAX_HEIGHT = 1600, 800
HEADER = struct.Struct('=QQIIIIQQQd64s')
SEQ = struct.Struct('=Q')
SESSION = re.compile(r'[a-f0-9]{32}\Z')
PROFILES = {'large': 1, 'compact': 2}
@dataclass(frozen=True)
class ReplayFrame:
data: bytes
width: int
height: int
session_id: str
epoch: int
position: float
frame_id: int
captured_ns: int
class ReplayFramePublisher:
def __init__(self, path: Path, session_id: str, profile: str, width: int, height: int):
if SESSION.fullmatch(session_id) is None or profile not in PROFILES or not 0 < width <= MAX_WIDTH or not 0 < height <= MAX_HEIGHT:
raise ValueError('Invalid replay frame geometry or identity')
self.path, self.session_id, self.profile = Path(path), session_id, profile
self.width, self.height = width, height
self.size = HEADER_SIZE + width * height * 4
self.frame_id = 0
fd = os.open(path, os.O_RDWR | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW | os.O_CLOEXEC, 0o600)
try:
os.ftruncate(fd, self.size)
self.mm = mmap.mmap(fd, self.size)
except BaseException:
os.close(fd)
raise
self.fd = fd
def publish(self, pixels, *, epoch: int, position: float, captured_ns: int):
if type(epoch) is not int or epoch <= 0 or not math.isfinite(position) or position < 0 or captured_ns <= 0:
raise ValueError('Invalid replay frame clock')
if len(pixels) != self.width * self.height * 4:
raise ValueError('Invalid RGBA replay frame')
# Kernel locks also establish ordering on ARM; a Python mmap seqlock alone
# cannot provide acquire/release barriers while a reader copies the pixels.
deadline = time.monotonic() + .1
while True:
try:
fcntl.flock(self.fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
break
except BlockingIOError:
if time.monotonic() >= deadline:
raise RuntimeError('Replay frame consumer stalled') from None
time.sleep(.001)
try:
self._publish(pixels, epoch=epoch, position=position, captured_ns=captured_ns)
finally:
fcntl.flock(self.fd, fcntl.LOCK_UN)
def _publish(self, pixels, *, epoch, position, captured_ns):
seq = SEQ.unpack_from(self.mm, 8)[0]
SEQ.pack_into(self.mm, 8, seq | 1)
self.mm[HEADER_SIZE:] = pixels
self.frame_id += 1
HEADER.pack_into(self.mm, 0, MAGIC, seq | 1, VERSION, self.width, self.height, PROFILES[self.profile],
epoch, self.frame_id, captured_ns, position, self.session_id.encode())
SEQ.pack_into(self.mm, 8, (seq | 1) + 1)
def close(self):
self.mm.close()
os.close(self.fd)
self.path.unlink(missing_ok=True)
class ReplayFrameReader:
def __init__(self, path: Path, session_id: str, profile: str | None = None):
if SESSION.fullmatch(session_id) is None or profile is not None and profile not in PROFILES:
raise ValueError('Invalid replay frame identity')
self.session_id, self.profile, self.last_key = session_id, profile, None
self.mm = None
self.fd = None
fd = os.open(path, os.O_RDONLY | os.O_NONBLOCK | os.O_NOFOLLOW | os.O_CLOEXEC)
try:
info = os.fstat(fd)
if (not stat.S_ISREG(info.st_mode) or info.st_uid != os.getuid() or stat.S_IMODE(info.st_mode) != 0o600 or
info.st_nlink != 1 or not HEADER_SIZE < info.st_size <= HEADER_SIZE + MAX_WIDTH * MAX_HEIGHT * 4):
raise ValueError('Unsafe replay frame file')
self.mm = mmap.mmap(fd, info.st_size, access=mmap.ACCESS_READ)
except BaseException:
os.close(fd)
raise
self.fd = fd
def latest(self) -> ReplayFrame | None:
mm, fd = self.mm, self.fd
if mm is None or fd is None:
return None
try:
fcntl.flock(fd, fcntl.LOCK_SH | fcntl.LOCK_NB)
except BlockingIOError:
return None
try:
return self._latest(mm)
finally:
fcntl.flock(fd, fcntl.LOCK_UN)
def _latest(self, mm: mmap.mmap):
for _ in range(3):
seq = SEQ.unpack_from(mm, 8)[0]
if not seq or seq & 1:
continue
magic, stamp, version, width, height, profile, epoch, frame_id, captured_ns, position, session = HEADER.unpack_from(mm)
if (magic != MAGIC or stamp != seq or version != VERSION or profile not in PROFILES.values() or
self.profile is not None and profile != PROFILES[self.profile] or
not 0 < width <= MAX_WIDTH or not 0 < height <= MAX_HEIGHT or
len(mm) != HEADER_SIZE + width * height * 4 or session.rstrip(b'\0') != self.session_id.encode() or
epoch <= 0 or frame_id <= 0 or captured_ns <= 0 or not math.isfinite(position) or position < 0):
return None
key = epoch, frame_id
if key == self.last_key:
return None
data = mm[HEADER_SIZE:]
if SEQ.unpack_from(mm, 8)[0] != seq:
continue
self.last_key = key
return ReplayFrame(data, width, height, self.session_id, epoch, position, frame_id, captured_ns)
return None
def close(self):
if self.mm is not None:
self.mm.close()
self.mm = None
if self.fd is not None:
os.close(self.fd)
self.fd = None