From 582809427e726e72b3787b7b3aac6e8487f7e352 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Thu, 19 Feb 2026 21:50:29 +0100 Subject: [PATCH] fix: linux mpv dispose deadlock on wayland --- linux/runner/mpv/mpv_player.cc | 33 ++++++++++++++++++++------------- linux/runner/mpv/mpv_player.h | 3 +++ linux/runner/mpv/mpv_plugin.cc | 7 ++++--- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/linux/runner/mpv/mpv_player.cc b/linux/runner/mpv/mpv_player.cc index 813bc18f..1ddfd39f 100644 --- a/linux/runner/mpv/mpv_player.cc +++ b/linux/runner/mpv/mpv_player.cc @@ -130,13 +130,27 @@ bool MpvPlayer::InitRenderContext() { } void MpvPlayer::Dispose() { - std::lock_guard lock(callback_mutex_); - + // 1. Set disposed flag atomically FIRST — all callback paths check this if (disposed_.exchange(true)) { return; } - // Cancel pending async commands + // 2. Clear mpv's native callbacks to prevent new ones from firing + if (mpv_gl_) { + mpv_render_context_set_update_callback(mpv_gl_, nullptr, nullptr); + } + if (mpv_) { + mpv_set_wakeup_callback(mpv_, nullptr, nullptr); + } + + // 3. Briefly hold mutex to null our callbacks + { + std::lock_guard lock(callback_mutex_); + redraw_callback_ = nullptr; + event_callback_ = nullptr; + } + + // 4. Cancel pending async commands { std::lock_guard cmd_lock(pending_commands_mutex_); for (auto& pair : pending_commands_) { @@ -145,20 +159,14 @@ void MpvPlayer::Dispose() { pending_commands_.clear(); } - // Clear mpv callbacks BEFORE freeing - if (mpv_gl_) { - mpv_render_context_set_update_callback(mpv_gl_, nullptr, nullptr); - } - if (mpv_) { - mpv_set_wakeup_callback(mpv_, nullptr, nullptr); - } - - // Remove pending idle callbacks + // 5. Remove pending idle callbacks if (event_source_id_ != 0) { g_source_remove(event_source_id_); event_source_id_ = 0; } + // 6. Free render context and mpv handle WITHOUT holding callback_mutex_ + // (mpv_render_context_free can block waiting for render thread) if (mpv_gl_) { mpv_render_context_free(mpv_gl_); mpv_gl_ = nullptr; @@ -170,7 +178,6 @@ void MpvPlayer::Dispose() { } observed_properties_.clear(); - redraw_callback_ = nullptr; } void MpvPlayer::Command(const std::vector& args) { diff --git a/linux/runner/mpv/mpv_player.h b/linux/runner/mpv/mpv_player.h index 3aaf3240..2c3f988a 100644 --- a/linux/runner/mpv/mpv_player.h +++ b/linux/runner/mpv/mpv_player.h @@ -55,6 +55,9 @@ class MpvPlayer { /// Returns true if mpv is initialized (has both mpv handle and render context). bool IsInitialized() const { return mpv_ != nullptr && mpv_gl_ != nullptr; } + /// Returns true if this player has been disposed. + bool IsDisposed() const { return disposed_.load(); } + /// Returns true if mpv handle exists (even without render context). bool HasMpvHandle() const { return mpv_ != nullptr; } diff --git a/linux/runner/mpv/mpv_plugin.cc b/linux/runner/mpv/mpv_plugin.cc index b171cc54..ee2e36b4 100644 --- a/linux/runner/mpv/mpv_plugin.cc +++ b/linux/runner/mpv/mpv_plugin.cc @@ -123,8 +123,8 @@ static void mpv_plugin_handle_method_call(FlMethodChannel* channel, response = FL_METHOD_RESPONSE(fl_method_success_response_new( fl_value_new_int(mpv_texture_get_id(self->texture)))); } else { - // Create player if it was disposed - if (!self->player) { + // Create player if it was disposed or doesn't exist + if (!self->player || self->player->IsDisposed()) { self->player = std::make_unique(); } @@ -161,7 +161,8 @@ static void mpv_plugin_handle_method_call(FlMethodChannel* channel, } else if (strcmp(method, "dispose") == 0) { if (self->player) { self->player->Dispose(); - self->player.reset(); + // Don't reset player here — stray g_idle callbacks still reference it. + // It will be replaced on next initialize() call. } if (self->texture) { mpv_texture_dispose(self->texture);