From ad4b23b18bbeafb0f3f020a8ff5a0a501daa2992 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Thu, 19 Feb 2026 22:32:32 +0100 Subject: [PATCH] fix: mpv dispose deadlock on wayland --- linux/runner/mpv/mpv_player.cc | 48 +++++++++++++++++++++++----------- linux/runner/mpv/mpv_plugin.cc | 13 +++++---- 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/linux/runner/mpv/mpv_player.cc b/linux/runner/mpv/mpv_player.cc index 1ddfd39f..b6169834 100644 --- a/linux/runner/mpv/mpv_player.cc +++ b/linux/runner/mpv/mpv_player.cc @@ -165,17 +165,23 @@ void MpvPlayer::Dispose() { 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; - } + // 6. Free render context and mpv handle in a background thread. + // mpv_render_context_free() can block waiting for mpv's render/VO thread, + // and mpv_terminate_destroy() can block on demuxer/network I/O. + // Running these off the main thread prevents stalling the GLib main loop. + auto* gl = mpv_gl_; + auto* handle = mpv_; + mpv_gl_ = nullptr; + mpv_ = nullptr; - if (mpv_) { - mpv_terminate_destroy(mpv_); - mpv_ = nullptr; - } + std::thread([gl, handle]() { + if (gl) { + mpv_render_context_free(gl); + } + if (handle) { + mpv_terminate_destroy(handle); + } + }).detach(); observed_properties_.clear(); } @@ -343,11 +349,23 @@ void MpvPlayer::OnMpvRenderUpdate(void* ctx) { return; } - // Call the redraw callback to notify MpvTexture via the registrar - std::lock_guard lock(player->callback_mutex_); - if (player->redraw_callback_) { - player->redraw_callback_(); - } + // Schedule redraw on main thread. Calling Flutter's + // fl_texture_registrar_mark_texture_frame_available directly from mpv's + // render/VO thread can deadlock during disposal on Wayland: the main thread + // blocks in mpv_render_context_free() waiting for the VO thread, while the + // VO thread blocks in the Flutter registrar waiting for the main thread. + g_idle_add( + [](gpointer data) -> gboolean { + auto* player = static_cast(data); + if (player->disposed_) return G_SOURCE_REMOVE; + + std::lock_guard lock(player->callback_mutex_); + if (player->redraw_callback_) { + player->redraw_callback_(); + } + return G_SOURCE_REMOVE; + }, + player); } bool MpvPlayer::ProcessEvents() { diff --git a/linux/runner/mpv/mpv_plugin.cc b/linux/runner/mpv/mpv_plugin.cc index ee2e36b4..df0fadd5 100644 --- a/linux/runner/mpv/mpv_plugin.cc +++ b/linux/runner/mpv/mpv_plugin.cc @@ -159,11 +159,9 @@ static void mpv_plugin_handle_method_call(FlMethodChannel* channel, } } } else if (strcmp(method, "dispose") == 0) { - if (self->player) { - self->player->Dispose(); - // Don't reset player here — stray g_idle callbacks still reference it. - // It will be replaced on next initialize() call. - } + // Disconnect and unregister texture FIRST — this stops Flutter from + // calling populate(), preventing concurrent mpv_render_context_render() + // during player disposal. if (self->texture) { mpv_texture_dispose(self->texture); fl_texture_registrar_unregister_texture(self->texture_registrar, @@ -171,6 +169,11 @@ static void mpv_plugin_handle_method_call(FlMethodChannel* channel, g_object_unref(self->texture); self->texture = nullptr; } + if (self->player) { + self->player->Dispose(); + // Don't reset player here — stray g_idle callbacks still reference it. + // It will be replaced on next initialize() call. + } self->initialized = FALSE; self->visible = FALSE; response = FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));