From 639b229d947ed0eff68e21205f273b45989a5c45 Mon Sep 17 00:00:00 2001 From: patrykorwat Date: Fri, 21 Aug 2026 12:55:57 +0000 Subject: [PATCH] fix(axelera): keep SDK source fed during init with sparse safety keep-alives create_inference_stream consumes the source generator from the very start; a blocking queue.get() in the feed left the source empty during stream init, so the detector process hung before the async worker threads started (no events). Re-add a sparse keep-alive zero (0.1 s) that fires only while the incoming queue is momentarily empty and carries no request token (dropped by collect); steady-state real frames still clock the pipeline back-to-back via AsyncDetectorRunner. --- frigate/detectors/plugins/axelera.py | 35 +++++++++++++++++++++------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/frigate/detectors/plugins/axelera.py b/frigate/detectors/plugins/axelera.py index 67a1e0b29e..3e83ca365b 100644 --- a/frigate/detectors/plugins/axelera.py +++ b/frigate/detectors/plugins/axelera.py @@ -64,6 +64,15 @@ DETECTOR_KEY = "axelera" AX_DEFAULT_NETWORK = "yolox-s-coco-onnx" MAX_ROWS = 20 PRIMER_FRAMES = 3 +# Safety-net keep-alive spacing while the incoming queue is momentarily empty +# (stream init, brief scheduling gaps, true global idle). AsyncDetectorRunner +# keeps the queue full with real frames in steady state, so these zeros rarely +# fire and real frames still clock the pipeline back-to-back (measured ~14 ms). +# A short spacing matters when they DO fire: the SDK pipeline is clocked by the +# input arrival interval (depth 4 x gap), so a 1 s gap would stretch the next +# real frame's result to ~4 s; 0.1 s keeps that penalty in the tens/hundreds +# of ms. These zeros carry no request token and are dropped by collect(). +KEEPALIVE_INTERVAL = 0.1 # Per-frame tag: the SDK returns each source frame back 1:1 in the result # (verified on hardware: BGR pass-through is exact, image dimension preserved), @@ -214,10 +223,14 @@ class _AxeleraInference: kick-starts model pipeline init; their results carry no token and are dropped by ``collect``. - No keep-alives: the caller (Frigate's AsyncDetectorRunner) submits real - frames continuously as cameras feed them, so the SDK pipeline is clocked - by real frames (verified: 1 s input gaps stretch a real frame's latency to - ~4 s on this depth-4 pipeline, back-to-back frames round-trip in ~14 ms). + In steady state real frames clock the SDK pipeline back-to-back: the + caller (Frigate's AsyncDetectorRunner) submits frames continuously as + cameras feed them, so the pipeline is never idle (verified: back-to-back + frames round-trip in ~14 ms, while 1 s input gaps stretch a real frame's + latency to ~4 s on this depth-4 pipeline). A sparse 0.1 s keep-alive zero + is emitted only while the incoming queue is momentarily empty (stream + init, scheduling gaps, true idle) so the SDK source never starves; these + zeros carry no token and are dropped. Results are STRICTLY paired to requests by CONTENT: every real frame is stamped with a 32-bit request token in its top-left pixel band, ``collect`` @@ -260,12 +273,18 @@ class _AxeleraInference: for _ in range(PRIMER_FRAMES): yield np.zeros((self._height, self._width, 3), np.uint8) while True: - # Blocking: the SDK input side pulls the next frame when ready. - # Real frames (pushed by submit) clock the pipeline. - token, frame = self._frame_q.get() + frame = None + try: + token, frame = self._frame_q.get(timeout=KEEPALIVE_INTERVAL) + _ = token # the token was already stamped in submit() + except queue.Empty: + # safety net: the SDK source must never starve (stream init, + # scheduling gaps, true idle). spawn a token-less zero frame; + # its result is dropped by collect(). In steady state real + # frames keep the queue populated, so this rarely fires. + frame = np.zeros((self._height, self._width, 3), np.uint8) if frame is None: # shutdown sentinel from stop() return - _ = token # the token was already stamped in submit() yield frame def _detection_rows(self, frame_result) -> list: