diff --git a/glib/gdataset.c b/glib/gdataset.c index 12c77b959..98ef968d3 100644 --- a/glib/gdataset.c +++ b/glib/gdataset.c @@ -827,6 +827,107 @@ g_datalist_id_remove_no_notify (GData **datalist, return ret_data; } +/** + * g_datalist_id_update_atomic: + * @datalist: the data list + * @key_id: they key to add. + * @callback: callback to update (set, remove, steal, update) the + * data. + * @user_data: the user data for @callback. + * + * Will call @callback while holding the lock on @datalist. Be careful, to not + * end up calling into another data-list function, because the lock is not reentrant + * and deadlock will happen. + * + * The callback receives the current data and destroy function. If the key + * is currently not in @datalist, they will be %NULL. + * The callback can update those pointers, and @datalist will be updated. + * Note that if callback modifies a received data, then it MUST steal it + * and take ownership on it. Possibly freeing it with the provided destroy function. + * + * The point is to atomically update the entry, while holding a lock. + * + * Returns: the data that @callback returns. + */ +gpointer +g_datalist_id_update_atomic (GData **datalist, + GQuark key_id, + GDataListUpdateAtomicFunc callback, + gpointer user_data) +{ + GData *d; + GDataElt *data; + gpointer new_data; + GDestroyNotify new_destroy; + guint32 idx; + gboolean to_unlock = TRUE; + + d = g_datalist_lock_and_get (datalist); + + data = datalist_find (d, key_id, &idx); + + if (data) + { + new_data = data->data; + new_destroy = data->destroy; + } + else + { + new_data = NULL; + new_destroy = NULL; + } + + callback (key_id, &new_data, &new_destroy, user_data); + + if (data && !new_data) + { + /* Remove. + * + * The old data->data was already stolen by callback(). */ + if (idx != d->len - 1u) + *data = d->data[d->len - 1u]; + d->len--; + + /* We don't bother to shrink, but if all data are now gone + * we at least free the memory + */ + if (d->len == 0) + { + /* datalist may be situated in dataset, so must not be + * unlocked when we free it + */ + g_datalist_unlock_and_set (datalist, NULL); + g_free (d); + to_unlock = FALSE; + } + } + else if (data) + { + /* Update. + * + * The old data was stolen by callback(). We only update the pointers and are done. */ + data->data = new_data; + data->destroy = new_destroy; + } + else if (!data && !new_data) + { + /* Absent, no change. We had no data, and nothing to add. */ + } + else + { + /* Add */ + if (datalist_append (&d, key_id, new_data, new_destroy)) + { + g_datalist_unlock_and_set (datalist, d); + to_unlock = FALSE; + } + } + + if (to_unlock) + g_datalist_unlock (datalist); + return new_data; +} + /** * g_dataset_id_get_data: * @dataset_location: (not nullable): the location identifying the dataset. diff --git a/glib/gdatasetprivate.h b/glib/gdatasetprivate.h index f22cf381f..19e0ba3c7 100644 --- a/glib/gdatasetprivate.h +++ b/glib/gdatasetprivate.h @@ -38,6 +38,15 @@ G_BEGIN_DECLS #define G_DATALIST_GET_FLAGS(datalist) \ ((gsize) g_atomic_pointer_get (datalist) & G_DATALIST_FLAGS_MASK) +typedef gpointer (*GDataListUpdateAtomicFunc) (GQuark key_id, + gpointer *data, + GDestroyNotify *destroy_notify, + gpointer user_data); + +gpointer g_datalist_id_update_atomic (GData **datalist, + GQuark key_id, + GDataListUpdateAtomicFunc callback, + gpointer user_data); G_END_DECLS diff --git a/glib/glib-private.c b/glib/glib-private.c index c2b68a060..27deba46a 100644 --- a/glib/glib-private.c +++ b/glib/glib-private.c @@ -24,6 +24,7 @@ #include "glib-private.h" #include "glib-init.h" #include "gutilsprivate.h" +#include "gdatasetprivate.h" #ifdef USE_INVALID_PARAMETER_HANDLER #include @@ -74,6 +75,8 @@ glib__private__ (void) g_uri_get_default_scheme_port, g_set_prgname_once, + + g_datalist_id_update_atomic, }; return &table; diff --git a/glib/glib-private.h b/glib/glib-private.h index 479ebb9df..4b153427e 100644 --- a/glib/glib-private.h +++ b/glib/glib-private.h @@ -75,6 +75,11 @@ void __lsan_ignore_object (const void *p) __attribute__ ((weak)); */ #define G_CONTAINER_OF(ptr, type, field) ((type *) G_STRUCT_MEMBER_P (ptr, -G_STRUCT_OFFSET (type, field))) +typedef gpointer (*GDataListUpdateAtomicFunc) (GQuark key_id, + gpointer *data, + GDestroyNotify *destroy_notify, + gpointer user_data); + /* * g_leak_sanitizer_is_supported: * @@ -291,6 +296,11 @@ typedef struct { /* See gutils.c */ gboolean (* g_set_prgname_once) (const gchar *prgname); + gpointer (*g_datalist_id_update_atomic) (GData **datalist, + GQuark key_id, + GDataListUpdateAtomicFunc callback, + gpointer user_data); + /* Add other private functions here, initialize them in glib-private.c */ } GLibPrivateVTable;