0.1.8: media_kit hot-restart guard (Linux, debug)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
FoxyzandClaude Opus 5.5 committed 2026-10-09 18:57:23 +03:00
1 parent dc16bba054
commit d2435a06b2
7 files changed
+112 -1

No files matched your search

+11
View File
@@ -1,3 +1,14 @@
## 0.1.8
* Linux, debug builds only: a Flutter hot restart no longer crashes apps that
use media_kit with live players. Hot restart frees the old isolate's
NativeCallables while the mpv handles live on; media_kit then sends the
leftovers "quit" and mpv calls a dead wakeup callback (SIGSEGV on mpv's
"core" thread). The plugin clears every live handle's wakeup callback on
the engine's `on-pre-engine-restart` signal (handles found through
media_kit's NativeReferenceHolder file). Automatic: depending on fox_utils
is enough. Relies on media_kit internals (checked against 1.2.6).
## 0.1.7
* Add `DesktopDropX` + `DropTarget` — OS-level drag & drop of files and
+10
View File
@@ -136,6 +136,16 @@ Platform notes:
- `readImage` never throws: unreadable files are logged with a stack trace
and skipped; `null` means "no image in this drop".
### media_kit hot-restart guard (Linux, debug)
Apps using media_kit crash on a hot restart (`R`) while any `Player` is
alive: mpv calls a wakeup callback that belonged to the discarded isolate.
fox_utils' Linux plugin clears those callbacks just before the engine
restarts, so hot restart works. Nothing to call; release builds compile it
out; it does nothing without media_kit. It reads media_kit's private
NativeReferenceHolder file (checked against media_kit 1.2.6): re-check after
media_kit upgrades.
## Vendored engines
The clipboard channels (`pasteboard` method channel) are **vendored from
+2
View File
@@ -10,6 +10,7 @@ add_library(${PLUGIN_NAME} SHARED
"fox_utils_plugin.cc"
"pasteboard_plugin.cc"
"desktop_drop_plugin.cc"
"media_kit_restart_guard.cc"
)
apply_standard_settings(${PLUGIN_NAME})
set_target_properties(${PLUGIN_NAME} PROPERTIES
@@ -19,6 +20,7 @@ target_include_directories(${PLUGIN_NAME} INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(${PLUGIN_NAME} PRIVATE flutter)
target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK)
target_link_libraries(${PLUGIN_NAME} PRIVATE ${CMAKE_DL_LIBS})
# List of absolute paths to libraries that should be bundled with the plugin
set(fox_utils_bundled_libraries
+2
View File
@@ -1,8 +1,10 @@
#include "include/fox_utils/fox_utils_plugin.h"
#include "include/fox_utils/pasteboard_plugin.h"
#include "include/fox_utils/desktop_drop_plugin.h"
#include "include/fox_utils/media_kit_restart_guard.h"
void fox_utils_plugin_register_with_registrar(FlPluginRegistrar* registrar) {
pasteboard_plugin_register_with_registrar(registrar);
desktop_drop_plugin_register_with_registrar(registrar);
media_kit_restart_guard_register_with_registrar(registrar);
}
@@ -0,0 +1,15 @@
// Debug builds only: keeps a Flutter hot restart from crashing apps that use
// media_kit on Linux. Hot restart frees the old isolate's NativeCallables
// while mpv handles stay alive; media_kit then "quit"s the leftovers and mpv
// calls a dead wakeup callback (SIGSEGV on mpv's "core" thread). On the
// engine's on-pre-engine-restart signal this clears every live handle's
// wakeup callback (found through media_kit's NativeReferenceHolder file).
// Does nothing without media_kit/libmpv, and nothing in release builds.
#ifndef FOX_UTILS_MEDIA_KIT_RESTART_GUARD_H_
#define FOX_UTILS_MEDIA_KIT_RESTART_GUARD_H_
#include <flutter_linux/flutter_linux.h>
void media_kit_restart_guard_register_with_registrar(FlPluginRegistrar* registrar);
#endif
+71
View File
@@ -0,0 +1,71 @@
#include "include/fox_utils/media_kit_restart_guard.h"
#ifdef NDEBUG
void media_kit_restart_guard_register_with_registrar(FlPluginRegistrar* registrar) {}
#else
#include <dlfcn.h>
#include <stdint.h>
#include <unistd.h>
namespace {
// Mirrors media_kit's NativeReferenceHolder (debug only): a calloc'd buffer
// of kReferenceBufferSize intptr slots holding live mpv_handle addresses,
// its address written in decimal to <tmp>/<kFilePrefix><pid>.
constexpr int kReferenceBufferSize = 512;
constexpr char kFilePrefix[] = "com.alexmercerind.media_kit.NativeReferenceHolder.";
using SetWakeupCallback = void (*)(void* ctx, void (*cb)(void*), void* d);
SetWakeupCallback find_set_wakeup_callback() {
static const char* const kNames[] = {"libmpv.so.2", "libmpv.so", "libmpv.so.1"};
for (const char* name : kNames) {
void* lib = dlopen(name, RTLD_NOW | RTLD_NOLOAD);
if (lib == nullptr) continue;
auto fn = reinterpret_cast<SetWakeupCallback>(
dlsym(lib, "mpv_set_wakeup_callback"));
dlclose(lib); // Drops only the reference NOLOAD added.
if (fn != nullptr) return fn;
}
return nullptr;
}
// Runs on the engine's UI thread before the old isolate shuts down, so the
// buffer is not being written concurrently. Clearing the callback takes the
// same lock mpv calls it under: once this returns, mpv never calls it again.
// The handles stay in the buffer: media_kit's startup in the new isolate
// still sends them "quit".
void on_pre_engine_restart(FlEngine* engine, gpointer user_data) {
g_autofree gchar* name = g_strdup_printf("%s%d", kFilePrefix, getpid());
g_autofree gchar* path = g_build_filename(g_get_tmp_dir(), name, nullptr);
g_autofree gchar* contents = nullptr;
if (!g_file_get_contents(path, &contents, nullptr, nullptr)) return;
const guint64 address = g_ascii_strtoull(contents, nullptr, 10);
if (address == 0) return;
const SetWakeupCallback set_wakeup = find_set_wakeup_callback();
if (set_wakeup == nullptr) return;
const auto* slots = reinterpret_cast<const intptr_t*>(address);
int cleared = 0;
for (int i = 0; i < kReferenceBufferSize; i++) {
if (slots[i] == 0) continue;
set_wakeup(reinterpret_cast<void*>(slots[i]), nullptr, nullptr);
cleared++;
}
if (cleared > 0) {
g_message("hot restart: unhooked %d mpv wakeup callback(s)", cleared);
}
}
} // namespace
void media_kit_restart_guard_register_with_registrar(FlPluginRegistrar* registrar) {
FlView* view = fl_plugin_registrar_get_view(registrar);
if (view == nullptr) return; // headless: no hot restart to guard
g_signal_connect(fl_view_get_engine(view), "on-pre-engine-restart",
G_CALLBACK(on_pre_engine_restart), nullptr);
}
#endif
+1 -1
View File
@@ -1,6 +1,6 @@
name: fox_utils
description: A collection of reusable Flutter widgets and extensions.
version: 0.1.7
version: 0.1.8
environment:
sdk: ^3.9.2