Initialise the global GParamSpecPool in more places

Right now, we're assuming that GObjectClass will be initialised first
and under a lock, but that's not always the case: when traversing a list
of type, the first one might be a GTypeInterface, and if we initialise
an interface that installs a property, the whole thing comes crashing
down because the global GParamSpecPool is not initialised.

Instead of taking a lock everywhere, we can use an atomic compare and
swap; the first thread that installs a property wins the race, as any
other access to the GParamSpecPool is performed under a lock.
This commit is contained in:
Emmanuele Bassi 2023-12-14 14:46:44 +00:00
parent 8ce40ac590
commit fc5f986e60

View File

@ -403,6 +403,26 @@ _g_object_type_init (void)
#endif /* G_ENABLE_DEBUG */ #endif /* G_ENABLE_DEBUG */
} }
/* Initialize the global GParamSpecPool; this function needs to be
* called whenever we access the GParamSpecPool and we cannot guarantee
* that g_object_do_class_init() has been called: for instance, by the
* interface property API.
*
* To avoid yet another global lock, we use atomic pointer checks: the
* first caller of this function will win the race. Any other access to
* the GParamSpecPool is done under its own mutex.
*/
static inline void
g_object_init_pspec_pool (void)
{
if (G_UNLIKELY (g_atomic_pointer_get (&pspec_pool) == NULL))
{
GParamSpecPool *pool = g_param_spec_pool_new (TRUE);
if (!g_atomic_pointer_compare_and_exchange (&pspec_pool, NULL, pool))
g_param_spec_pool_free (pool);
}
}
static void static void
g_object_base_class_init (GObjectClass *class) g_object_base_class_init (GObjectClass *class)
{ {
@ -459,7 +479,8 @@ g_object_do_class_init (GObjectClass *class)
#ifndef HAVE_OPTIONAL_FLAGS #ifndef HAVE_OPTIONAL_FLAGS
quark_in_construction = g_quark_from_static_string ("GObject-in-construction"); quark_in_construction = g_quark_from_static_string ("GObject-in-construction");
#endif #endif
pspec_pool = g_param_spec_pool_new (TRUE);
g_object_init_pspec_pool ();
class->constructor = g_object_constructor; class->constructor = g_object_constructor;
class->constructed = g_object_constructed; class->constructed = g_object_constructed;
@ -525,6 +546,8 @@ install_property_internal (GType g_type,
{ {
g_param_spec_ref_sink (pspec); g_param_spec_ref_sink (pspec);
g_object_init_pspec_pool ();
if (g_param_spec_pool_lookup (pspec_pool, pspec->name, g_type, FALSE)) if (g_param_spec_pool_lookup (pspec_pool, pspec->name, g_type, FALSE))
{ {
g_critical ("When installing property: type '%s' already has a property named '%s'", g_critical ("When installing property: type '%s' already has a property named '%s'",
@ -951,6 +974,8 @@ g_object_interface_find_property (gpointer g_iface,
g_return_val_if_fail (G_TYPE_IS_INTERFACE (iface_class->g_type), NULL); g_return_val_if_fail (G_TYPE_IS_INTERFACE (iface_class->g_type), NULL);
g_return_val_if_fail (property_name != NULL, NULL); g_return_val_if_fail (property_name != NULL, NULL);
g_object_init_pspec_pool ();
return g_param_spec_pool_lookup (pspec_pool, return g_param_spec_pool_lookup (pspec_pool,
property_name, property_name,
iface_class->g_type, iface_class->g_type,
@ -1091,6 +1116,8 @@ g_object_interface_list_properties (gpointer g_iface,
g_return_val_if_fail (G_TYPE_IS_INTERFACE (iface_class->g_type), NULL); g_return_val_if_fail (G_TYPE_IS_INTERFACE (iface_class->g_type), NULL);
g_object_init_pspec_pool ();
pspecs = g_param_spec_pool_list (pspec_pool, pspecs = g_param_spec_pool_list (pspec_pool,
iface_class->g_type, iface_class->g_type,
&n); &n);