diff --git a/gobject/gobject.c b/gobject/gobject.c index 7ec5902b6e4591b6036a63aef2dcce87ac7850ef..5b1b108d3dd6c2ace16c20fd380d654515b4f812 100644 --- a/gobject/gobject.c +++ b/gobject/gobject.c @@ -125,9 +125,8 @@ enum { * integers to GObjectPrivate. But increasing memory usage for more parallelism * (per-object!) is not worth it. */ #define OPTIONAL_BIT_LOCK_WEAK_REFS 1 -#define OPTIONAL_BIT_LOCK_NOTIFY 2 -#define OPTIONAL_BIT_LOCK_TOGGLE_REFS 3 -#define OPTIONAL_BIT_LOCK_CLOSURE_ARRAY 4 +#define OPTIONAL_BIT_LOCK_TOGGLE_REFS 2 +#define OPTIONAL_BIT_LOCK_CLOSURE_ARRAY 3 #if SIZEOF_INT == 4 && GLIB_SIZEOF_VOID_P >= 8 #define HAVE_OPTIONAL_FLAGS_IN_GOBJECT 1 @@ -214,14 +213,14 @@ static void object_interface_check_properties (gpointer check_d gpointer g_iface); /* --- typedefs --- */ -typedef struct _GObjectNotifyQueue GObjectNotifyQueue; -struct _GObjectNotifyQueue +typedef struct { - GSList *pspecs; - guint16 n_pspecs; - guint16 freeze_count; -}; + guint16 freeze_count; + guint16 len; + guint16 alloc; + GParamSpec *pspecs[1]; +} GObjectNotifyQueue; /* --- variables --- */ static GQuark quark_closure_array = 0; @@ -648,161 +647,212 @@ object_bit_unlock (GObject *object, guint lock_bit) } /* --- functions --- */ -static void -g_object_notify_queue_free (gpointer data) -{ - GObjectNotifyQueue *nqueue = data; - - g_slist_free (nqueue->pspecs); - g_free_sized (nqueue, sizeof (GObjectNotifyQueue)); -} static GObjectNotifyQueue * -g_object_notify_queue_create_queue_frozen (GObject *object) +g_object_notify_queue_new (void) { GObjectNotifyQueue *nqueue; - nqueue = g_new0 (GObjectNotifyQueue, 1); + nqueue = g_malloc (G_STRUCT_OFFSET (GObjectNotifyQueue, pspecs) + (4 * sizeof (GParamSpec *))); - *nqueue = (GObjectNotifyQueue){ - .freeze_count = 1, - }; - - g_datalist_id_set_data_full (&object->qdata, quark_notify_queue, - nqueue, g_object_notify_queue_free); + nqueue->freeze_count = 1; + nqueue->alloc = 4; + nqueue->len = 0; return nqueue; } -static GObjectNotifyQueue * -g_object_notify_queue_freeze (GObject *object) +static gpointer +g_object_notify_queue_freeze_cb (GQuark key_id, + gpointer *data, + GDestroyNotify *destroy_notify, + gpointer user_data) { - GObjectNotifyQueue *nqueue; + GObject *object = ((gpointer *) user_data)[0]; + gboolean freeze_always = GPOINTER_TO_INT (((gpointer *) user_data)[1]); + GObjectNotifyQueue *nqueue = *data; - object_bit_lock (object, OPTIONAL_BIT_LOCK_NOTIFY); - nqueue = g_datalist_id_get_data (&object->qdata, quark_notify_queue); if (!nqueue) { - nqueue = g_object_notify_queue_create_queue_frozen (object); - goto out; + /* The nqueue doesn't exist yet. We create it, and freeze thus 1 time. */ + nqueue = g_object_notify_queue_new (); + *data = nqueue; + *destroy_notify = g_free; + } + else if (!freeze_always) + { + /* The caller only wants to ensure we are frozen once. If we are already frozen, + * don't freeze another time. + * + * This is only relevant during the object initialization. */ } - - if (nqueue->freeze_count >= 65535) - g_critical("Free queue for %s (%p) is larger than 65535," - " called g_object_freeze_notify() too often." - " Forgot to call g_object_thaw_notify() or infinite loop", - G_OBJECT_TYPE_NAME (object), object); else - nqueue->freeze_count++; - -out: - object_bit_unlock (object, OPTIONAL_BIT_LOCK_NOTIFY); + { + if (nqueue->freeze_count == G_MAXUINT16) + { + g_critical ("Free queue for %s (%p) is larger than 65535," + " called g_object_freeze_notify() too often." + " Forgot to call g_object_thaw_notify() or infinite loop", + G_OBJECT_TYPE_NAME (object), object); + } + else + nqueue->freeze_count++; + } - return nqueue; + return NULL; } static void -g_object_notify_queue_thaw (GObject *object, - GObjectNotifyQueue *nqueue, - gboolean take_ref) +g_object_notify_queue_freeze (GObject *object, gboolean freeze_always) { - GParamSpec *pspecs_mem[16], **pspecs, **free_me = NULL; - GSList *slist; - guint n_pspecs = 0; - - object_bit_lock (object, OPTIONAL_BIT_LOCK_NOTIFY); + _g_datalist_id_update_atomic (&object->qdata, + quark_notify_queue, + g_object_notify_queue_freeze_cb, + ((gpointer[]){ object, GINT_TO_POINTER (!!freeze_always) })); +} - if (!nqueue) - { - /* Caller didn't look up the queue yet. Do it now. */ - nqueue = g_datalist_id_get_data (&object->qdata, quark_notify_queue); - } +static gpointer +g_object_notify_queue_thaw_cb (GQuark key_id, + gpointer *data, + GDestroyNotify *destroy_notify, + gpointer user_data) +{ + GObject *object = user_data; + GObjectNotifyQueue *nqueue = *data; - /* Just make sure we never get into some nasty race condition */ if (G_UNLIKELY (!nqueue || nqueue->freeze_count == 0)) { - object_bit_unlock (object, OPTIONAL_BIT_LOCK_NOTIFY); g_critical ("%s: property-changed notification for %s(%p) is not frozen", G_STRFUNC, G_OBJECT_TYPE_NAME (object), object); - return; + return NULL; } nqueue->freeze_count--; - if (nqueue->freeze_count) - { - object_bit_unlock (object, OPTIONAL_BIT_LOCK_NOTIFY); - return; - } - pspecs = nqueue->n_pspecs > 16 ? free_me = g_new (GParamSpec*, nqueue->n_pspecs) : pspecs_mem; + if (nqueue->freeze_count > 0) + return NULL; - for (slist = nqueue->pspecs; slist; slist = slist->next) - { - pspecs[n_pspecs++] = slist->data; - } - g_datalist_id_set_data (&object->qdata, quark_notify_queue, NULL); + *data = NULL; + return nqueue; +} - object_bit_unlock (object, OPTIONAL_BIT_LOCK_NOTIFY); +static void +g_object_notify_queue_thaw (GObject *object, gboolean take_ref) +{ + GObjectNotifyQueue *nqueue; - if (n_pspecs) + nqueue = _g_datalist_id_update_atomic (&object->qdata, + quark_notify_queue, + g_object_notify_queue_thaw_cb, + object); + + if (!nqueue) + return; + + if (nqueue->len > 0) { + guint16 i; + guint16 j; + + /* Reverse the list. This is the order that we historically had. */ + for (i = 0, j = nqueue->len - 1u; i < j; i++, j--) + { + GParamSpec *tmp; + + tmp = nqueue->pspecs[i]; + nqueue->pspecs[i] = nqueue->pspecs[j]; + nqueue->pspecs[j] = tmp; + } + if (take_ref) g_object_ref (object); - G_OBJECT_GET_CLASS (object)->dispatch_properties_changed (object, n_pspecs, pspecs); + G_OBJECT_GET_CLASS (object)->dispatch_properties_changed (object, nqueue->len, nqueue->pspecs); if (take_ref) g_object_unref (object); } - g_free (free_me); + + g_free (nqueue); } -static gboolean -g_object_notify_queue_add (GObject *object, - GObjectNotifyQueue *nqueue, - GParamSpec *pspec, - gboolean in_init) +static gpointer +g_object_notify_queue_add_cb (GQuark key_id, + gpointer *data, + GDestroyNotify *destroy_notify, + gpointer user_data) { - object_bit_lock (object, OPTIONAL_BIT_LOCK_NOTIFY); + GParamSpec *pspec = ((gpointer *) user_data)[0]; + gboolean in_init = GPOINTER_TO_INT (((gpointer *) user_data)[1]); + GObjectNotifyQueue *nqueue = *data; + guint16 i; if (!nqueue) { - /* We are called without an nqueue. Figure out whether a notification - * should be queued. */ - nqueue = g_datalist_id_get_data (&object->qdata, quark_notify_queue); + if (!in_init) + { + /* We are not in-init and are currently not frozen. There is nothing + * to do. We return FALSE to the caller, which then will dispatch + * the event right away. */ + return GINT_TO_POINTER (FALSE); + } + + /* If we are "in_init", we always want to create a queue now. + * + * Note in that case, the freeze will be balanced at the end of object + * initialization. + * + * We only ensure that a nqueue exists. If it doesn't exist, we create + * it (and freeze once). If it already exists (and is frozen), we don't + * freeze an additional time. */ + nqueue = g_object_notify_queue_new (); + *data = nqueue; + *destroy_notify = g_free; + } + else + { + for (i = 0; i < nqueue->len; i++) + { + if (nqueue->pspecs[i] == pspec) + goto out; + } - if (!nqueue) + if (G_UNLIKELY (nqueue->len == nqueue->alloc)) { - if (!in_init) + guint32 alloc; + + alloc = ((guint32) nqueue->alloc) * 2u; + if (alloc >= G_MAXUINT16) { - /* We don't have a notify queue and are not in_init. The event - * is not to be queued. The caller will dispatch directly. */ - object_bit_unlock (object, OPTIONAL_BIT_LOCK_NOTIFY); - return FALSE; + g_assert (nqueue->len < G_MAXUINT16); + alloc = G_MAXUINT16; } + nqueue = g_realloc (nqueue, G_STRUCT_OFFSET (GObjectNotifyQueue, pspecs) + (alloc * sizeof (GParamSpec *))); + nqueue->alloc = alloc; - /* We are "in_init", but did not freeze the queue in g_object_init - * yet. Instead, we gained a notify handler in instance init, so now - * we need to freeze just-in-time. - * - * Note that this freeze will be balanced at the end of object - * initialization. - */ - nqueue = g_object_notify_queue_create_queue_frozen (object); + *data = nqueue; } } - g_assert (nqueue->n_pspecs < 65535); + nqueue->pspecs[nqueue->len++] = pspec; - if (g_slist_find (nqueue->pspecs, pspec) == NULL) - { - nqueue->pspecs = g_slist_prepend (nqueue->pspecs, pspec); - nqueue->n_pspecs++; - } +out: + return GINT_TO_POINTER (TRUE); +} + +static gboolean +g_object_notify_queue_add (GObject *object, + GParamSpec *pspec, + gboolean in_init) +{ + gpointer result; - object_bit_unlock (object, OPTIONAL_BIT_LOCK_NOTIFY); + result = _g_datalist_id_update_atomic (&object->qdata, + quark_notify_queue, + g_object_notify_queue_add_cb, + ((gpointer[]){ pspec, GINT_TO_POINTER (!!in_init) })); - return TRUE; + return GPOINTER_TO_INT (result); } #ifdef G_ENABLE_DEBUG @@ -1700,7 +1750,7 @@ g_object_init (GObject *object, if (CLASS_HAS_PROPS (class) && CLASS_NEEDS_NOTIFY (class)) { /* freeze object's notification queue, g_object_new_internal() preserves pairedness */ - g_object_notify_queue_freeze (object); + g_object_notify_queue_freeze (object, TRUE); } /* mark object in-construction for notify_queue_thaw() and to allow construct-only properties */ @@ -1880,7 +1930,7 @@ g_object_freeze_notify (GObject *object) } #endif - g_object_notify_queue_freeze (object); + g_object_notify_queue_freeze (object, TRUE); } static inline void @@ -1904,7 +1954,7 @@ g_object_notify_by_spec_internal (GObject *object, if (pspec != NULL && needs_notify) { - if (!g_object_notify_queue_add (object, NULL, pspec, in_init)) + if (!g_object_notify_queue_add (object, pspec, in_init)) { /* * Coverity doesn’t understand the paired ref/unref here and seems to @@ -2057,7 +2107,7 @@ g_object_thaw_notify (GObject *object) } #endif - g_object_notify_queue_thaw (object, NULL, TRUE); + g_object_notify_queue_thaw (object, TRUE); } static void @@ -2141,7 +2191,7 @@ static inline void object_set_property (GObject *object, GParamSpec *pspec, const GValue *value, - GObjectNotifyQueue *nqueue, + gboolean nqueue_is_frozen, gboolean user_specified) { GTypeInstance *inst = (GTypeInstance *) object; @@ -2200,8 +2250,8 @@ object_set_property (GObject *object, } if ((pspec->flags & (G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READABLE)) == G_PARAM_READABLE && - nqueue != NULL) - g_object_notify_queue_add (object, nqueue, pspec, FALSE); + nqueue_is_frozen) + g_object_notify_queue_add (object, pspec, FALSE); } static void @@ -2443,7 +2493,7 @@ g_object_new_with_custom_constructor (GObjectClass *class, GObjectConstructParam *params, guint n_params) { - GObjectNotifyQueue *nqueue = NULL; + gboolean nqueue_is_frozen = FALSE; gboolean newly_constructed; GObjectConstructParam *cparams; gboolean free_cparams = FALSE; @@ -2566,9 +2616,8 @@ g_object_new_with_custom_constructor (GObjectClass *class, /* This may or may not have been setup in g_object_init(). * If it hasn't, we do it now. */ - nqueue = g_datalist_id_get_data (&object->qdata, quark_notify_queue); - if (!nqueue) - nqueue = g_object_notify_queue_freeze (object); + g_object_notify_queue_freeze (object, FALSE); + nqueue_is_frozen = TRUE; } } @@ -2579,11 +2628,10 @@ g_object_new_with_custom_constructor (GObjectClass *class, /* set remaining properties */ for (i = 0; i < n_params; i++) if (!(params[i].pspec->flags & (G_PARAM_CONSTRUCT | G_PARAM_CONSTRUCT_ONLY))) - object_set_property (object, params[i].pspec, params[i].value, nqueue, TRUE); + object_set_property (object, params[i].pspec, params[i].value, nqueue_is_frozen, TRUE); - /* If nqueue is non-NULL then we are frozen. Thaw it. */ - if (nqueue) - g_object_notify_queue_thaw (object, nqueue, FALSE); + if (nqueue_is_frozen) + g_object_notify_queue_thaw (object, FALSE); return object; } @@ -2593,7 +2641,7 @@ g_object_new_internal (GObjectClass *class, GObjectConstructParam *params, guint n_params) { - GObjectNotifyQueue *nqueue = NULL; + gboolean nqueue_is_frozen = FALSE; GObject *object; guint i; @@ -2615,9 +2663,8 @@ g_object_new_internal (GObjectClass *class, /* This may or may not have been setup in g_object_init(). * If it hasn't, we do it now. */ - nqueue = g_datalist_id_get_data (&object->qdata, quark_notify_queue); - if (!nqueue) - nqueue = g_object_notify_queue_freeze (object); + g_object_notify_queue_freeze (object, FALSE); + nqueue_is_frozen = TRUE; } /* We will set exactly n_construct_properties construct @@ -2645,7 +2692,7 @@ g_object_new_internal (GObjectClass *class, if (value == NULL) value = g_param_spec_get_default_value (pspec); - object_set_property (object, pspec, value, nqueue, user_specified); + object_set_property (object, pspec, value, nqueue_is_frozen, user_specified); } } @@ -2658,10 +2705,10 @@ g_object_new_internal (GObjectClass *class, */ for (i = 0; i < n_params; i++) if (!(params[i].pspec->flags & (G_PARAM_CONSTRUCT | G_PARAM_CONSTRUCT_ONLY))) - object_set_property (object, params[i].pspec, params[i].value, nqueue, TRUE); + object_set_property (object, params[i].pspec, params[i].value, nqueue_is_frozen, TRUE); - if (nqueue) - g_object_notify_queue_thaw (object, nqueue, FALSE); + if (nqueue_is_frozen) + g_object_notify_queue_thaw (object, FALSE); return object; } @@ -2980,7 +3027,7 @@ g_object_constructor (GType type, /* set construction parameters */ if (n_construct_properties) { - GObjectNotifyQueue *nqueue = g_object_notify_queue_freeze (object); + g_object_notify_queue_freeze (object, TRUE); /* set construct properties */ while (n_construct_properties--) @@ -2989,9 +3036,10 @@ g_object_constructor (GType type, GParamSpec *pspec = construct_params->pspec; construct_params++; - object_set_property (object, pspec, value, nqueue, TRUE); + object_set_property (object, pspec, value, TRUE, TRUE); } - g_object_notify_queue_thaw (object, nqueue, FALSE); + + g_object_notify_queue_thaw (object, FALSE); /* the notification queue is still frozen from g_object_init(), so * we don't need to handle it here, g_object_newv() takes * care of that @@ -3054,7 +3102,7 @@ g_object_setv (GObject *object, const GValue values[]) { guint i; - GObjectNotifyQueue *nqueue = NULL; + gboolean nqueue_is_frozen = FALSE; GParamSpec *pspec; GObjectClass *class; @@ -3068,7 +3116,10 @@ g_object_setv (GObject *object, class = G_OBJECT_GET_CLASS (object); if (_g_object_has_notify_handler (object)) - nqueue = g_object_notify_queue_freeze (object); + { + g_object_notify_queue_freeze (object, TRUE); + nqueue_is_frozen = TRUE; + } for (i = 0; i < n_properties; i++) { @@ -3077,11 +3128,11 @@ g_object_setv (GObject *object, if (!g_object_set_is_valid_property (object, pspec, names[i])) break; - object_set_property (object, pspec, &values[i], nqueue, TRUE); + object_set_property (object, pspec, &values[i], nqueue_is_frozen, TRUE); } - if (nqueue) - g_object_notify_queue_thaw (object, nqueue, FALSE); + if (nqueue_is_frozen) + g_object_notify_queue_thaw (object, FALSE); g_object_unref (object); } @@ -3100,7 +3151,7 @@ g_object_set_valist (GObject *object, const gchar *first_property_name, va_list var_args) { - GObjectNotifyQueue *nqueue = NULL; + gboolean nqueue_is_frozen = FALSE; const gchar *name; GObjectClass *class; @@ -3109,7 +3160,10 @@ g_object_set_valist (GObject *object, g_object_ref (object); if (_g_object_has_notify_handler (object)) - nqueue = g_object_notify_queue_freeze (object); + { + g_object_notify_queue_freeze (object, TRUE); + nqueue_is_frozen = TRUE; + } class = G_OBJECT_GET_CLASS (object); @@ -3135,7 +3189,7 @@ g_object_set_valist (GObject *object, break; } - object_set_property (object, pspec, &value, nqueue, TRUE); + object_set_property (object, pspec, &value, nqueue_is_frozen, TRUE); /* We open-code g_value_unset() here to avoid the * cost of looking up the GTypeValueTable again. @@ -3146,8 +3200,8 @@ g_object_set_valist (GObject *object, name = va_arg (var_args, gchar*); } - if (nqueue) - g_object_notify_queue_thaw (object, nqueue, FALSE); + if (nqueue_is_frozen) + g_object_notify_queue_thaw (object, FALSE); g_object_unref (object); } @@ -4280,7 +4334,7 @@ g_object_unref (gpointer _object) gint old_ref; GToggleNotify toggle_notify; gpointer toggle_data; - GObjectNotifyQueue *nqueue; + gboolean nqueue_is_frozen; gboolean do_retry; GType obj_gtype; @@ -4375,7 +4429,8 @@ retry_beginning: * notification queue gets automatically drained when g_object_finalize() is * reached and the qdata is cleared. */ - nqueue = g_object_notify_queue_freeze (object); + g_object_notify_queue_freeze (object, TRUE); + nqueue_is_frozen = TRUE; TRACE (GOBJECT_OBJECT_DISPOSE (object, G_TYPE_FROM_INSTANCE (object), 1)); G_OBJECT_GET_CLASS (object)->dispose (object); @@ -4389,12 +4444,12 @@ retry_decrement: /* Here, old_ref is 1 if we just come from dispose(). If the object was resurrected, * we can hit `goto retry_decrement` and be here with a larger old_ref. */ - if (old_ref > 1 && nqueue) + if (old_ref > 1 && nqueue_is_frozen) { /* If the object was resurrected, we need to unfreeze the notify * queue. */ - g_object_notify_queue_thaw (object, nqueue, FALSE); - nqueue = NULL; + g_object_notify_queue_thaw (object, FALSE); + nqueue_is_frozen = FALSE; /* Note at this point, @old_ref might be wrong. *