web UI added and refactoring done
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user