Intermittent EVAFenceUnsignaled crash → null-pointer SEGV in CameraSensor.cpp send_frame() on OAK4-S / RVC4
A Camera-based pipeline intermittently crashes the device with an unrecoverable firmware error. Three independently captured crash dumps, spanning different resolutions, codecs, and encoder settings, all show the identical signature and the identical internal request ID — pointing to an early-pipeline race
Device: OAK4-S R9
Platform: RVC4, firmware version: 0.0.1+656f19d8caeedccb34cd4ba2fe4b2711affab61b
depthai versionL 3.7.1 ; Bootloader version: 0.0.28
Crash details:
CamX HAL reports EVAFenceUnsignaled — "EVA node has 1 unsignaled fences for requestID 9" (error 1173760) — and declares the camera usecase unrecoverable, raising SIGABRT.
depthai-gate's Camera(0) process then hits a null-pointer SEGV in send_frame() (CameraSensor.cpp:188), caught by UBSan.
The device fails to reconnect within the 10s timeout, requiring a full host-side reconnect/power cycle.
Three crashes captured so far. If helpful, can provide crash logs. All hit the same internal requestID 9, despite no other parameter matching:
1st and 2nd crashes:
- Resolution: 512x288; H264, CBR, 500 kbps target bitrate, requestID: 9
3rd Crash
- Resolution: 1920x1080; H265, default rate control, 2000 kbps target bitrate, requestID: 9
Crash 3 has no resolution, codec, or rate-control setting in common with 1 and 2 — only the request ID matches. That points to a race in CamX/EVA's fence handling tied to a specific point early in pipeline warmup, independent of resolution, codec, and encoder configuration.
Frequency: in a targeted 125-run campaign at the crash 1/2 configuration, measured 1 crash (~0.8%, 95% CI ~0.1–4.4%) — rare, but real and reproducible. A separate 100-run test with VideoEncoder removed entirely (bare Camera + raw output) produced no crashes, suggesting the fault lives in the camera/ISP capture layer itself, below and independent of the encoder.
#!/usr/bin/env python3
"""EVAFenceUnsignaled repro on OAK4-S (RVC4). Rare (~0.8%) -- run ~100+ times to catch it."""
import time
import depthai as dai
pipeline = dai.Pipeline()
cam = pipeline.create(dai.node.Camera)
cam.build()
cam_video = cam.requestOutput((512, 288), dai.ImgFrame.Type.NV12)
enc = pipeline.create(dai.node.VideoEncoder)
enc.setDefaultProfilePreset(30, dai.VideoEncoderProperties.Profile.H264_HIGH)
enc.setBitrateKbps(500)
enc.setRateControlMode(dai.VideoEncoderProperties.RateControlMode.CBR)
cam_video.link(enc.input)
video_q = enc.bitstream.createOutputQueue(maxSize=60, blocking=False)
imu = pipeline.create(dai.node.IMU)
imu.enableIMUSensor([dai.IMUSensor.ACCELEROMETER_RAW, dai.IMUSensor.GYROSCOPE_RAW], 500)
imu.setBatchReportThreshold(1)
imu.setMaxBatchReports(10)
nn = pipeline.create(dai.node.DetectionNetwork).build(
cam, dai.NNModelDescription("luxonis/yolov10-nano:coco-512x288"))
nn.setConfidenceThreshold(0.5)
tracker = pipeline.create(dai.node.ObjectTracker)
nn.out.link(tracker.inputDetections)
nn.passthrough.link(tracker.inputTrackerFrame)
nn.passthrough.link(tracker.inputDetectionFrame)
pipeline.start()
t0 = time.monotonic()
n_frames = 0
while time.monotonic() - t0 < 8.0:
pkt = video_q.tryGet()
if pkt is not None:
n_frames += 1
time.sleep(0.002)
pipeline.stop()
print(f"received {n_frames} frames in 8s")
for i in $(seq 1 150); do echo "=== run $i ==="; python3 repro_eva_fence_crash.py; done
Further minimized variant - bare camera, no encoder/IMU/detection/tracker, to isolate the camera/ISP layer from everything downstream:
#!/usr/bin/env python3
"""Bare-Camera EVAFenceUnsignaled repro attempt on OAK4-S (RVC4)."""
import time
import depthai as dai
pipeline = dai.Pipeline()
cam = pipeline.create(dai.node.Camera)
cam.build()
cam_video = cam.requestOutput((512, 288), dai.ImgFrame.Type.NV12)
video_q = cam_video.createOutputQueue(maxSize=60, blocking=False)
pipeline.start()
t0 = time.monotonic()
n_frames = 0
while time.monotonic() - t0 < 8.0:
pkt = video_q.tryGet()
if pkt is not None:
n_frames += 1
time.sleep(0.002)
pipeline.stop()
print(f"received {n_frames} frames in 8s")
Ran this variant 100 times without reproducing the crash — consistent with the encoder being uninvolved, though not yet a confirmed independent repro at this sample size.