web UI added and refactoring done

This commit is contained in:
Ayzen
2026-06-06 00:06:30 +03:00
parent 3c30a12d4a
commit af6005d68f
65 changed files with 3630 additions and 4720 deletions
+34 -21
View File
@@ -1,4 +1,9 @@
"""Byte-wise cursor utilities for decoding binary ring payloads."""
"""Byte-wise cursor utilities for decoding binary ring payloads.
Every read is bounds-checked and raises :class:`ValueError` on a truncated or
malformed payload, so decoders surface a single, catchable error type for any
corruption (rather than leaking ``struct.error``/``UnicodeDecodeError``).
"""
from __future__ import annotations
@@ -6,48 +11,56 @@ import struct
class ByteCursor:
"""Read primitive values from bytes while tracking offset."""
"""Read primitive values from bytes while tracking offset (bounds-checked)."""
def __init__(self, payload: bytes) -> None:
"""Create cursor at start of payload."""
self.payload = payload
self.offset = 0
def _take(self, size: int) -> bytes:
"""Consume ``size`` bytes, raising ValueError if the payload is too short."""
end = self.offset + size
if size < 0 or end > len(self.payload):
raise ValueError(
f"truncated payload: need {size} bytes at offset {self.offset}, "
f"only {len(self.payload) - self.offset} remain"
)
data = self.payload[self.offset : end]
self.offset = end
return data
def read_u8(self) -> int:
"""Read unsigned 8-bit integer."""
value = struct.unpack_from("<B", self.payload, self.offset)[0]
self.offset += 1
return value
return struct.unpack("<B", self._take(1))[0]
def read_u16(self) -> int:
"""Read unsigned 16-bit integer."""
value = struct.unpack_from("<H", self.payload, self.offset)[0]
self.offset += 2
return value
return struct.unpack("<H", self._take(2))[0]
def read_u32(self) -> int:
"""Read unsigned 32-bit integer."""
value = struct.unpack_from("<I", self.payload, self.offset)[0]
self.offset += 4
return value
return struct.unpack("<I", self._take(4))[0]
def read_u64(self) -> int:
"""Read unsigned 64-bit integer."""
value = struct.unpack_from("<Q", self.payload, self.offset)[0]
self.offset += 8
return value
return struct.unpack("<Q", self._take(8))[0]
def read_f32(self) -> float:
"""Read 32-bit float."""
value = struct.unpack_from("<f", self.payload, self.offset)[0]
self.offset += 4
return float(value)
return float(struct.unpack("<f", self._take(4))[0])
def read_bytes(self, size: int) -> bytes:
"""Read raw byte slice of fixed size."""
data = self.payload[self.offset : self.offset + size]
self.offset += size
return data
"""Read raw byte slice of fixed size (bounds-checked)."""
return self._take(size)
def read_str(self, size: int) -> str:
"""Read a UTF-8 string of ``size`` bytes, raising ValueError on bad UTF-8."""
raw = self._take(size)
try:
return raw.decode("utf-8")
except UnicodeDecodeError as exc:
raise ValueError("invalid UTF-8 in payload string") from exc
def remaining_bytes(self) -> int:
"""Return unread byte count."""
+1 -1
View File
@@ -78,7 +78,7 @@ def decode_result_collection(payload: bytes) -> ResultCollection:
"""Decode one result payload from stream."""
kind = cursor.read_u8()
name_size = cursor.read_u16()
name = cursor.read_bytes(name_size).decode("utf-8")
name = cursor.read_str(name_size)
if kind == 1:
point_count = cursor.read_u32()
+50 -3
View File
@@ -35,12 +35,20 @@ class ShmRingReader:
self._wait_for_ring_file(timeout_s=open_timeout_s, poll_s=open_poll_s)
self._file = self._path.open("r+b", buffering=0)
self._mmap = mmap.mmap(self._file.fileno(), 0)
self._validate_header_with_wait(timeout_s=1.0, poll_s=0.002)
self._mmap: mmap.mmap | None = None
try:
self._mmap = mmap.mmap(self._file.fileno(), 0)
self._validate_header_with_wait(timeout_s=1.0, poll_s=0.002)
except BaseException:
# A fail-fast open (absent/incompatible ring) must not leak the fd/mapping.
self.close()
raise
def close(self) -> None:
"""Close mmap and file handle."""
self._mmap.close()
if self._mmap is not None:
self._mmap.close()
self._mmap = None
self._file.close()
def pop_payload(self) -> bytes | None:
@@ -103,6 +111,45 @@ class ShmRingReader:
return None
return decode_result_collection(payload)
def peek_latest_payload(self) -> bytes | None:
"""Return the most recently published payload WITHOUT consuming it.
Reads the newest slot through the seqlock and never advances `read_seq`, so a
viewer can peek the freshest frame while the ring's real consumer keeps its own
cursor — the two coexist without stealing each other's payloads. Inherently
latest-wins: always the freshest published frame, or `None` when nothing has
been published yet or the slot is being overwritten at this instant.
"""
write_seq = self._read_u64(24)
if write_seq == 0:
return None
latest_seq = write_seq - 1
index = latest_seq % self.capacity
slot_offset = _HEADER_SIZE + index * (_SLOT_HEADER_SIZE + self.slot_size_bytes)
# Seqlock read with NO cursor advance: accept the slot only if its sequence
# equals the published value both before and after the copy (i.e. the producer
# did not lap this slot mid-read). Never touch read_seq, so the real consumer
# is undisturbed.
if self._read_u64(slot_offset + 8) != latest_seq + 1:
return None
payload_size = self._read_u32(slot_offset)
if payload_size > self.slot_size_bytes:
return None
payload_offset = slot_offset + _SLOT_HEADER_SIZE
payload = bytes(self._mmap[payload_offset : payload_offset + payload_size])
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)
+6 -3
View File
@@ -98,9 +98,12 @@ class ShmRingWriter:
write_seq = self._read_u64(24)
read_seq = self._read_u64(32)
if max(0, write_seq - read_seq) >= self._capacity:
self._write_u64(32, read_seq + 1)
dropped = self._read_u64(40)
self._write_u64(40, dropped + 1)
# Advance the consumer cursor past the slot we are about to overwrite, but
# 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))
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)