/*
* This file is part of the ZoneMinder Project. See AUTHORS file for contributors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "zm_catch2.h"
#include "zm_packet.h"
#include "zm_packetqueue.h"
#include
namespace {
std::shared_ptr video_packet(int keyframe) {
auto packet = std::make_shared();
packet->packet->stream_index = 0;
packet->keyframe = keyframe;
packet->codec_type = AVMEDIA_TYPE_VIDEO;
packet->timestamp = std::chrono::system_clock::now();
return packet;
}
} // namespace
// Monitor::openEvent() takes an iterator from get_event_start_packet_it() and
// normally hands it to the Event, which frees it in ~Event. The error path that
// fails to lock the starting packet used to return without freeing it. This
// covers what that leak does to the queue: a registered iterator sitting on the
// front packet stops clearPackets() removing anything, permanently, because
// deletePacket() drags registered iterators onto each new front packet.
TEST_CASE("PacketQueue: an abandoned iterator blocks trimming until it is freed") {
PacketQueue queue;
queue.addStream();
queue.setKeepKeyframes(false);
queue.setMaxVideoPackets(10);
queue.setPreEventVideoPackets(2);
// queuePacket() drops everything when no iterator is registered, so stand in
// for the analysis thread and keep one registered for the whole test.
packetqueue_iterator *analysis_it = queue.get_video_it(false);
REQUIRE(analysis_it != nullptr);
for (int i = 0; i < 6; i++) {
REQUIRE(queue.queuePacket(video_packet(i == 0 ? 1 : 0)));
}
REQUIRE(queue.size() == 6);
SECTION("the queue trims once nothing points at the front") {
// Analysis has consumed the queue, so its iterator sits at the tail.
while (queue.increment_it(analysis_it, false)) {}
auto trigger = video_packet(1);
REQUIRE(queue.queuePacket(trigger));
REQUIRE(queue.clearPackets(trigger));
REQUIRE(queue.size() < 7);
}
SECTION("an abandoned iterator on the front packet stops every removal") {
// What openEvent() used to leave behind: a registered iterator that nothing
// owns any more, pointing at the front of the queue.
packetqueue_iterator *leaked_it = queue.get_video_it(false);
REQUIRE(leaked_it != nullptr);
while (queue.increment_it(analysis_it, false)) {}
auto trigger = video_packet(1);
REQUIRE(queue.queuePacket(trigger));
queue.clearPackets(trigger);
REQUIRE(queue.size() == 7);
// Freeing it is all the fix does, and the queue drains on the next attempt.
queue.free_it(leaked_it);
auto next = video_packet(1);
REQUIRE(queue.queuePacket(next));
REQUIRE(queue.clearPackets(next));
REQUIRE(queue.size() < 8);
}
queue.stop();
queue.clear();
}
// The pair of calls the fix in Monitor::openEvent() now makes on its error path:
// an iterator from get_event_start_packet_it() is registered with the queue, and
// free_it() is what takes it back out again.
TEST_CASE("PacketQueue: free_it unregisters an event start iterator") {
PacketQueue queue;
queue.addStream();
queue.setKeepKeyframes(false);
queue.setMaxVideoPackets(0);
queue.setPreEventVideoPackets(2);
packetqueue_iterator *analysis_it = queue.get_video_it(false);
REQUIRE(analysis_it != nullptr);
for (int i = 0; i < 6; i++) {
REQUIRE(queue.queuePacket(video_packet(i == 0 ? 1 : 0)));
}
std::shared_ptr front = *queue.begin();
while (queue.increment_it(analysis_it, false)) {}
REQUIRE_FALSE(queue.is_there_an_iterator_pointing_to_packet(front));
packetqueue_iterator *start_it = queue.get_event_start_packet_it(*analysis_it, 10);
REQUIRE(start_it != nullptr);
REQUIRE(queue.is_there_an_iterator_pointing_to_packet(front));
queue.free_it(start_it);
REQUIRE_FALSE(queue.is_there_an_iterator_pointing_to_packet(front));
queue.stop();
queue.clear();
}