1 Commits
Author SHA1 Message Date
Isaac ConnorandClaude Opus 5 99ae7b8e98 fix: free the event start iterator when openEvent cannot lock its packet fixes #5127
PacketQueue::get_event_start_packet_it() allocates a packetqueue_iterator and
registers it in PacketQueue::iterators. Every path out of Monitor::openEvent()
hands that pointer to the Event, which releases it in ~Event via free_it, except
the one that fails to lock the starting packet. That path returned nullptr and
left the iterator registered for the life of the process.

clearPackets() takes min_iterator_queue_index across all registered iterators and
stops removing at the first packet whose queue_index reaches it. An abandoned
iterator pins that to the front packet, so the trimmer removes nothing on every
invocation, and deletePacket() advances registered iterators onto the packet
after the one it removed, which drags the orphan onto each new front packet so it
can never age out. clear_packets_pending_ then latches true, downgrading the gate
so the futile scan re-runs on every video packet rather than only on keyframes.

The only removal path left is the emergency one-GOP eviction in queuePacket, so
the queue settles into refill a GOP, exceed max_video_packet_count, warn, evict a
GOP, repeat. It is pinned at its cap by construction, which is why the monitor
warns once per GOP at a constant rate for as long as zmc runs and no amount of
idle time or extra headroom recovers it.

Our trigger was an RTSP stream EOF mid-event during a reconnect on an ONVIF
camera. It is a race, so it leaks only when openEvent loses it, and each
occurrence leaks one more iterator.

Add tests covering both halves: that free_it takes an event start iterator back
out of the queue, and what an abandoned one does to trimming until it does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 21:49:34 -04:00