Merge branch 'prepare-shutdown-lua-gc-race' into 'master'

lib: core: add prepare_shutdown vfunc to fix Lua GC crash on exit

See merge request pipewire/wireplumber!832
This commit is contained in:
zhaochengyi 2026-05-08 06:45:49 +00:00
commit bed6148e4d
6 changed files with 105 additions and 6 deletions

View file

@ -397,7 +397,21 @@ static void
wp_core_dispose (GObject * obj)
{
WpCore *self = WP_CORE (obj);
GPtrArray *objects = self->registry.objects;
guint i;
/* Notify all plugins to prepare for shutdown before the registry is cleared.
* This allows the Lua scripting plugin to close its lua_State while all
* GObjects are still alive, preventing use-after-free in finalizers. */
if (objects) {
for (i = 0; i < objects->len; i++) {
gpointer item = objects->pdata[i];
if (WP_IS_PLUGIN (item))
wp_plugin_prepare_shutdown (WP_PLUGIN (item));
}
}
/* Now it is safe to clear the registry */
wp_registry_clear (&self->registry);
wp_object_update_features (WP_OBJECT (self), 0, WP_CORE_FEATURE_COMPONENTS);

View file

@ -213,6 +213,21 @@ wp_plugin_get_name (WpPlugin * self)
return g_quark_to_string (priv->name_quark);
}
/*!
* \brief Called during core shutdown before registry is cleared.
*
* \ingroup wpplugin
* \param self the plugin
* \returns void
*/
void
wp_plugin_prepare_shutdown (WpPlugin * self)
{
g_return_if_fail (WP_IS_PLUGIN (self));
if (WP_PLUGIN_GET_CLASS (self)->prepare_shutdown)
WP_PLUGIN_GET_CLASS (self)->prepare_shutdown (self);
}
/**
* \var _WpPluginClass::enable
*

View file

@ -36,9 +36,12 @@ struct _WpPluginClass
void (*enable) (WpPlugin * self, WpTransition * transition);
void (*disable) (WpPlugin * self);
/* New virtual method: called during core shutdown before registry is cleared.
Uses one of the padding slots to preserve ABI. */
void (*prepare_shutdown) (WpPlugin * self);
/*< private >*/
WP_PADDING(6)
WP_PADDING(5)
};
WP_API
@ -47,6 +50,9 @@ WpPlugin * wp_plugin_find (WpCore * core, const gchar * plugin_name);
WP_API
const gchar * wp_plugin_get_name (WpPlugin * self);
WP_API
void wp_plugin_prepare_shutdown (WpPlugin * self);
G_END_DECLS
#endif

View file

