Loading debian/changelog +9 −0 Original line number Diff line number Diff line mediadb (20260419+25) unstable; urgency=critical * Fix preview render queue lock-up where an exception thrown during FFmpeg initialization would jump the stack and skip the lock release on the `inflight_mutex_`, permanently deadlocking duplicate preview requests with a "timeout waiting for in-flight preview" error. -- Jan Koester <jan.koester@tuxist.de> Sun, 19 Apr 2026 14:00:00 +0200 mediadb (20260419+24) unstable; urgency=critical * Fix broken cluster fetch and index replication operations on startup: Add Loading src/preview.cpp +15 −17 Original line number Diff line number Diff line Loading @@ -699,18 +699,28 @@ std::shared_ptr<const BlobValue> PreviewService::get_or_create_streaming(const M inflight_.insert(key); } struct InflightGuard { PreviewService* self; std::string key; ~InflightGuard() { std::lock_guard<std::mutex> lk(self->inflight_mutex_); self->inflight_.erase(key); self->inflight_cv_.notify_all(); } } inflight_guard{this, key}; // Acquire render semaphore with timeout — limits concurrent FFmpeg // processes to avoid overloading the CPU. if (!render_sem_.try_acquire_for(std::chrono::seconds(30))) { { std::lock_guard<std::mutex> lk(inflight_mutex_); inflight_.erase(key); } inflight_cv_.notify_all(); error_out = "preview render queue full (semaphore timeout)"; return nullptr; } struct SemGuard { PreviewService* self; ~SemGuard() { self->render_sem_.release(); } } sem_guard{this}; // Run FFmpeg on the io_pool with a timeout. This prevents a stuck // FFmpeg render from blocking the HTTP worker thread indefinitely. // Captures are by value so the lambda is safe if we return on timeout. Loading @@ -727,14 +737,8 @@ std::shared_ptr<const BlobValue> PreviewService::get_or_create_streaming(const M }); auto status = fut.wait_for(std::chrono::seconds(60)); render_sem_.release(); if (status == std::future_status::timeout) { { std::lock_guard<std::mutex> lk(inflight_mutex_); inflight_.erase(key); } inflight_cv_.notify_all(); error_out = "preview render timed out after 60s"; return nullptr; } Loading @@ -742,12 +746,6 @@ std::shared_ptr<const BlobValue> PreviewService::get_or_create_streaming(const M auto [result, render_err] = fut.get(); error_out = std::move(render_err); { std::lock_guard<std::mutex> lk(inflight_mutex_); inflight_.erase(key); } inflight_cv_.notify_all(); if (!result.has_value()) { return nullptr; } Loading Loading
debian/changelog +9 −0 Original line number Diff line number Diff line mediadb (20260419+25) unstable; urgency=critical * Fix preview render queue lock-up where an exception thrown during FFmpeg initialization would jump the stack and skip the lock release on the `inflight_mutex_`, permanently deadlocking duplicate preview requests with a "timeout waiting for in-flight preview" error. -- Jan Koester <jan.koester@tuxist.de> Sun, 19 Apr 2026 14:00:00 +0200 mediadb (20260419+24) unstable; urgency=critical * Fix broken cluster fetch and index replication operations on startup: Add Loading
src/preview.cpp +15 −17 Original line number Diff line number Diff line Loading @@ -699,18 +699,28 @@ std::shared_ptr<const BlobValue> PreviewService::get_or_create_streaming(const M inflight_.insert(key); } struct InflightGuard { PreviewService* self; std::string key; ~InflightGuard() { std::lock_guard<std::mutex> lk(self->inflight_mutex_); self->inflight_.erase(key); self->inflight_cv_.notify_all(); } } inflight_guard{this, key}; // Acquire render semaphore with timeout — limits concurrent FFmpeg // processes to avoid overloading the CPU. if (!render_sem_.try_acquire_for(std::chrono::seconds(30))) { { std::lock_guard<std::mutex> lk(inflight_mutex_); inflight_.erase(key); } inflight_cv_.notify_all(); error_out = "preview render queue full (semaphore timeout)"; return nullptr; } struct SemGuard { PreviewService* self; ~SemGuard() { self->render_sem_.release(); } } sem_guard{this}; // Run FFmpeg on the io_pool with a timeout. This prevents a stuck // FFmpeg render from blocking the HTTP worker thread indefinitely. // Captures are by value so the lambda is safe if we return on timeout. Loading @@ -727,14 +737,8 @@ std::shared_ptr<const BlobValue> PreviewService::get_or_create_streaming(const M }); auto status = fut.wait_for(std::chrono::seconds(60)); render_sem_.release(); if (status == std::future_status::timeout) { { std::lock_guard<std::mutex> lk(inflight_mutex_); inflight_.erase(key); } inflight_cv_.notify_all(); error_out = "preview render timed out after 60s"; return nullptr; } Loading @@ -742,12 +746,6 @@ std::shared_ptr<const BlobValue> PreviewService::get_or_create_streaming(const M auto [result, render_err] = fut.get(); error_out = std::move(render_err); { std::lock_guard<std::mutex> lk(inflight_mutex_); inflight_.erase(key); } inflight_cv_.notify_all(); if (!result.has_value()) { return nullptr; } Loading