[glib/wip/tingping/gresolver-cache] gresolver: Implement a simple and short-lived dns cache



commit 744a55b9986a4945638d4fc16f1f26d8296a279f
Author: Patrick Griffis <pgriffis igalia com>
Date:   Tue Feb 19 09:46:29 2019 -0500

    gresolver: Implement a simple and short-lived dns cache
    
    Introducing a local cache is an optimization that smooths over
    clients making many requests to the same host in a short period
    of time unnecessarily resolving it repeatedly.
    
    The difference can vary from dramatic on systems lacking a caching
    resolver to minor on fast and cached systems but in some workloads
    like a web browser it can be measurable.
    
    It is only short-lived (1 minute currently) as it is not intended
    to fully replace caching system resolvers.

 gio/gthreadedresolver.c | 238 +++++++++++++++++++++++++++++++++++++-----------
 gio/gthreadedresolver.h |   2 +
 2 files changed, 189 insertions(+), 51 deletions(-)
---
diff --git a/gio/gthreadedresolver.c b/gio/gthreadedresolver.c
index a6dd35c1b..e73853b64 100644
--- a/gio/gthreadedresolver.c
+++ b/gio/gthreadedresolver.c
@@ -39,9 +39,124 @@
 
 G_DEFINE_TYPE (GThreadedResolver, g_threaded_resolver, G_TYPE_RESOLVER)
 
+/* These match Firefox */
+#define DNS_CACHE_MAX_SIZE 400
+#define DNS_CACHE_EXPIRE_SECONDS 60
+
+typedef struct {
+  GList *addresses; /* owned */
+  gint64 expiration;
+} CachedResponse;
+
+static void
+cached_response_free (CachedResponse *cache)
+{
+  g_resolver_free_addresses (cache->addresses);
+  g_free (cache);
+}
+
+static GHashTable *
+get_dns_cache_for_flags (GThreadedResolver        *gtr,
+                         GResolverNameLookupFlags  flags)
+{
+  /* A cache is kept for each type of response to avoid
+   * the overcomplication of combining or filtering results.
+   */
+  if (flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY)
+    return gtr->dns_caches[0];
+  else if (flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY)
+    return gtr->dns_caches[1];
+  else
+    return gtr->dns_caches[2];
+}
+
+static gpointer
+copy_object (gconstpointer obj, gpointer user_data)
+{
+  return g_object_ref (G_OBJECT (obj));
+}
+
+static GList *
+copy_addresses (GList *addresses)
+{
+  return g_list_copy_deep (addresses, copy_object, NULL);
+}
+
+static void
+update_dns_cache (GThreadedResolver        *gtr,
+                  const char               *hostname,
+                  GList                    *addresses,
+                  GResolverNameLookupFlags  flags)
+{
+  CachedResponse *cached;
+  GHashTable *cache;
+  gint64 now = g_get_monotonic_time ();
+  GHashTableIter iter;
+  size_t size = 0;
+
+  cache = get_dns_cache_for_flags (gtr, flags);
+  cached = g_new (CachedResponse, 1);
+  cached->addresses = copy_addresses (addresses);
+  cached->expiration = g_get_monotonic_time () + (DNS_CACHE_EXPIRE_SECONDS * 1000);
+
+  g_mutex_lock (&gtr->dns_cache_lock);
+
+  /* Cleanup while we are at it. */
+  g_hash_table_iter_init (&iter, cache);
+  while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &cached))
+    {
+      if (cached->expiration <= now || size >= DNS_CACHE_MAX_SIZE)
+        g_hash_table_iter_remove (&iter);
+      else
+        ++size;
+    }
+
+  g_hash_table_insert (cache, g_strdup (hostname), cached);
+
+  g_mutex_unlock (&gtr->dns_cache_lock);
+}
+
+/*
+ * Returns: (transfer full): List of addresses
+ */
+static GList *
+query_dns_cache (GThreadedResolver        *gtr,
+                 const char               *hostname,
+                 GResolverNameLookupFlags  flags)
+{
+  CachedResponse *cached;
+  GHashTable *cache;
+  GList *addresses = NULL;
+  gint64 now = g_get_monotonic_time ();
+
+  cache = get_dns_cache_for_flags (gtr, flags);
+
+  g_mutex_lock (&gtr->dns_cache_lock);
+
+  cached = g_hash_table_lookup (cache, hostname);
+  if (cached && cached->expiration < now)
+    addresses = copy_addresses (cached->addresses);
+
+  g_mutex_unlock (&gtr->dns_cache_lock);
+
+  return addresses;
+}
+
 static void
 g_threaded_resolver_init (GThreadedResolver *gtr)
 {
+  guint i;
+  for (i = 0; i < G_N_ELEMENTS (gtr->dns_caches); ++i)
+    gtr->dns_caches[i] = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) 
cached_response_free);
+}
+
+static void
+g_threaded_resolver_finalize (GObject *object)
+{
+  GThreadedResolver *gtr = (GThreadedResolver*) object;
+  guint i;
+  for (i = 0; i < G_N_ELEMENTS (gtr->dns_caches); ++i)
+    g_hash_table_destroy (gtr->dns_caches[i]);
 }
 
 static GResolverError