@ -21,6 +21,8 @@ struct _WpLuaScriptingPlugin
{
WpPlugin parent;
lua_State *L;
GPtrArray *scripts; /* List of all loaded WpLuaScript objects */
gboolean is_shutting_down; /* Flag to avoid double cleanup */
};
static int
@ -90,6 +92,8 @@ G_DEFINE_TYPE_WITH_CODE (WpLuaScriptingPlugin, wp_lua_scripting_plugin,
static void
wp_lua_scripting_plugin_init (WpLuaScriptingPlugin * self)
{
self->scripts = g_ptr_array_new_with_free_func (g_object_unref);
self->is_shutting_down = FALSE;
}
static void
@ -112,12 +116,49 @@ wp_lua_scripting_plugin_enable (WpPlugin * plugin, WpTransition * transition)
wp_object_update_features (WP_OBJECT (self), WP_PLUGIN_FEATURE_ENABLED, 0);
}
static void
wp_lua_scripting_plugin_prepare_shutdown (WpPlugin *plugin)
{
WpLuaScriptingPlugin *self = WP_LUA_SCRIPTING_PLUGIN (plugin);
guint i;
/* Avoid double execution */
if (self->is_shutting_down)
return;
self->is_shutting_down = TRUE;
/* 1. Close the lua_State early. This will run all pending __gc finalizers
* while all GObjects are still alive. */
if (self->L)
g_clear_pointer (&self->L, wplua_unref);
/* 2. Clear the lua_State pointer from every WpLuaScript that this plugin
* has loaded. This prevents them from trying to close the same state
* again in their finalize handlers during registry cleanup. */
for (i = 0; i < self->scripts->len; i++) {
WpLuaScript *script = g_ptr_array_index (self->scripts, i);
if (WP_IS_LUA_SCRIPT (script))
wp_lua_script_clear_lua_state (script);
}
}
static void
wp_lua_scripting_plugin_disable (WpPlugin * plugin)
{
WpLuaScriptingPlugin * self = WP_LUA_SCRIPTING_PLUGIN (plugin);
if (self->is_shutting_down)
return;
g_clear_pointer (&self->L, wplua_unref);
if (self->L)
g_clear_pointer (&self->L, wplua_unref);
}
static void
wp_lua_scripting_plugin_finalize (GObject *object)
{
WpLuaScriptingPlugin *self = WP_LUA_SCRIPTING_PLUGIN (object);
g_clear_pointer (&self->scripts, g_ptr_array_unref);
G_OBJECT_CLASS (wp_lua_scripting_plugin_parent_class)->finalize (object);
}
static gboolean
@ -187,6 +228,7 @@ wp_lua_scripting_plugin_load (WpComponentLoader * cl, WpCore * core,
"arguments", args,
NULL);
g_ptr_array_add (self->scripts, g_object_ref (script));
g_task_return_pointer (task, g_steal_pointer (&script), g_object_unref);
}
@ -204,9 +246,12 @@ static void
wp_lua_scripting_plugin_class_init (WpLuaScriptingPluginClass * klass)
{
WpPluginClass *plugin_class = (WpPluginClass *) klass;
GObjectClass *object_class = G_OBJECT_CLASS (klass);
plugin_class->enable = wp_lua_scripting_plugin_enable;
plugin_class->disable = wp_lua_scripting_plugin_disable;
plugin_class->prepare_shutdown = wp_lua_scripting_plugin_prepare_shutdown;
object_class->finalize = wp_lua_scripting_plugin_finalize;
}
static void

View file

@ -61,11 +61,15 @@ wp_lua_script_finalize (GObject * object)
{
WpLuaScript *self = WP_LUA_SCRIPT (object);
wp_lua_script_cleanup (self);
g_clear_pointer (&self->L, wplua_unref);
g_clear_pointer (&self->filename, g_free);
g_clear_pointer (&self->args, wp_spa_json_unref);
/* If the lua_State has already been cleared by the plugin's prepare_shutdown,
* skip closing it again, but still free other resources. */
if (self->L != NULL) {
wp_lua_script_cleanup (self);
g_clear_pointer (&self->L, wplua_unref);
}
g_clear_pointer (&self->filename, g_free);
g_clear_pointer (&self->args, wp_spa_json_unref); /* if args exists */
G_OBJECT_CLASS (wp_lua_script_parent_class)->finalize (object);
}
@ -286,3 +290,12 @@ wp_lua_script_class_init (WpLuaScriptClass * klass)
WP_TYPE_SPA_JSON,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
}
void
wp_lua_script_clear_lua_state (WpLuaScript *self)
{
g_return_if_fail (WP_IS_LUA_SCRIPT (self));
/* Only clear the pointer; the lua_State is owned and destroyed by the
* WpLuaScriptingPlugin. Setting it to NULL prevents double closing. */
self->L = NULL;
}

View file

@ -17,6 +17,12 @@ G_BEGIN_DECLS
#define WP_TYPE_LUA_SCRIPT (wp_lua_script_get_type ())
G_DECLARE_FINAL_TYPE (WpLuaScript, wp_lua_script, WP, LUA_SCRIPT, WpPlugin)
/* Clear the internal lua_State pointer to prevent the finalizer from
* attempting to close the already-destroyed state. Called by the Lua
* scripting plugin during prepare_shutdown. */
WP_API
void wp_lua_script_clear_lua_state (WpLuaScript *self);
G_END_DECLS
#endif