fix: prevent libzmq abort and per-frame decode errors in prediction subscriber
CI / lint (push) Skipped
CI / test (3.11) (push) Skipped
CI / test (3.12) (push) Skipped
CI / test (3.13) (push) Skipped
CI / test-with-beamline-plugins (pxi_bec) (push) Skipped
CI / test-with-beamline-plugins (pxii_bec) (push) Skipped
CI / test-with-beamline-plugins (pxiii_bec) (push) Skipped
CI / lint (push) Skipped
CI / test (3.11) (push) Skipped
CI / test (3.12) (push) Skipped
CI / test (3.13) (push) Skipped
CI / test-with-beamline-plugins (pxi_bec) (push) Skipped
CI / test-with-beamline-plugins (pxii_bec) (push) Skipped
CI / test-with-beamline-plugins (pxiii_bec) (push) Skipped
Sniff for '{' before JSON-parsing zmq parts: JPEG frames start with 0xff
and raised UnicodeDecodeError on every frame. Stop closing the socket
from the GUI thread in stop(): zmq sockets are not thread-safe and the
concurrent close/recv tripped the signaler.cpp POLLIN assertion (core
dump); run() closes it in its own thread via the 500ms RCVTIMEO.
uv.lock: sync version to 0.8.3.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -87,6 +87,10 @@ class PredictionSubscriber(QThread):
|
||||
self._measure_focus = enabled
|
||||
|
||||
def _try_parse_json(self, part: bytes) -> dict | None:
|
||||
# Binary frames (JPEG starts with 0xff) are not JSON; decoding them as
|
||||
# utf-8 raises UnicodeDecodeError on every frame. Cheap sniff instead.
|
||||
if not part.lstrip()[:1] == b"{":
|
||||
return None
|
||||
try:
|
||||
decoded = json.loads(part.decode("utf-8"))
|
||||
return decoded if isinstance(decoded, dict) else None
|
||||
@@ -254,11 +258,9 @@ class PredictionSubscriber(QThread):
|
||||
self.running = False
|
||||
self.requestInterruption()
|
||||
|
||||
try:
|
||||
if self._sock is not None:
|
||||
self._sock.close(0)
|
||||
except Exception:
|
||||
logger.debug("Error while stopping the prediction subscriber", exc_info=True)
|
||||
|
||||
# Do NOT close the socket here: zmq sockets are not thread-safe, and
|
||||
# closing from the GUI thread while run() uses it aborts libzmq
|
||||
# (Assertion failed: pfd.revents & POLLIN, signaler.cpp). RCVTIMEO=500ms
|
||||
# guarantees run() notices running=False and closes it in its own thread.
|
||||
if not self.wait(1500):
|
||||
logger.warning("PredictionSubscriber did not stop within timeout")
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
"""Checks the binary-frame sniff in _try_parse_json: JPEG frames (0xff lead
|
||||
byte) must be rejected without attempting utf-8 decode (previously raised
|
||||
UnicodeDecodeError on every frame)."""
|
||||
|
||||
from aare.gui.threads.prediction_subscriber import PredictionSubscriber
|
||||
|
||||
|
||||
def _parse(part: bytes):
|
||||
# Called unbound: _try_parse_json touches no instance state, so no
|
||||
# QThread/zmq construction is needed.
|
||||
return PredictionSubscriber._try_parse_json(object(), part)
|
||||
|
||||
|
||||
def test_binary_jpeg_frame_is_not_json():
|
||||
assert _parse(b"\xff\xd8\xff\xe0somejpegbytes") is None
|
||||
|
||||
|
||||
def test_json_dict_is_parsed():
|
||||
assert _parse(b'{"encoding": "jpeg"}') == {"encoding": "jpeg"}
|
||||
|
||||
|
||||
def test_json_non_dict_is_rejected():
|
||||
assert _parse(b"[1, 2]") is None
|
||||
assert _parse(b"") is None
|
||||
|
||||
Reference in New Issue
Block a user