Refactor radar system
This commit is contained in:
@@ -6,9 +6,11 @@ import logging
|
||||
import mmap
|
||||
import os
|
||||
from pathlib import Path
|
||||
import struct
|
||||
from typing import Final
|
||||
|
||||
from python_app.models.run_config_schema import RingEndpointModel
|
||||
from python_app.models.run_config_validation import validate_ring_endpoint
|
||||
from python_app.orchestration.shm import atomics
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_HEADER_SIZE: Final[int] = 64
|
||||
@@ -32,19 +34,21 @@ class ShmRingWriter:
|
||||
|
||||
def __init__(self, ring_name: str, capacity: int, slot_size_bytes: int) -> None:
|
||||
"""Open or create a POSIX SHM ring by name (as the ring owner)."""
|
||||
if not ring_name.startswith("/"):
|
||||
raise ValueError("ring_name must start with '/'")
|
||||
if capacity <= 0:
|
||||
raise ValueError("capacity must be > 0")
|
||||
if slot_size_bytes <= 0:
|
||||
raise ValueError("slot_size_bytes must be > 0")
|
||||
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")
|
||||
validate_ring_endpoint(RingEndpointModel(ring_name, capacity, slot_size_bytes))
|
||||
|
||||
self._ring_name = ring_name
|
||||
self._capacity = int(capacity)
|
||||
self._slot_size_bytes = int(slot_size_bytes)
|
||||
self._mapped_size = _HEADER_SIZE + self._capacity * (_SLOT_HEADER_SIZE + self._slot_size_bytes)
|
||||
self._path = Path("/dev/shm") / ring_name[1:]
|
||||
self._open_owned()
|
||||
self._file = self._mmap = None
|
||||
try:
|
||||
self._open_owned()
|
||||
except BaseException:
|
||||
self.close()
|
||||
raise
|
||||
|
||||
def _open_owned(self) -> None:
|
||||
"""Open the ring, recreating it from scratch on a geometry/header mismatch.
|
||||
@@ -63,6 +67,10 @@ class ShmRingWriter:
|
||||
# under a producer/consumer that still expects the old layout.
|
||||
if not created:
|
||||
logger.info("Ring %s: stale segment with wrong size, recreating", self._ring_name)
|
||||
self._file.close()
|
||||
self._unlink_if_present()
|
||||
fd = os.open(self._path, os.O_RDWR | os.O_CREAT | os.O_EXCL, 0o660)
|
||||
self._file = os.fdopen(fd, "r+b", buffering=0)
|
||||
self._file.truncate(self._mapped_size)
|
||||
created = True
|
||||
|
||||
@@ -84,7 +92,7 @@ class ShmRingWriter:
|
||||
self._mmap.close()
|
||||
self._file.close()
|
||||
self._unlink_if_present()
|
||||
fd = os.open(self._path, os.O_RDWR | os.O_CREAT, 0o660)
|
||||
fd = os.open(self._path, os.O_RDWR | os.O_CREAT | os.O_EXCL, 0o660)
|
||||
self._file = os.fdopen(fd, "r+b", buffering=0)
|
||||
self._file.truncate(self._mapped_size)
|
||||
self._mmap = mmap.mmap(self._file.fileno(), self._mapped_size)
|
||||
@@ -101,8 +109,12 @@ class ShmRingWriter:
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close mmap and file handle."""
|
||||
self._mmap.close()
|
||||
self._file.close()
|
||||
if self._mmap is not None:
|
||||
self._mmap.close()
|
||||
self._mmap = None
|
||||
if self._file is not None:
|
||||
self._file.close()
|
||||
self._file = None
|
||||
logger.debug("Closed SHM ring writer %s", self._ring_name)
|
||||
|
||||
def push(self, payload: bytes) -> bool:
|
||||
@@ -121,13 +133,17 @@ class ShmRingWriter:
|
||||
# re-read it first and move it only forward: a concurrent reader may have
|
||||
# already advanced it, and clobbering that backward would re-deliver an
|
||||
# already-consumed slot as a duplicate.
|
||||
self._write_u64(32, max(self._read_u64(32), read_seq + 1))
|
||||
atomics.advance(self._mmap, 32, read_seq + 1)
|
||||
self._write_u64(40, self._read_u64(40) + 1)
|
||||
|
||||
index = write_seq % self._capacity
|
||||
slot_offset = _HEADER_SIZE + index * (_SLOT_HEADER_SIZE + self._slot_size_bytes)
|
||||
payload_offset = slot_offset + _SLOT_HEADER_SIZE
|
||||
|
||||
# Invalidate the previous publication before changing its bytes. A peek or
|
||||
# a lapped consumer must not accept the old sequence during this overwrite.
|
||||
self._write_u64(slot_offset + 8, 0)
|
||||
atomics.fence()
|
||||
self._write_u32(slot_offset, len(payload))
|
||||
self._write_u32(slot_offset + 4, 0)
|
||||
self._mmap[payload_offset : payload_offset + len(payload)] = payload
|
||||
@@ -146,7 +162,9 @@ class ShmRingWriter:
|
||||
return self._slot_size_bytes
|
||||
|
||||
def _initialize_header(self) -> None:
|
||||
self._mmap[:] = b"\x00" * self._mapped_size
|
||||
# A new ftruncate-extended segment is already zero-filled by the kernel;
|
||||
# avoid allocating and copying another complete ring just to initialize it.
|
||||
self._mmap[:_HEADER_SIZE] = b"\x00" * _HEADER_SIZE
|
||||
self._mmap[:8] = _MAGIC
|
||||
self._write_u32(8, _VERSION)
|
||||
self._write_u32(12, self._capacity)
|
||||
@@ -170,13 +188,13 @@ class ShmRingWriter:
|
||||
)
|
||||
|
||||
def _read_u32(self, offset: int) -> int:
|
||||
return struct.unpack_from("<I", self._mmap, offset)[0]
|
||||
return atomics.load(self._mmap, offset, 4)
|
||||
|
||||
def _read_u64(self, offset: int) -> int:
|
||||
return struct.unpack_from("<Q", self._mmap, offset)[0]
|
||||
return atomics.load(self._mmap, offset, 8)
|
||||
|
||||
def _write_u32(self, offset: int, value: int) -> None:
|
||||
struct.pack_into("<I", self._mmap, offset, int(value))
|
||||
atomics.store(self._mmap, offset, int(value), 4)
|
||||
|
||||
def _write_u64(self, offset: int, value: int) -> None:
|
||||
struct.pack_into("<Q", self._mmap, offset, int(value))
|
||||
atomics.store(self._mmap, offset, int(value), 8)
|
||||
|
||||
Reference in New Issue
Block a user