diff --git a/frigate/camera/maintainer.py b/frigate/camera/maintainer.py index c4ddc51e89..5fd4dc4aad 100644 --- a/frigate/camera/maintainer.py +++ b/frigate/camera/maintainer.py @@ -202,6 +202,25 @@ class CameraMaintainer(threading.Thread): capture_process.terminate() capture_process.join() + def __unlink_camera_frame_slots(self, camera: str) -> None: + """Drop the camera's per-frame YUV SHM segments from this + process's frame_manager and unlink them at the OS level. + + Safe to call after the camera's capture/processor subprocesses + have been joined — they no longer hold mappings, so unlink frees + the segments immediately. Other long-lived processes that opened + these slots will continue using their existing mappings until + they call frame_manager.get with a shape that no longer fits + (the get path drops and reopens stale refs). + """ + prefix = f"{camera}_frame" + names = [n for n in list(self.frame_manager.shm_store) if n.startswith(prefix)] + for name in names: + try: + self.frame_manager.delete(name) + except Exception as exc: + logger.debug("Could not unlink SHM %s: %s", name, exc) + def __stop_camera_process(self, camera: str) -> None: camera_process = self.camera_processes.get(camera) if camera_process is not None: @@ -253,6 +272,7 @@ class CameraMaintainer(threading.Thread): for camera in updated_cameras: self.__stop_camera_capture_process(camera) self.__stop_camera_process(camera) + self.__unlink_camera_frame_slots(camera) self.capture_processes.pop(camera, None) self.camera_processes.pop(camera, None) self.camera_stop_events.pop(camera, None)