diff options
author | Patrick Griffis <pgriffis@igalia.com> | 2019-02-26 10:22:55 -0500 |
---|---|---|
committer | Patrick Griffis <pgriffis@igalia.com> | 2019-02-28 09:26:07 -0500 |
commit | 7e5dd0f968b3637d6ab71720cbb36d3271112b82 (patch) | |
tree | 5d0e0c1f892ec7e7dea623d302867cd98a7cf7c7 | |
parent | 0648feb20c93fc9c2972025b90388aeb9fcf5983 (diff) | |
download | libsoup-wip/tingping/cached-resolver.tar.gz |
Create SoupCachedResolverwip/tingping/cached-resolver
This is a simple implementation of GResolver to add caching
to an existing GResolver.
-rw-r--r-- | docs/reference/libsoup-2.4-sections.txt | 17 | ||||
-rw-r--r-- | libsoup/meson.build | 2 | ||||
-rw-r--r-- | libsoup/soup-cached-resolver.c | 624 | ||||
-rw-r--r-- | libsoup/soup-cached-resolver.h | 39 | ||||
-rw-r--r-- | libsoup/soup.h | 1 | ||||
-rw-r--r-- | tests/cached-resolver-test.c | 137 | ||||
-rw-r--r-- | tests/meson.build | 1 |
7 files changed, 821 insertions, 0 deletions
diff --git a/docs/reference/libsoup-2.4-sections.txt b/docs/reference/libsoup-2.4-sections.txt index 354c0783..efda43d7 100644 --- a/docs/reference/libsoup-2.4-sections.txt +++ b/docs/reference/libsoup-2.4-sections.txt @@ -1097,6 +1097,23 @@ SoupCacheability </SECTION> <SECTION> +<FILE>soup-cached-resolver</FILE> +<TITLE>SoupCachedResolver</TITLE> +SoupCachedResolver +SoupCachedResolverType +soup_cached_resolver_ensure_default +soup_cached_resolver_new +<SUBSECTION Standard> +SOUP_TYPE_CACHED_RESOLVER +SOUP_IS_CACHED_RESOLVER +SOUP_IS_CACHED_RESOLVER_CLASS +SOUP_CACHED_RESOLVER +SOUP_CACHED_RESOLVER_CLASS +SOUP_CACHED_RESOLVER_GET_CLASS +soup_cached_resolver_get_type +</SECTION> + +<SECTION> <FILE>soup-content-decoder</FILE> <TITLE>SoupContentDecoder</TITLE> SoupContentDecoder diff --git a/libsoup/meson.build b/libsoup/meson.build index 5f2a2156..39f0a951 100644 --- a/libsoup/meson.build +++ b/libsoup/meson.build @@ -16,6 +16,7 @@ soup_sources = [ 'soup-cache.c', 'soup-cache-client-input-stream.c', 'soup-cache-input-stream.c', + 'soup-cached-resolver.c', 'soup-client-input-stream.c', 'soup-connection.c', 'soup-connection-auth.c', @@ -114,6 +115,7 @@ soup_introspection_headers = [ 'soup-auth-manager.h', 'soup-autocleanups.h', 'soup-cache.h', + 'soup-cached-resolver.h', 'soup-content-decoder.h', 'soup-content-sniffer.h', 'soup-cookie.h', diff --git a/libsoup/soup-cached-resolver.c b/libsoup/soup-cached-resolver.c new file mode 100644 index 00000000..f7c64753 --- /dev/null +++ b/libsoup/soup-cached-resolver.c @@ -0,0 +1,624 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-cached-resolver.c + * + * Copyright (C) 2019 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "soup-cached-resolver.h" + +/** + * SECTION:soup-cached-resolver + * @short_description: Cached DNS Resolver + * + * #SoupCachedResolver wraps any existing #GResolver to allow + * basic caching of DNS responses. + * + * The cache is designed to be short lived and small and not intended + * to fully replace system resolver. It currently only caches resolving + * names and does not cache any other record lookups. + * + * For best performance use in combination with soup_session_prefetch_dns(). + * + * Note that the #GResolver that Gio uses is a global setting so + * you will generally opt into it via soup_cached_resolver_ensure_default(). + * See also g_resolver_set_default() and g_resolver_get_default(). + */ + +/* This version introduces querying names for ipv4/ipv6 + * separately which we cache separately */ +#if GLIB_CHECK_VERSION (2, 59, 0) +#define N_CACHES 3 +#else +#define N_CACHES 1 +#endif + +struct _SoupCachedResolver +{ + GResolver parent_instance; + GResolver *wrapped_resolver; + GHashTable *dns_caches[N_CACHES]; + GMutex dns_cache_lock; + guint64 max_size; +}; + +SOUP_AVAILABLE_IN_2_66 +GType soup_cached_resolver_get_type (void) G_GNUC_CONST; + +G_DEFINE_TYPE (SoupCachedResolver, soup_cached_resolver, G_TYPE_RESOLVER) + +#define DNS_CACHE_EXPIRE_SECONDS 60 + +enum { + PROP_0, + PROP_WRAPPED_RESOLVER, + PROP_MAX_SIZE, + N_PROPS +}; + +/** + * soup_cached_resolver_ensure_default: + * + * Ensures that the global default #GResolver is a #SoupCachedResolver + * or creates a new one wrapping the current default and sets that as + * default. + * + * Since: 2.66 + */ +void +soup_cached_resolver_ensure_default (void) +{ + GResolver *default_resolver = g_resolver_get_default (); + if (!SOUP_IS_CACHED_RESOLVER (default_resolver)) { + SoupCachedResolver *resolver = soup_cached_resolver_new (default_resolver); + g_resolver_set_default (G_RESOLVER (resolver)); + g_object_unref (resolver); + } + g_object_unref (default_resolver); +} + +/** + * soup_cached_resolver_new: + * @wrapped_resolver: Underlying #GResolver to be cached + * + * Note that @wrapped_resolver must not be a #SoupCachedResolver. + * + * You should generally use soup_cached_resolver_ensure_default() + * rather than this API directly. + * + * Returns: (transfer full): A new #SoupCachedResolver + * Since: 2.66 + */ +SoupCachedResolver * +soup_cached_resolver_new (GResolver *wrapped_resolver) +{ + g_return_val_if_fail (wrapped_resolver != NULL, NULL); + g_return_val_if_fail (!SOUP_IS_CACHED_RESOLVER (wrapped_resolver), NULL); + + return g_object_new (SOUP_TYPE_CACHED_RESOVLER, + "wrapped-resolver", wrapped_resolver, + NULL); +} + +typedef struct { + GList *addresses; /* owned */ + gint64 expiration; +} CachedResponse; + +static void +cached_response_free (CachedResponse *cache) +{ + g_resolver_free_addresses (cache->addresses); + g_free (cache); +} + +#if GLIB_CHECK_VERSION (2, 59, 0) + +static GHashTable * +get_dns_cache_for_flags (SoupCachedResolver *self, + 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 self->dns_caches[0]; + else if (flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY) + return self->dns_caches[1]; + else + return self->dns_caches[2]; +} + +#else + +#define G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT 0 +typedef int GResolverNameLookupFlags; + +static GHashTable * +get_dns_cache_for_flags (SoupCachedResolver *self, + int ignored) +{ + return self->dns_caches[0]; +} + +#endif + +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 +cleanup_dns_cache (SoupCachedResolver *self, + GHashTable *cache) +{ + GHashTableIter iter; + CachedResponse *cached; + gint64 now = g_get_monotonic_time (); + guint64 size = 0; + + g_mutex_lock (&self->dns_cache_lock); + + g_hash_table_iter_init (&iter, cache); + while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &cached)) { + if (cached->expiration <= now || size > self->max_size) + g_hash_table_iter_remove (&iter); + else + ++size; + } + + g_mutex_unlock (&self->dns_cache_lock); +} + +static void +cleanup_all_dns_caches (SoupCachedResolver *self) +{ + guint i; + for (i = 0; i < G_N_ELEMENTS (self->dns_caches); ++i) + cleanup_dns_cache (self, self->dns_caches[i]); +} + +static void +update_dns_cache (SoupCachedResolver *self, + const char *hostname, + GList *addresses, + GResolverNameLookupFlags flags) +{ + CachedResponse *cached; + GHashTable *cache; + + if (addresses == NULL) + return; + + cache = get_dns_cache_for_flags (self, flags); + cached = g_new (CachedResponse, 1); + cached->addresses = copy_addresses (addresses); + cached->expiration = g_get_monotonic_time () + (DNS_CACHE_EXPIRE_SECONDS * 1000); + + /* Cleanup while we are at it. */ + cleanup_dns_cache (self, cache); + + g_mutex_lock (&self->dns_cache_lock); + + g_hash_table_replace (cache, g_strdup (hostname), cached); + + g_mutex_unlock (&self->dns_cache_lock); +} + +/* + * Returns: (transfer full): List of addresses + */ +static GList * +query_dns_cache (SoupCachedResolver *self, + const char *hostname, + GResolverNameLookupFlags flags) +{ + CachedResponse *cached; + GHashTable *cache; + GList *addresses = NULL; + gint64 now = g_get_monotonic_time (); + + cache = get_dns_cache_for_flags (self, flags); + + g_mutex_lock (&self->dns_cache_lock); + + cached = g_hash_table_lookup (cache, hostname); + if (cached && cached->expiration > now) + addresses = copy_addresses (cached->addresses); + + g_mutex_unlock (&self->dns_cache_lock); + + return addresses; +} + +static void +reload (GResolver *resolver) +{ + SoupCachedResolver *self = SOUP_CACHED_RESOLVER (resolver); + guint i; + + g_info ("Flushing DNS Cache"); + + /* Empty caches on system DNS changes */ + for (i = 0; i < G_N_ELEMENTS (self->dns_caches); ++i) + g_hash_table_remove_all (self->dns_caches[i]); +} + +#if GLIB_CHECK_VERSION (2, 59, 0) + +typedef struct { + char *hostname; + GResolverNameLookupFlags flags; +} LookupData; + +static LookupData * +lookup_data_new (const char *hostname, GResolverNameLookupFlags flags) +{ + LookupData *lookup_data = g_new (LookupData, 1); + lookup_data->hostname = g_strdup (hostname); + lookup_data->flags = flags; + return lookup_data; +} + +static void +lookup_data_free (LookupData *lookup_data) +{ + g_free (lookup_data->hostname); + g_free (lookup_data); +} + +static GList * +lookup_by_name_with_flags (GResolver *resolver, + const gchar *hostname, + GResolverNameLookupFlags flags, + GCancellable *cancellable, + GError **error) +{ + SoupCachedResolver *self = SOUP_CACHED_RESOLVER (resolver); + GList *addresses = query_dns_cache (self, hostname, flags); + + if (addresses) + return addresses; + + addresses = g_resolver_lookup_by_name_with_flags (self->wrapped_resolver, + hostname, + flags, + cancellable, + error); + update_dns_cache (self, hostname, addresses, flags); + return addresses; +} + +static GList * +lookup_by_name_with_flags_finish (GResolver *resolver, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, resolver), NULL); + + return g_task_propagate_pointer (G_TASK (result), error); +} + +static void +on_lookup_by_name_with_flags_finish (GObject *resolver, + GAsyncResult *result, + gpointer user_data) +{ + GTask *task = G_TASK (user_data); + SoupCachedResolver *self = g_task_get_source_object (task); + LookupData *data = g_task_get_task_data (task); + GError *error = NULL; + GList *addresses; + + addresses = g_resolver_lookup_by_name_with_flags_finish (G_RESOLVER (resolver), result, &error); + if (addresses) { + update_dns_cache (self, data->hostname, addresses, data->flags); + g_task_return_pointer (task, addresses, (GDestroyNotify) g_resolver_free_addresses); + } else + g_task_return_error (task, error); + + g_object_unref (task); +} + +static void +lookup_by_name_with_flags_async (GResolver *resolver, + const gchar *hostname, + GResolverNameLookupFlags flags, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + SoupCachedResolver *self = SOUP_CACHED_RESOLVER (resolver); + GTask *task = g_task_new (self, cancellable, callback, user_data); + GList *cached = query_dns_cache (self, hostname, flags); + + if (cached) + g_task_return_pointer (task, cached, (GDestroyNotify) g_resolver_free_addresses); + else { + g_task_set_task_data (task, lookup_data_new (hostname, flags), (GDestroyNotify) lookup_data_free); + g_resolver_lookup_by_name_with_flags_async (self->wrapped_resolver, + hostname, + flags, + cancellable, + on_lookup_by_name_with_flags_finish, + g_steal_pointer (&task)); + } + + g_clear_object (&task); +} + +#endif + +static void +on_lookup_by_name_finish (GObject *resolver, + GAsyncResult *result, + gpointer user_data) +{ + GTask *task = G_TASK (user_data); + SoupCachedResolver *self = g_task_get_source_object (task); + char *hostname = g_task_get_task_data (task); + GError *error = NULL; + GList *addresses; + + addresses = g_resolver_lookup_by_name_finish (G_RESOLVER (resolver), result, &error); + if (addresses) { + update_dns_cache (self, hostname, addresses, G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT); + g_task_return_pointer (task, addresses, (GDestroyNotify) g_resolver_free_addresses); + } else + g_task_return_error (task, error); + + g_object_unref (task); +} + +static void +lookup_by_name_async (GResolver *resolver, + const gchar *hostname, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + SoupCachedResolver *self = SOUP_CACHED_RESOLVER (resolver); + GTask *task = g_task_new (self, cancellable, callback, user_data); + GList *cached = query_dns_cache (self, hostname, G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT); + + if (cached) + g_task_return_pointer (task, cached, (GDestroyNotify) g_resolver_free_addresses); + else { + g_task_set_task_data (task, g_strdup (hostname), g_free); + g_resolver_lookup_by_name_async (self->wrapped_resolver, + hostname, + cancellable, + on_lookup_by_name_finish, + g_steal_pointer (&task)); + } + + g_clear_object (&task); +} + +static GList * +lookup_by_name_finish (GResolver *resolver, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, resolver), NULL); + + return g_task_propagate_pointer (G_TASK (result), error); +} + +static GList * +lookup_by_name (GResolver *resolver, + const gchar *hostname, + GCancellable *cancellable, + GError **error) +{ + SoupCachedResolver *self = SOUP_CACHED_RESOLVER (resolver); + GList *addresses = query_dns_cache (self, hostname, G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT); + + if (addresses) + return addresses; + + addresses = g_resolver_lookup_by_name (self->wrapped_resolver, + hostname, + cancellable, + error); + update_dns_cache (self, hostname, addresses, G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT); + return addresses; +} + + +static gchar * +lookup_by_address (GResolver *resolver, + GInetAddress *address, + GCancellable *cancellable, + GError **error) +{ + return g_resolver_lookup_by_address (SOUP_CACHED_RESOLVER (resolver)->wrapped_resolver, address, cancellable, error); +} + +static void +lookup_by_address_async (GResolver *resolver, + GInetAddress *address, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_resolver_lookup_by_address_async (SOUP_CACHED_RESOLVER (resolver)->wrapped_resolver, address, cancellable, callback, user_data); +} + +static gchar * +lookup_by_address_finish (GResolver *resolver, + GAsyncResult *result, + GError **error) +{ + return g_resolver_lookup_by_address_finish (SOUP_CACHED_RESOLVER (resolver)->wrapped_resolver, result, error); +} + +static GList * +lookup_records (GResolver *resolver, + const gchar *rrname, + GResolverRecordType record_type, + GCancellable *cancellable, + GError **error) +{ + return g_resolver_lookup_records (SOUP_CACHED_RESOLVER (resolver)->wrapped_resolver, rrname, record_type, cancellable, error); +} + +static void +lookup_records_async (GResolver *resolver, + const char *rrname, + GResolverRecordType record_type, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_resolver_lookup_records_async (SOUP_CACHED_RESOLVER (resolver)->wrapped_resolver, rrname, record_type, cancellable, callback, user_data); +} + +static GList * +lookup_records_finish (GResolver *resolver, + GAsyncResult *result, + GError **error) +{ + return g_resolver_lookup_records_finish (SOUP_CACHED_RESOLVER (resolver)->wrapped_resolver, result, error); +} + +static void +soup_cached_resolver_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SoupCachedResolver *self = SOUP_CACHED_RESOLVER (object); + + switch (prop_id) + { + case PROP_WRAPPED_RESOLVER: + g_value_set_object (value, self->wrapped_resolver); + break; + case PROP_MAX_SIZE: + g_value_set_uint64 (value, self->max_size); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +soup_cached_resolver_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SoupCachedResolver *self = SOUP_CACHED_RESOLVER (object); + + switch (prop_id) + { + case PROP_WRAPPED_RESOLVER: + g_assert (self->wrapped_resolver == NULL); + self->wrapped_resolver = g_value_dup_object (value); + g_assert (self->wrapped_resolver != NULL); + break; + case PROP_MAX_SIZE: + self->max_size = g_value_get_uint64 (value); + cleanup_all_dns_caches (self); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +soup_cached_resolver_finalize (GObject *object) +{ + SoupCachedResolver *self = SOUP_CACHED_RESOLVER (object); + g_clear_object (&self->wrapped_resolver); + G_OBJECT_CLASS (soup_cached_resolver_parent_class)->finalize (object); +} + +static void +soup_cached_resolver_class_init (SoupCachedResolverClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GResolverClass *resolver_class = G_RESOLVER_CLASS (klass); + + object_class->finalize = soup_cached_resolver_finalize; + object_class->get_property = soup_cached_resolver_get_property; + object_class->set_property = soup_cached_resolver_set_property; + + resolver_class->lookup_by_name = lookup_by_name; + resolver_class->lookup_by_name_async = lookup_by_name_async; + resolver_class->lookup_by_name_finish = lookup_by_name_finish; +#if GLIB_CHECK_VERSION (2, 59, 0) + resolver_class->lookup_by_name_with_flags = lookup_by_name_with_flags; + resolver_class->lookup_by_name_with_flags_async = lookup_by_name_with_flags_async; + resolver_class->lookup_by_name_with_flags_finish = lookup_by_name_with_flags_finish; +#endif + resolver_class->lookup_by_address = lookup_by_address; + resolver_class->lookup_by_address_async = lookup_by_address_async; + resolver_class->lookup_by_address_finish = lookup_by_address_finish; + resolver_class->lookup_records = lookup_records; + resolver_class->lookup_records_async = lookup_records_async; + resolver_class->lookup_records_finish = lookup_records_finish; + + resolver_class->reload = reload; + + /** + * SoupCachedResolver:wrapped-resolver: + * + * The #GResolver that is cached. + * + * Since: 2.66 + */ + g_object_class_install_property (object_class, PROP_WRAPPED_RESOLVER, + g_param_spec_object ("wrapped-resolver", "wrapped-resolver", + "DNS Resolver that is wrapped", + G_TYPE_RESOLVER, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + /** + * SoupCachedResolver:max-size: + * + * The maximum size of the DNS cache. + * + * Since: 2.66 + */ + g_object_class_install_property (object_class, PROP_MAX_SIZE, + g_param_spec_uint64 ("max-size", "max-size", + "Max size of DNS cache", + 0, G_MAXUINT64, 400, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); +} + +static void +soup_cached_resolver_init (SoupCachedResolver *self) +{ + guint i; + for (i = 0; i < G_N_ELEMENTS (self->dns_caches); ++i) + self->dns_caches[i] = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) cached_response_free); +} diff --git a/libsoup/soup-cached-resolver.h b/libsoup/soup-cached-resolver.h new file mode 100644 index 00000000..4f4c31fe --- /dev/null +++ b/libsoup/soup-cached-resolver.h @@ -0,0 +1,39 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-cached-resolver.h: + * + * Copyright (C) 2019 Igalia, S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include <gio/gio.h> +#include "soup-version.h" + +G_BEGIN_DECLS + +#define SOUP_TYPE_CACHED_RESOVLER (soup_cached_resolver_get_type()) +G_DECLARE_FINAL_TYPE (SoupCachedResolver, soup_cached_resolver, SOUP, CACHED_RESOLVER, GResolver) + +SOUP_AVAILABLE_IN_2_66 +void soup_cached_resolver_ensure_default (void); + +SOUP_AVAILABLE_IN_2_66 +SoupCachedResolver *soup_cached_resolver_new (GResolver *wrapped_resolver); + +G_END_DECLS diff --git a/libsoup/soup.h b/libsoup/soup.h index 4a3ac2ef..1fdd02e5 100644 --- a/libsoup/soup.h +++ b/libsoup/soup.h @@ -19,6 +19,7 @@ extern "C" { #include <libsoup/soup-auth-domain-digest.h> #include <libsoup/soup-auth-manager.h> #include <libsoup/soup-cache.h> +#include <libsoup/soup-cached-resolver.h> #include <libsoup/soup-content-decoder.h> #include <libsoup/soup-content-sniffer.h> #include <libsoup/soup-cookie.h> diff --git a/tests/cached-resolver-test.c b/tests/cached-resolver-test.c new file mode 100644 index 00000000..54540363 --- /dev/null +++ b/tests/cached-resolver-test.c @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2019 Igalia S.L. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "test-utils.h" + +static void +test_cached_resolver (void) +{ + GResolver *default_resolver = g_resolver_get_default (); + GResolver *cached_resolver; + GList *results; + double default_time, cached_time; + guint i; + + /* Just verify caching works at a basic level by being faster */ + + g_assert_false (SOUP_IS_CACHED_RESOLVER (default_resolver)); + + /* Warm any DNS cache */ + results = g_resolver_lookup_by_name (default_resolver, "gnome.org", NULL, NULL); + g_assert_nonnull (results); + g_resolver_free_addresses (results); + + g_test_timer_start (); + + for (i = 0; i < 100; ++i) { + results = g_resolver_lookup_by_name (default_resolver, "gnome.org", NULL, NULL); + g_assert_nonnull (results); + g_resolver_free_addresses (results); + } + default_time = g_test_timer_elapsed (); + + soup_cached_resolver_ensure_default (); + /* Test that its safe to call multiple times */ + soup_cached_resolver_ensure_default (); + + cached_resolver = g_resolver_get_default (); + g_assert_true (SOUP_IS_CACHED_RESOLVER (cached_resolver)); + + + /* Warm the DNS cache */ + results = g_resolver_lookup_by_name (cached_resolver, "gnome.org", NULL, NULL); + g_assert_nonnull (results); + g_resolver_free_addresses (results); + + g_test_timer_start (); + + for (i = 0; i < 100; ++i) { + results = g_resolver_lookup_by_name (cached_resolver, "gnome.org", NULL, NULL); + g_assert_nonnull (results); + g_resolver_free_addresses (results); + } + cached_time = g_test_timer_elapsed (); + + /* Cached will always be faster or else whats the point */ + g_assert_cmpfloat (default_time, >, cached_time); + g_info ("%f > %f", default_time, cached_time); + + g_resolver_set_default (default_resolver); + g_object_unref (default_resolver); + g_object_unref (cached_resolver); +} + +static void +on_lookup_by_name_finish (GResolver *resolver, + GAsyncResult *result, + gboolean *done) +{ + GError *error = NULL; + GList *addresses; + + addresses = g_resolver_lookup_by_name_finish (resolver, result, &error); + g_assert_no_error (error); + g_assert_nonnull (addresses); + g_resolver_free_addresses (addresses); + *done = TRUE; +} + +static void +test_cached_resolver_async (void) +{ + GResolver *default_resolver = g_resolver_get_default (); + GResolver *cached_resolver; + gboolean done = FALSE; + + soup_cached_resolver_ensure_default (); + cached_resolver = g_resolver_get_default (); + + /* Just sanity check it works */ + g_resolver_lookup_by_name_async (cached_resolver, "gnome.org", NULL, (GAsyncReadyCallback) on_lookup_by_name_finish, &done); + + while (done != TRUE) + g_main_context_iteration (NULL, TRUE); + + /* And again cached */ + done = FALSE; + g_resolver_lookup_by_name_async (cached_resolver, "gnome.org", NULL, (GAsyncReadyCallback) on_lookup_by_name_finish, &done); + + while (done != TRUE) + g_main_context_iteration (NULL, TRUE); + + g_resolver_set_default (default_resolver); + g_object_unref (default_resolver); + g_object_unref (cached_resolver); +} + +int +main (int argc, char **argv) +{ + int ret; + + test_init (argc, argv, NULL); + + g_test_add_func ("/cached-resolver", test_cached_resolver); + g_test_add_func ("/cached-resolver/async", test_cached_resolver_async); + + ret = g_test_run (); + + test_cleanup (); + return ret; +} diff --git a/tests/meson.build b/tests/meson.build index 8176f29b..370a11b3 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -15,6 +15,7 @@ test_resources = gnome.compile_resources('soup-tests', # ['name', is_parallel] tests = [ ['cache', true], + ['cached-resolver', true], ['chunk', true], ['chunk-io', true], ['coding', true], |