Trace stream image worker and GUI shutdown waits

This commit is contained in:
evgeny committed 2026-10-16 22:45:58 +02:00
1 parent a63af43e59
commit 1e21912dc6
2 files changed
+57 -5

No files matched your search

+56 -5
View File
@@ -12,6 +12,7 @@
#include <QJsonArray>
#include <QJsonDocument>
#include <QEventLoop>
#include <QScopeGuard>
#include <thread>
#include <mutex>
#include <condition_variable>
@@ -86,10 +87,16 @@ struct StreamPublishEngine::Impl {
int64_t captureReport = 0;
std::atomic<const char*> videoStage{"stopped"};
std::atomic<int64_t> videoStageAt{0};
std::atomic<const char*> imageStage{"starting"};
std::atomic<int64_t> imageStageAt{monotonicUs()};
std::atomic<uint64_t> imageFrames{0};
std::atomic<uint64_t> convertedFrames{0}, encodedFrames{0}, deliveredFrames{0}, repeatedFrames{0};
void videoProgress(const char* stage) {
videoStageAt.store(monotonicUs()); videoStage.store(stage);
}
void imageProgress(const char* stage) {
imageStageAt.store(monotonicUs()); imageStage.store(stage);
}
bool hasGood = false, rollingBack = false, videoReady = false;
uint64_t softwareRequest = 0;
int softwareCodec = 0;
@@ -149,28 +156,65 @@ StreamPublishEngine::StreamPublishEngine(QObject* parent) : QObject(parent), m(s
m_capture.close(); // Контекст нельзя освободить до завершения фонового закрытия устройства.
});
#ifdef HAVE_STREAM_CAPTURE
m->imageThread = std::thread(&StreamPublishEngine::imageLoop, this);
m->imageThread = std::thread([this] {
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "desktop image worker started tid=%llu", (unsigned long long)(quintptr)QThread::currentThreadId());
imageLoop();
m->imageProgress("release-thread-resources");
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "desktop image loop returned; releasing thread resources frames=%llu tid=%llu",
(unsigned long long)m->imageFrames.load(), (unsigned long long)(quintptr)QThread::currentThreadId());
});
#else
m->imageJoined = true;
#endif
}
StreamPublishEngine::~StreamPublishEngine() {
const int64_t destroyAt = monotonicUs();
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "desktop publisher destruction begin id=%llu running=%d stopping=%d image_stage=%s image_frames=%llu",
(unsigned long long)m->id, m->running.load(), m->stopping, m->imageStage.load(), (unsigned long long)m->imageFrames.load());
m->destroying = true; blockSignals(true);
stop(); m_timer.stop(); m_previewTimer.stop();
closeSource();
#ifdef HAVE_STREAM_CAPTURE
{ std::lock_guard<std::mutex> guard(m->lock); m->imageStop = true; m->imagePending = false; m->imageInput = {}; }
m->imageWake.notify_all();
m->imageJoinThread = std::thread([this] { m->imageThread.join(); m->imageJoined = true; });
m->imageJoinThread = std::thread([this] {
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "desktop image join begin stage=%s stage_age_ms=%lld",
m->imageStage.load(), (long long)((monotonicUs() - m->imageStageAt.load()) / 1000));
m->imageThread.join();
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "desktop image join complete; native thread resources released");
m->imageJoined = true;
});
#endif
if (!finishStop() || !m->imageJoined) {
// Qt освобождает thread-local графику через GUI. Даже при выходе приложения нельзя блокировать его join().
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop publisher destruction waits for worker resource cleanup with GUI events enabled");
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop publisher destruction waits for workers stopping=%d image_joined=%d image_stage=%s",
m->stopping, m->imageJoined.load(), m->imageStage.load());
QEventLoop loop; QTimer timer;
connect(&timer, &QTimer::timeout, &loop, [this, &loop] { if (finishStop() && m->imageJoined) loop.quit(); });
timer.start(10); loop.exec(QEventLoop::ExcludeUserInputEvents);
int64_t reportAt = destroyAt;
bool firstPoll = true;
connect(&timer, &QTimer::timeout, &loop, [this, &loop, destroyAt, &reportAt, &firstPoll] {
if (firstPoll) {
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "desktop publisher destruction GUI timer active"); firstPoll = false;
}
if (finishStop() && m->imageJoined) { loop.quit(); return; }
const int64_t now = monotonicUs();
if (now - reportAt < 5000000) return;
reportAt = now;
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "desktop publisher destruction waiting elapsed_ms=%lld stopping=%d workers_joined=%d "
"video_stage=%s image_joined=%d image_stage=%s image_stage_age_ms=%lld image_frames=%llu microphone_closing=%d",
(long long)((now - destroyAt) / 1000), m->stopping, m->workersJoined.load(), m->videoStage.load(),
m->imageJoined.load(), m->imageStage.load(), (long long)((now - m->imageStageAt.load()) / 1000),
(unsigned long long)m->imageFrames.load(), m_capture.closing());
});
timer.start(10);
const int result = loop.exec(QEventLoop::ExcludeUserInputEvents);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "desktop publisher destruction GUI loop exited result=%d stopping=%d image_joined=%d",
result, m->stopping, m->imageJoined.load());
if (m->stopping || !m->imageJoined)
DEBUG_ERROR(DEBUG_CATEGORY_DEBUG, "desktop publisher destruction GUI loop exited before worker cleanup completed");
}
if (m->imageJoinThread.joinable()) m->imageJoinThread.join();
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "desktop publisher destruction complete elapsed_ms=%lld", (long long)((monotonicUs() - destroyAt) / 1000));
}
bool StreamPublishEngine::supported() {
#ifdef HAVE_STREAM_CAPTURE
@@ -433,6 +477,7 @@ void StreamPublishEngine::imageLoop() {
#ifdef HAVE_STREAM_CAPTURE
for (;;) {
QVideoFrame frame; uint64_t generation; int64_t pts;
m->imageProgress("wait-frame");
{
std::unique_lock<std::mutex> lock(m->lock);
m->imageWake.wait(lock, [this] { return m->imageStop || m->imagePending; });
@@ -440,7 +485,12 @@ void StreamPublishEngine::imageLoop() {
frame = std::move(m->imageInput); generation = m->imageGeneration; pts = m->imagePts; m->imagePending = false;
}
int64_t convertAt = monotonicUs();
m->imageProgress("convert-frame");
QImage image = frame.toImage();
// Отметка ставится перед деструкторами image/frame, включая return/continue внутри итерации.
auto releaseFrame = qScopeGuard([this] { m->imageProgress("release-frame"); });
m->imageFrames++;
m->imageProgress("transform-image");
if (image.isNull()) {
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop captured frame conversion failed generation=%llu format=%d size=%dx%d",
(unsigned long long)generation, frame.pixelFormat(), frame.width(), frame.height());
@@ -453,6 +503,7 @@ void StreamPublishEngine::imageLoop() {
#else
if (frame.rotationAngle()) image = image.transformed(QTransform().rotate(frame.rotationAngle()));
#endif
m->imageProgress("publish-image");
{
std::lock_guard<std::mutex> guard(m->lock);
if (m->imageStop) return;
@@ -162,6 +162,7 @@ private slots:
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); debug_set_category_level(DEBUG_CATEGORY_STREAM, DEBUG_LEVEL_DEBUG);
debug_set_category_level(DEBUG_CATEGORY_SYS, DEBUG_LEVEL_ERROR);
debug_set_category_level(DEBUG_CATEGORY_VIDEO, DEBUG_LEVEL_DEBUG);
debug_set_category_level(DEBUG_CATEGORY_DEBUG, DEBUG_LEVEL_INFO);
if (qEnvironmentVariableIsSet("UTUN_TEST_SCREEN_CAPTURE")) {
qunsetenv("WAYLAND_DISPLAY"); qputenv("XDG_SESSION_TYPE", "x11");
QLoggingCategory::setFilterRules("qt.multimedia.*=true");