@@ -66,26 +181,48 @@ g_resolver_error_from_addrinfo_error (gint err)
 
 typedef struct {
   char *hostname;
-  int address_family;
+  GThreadedResolver *resolver;
+  GResolverNameLookupFlags flags;
 } LookupData;
 
 static LookupData *
-lookup_data_new (const char *hostname,
-                 int         address_family)
+lookup_data_new (GThreadedResolver        *gtr,
+                 const char               *hostname,
+                 GResolverNameLookupFlags  flags)
 {
   LookupData *data = g_new (LookupData, 1);
   data->hostname = g_strdup (hostname);
-  data->address_family = address_family;
+  data->resolver = g_object_ref (gtr);
+  data->flags = flags;
   return data;
 }
 
 static void
 lookup_data_free (LookupData *data)
 {
+  g_object_unref (data->resolver);
   g_free (data->hostname);
   g_free (data);
 }
 
+static int
+flags_to_family (GResolverNameLookupFlags flags)
+{
+  int address_family = AF_UNSPEC;
+
+  if (flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY)
+    address_family = AF_INET;
+
+  if (flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY)
+    {
+      address_family = AF_INET6;
+      /* You can only filter by one family at a time */
+      g_return_val_if_fail (!(flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY), address_family);
+    }
+
+  return address_family;
+}
+
 static void
 do_lookup_by_name (GTask         *task,
                    gpointer       source_object,
@@ -109,7 +246,7 @@ do_lookup_by_name (GTask         *task,
   addrinfo_hints.ai_socktype = SOCK_STREAM;
   addrinfo_hints.ai_protocol = IPPROTO_TCP;
 
-  addrinfo_hints.ai_family = lookup_data->address_family;
+  addrinfo_hints.ai_family = flags_to_family (lookup_data->flags);
   retval = getaddrinfo (hostname, NULL, &addrinfo_hints, &res);
 
   if (retval == 0)
@@ -140,6 +277,7 @@ do_lookup_by_name (GTask         *task,
           addresses = g_list_reverse (addresses);
           g_task_return_pointer (task, addresses,
                                  (GDestroyNotify)g_resolver_free_addresses);
+          update_dns_cache (lookup_data->resolver, hostname, addresses, lookup_data->flags);
         }
       else
         {
@@ -165,46 +303,6 @@ do_lookup_by_name (GTask         *task,
     freeaddrinfo (res);
 }
 
-static GList *
-lookup_by_name (GResolver     *resolver,
-                const gchar   *hostname,
-                GCancellable  *cancellable,
-                GError       **error)
-{
-  GTask *task;
-  GList *addresses;
-  LookupData *data;
-
-  data = lookup_data_new (hostname, AF_UNSPEC);
-  task = g_task_new (resolver, cancellable, NULL, NULL);
-  g_task_set_source_tag (task, lookup_by_name);
-  g_task_set_task_data (task, data, (GDestroyNotify)lookup_data_free);
-  g_task_set_return_on_cancel (task, TRUE);
-  g_task_run_in_thread_sync (task, do_lookup_by_name);
-  addresses = g_task_propagate_pointer (task, error);
-  g_object_unref (task);
-
-  return addresses;
-}
-
-static int
-flags_to_family (GResolverNameLookupFlags flags)
-{
-  int address_family = AF_UNSPEC;
-
-  if (flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY)
-    address_family = AF_INET;
-
-  if (flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY)
-    {
-      address_family = AF_INET6;
-      /* You can only filter by one family at a time */
-      g_return_val_if_fail (!(flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY), address_family);
-    }
-
-  return address_family;
-}
-
 static GList *
 lookup_by_name_with_flags (GResolver                 *resolver,
                            const gchar               *hostname,
@@ -215,19 +313,43 @@ lookup_by_name_with_flags (GResolver                 *resolver,
   GTask *task;
   GList *addresses;
   LookupData *data;
+  GList *cached;
+  GThreadedResolver *gtr = (GThreadedResolver*) resolver;
 
-  data = lookup_data_new (hostname, AF_UNSPEC);
   task = g_task_new (resolver, cancellable, NULL, NULL);
   g_task_set_source_tag (task, lookup_by_name_with_flags);
-  g_task_set_task_data (task, data, (GDestroyNotify)lookup_data_free);
   g_task_set_return_on_cancel (task, TRUE);
-  g_task_run_in_thread_sync (task, do_lookup_by_name);
+
+  cached = query_dns_cache (gtr, hostname, flags);
+  if (cached)
+      g_task_return_pointer (task, cached,
+                            (GDestroyNotify)g_resolver_free_addresses);
+  else
+    {
+      data = lookup_data_new (gtr, hostname, flags);
+      g_task_set_task_data (task, data, (GDestroyNotify)lookup_data_free);
+      g_task_run_in_thread_sync (task, do_lookup_by_name);
+    }
+
   addresses = g_task_propagate_pointer (task, error);
   g_object_unref (task);
 
   return addresses;
 }
 
+static GList *
+lookup_by_name (GResolver     *resolver,
+                const gchar   *hostname,
+                GCancellable  *cancellable,
+                GError       **error)
+{
+  return lookup_by_name_with_flags (resolver,
+                                    hostname,
+                                    G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT,
+                                    cancellable,
+                                    error);
+}
+
 static void
 lookup_by_name_with_flags_async (GResolver                *resolver,
                                  const gchar              *hostname,
@@ -238,13 +360,24 @@ lookup_by_name_with_flags_async (GResolver                *resolver,
 {
   GTask *task;
   LookupData *data;
+  GList *cached;
+  GThreadedResolver *gtr = (GThreadedResolver*) resolver;
 
-  data = lookup_data_new (hostname, flags_to_family (flags));
   task = g_task_new (resolver, cancellable, callback, user_data);
   g_task_set_source_tag (task, lookup_by_name_with_flags_async);
-  g_task_set_task_data (task, data, (GDestroyNotify)lookup_data_free);
   g_task_set_return_on_cancel (task, TRUE);
-  g_task_run_in_thread (task, do_lookup_by_name);
+
+  cached = query_dns_cache (gtr, hostname, flags);
+  if (cached)
+      g_task_return_pointer (task, cached,
+                            (GDestroyNotify)g_resolver_free_addresses);
+  else
+    {
+      data = lookup_data_new (gtr, hostname, flags);
+      g_task_set_task_data (task, data, (GDestroyNotify)lookup_data_free);
+      g_task_run_in_thread (task, do_lookup_by_name);
+    }
+
   g_object_unref (task);
 }
 
@@ -1073,6 +1206,7 @@ static void
 g_threaded_resolver_class_init (GThreadedResolverClass *threaded_class)
 {
   GResolverClass *resolver_class = G_RESOLVER_CLASS (threaded_class);
+  GObjectClass *object_class = G_OBJECT_CLASS (threaded_class);
 
   resolver_class->lookup_by_name                   = lookup_by_name;
   resolver_class->lookup_by_name_async             = lookup_by_name_async;
@@ -1086,4 +1220,6 @@ g_threaded_resolver_class_init (GThreadedResolverClass *threaded_class)
   resolver_class->lookup_records                   = lookup_records;
   resolver_class->lookup_records_async             = lookup_records_async;
   resolver_class->lookup_records_finish            = lookup_records_finish;
+
+  object_class->finalize                          = g_threaded_resolver_finalize;
 }
diff --git a/gio/gthreadedresolver.h b/gio/gthreadedresolver.h
index 395d850c8..8ebae9bb7 100644
--- a/gio/gthreadedresolver.h
+++ b/gio/gthreadedresolver.h
@@ -34,6 +34,8 @@ typedef struct {
   GResolver parent_instance;
 
   GThreadPool *thread_pool;
+  GHashTable *dns_caches[3];
+  GMutex dns_cache_lock;
 } GThreadedResolver;
 
 typedef struct {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]