Refactor radar system
This commit is contained in:
@@ -5,11 +5,11 @@ from __future__ import annotations
|
||||
import logging
|
||||
import mmap
|
||||
from pathlib import Path
|
||||
import struct
|
||||
import time
|
||||
from typing import Final
|
||||
|
||||
from python_app.models.dataset_model import ResultCollection, SweepCollection
|
||||
from python_app.orchestration.shm import atomics
|
||||
from python_app.orchestration.shm.decoder import (
|
||||
PREPROC_MAGIC,
|
||||
RAW_MAGIC,
|
||||
@@ -30,8 +30,8 @@ class ShmRingReader:
|
||||
|
||||
def __init__(self, ring_name: str, open_timeout_s: float = 2.0, open_poll_s: float = 0.01) -> None:
|
||||
"""Open and validate ring by name, for example `/radar_results`."""
|
||||
if not ring_name.startswith("/"):
|
||||
raise ValueError("ring_name must start with '/'")
|
||||
if not ring_name.startswith("/") or len(ring_name) < 2 or "/" in ring_name[1:] or "\x00" in ring_name:
|
||||
raise ValueError("ring_name must be '/' followed by a single nonempty filename")
|
||||
|
||||
self._ring_name = ring_name
|
||||
self._path = Path("/dev/shm") / ring_name[1:]
|
||||
@@ -79,7 +79,7 @@ class ShmRingReader:
|
||||
if sequence != read_seq + 1:
|
||||
# Producer overwrote this slot before we read it. Resync to latest.
|
||||
logger.debug("Ring %s: slot lapped before read, resyncing to write_seq=%d", self._ring_name, write_seq)
|
||||
self._write_u64(32, write_seq)
|
||||
atomics.advance(self._mmap, 32, write_seq)
|
||||
return None
|
||||
|
||||
payload_size = self._read_u32(slot_offset)
|
||||
@@ -92,20 +92,21 @@ class ShmRingReader:
|
||||
payload_size,
|
||||
self.slot_size_bytes,
|
||||
)
|
||||
self._write_u64(32, write_seq)
|
||||
atomics.advance(self._mmap, 32, write_seq)
|
||||
return None
|
||||
|
||||
payload_offset = slot_offset + _SLOT_HEADER_SIZE
|
||||
payload = bytes(self._mmap[payload_offset : payload_offset + payload_size])
|
||||
atomics.fence()
|
||||
|
||||
# Re-read the slot sequence after the copy; if it changed, the producer
|
||||
# overwrote this slot mid-copy and the payload is torn — discard and resync.
|
||||
if self._read_u64(slot_offset + 8) != read_seq + 1:
|
||||
logger.debug("Ring %s: slot overwritten mid-copy, discarding torn payload", self._ring_name)
|
||||
self._write_u64(32, write_seq)
|
||||
atomics.advance(self._mmap, 32, write_seq)
|
||||
return None
|
||||
|
||||
self._write_u64(32, read_seq + 1)
|
||||
atomics.advance(self._mmap, 32, read_seq + 1)
|
||||
return payload
|
||||
|
||||
def pop_raw_collection(self) -> SweepCollection | None:
|
||||
@@ -157,17 +158,11 @@ class ShmRingReader:
|
||||
return None
|
||||
payload_offset = slot_offset + _SLOT_HEADER_SIZE
|
||||
payload = bytes(self._mmap[payload_offset : payload_offset + payload_size])
|
||||
atomics.fence()
|
||||
if self._read_u64(slot_offset + 8) != latest_seq + 1:
|
||||
return None
|
||||
return payload
|
||||
|
||||
def peek_latest_result_collection(self) -> ResultCollection | None:
|
||||
"""Return the most recently published result collection without consuming it."""
|
||||
payload = self.peek_latest_payload()
|
||||
if payload is None:
|
||||
return None
|
||||
return decode_result_collection(payload)
|
||||
|
||||
def drop_all(self) -> int:
|
||||
"""Mark all unread slots as consumed and return number of dropped payloads."""
|
||||
write_seq = self._read_u64(24)
|
||||
@@ -175,7 +170,7 @@ class ShmRingReader:
|
||||
if read_seq >= write_seq:
|
||||
return 0
|
||||
dropped = int(write_seq - read_seq)
|
||||
self._write_u64(32, write_seq)
|
||||
atomics.advance(self._mmap, 32, write_seq)
|
||||
logger.debug("Ring %s: dropped %d unread payload(s)", self._ring_name, dropped)
|
||||
return dropped
|
||||
|
||||
@@ -196,7 +191,11 @@ class ShmRingReader:
|
||||
magic = self._mmap[:8]
|
||||
version = self._read_u32(8)
|
||||
if magic == _MAGIC and version == _VERSION:
|
||||
return
|
||||
capacity = self.capacity
|
||||
slot_size = self.slot_size_bytes
|
||||
expected_size = _HEADER_SIZE + capacity * (_SLOT_HEADER_SIZE + slot_size)
|
||||
if capacity > 0 and slot_size > 0 and slot_size % 8 == 0 and expected_size == len(self._mmap):
|
||||
return
|
||||
|
||||
if time.monotonic() >= deadline:
|
||||
if magic != _MAGIC:
|
||||
@@ -206,29 +205,32 @@ class ShmRingReader:
|
||||
f"Shared memory ring magic mismatch for {self._ring_name}: "
|
||||
f"got=0x{got_magic}, expected=0x{expected_magic}"
|
||||
)
|
||||
raise RuntimeError(
|
||||
f"Shared memory ring version mismatch for {self._ring_name}: "
|
||||
f"got={version}, expected={_VERSION}"
|
||||
)
|
||||
if version != _VERSION:
|
||||
raise RuntimeError(
|
||||
f"Shared memory ring version mismatch for {self._ring_name}: "
|
||||
f"got={version}, expected={_VERSION}"
|
||||
)
|
||||
raise RuntimeError(f"Shared memory ring geometry mismatch for {self._ring_name}")
|
||||
|
||||
time.sleep(poll_s)
|
||||
|
||||
def _wait_for_ring_file(self, timeout_s: float, poll_s: float) -> None:
|
||||
"""Wait until ring file appears in `/dev/shm`."""
|
||||
"""Wait for the creator to allocate the ring before mapping its header."""
|
||||
deadline = time.monotonic() + timeout_s
|
||||
while not self._path.exists():
|
||||
while True:
|
||||
try:
|
||||
if self._path.stat().st_size >= _HEADER_SIZE:
|
||||
return
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
if time.monotonic() >= deadline:
|
||||
raise FileNotFoundError(f"Shared memory ring does not exist: {self._ring_name}")
|
||||
time.sleep(poll_s)
|
||||
|
||||
def _read_u32(self, offset: int) -> int:
|
||||
"""Read little-endian u32 at mmap offset."""
|
||||
return struct.unpack_from("<I", self._mmap, offset)[0]
|
||||
return atomics.load(self._mmap, offset, 4)
|
||||
|
||||
def _read_u64(self, offset: int) -> int:
|
||||
"""Read little-endian u64 at mmap offset."""
|
||||
return struct.unpack_from("<Q", self._mmap, offset)[0]
|
||||
|
||||
def _write_u64(self, offset: int, value: int) -> None:
|
||||
"""Write little-endian u64 at mmap offset."""
|
||||
struct.pack_into("<Q", self._mmap, offset, value)
|
||||
return atomics.load(self._mmap, offset, 8)
|
||||
|
||||
Reference in New Issue
Block a user