fix: mpv dispose deadlock on wayland

This commit is contained in:
edde746
2026-02-19 22:32:32 +01:00
parent 582809427e
commit ad4b23b18b
2 changed files with 41 additions and 20 deletions
+33 -15
View File
@@ -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<std::mutex> 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<MpvPlayer*>(data);
if (player->disposed_) return G_SOURCE_REMOVE;
std::lock_guard<std::mutex> lock(player->callback_mutex_);
if (player->redraw_callback_) {
player->redraw_callback_();
}
return G_SOURCE_REMOVE;
},
player);
}
bool MpvPlayer::ProcessEvents() {
+8 -5
View File
@@ -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));