diff options
author | Dan Williams <dcbw@redhat.com> | 2014-10-22 16:44:26 -0500 |
---|---|---|
committer | Dan Williams <dcbw@redhat.com> | 2014-11-06 20:51:58 -0600 |
commit | 52ae28f6e5bf68c575145f633f7c85c145aa20fe (patch) | |
tree | d7e016226e0e4e380964891c57c4b49ef484f33c /libnm | |
parent | 0a0f9a3b4e72e4cd7dd84661a19731b5995d8950 (diff) | |
download | NetworkManager-52ae28f6e5bf68c575145f633f7c85c145aa20fe.tar.gz |
libnm: queue added/removed signals and suppress uninitialized notifications
Property notifications are queued during object initialization and
reloading, but the added/removed signals were emitted immediately
even before the object was fully initialized.
Additionally, depending on how long asynchronous initialization took,
the notifications could have been emitted before the object was
fully initialized as deferred_notify_cb() wasn't being suppressed
until all the properties were complete.
For synchronous intialization, signals could be emitted at various
times during initialization and not all of the object's properties
may be read. Furthermore property notifications were queued in an
idle handler, which breaks users that may not use a mainloop. All
signals and notifications should be emitted immediately after
initialization is complete for synchronous initialization.
To make things consistent and ensure that all signals and notifications
are emitted only when initialization is complete, queue signals for
deferred emission and only run notifications/signals when all the
object's properties have been read. For synchronous initialization,
emit all notifications and signals immediately after initialization
and not from an idle handler.
Diffstat (limited to 'libnm')
-rw-r--r-- | libnm/nm-object.c | 230 | ||||
-rw-r--r-- | libnm/tests/test-nm-client.c | 99 |
2 files changed, 279 insertions, 50 deletions
diff --git a/libnm/nm-object.c b/libnm/nm-object.c index b6e697c25d..263d7785bb 100644 --- a/libnm/nm-object.c +++ b/libnm/nm-object.c @@ -69,7 +69,7 @@ typedef struct { const char *signal_prefix; } PropertyInfo; -static void reload_complete (NMObject *object); +static void reload_complete (NMObject *object, gboolean emit_now); static gboolean demarshal_generic (NMObject *object, GParamSpec *pspec, GVariant *value, gpointer field); typedef struct { @@ -83,7 +83,7 @@ typedef struct { NMObject *parent; gboolean suppress_property_updates; - GSList *notify_props; + GSList *notify_items; guint32 notify_id; GSList *reload_results; @@ -164,57 +164,180 @@ _nm_object_get_proxy (NMObject *object, return proxy; } +typedef enum { + NOTIFY_SIGNAL_PENDING_NONE, + NOTIFY_SIGNAL_PENDING_ADDED, + NOTIFY_SIGNAL_PENDING_REMOVED, + NOTIFY_SIGNAL_PENDING_ADDED_REMOVED, +} NotifySignalPending; + +typedef struct { + const char *property; + const char *signal_prefix; + NotifySignalPending pending; + NMObject *changed; +} NotifyItem; + +static void +notify_item_free (NotifyItem *item) +{ + g_clear_object (&item->changed); + g_slice_free (NotifyItem, item); +} + static gboolean deferred_notify_cb (gpointer data) { NMObject *object = NM_OBJECT (data); NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object); + NMObjectClass *object_class = NM_OBJECT_GET_CLASS (object); GSList *props, *iter; priv->notify_id = 0; - /* Clear priv->notify_props early so that an NMObject subclass that + /* Wait until all reloads are done before notifying */ + if (priv->reload_remaining) + return G_SOURCE_REMOVE; + + /* Clear priv->notify_items early so that an NMObject subclass that * listens to property changes can queue up other property changes * during the g_object_notify() call separately from the property * list we're iterating. */ - props = g_slist_reverse (priv->notify_props); - priv->notify_props = NULL; + props = g_slist_reverse (priv->notify_items); + priv->notify_items = NULL; g_object_ref (object); + + /* Emit property change notifications first */ for (iter = props; iter; iter = g_slist_next (iter)) { - g_object_notify (G_OBJECT (object), (const char *) iter->data); - g_free (iter->data); + NotifyItem *item = iter->data; + + if (item->property) + g_object_notify (G_OBJECT (object), item->property); + } + + /* And added/removed signals second */ + for (iter = props; iter; iter = g_slist_next (iter)) { + NotifyItem *item = iter->data; + char buf[50]; + gint ret = 0; + + switch (item->pending) { + case NOTIFY_SIGNAL_PENDING_ADDED: + ret = g_snprintf (buf, sizeof (buf), "%s-added", item->signal_prefix); + break; + case NOTIFY_SIGNAL_PENDING_REMOVED: + ret = g_snprintf (buf, sizeof (buf), "%s-removed", item->signal_prefix); + break; + case NOTIFY_SIGNAL_PENDING_ADDED_REMOVED: + if (object_class->object_creation_failed) + object_class->object_creation_failed (object, nm_object_get_path (item->changed)); + break; + case NOTIFY_SIGNAL_PENDING_NONE: + default: + break; + } + if (ret > 0) { + g_assert (ret < sizeof (buf)); + g_signal_emit_by_name (object, buf, item->changed); + } } g_object_unref (object); - g_slist_free (props); - return FALSE; + g_slist_free_full (props, (GDestroyNotify) notify_item_free); + return G_SOURCE_REMOVE; } -void -_nm_object_queue_notify (NMObject *object, const char *property) +static void +_nm_object_defer_notify (NMObject *object) +{ + NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object); + + if (!priv->notify_id) + priv->notify_id = g_idle_add_full (G_PRIORITY_LOW, deferred_notify_cb, object, NULL); +} + +static void +_nm_object_queue_notify_full (NMObject *object, + const char *property, + const char *signal_prefix, + gboolean added, + NMObject *changed) { NMObjectPrivate *priv; - gboolean found = FALSE; + NotifyItem *item; GSList *iter; g_return_if_fail (NM_IS_OBJECT (object)); - g_return_if_fail (property != NULL); + g_return_if_fail (!signal_prefix != !property); + g_return_if_fail (!signal_prefix == !changed); priv = NM_OBJECT_GET_PRIVATE (object); - if (!priv->notify_id) - priv->notify_id = g_idle_add_full (G_PRIORITY_LOW, deferred_notify_cb, object, NULL); - - for (iter = priv->notify_props; iter; iter = g_slist_next (iter)) { - if (!strcmp ((char *) iter->data, property)) { - found = TRUE; - break; + _nm_object_defer_notify (object); + + property = g_intern_string (property); + signal_prefix = g_intern_string (signal_prefix); + for (iter = priv->notify_items; iter; iter = g_slist_next (iter)) { + item = iter->data; + + if (property && (property == item->property)) + return; + + /* Collapse signals for the same object (such as "added->removed") to + * ensure we don't emit signals when their sum should have no effect. + * The "added->removed->removed" sequence requires special handling, + * hence the addition of the ADDED_REMOVED state to ensure that no + * signal is emitted in this case: + * + * Without the ADDED_REMOVED state: + * NONE + added -> ADDED + * ADDED + removed -> NONE + * NONE + removed -> REMOVED (would emit 'removed' signal) + * + * With the ADDED_REMOVED state: + * NONE | ADDED_REMOVED + added -> ADDED + * ADDED + removed -> ADDED_REMOVED + * ADDED_REMOVED + removed -> ADDED_REMOVED (emits no signal) + */ + if (signal_prefix && (changed == item->changed) && (item->signal_prefix == signal_prefix)) { + switch (item->pending) { + case NOTIFY_SIGNAL_PENDING_ADDED: + if (!added) + item->pending = NOTIFY_SIGNAL_PENDING_ADDED_REMOVED; + break; + case NOTIFY_SIGNAL_PENDING_REMOVED: + if (added) + item->pending = NOTIFY_SIGNAL_PENDING_NONE; + break; + case NOTIFY_SIGNAL_PENDING_ADDED_REMOVED: + if (added) + item->pending = NOTIFY_SIGNAL_PENDING_ADDED; + break; + case NOTIFY_SIGNAL_PENDING_NONE: + item->pending = added ? NOTIFY_SIGNAL_PENDING_ADDED : NOTIFY_SIGNAL_PENDING_REMOVED; + break; + default: + g_assert_not_reached (); + } + return; } } - if (!found) - priv->notify_props = g_slist_prepend (priv->notify_props, g_strdup (property)); + item = g_slice_new0 (NotifyItem); + item->property = property; + if (signal_prefix) { + item->signal_prefix = signal_prefix; + item->pending = added ? NOTIFY_SIGNAL_PENDING_ADDED : NOTIFY_SIGNAL_PENDING_REMOVED; + item->changed = changed ? g_object_ref (changed) : NULL; + } + priv->notify_items = g_slist_prepend (priv->notify_items, item); +} + +void +_nm_object_queue_notify (NMObject *object, const char *property) +{ + _nm_object_queue_notify_full (object, property, NULL, FALSE, NULL); } void @@ -526,17 +649,12 @@ array_diff (GPtrArray *needles, GPtrArray *haystack, GPtrArray *diff) } static void -emit_added_removed_signal (NMObject *self, - const char *signal_prefix, - NMObject *changed, - gboolean added) +queue_added_removed_signal (NMObject *self, + const char *signal_prefix, + NMObject *changed, + gboolean added) { - char buf[50]; - int ret; - - ret = g_snprintf (buf, sizeof (buf), "%s-%s", signal_prefix, added ? "added" : "removed"); - g_assert (ret < sizeof (buf)); - g_signal_emit_by_name (self, buf, changed); + _nm_object_queue_notify_full (self, NULL, signal_prefix, added, changed); } static void @@ -576,17 +694,17 @@ object_property_complete (ObjectCreatedData *odata) /* Emit added & removed */ for (i = 0; i < removed->len; i++) { - emit_added_removed_signal (self, - pi->signal_prefix, - g_ptr_array_index (removed, i), - FALSE); + queue_added_removed_signal (self, + pi->signal_prefix, + g_ptr_array_index (removed, i), + FALSE); } for (i = 0; i < added->len; i++) { - emit_added_removed_signal (self, - pi->signal_prefix, - g_ptr_array_index (added, i), - TRUE); + queue_added_removed_signal (self, + pi->signal_prefix, + g_ptr_array_index (added, i), + TRUE); } different = removed->len || added->len; @@ -617,8 +735,8 @@ object_property_complete (ObjectCreatedData *odata) if (different && odata->property_name) _nm_object_queue_notify (self, odata->property_name); - if (priv->reload_results && --priv->reload_remaining == 0) - reload_complete (self); + if (--priv->reload_remaining == 0) + reload_complete (self, FALSE); g_object_unref (self); g_free (odata->objects); @@ -661,8 +779,7 @@ handle_object_property (NMObject *self, const char *property_name, GVariant *val odata->array = FALSE; odata->property_name = property_name; - if (priv->reload_results) - priv->reload_remaining++; + priv->reload_remaining++; path = g_variant_get_string (value, NULL); @@ -709,8 +826,7 @@ handle_object_array_property (NMObject *self, const char *property_name, GVarian odata->array = TRUE; odata->property_name = property_name; - if (priv->reload_results) - priv->reload_remaining++; + priv->reload_remaining++; if (npaths == 0) { object_property_complete (odata); @@ -1029,6 +1145,8 @@ _nm_object_reload_properties (NMObject *object, GError **error) if (!g_hash_table_size (priv->proxies) || !priv->nm_running) return TRUE; + priv->reload_remaining++; + g_hash_table_iter_init (&iter, priv->proxies); while (g_hash_table_iter_next (&iter, (gpointer *) &interface, (gpointer *) &proxy)) { ret = g_dbus_proxy_call_sync (priv->properties_proxy, @@ -1048,6 +1166,9 @@ _nm_object_reload_properties (NMObject *object, GError **error) g_variant_unref (ret); } + if (--priv->reload_remaining == 0) + reload_complete (object, TRUE); + return TRUE; } @@ -1131,13 +1252,22 @@ _nm_object_set_property (NMObject *object, } static void -reload_complete (NMObject *object) +reload_complete (NMObject *object, gboolean emit_now) { NMObjectPrivate *priv = NM_OBJECT_GET_PRIVATE (object); GSimpleAsyncResult *simple; GSList *results, *iter; GError *error; + if (emit_now) { + if (priv->notify_id) { + g_source_remove (priv->notify_id); + priv->notify_id = 0; + } + deferred_notify_cb (object); + } else + _nm_object_defer_notify (object); + results = priv->reload_results; priv->reload_results = NULL; error = priv->reload_error; @@ -1183,7 +1313,7 @@ reload_got_properties (GObject *proxy, } if (--priv->reload_remaining == 0) - reload_complete (object); + reload_complete (object, FALSE); } void @@ -1546,8 +1676,8 @@ dispose (GObject *object) priv->notify_id = 0; } - g_slist_free_full (priv->notify_props, g_free); - priv->notify_props = NULL; + g_slist_free_full (priv->notify_items, (GDestroyNotify) notify_item_free); + priv->notify_items = NULL; g_clear_pointer (&priv->proxies, g_hash_table_unref); g_clear_object (&priv->properties_proxy); diff --git a/libnm/tests/test-nm-client.c b/libnm/tests/test-nm-client.c index e78e85c9e5..867a27933b 100644 --- a/libnm/tests/test-nm-client.c +++ b/libnm/tests/test-nm-client.c @@ -111,6 +111,104 @@ test_device_added (void) /*******************************************************************/ +typedef enum { + SIGNAL_FIRST = 0x01, + SIGNAL_SECOND = 0x02, + SIGNAL_MASK = 0x0F, + NOTIFY_FIRST = 0x10, + NOTIFY_SECOND = 0x20, + NOTIFY_MASK = 0xF0 +} DeviceSignaledAfterInitType; + +static void +device_sai_added_cb (NMClient *c, + NMDevice *device, + gpointer user_data) +{ + guint *result = user_data; + + g_assert (device); + g_assert_cmpstr (nm_device_get_iface (device), ==, "eth0"); + + g_assert ((*result & SIGNAL_MASK) == 0); + *result |= *result ? SIGNAL_SECOND : SIGNAL_FIRST; +} + +static void +devices_sai_notify_cb (NMClient *c, + GParamSpec *pspec, + gpointer user_data) +{ + guint *result = user_data; + const GPtrArray *devices; + NMDevice *device; + + devices = nm_client_get_devices (c); + g_assert (devices); + g_assert_cmpint (devices->len, ==, 1); + + device = g_ptr_array_index (devices, 0); + g_assert (device); + g_assert_cmpstr (nm_device_get_iface (device), ==, "eth0"); + + g_assert ((*result & NOTIFY_MASK) == 0); + *result |= *result ? NOTIFY_SECOND : NOTIFY_FIRST; +} + +static void +test_device_added_signal_after_init (void) +{ + NMClient *client; + const GPtrArray *devices; + NMDevice *device; + guint result = 0; + GError *error = NULL; + + sinfo = nm_test_service_init (); + client = nm_client_new (NULL, &error); + g_assert_no_error (error); + + devices = nm_client_get_devices (client); + g_assert (devices->len == 0); + + g_signal_connect (client, + NM_CLIENT_DEVICE_ADDED, + (GCallback) device_sai_added_cb, + &result); + + g_signal_connect (client, + "notify::" NM_CLIENT_DEVICES, + (GCallback) devices_sai_notify_cb, + &result); + + /* Tell the test service to add a new device */ + nm_test_service_add_device (sinfo, client, "AddWiredDevice", "eth0"); + + /* Ensure the 'device-added' signal doesn't show up before + * the 'Devices' property change notification */ + while (!(result & SIGNAL_MASK) && !(result & NOTIFY_MASK)) + g_main_context_iteration (NULL, TRUE); + + g_signal_handlers_disconnect_by_func (client, device_sai_added_cb, &result); + g_signal_handlers_disconnect_by_func (client, devices_sai_notify_cb, &result); + + g_assert ((result & NOTIFY_MASK) == NOTIFY_FIRST); + g_assert ((result & SIGNAL_MASK) == SIGNAL_SECOND); + + devices = nm_client_get_devices (client); + g_assert (devices); + g_assert_cmpint (devices->len, ==, 1); + + device = g_ptr_array_index (devices, 0); + g_assert (device); + g_assert_cmpstr (nm_device_get_iface (device), ==, "eth0"); + + g_object_unref (client); + g_clear_pointer (&sinfo, nm_test_service_cleanup); +} + +/*******************************************************************/ + static const char *expected_bssid = "66:55:44:33:22:11"; typedef struct { @@ -1077,6 +1175,7 @@ main (int argc, char **argv) loop = g_main_loop_new (NULL, FALSE); g_test_add_func ("/libnm/device-added", test_device_added); + g_test_add_func ("/libnm/device-added-signal-after-init", test_device_added_signal_after_init); g_test_add_func ("/libnm/wifi-ap-added-removed", test_wifi_ap_added_removed); g_test_add_func ("/libnm/wimax-nsp-added-removed", test_wimax_nsp_added_removed); g_test_add_func ("/libnm/devices-array", test_devices_array); |