fix(util): bound SafeSignal queue growth under burst load

SafeSignal could queue events forever when worker threads emitted faster than
the main loop could consume, which risks memory growth and stale updates.

I added a queue cap with a drop-oldest policy so growth stays bounded under
burst load, plus a regression test that validates bounded delivery.

Signed-off-by: Austin Horstman <khaneliman12@gmail.com>
This commit is contained in:
Austin Horstman
2026-02-09 14:06:27 -06:00
parent 31b373b984
commit e4ff024fa8
2 changed files with 51 additions and 0 deletions

View File

@ -9,6 +9,7 @@
#endif
#include <thread>
#include <type_traits>
#include <vector>
#include "fixtures/GlibTestsFixture.hpp"
@ -141,3 +142,33 @@ TEST_CASE_METHOD(GlibTestsFixture, "SafeSignal copy/move counter", "[signal][thr
producer.join();
REQUIRE(count == NUM_EVENTS);
}
TEST_CASE_METHOD(GlibTestsFixture, "SafeSignal queue stays bounded under burst load",
"[signal][thread][util][perf]") {
constexpr int NUM_EVENTS = 200;
constexpr std::size_t MAX_QUEUED_EVENTS = 8;
std::vector<int> received;
SafeSignal<int> test_signal;
test_signal.set_max_queued_events(MAX_QUEUED_EVENTS);
setTimeout(500);
test_signal.connect([&](auto value) { received.push_back(value); });
run([&]() {
std::thread producer([&]() {
for (int i = 1; i <= NUM_EVENTS; ++i) {
test_signal.emit(i);
}
});
producer.join();
Glib::signal_timeout().connect_once([this]() { this->quit(); }, 50);
});
REQUIRE(received.size() <= MAX_QUEUED_EVENTS);
REQUIRE_FALSE(received.empty());
REQUIRE(received.back() == NUM_EVENTS);
REQUIRE(received.front() == NUM_EVENTS - static_cast<int>(received.size()) + 1);
}