From d19b6dd129492ca0de2073d84d5af96462d9c934 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Tue, 7 Aug 2012 16:16:36 +0100 Subject: [PATCH 8/9] YtsContact: remember and replay service statuses for added services To reproduce a critical: * kill and restart MC * kill and restart Salut * run the demo client as of commit c792a82 * run the demo service (on the same laptop) The client often receives the service's status (a pseudo-PEP message) before it receives its capabilities (a XEP-0115 stanza). No order can be guaranteed for those two, because they follow different paths: status: * service receives client's _ytstenut._tcp * service pulls caps from client * service observes that client is interested in status * service pushes status to client caps: * service sends its own _ytstenut._tcp * client pulls caps from service This previously resulted in a critical warning and failure to process that status message. Now, the status is cached for future reference, and when the caps are retrieved, the old status is re-played. --- ytstenut/yts-contact.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/ytstenut/yts-contact.c b/ytstenut/yts-contact.c index c011315..26457dc 100644 --- a/ytstenut/yts-contact.c +++ b/ytstenut/yts-contact.c @@ -57,6 +57,8 @@ G_DEFINE_ABSTRACT_TYPE (YtsContact, yts_contact, G_TYPE_OBJECT) typedef struct { GHashTable *services; /* hash of YtsService instances */ + /* string (service ID) => GHashTable string status_xml> */ + GHashTable *deferred_service_statuses; TpContact *tp_contact; /* TpContact associated with YtsContact */ } YtsContactPrivate; @@ -119,6 +121,7 @@ _service_added (YtsContact *self, { YtsContactPrivate *priv = GET_PRIVATE (self); const char *service_id = yts_service_get_id (service); + GHashTable *id_status_map; DEBUG ("contact=%s service=%s", yts_contact_get_id (self), service_id); @@ -133,6 +136,25 @@ _service_added (YtsContact *self, G_CALLBACK (_service_send_message), self); g_signal_connect (service, "send-file", G_CALLBACK (_service_send_file), self); + + /* Apply deferred status updates */ + + id_status_map = g_hash_table_lookup (priv->deferred_service_statuses, + service_id); + + if (id_status_map != NULL) + { + GHashTableIter iter; + gpointer k, v; + + g_hash_table_iter_init (&iter, id_status_map); + + while (g_hash_table_iter_next (&iter, &k, &v)) + yts_service_update_status (service, k, v); + } + + g_hash_table_remove (priv->deferred_service_statuses, + service_id); } static void @@ -214,6 +236,11 @@ _dispose (GObject *object) priv->services = NULL; } + if (priv->deferred_service_statuses) { + g_hash_table_destroy (priv->deferred_service_statuses); + priv->deferred_service_statuses = NULL; + } + // FIXME tie to tp_contact lifecycle if (priv->tp_contact) { g_object_unref (priv->tp_contact); @@ -327,6 +354,9 @@ yts_contact_init (YtsContact *self) g_str_equal, g_free, g_object_unref); + + priv->deferred_service_statuses = g_hash_table_new_full (g_str_hash, + g_str_equal, g_free, (GDestroyNotify) g_hash_table_unref); } /** @@ -526,9 +556,34 @@ yts_contact_update_service_status (YtsContact *self, DEBUG ("contact=%s service=%s fqc=%s", yts_contact_get_id (self), service_id, fqc_id); service = g_hash_table_lookup (priv->services, service_id); - g_return_if_fail (service); - yts_service_update_status (service, fqc_id, status_xml); + if (service != NULL) + { + DEBUG ("Service already exists, updating status now"); + yts_service_update_status (service, fqc_id, status_xml); + } + else + { + /* We've hit a race condition between the service's status being + * discovered, and the service's capabilities being discovered. + * Save the status and apply it when we get the service's + * capabilities. */ + GHashTable *id_status_map = g_hash_table_lookup ( + priv->deferred_service_statuses, service_id); + + DEBUG ("Service does not already exist, saving its status for later"); + + if (id_status_map == NULL) + { + id_status_map = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_free); + g_hash_table_insert (priv->deferred_service_statuses, + g_strdup (service_id), id_status_map); + } + + g_hash_table_insert (id_status_map, g_strdup (fqc_id), + g_strdup (status_xml)); + } } /** -- 1.7.10.4