diff options
Diffstat (limited to 'gio/gthreadedresolver.c')
-rw-r--r-- | gio/gthreadedresolver.c | 588 |
1 files changed, 476 insertions, 112 deletions
diff --git a/gio/gthreadedresolver.c b/gio/gthreadedresolver.c index 68b5c20d7..b452d1e1b 100644 --- a/gio/gthreadedresolver.c +++ b/gio/gthreadedresolver.c @@ -28,6 +28,7 @@ #include <stdio.h> #include <string.h> +#include "glib/glib-private.h" #include "gthreadedresolver.h" #include "gnetworkingprivate.h" @@ -38,12 +39,83 @@ #include "gsocketaddress.h" #include "gsrvtarget.h" +/* + * GThreadedResolver is a threaded wrapper around the system libc’s + * `getaddrinfo()`. + * + * It has to be threaded, as `getaddrinfo()` is synchronous. libc does provide + * `getaddrinfo_a()` as an asynchronous version of `getaddrinfo()`, but it does + * not integrate with a poll loop. It requires use of sigevent to notify of + * completion of an asynchronous operation. That either emits a signal, or calls + * a callback function in a newly spawned thread. + * + * A signal (`SIGEV_SIGNAL`) can’t be used for completion as (aside from being + * another expensive round trip into the kernel) GLib cannot pick a `SIG*` + * number which is guaranteed to not be in use elsewhere in the process. Various + * other things could be interfering with signal dispositions, such as gdb or + * other libraries in the process. Using a `signalfd()` + * [cannot improve this situation](https://ldpreload.com/blog/signalfd-is-useless). + * + * A callback function in a newly spawned thread (`SIGEV_THREAD`) could be used, + * but that is very expensive. Internally, glibc currently also just implements + * `getaddrinfo_a()` + * [using its own thread pool](https://github.com/bminor/glibc/blob/master/resolv/gai_misc.c), + * and then + * [spawns an additional thread for each completion callback](https://github.com/bminor/glibc/blob/master/resolv/gai_notify.c). + * That is very expensive. + * + * No other appropriate sigevent callback types + * [currently exist](https://sourceware.org/bugzilla/show_bug.cgi?id=30287), and + * [others agree that sigevent is not great](http://davmac.org/davpage/linux/async-io.html#posixaio). + * + * Hence, #GThreadedResolver calls the normal synchronous `getaddrinfo()` in its + * own thread pool. Previously, #GThreadedResolver used the thread pool which is + * internal to #GTask by calling g_task_run_in_thread(). That lead to exhaustion + * of the #GTask thread pool in some situations, though, as DNS lookups are + * quite frequent leaf operations in some use cases. Now, #GThreadedResolver + * uses its own private thread pool. + * + * This is similar to what + * [libasyncns](http://git.0pointer.net/libasyncns.git/tree/libasyncns/asyncns.h) + * and other multi-threaded users of `getaddrinfo()` do. + */ + +struct _GThreadedResolver +{ + GResolver parent_instance; + + GThreadPool *thread_pool; /* (owned) */ +}; G_DEFINE_TYPE (GThreadedResolver, g_threaded_resolver, G_TYPE_RESOLVER) +static void run_task_in_thread_pool_async (GThreadedResolver *self, + GTask *task); +static void run_task_in_thread_pool_sync (GThreadedResolver *self, + GTask *task); +static void threaded_resolver_worker_cb (gpointer task_data, + gpointer user_data); + static void -g_threaded_resolver_init (GThreadedResolver *gtr) +g_threaded_resolver_init (GThreadedResolver *self) { + self->thread_pool = g_thread_pool_new_full (threaded_resolver_worker_cb, + self, + (GDestroyNotify) g_object_unref, + 20, + FALSE, + NULL); +} + +static void +g_threaded_resolver_finalize (GObject *object) +{ + GThreadedResolver *self = G_THREADED_RESOLVER (object); + + g_thread_pool_free (self->thread_pool, TRUE, FALSE); + self->thread_pool = NULL; + + G_OBJECT_CLASS (g_threaded_resolver_parent_class)->finalize (object); } static GResolverError @@ -67,35 +139,127 @@ g_resolver_error_from_addrinfo_error (gint err) } typedef struct { - char *hostname; - int address_family; + enum { + LOOKUP_BY_NAME, + LOOKUP_BY_ADDRESS, + LOOKUP_RECORDS, + } lookup_type; + + union { + struct { + char *hostname; + int address_family; + } lookup_by_name; + struct { + GInetAddress *address; /* (owned) */ + } lookup_by_address; + struct { + char *rrname; + GResolverRecordType record_type; + } lookup_records; + }; + + GCond cond; /* used for signalling completion of the task when running it sync */ + GMutex lock; + + GSource *timeout_source; /* (nullable) (owned) */ + GSource *cancellable_source; /* (nullable) (owned) */ + + /* This enum indicates that a particular code path has claimed the + * task and is shortly about to call g_task_return_*() on it. + * This must be accessed with GThreadedResolver.lock held. */ + enum + { + NOT_YET, + COMPLETED, /* libc lookup call has completed successfully or errored */ + TIMED_OUT, + CANCELLED, + } will_return; + + /* Whether the thread pool thread executing this lookup has finished executing + * it and g_task_return_*() has been called on it already. + * This must be accessed with GThreadedResolver.lock held. */ + gboolean has_returned; } LookupData; static LookupData * -lookup_data_new (const char *hostname, - int address_family) +lookup_data_new_by_name (const char *hostname, + int address_family) { - LookupData *data = g_new (LookupData, 1); - data->hostname = g_strdup (hostname); - data->address_family = address_family; - return data; + LookupData *data = g_new0 (LookupData, 1); + data->lookup_type = LOOKUP_BY_NAME; + g_cond_init (&data->cond); + g_mutex_init (&data->lock); + data->lookup_by_name.hostname = g_strdup (hostname); + data->lookup_by_name.address_family = address_family; + return g_steal_pointer (&data); +} + +static LookupData * +lookup_data_new_by_address (GInetAddress *address) +{ + LookupData *data = g_new0 (LookupData, 1); + data->lookup_type = LOOKUP_BY_ADDRESS; + g_cond_init (&data->cond); + g_mutex_init (&data->lock); + data->lookup_by_address.address = g_object_ref (address); + return g_steal_pointer (&data); +} + +static LookupData * +lookup_data_new_records (const gchar *rrname, + GResolverRecordType record_type) +{ + LookupData *data = g_new0 (LookupData, 1); + data->lookup_type = LOOKUP_RECORDS; + g_cond_init (&data->cond); + g_mutex_init (&data->lock); + data->lookup_records.rrname = g_strdup (rrname); + data->lookup_records.record_type = record_type; + return g_steal_pointer (&data); } static void lookup_data_free (LookupData *data) { - g_free (data->hostname); + switch (data->lookup_type) { + case LOOKUP_BY_NAME: + g_free (data->lookup_by_name.hostname); + break; + case LOOKUP_BY_ADDRESS: + g_clear_object (&data->lookup_by_address.address); + break; + case LOOKUP_RECORDS: + g_free (data->lookup_records.rrname); + break; + default: + g_assert_not_reached (); + } + + if (data->timeout_source != NULL) + { + g_source_destroy (data->timeout_source); + g_clear_pointer (&data->timeout_source, g_source_unref); + } + + if (data->cancellable_source != NULL) + { + g_source_destroy (data->cancellable_source); + g_clear_pointer (&data->cancellable_source, g_source_unref); + } + + g_mutex_clear (&data->lock); + g_cond_clear (&data->cond); + g_free (data); } -static void -do_lookup_by_name (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) +static GList * +do_lookup_by_name (const gchar *hostname, + int address_family, + GCancellable *cancellable, + GError **error) { - LookupData *lookup_data = task_data; - const char *hostname = lookup_data->hostname; struct addrinfo *res = NULL; GList *addresses; gint retval; @@ -111,7 +275,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 = address_family; retval = getaddrinfo (hostname, NULL, &addrinfo_hints, &res); if (retval == 0) @@ -137,21 +301,23 @@ do_lookup_by_name (GTask *task, g_object_unref (sockaddr); } + g_clear_pointer (&res, freeaddrinfo); + if (addresses != NULL) { addresses = g_list_reverse (addresses); - g_task_return_pointer (task, addresses, - (GDestroyNotify)g_resolver_free_addresses); + return g_steal_pointer (&addresses); } else { /* All addresses failed to be converted to GSocketAddresses. */ - g_task_return_new_error (task, - G_RESOLVER_ERROR, - G_RESOLVER_ERROR_NOT_FOUND, - _("Error resolving “%s”: %s"), - hostname, - _("No valid addresses were found")); + g_set_error (error, + G_RESOLVER_ERROR, + G_RESOLVER_ERROR_NOT_FOUND, + _("Error resolving “%s”: %s"), + hostname, + _("No valid addresses were found")); + return NULL; } } else @@ -164,16 +330,17 @@ do_lookup_by_name (GTask *task, error_message = g_strdup ("[Invalid UTF-8]"); #endif - g_task_return_new_error (task, - G_RESOLVER_ERROR, - g_resolver_error_from_addrinfo_error (retval), - _("Error resolving “%s”: %s"), - hostname, error_message); + g_clear_pointer (&res, freeaddrinfo); + + g_set_error (error, + G_RESOLVER_ERROR, + g_resolver_error_from_addrinfo_error (retval), + _("Error resolving “%s”: %s"), + hostname, error_message); g_free (error_message); - } - if (res) - freeaddrinfo (res); + return NULL; + } } static GList * @@ -182,17 +349,19 @@ lookup_by_name (GResolver *resolver, GCancellable *cancellable, GError **error) { + GThreadedResolver *self = G_THREADED_RESOLVER (resolver); GTask *task; GList *addresses; LookupData *data; - data = lookup_data_new (hostname, AF_UNSPEC); + data = lookup_data_new_by_name (hostname, AF_UNSPEC); task = g_task_new (resolver, cancellable, NULL, NULL); g_task_set_source_tag (task, lookup_by_name); g_task_set_name (task, "[gio] resolver lookup"); - 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); + g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) lookup_data_free); + + run_task_in_thread_pool_sync (self, task); + addresses = g_task_propagate_pointer (task, error); g_object_unref (task); @@ -224,17 +393,19 @@ lookup_by_name_with_flags (GResolver *resolver, GCancellable *cancellable, GError **error) { + GThreadedResolver *self = G_THREADED_RESOLVER (resolver); GTask *task; GList *addresses; LookupData *data; - data = lookup_data_new (hostname, flags_to_family (flags)); + data = lookup_data_new_by_name (hostname, flags_to_family (flags)); task = g_task_new (resolver, cancellable, NULL, NULL); g_task_set_source_tag (task, lookup_by_name_with_flags); g_task_set_name (task, "[gio] resolver lookup"); - 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); + g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) lookup_data_free); + + run_task_in_thread_pool_sync (self, task); + addresses = g_task_propagate_pointer (task, error); g_object_unref (task); @@ -249,16 +420,22 @@ lookup_by_name_with_flags_async (GResolver *resolver, GAsyncReadyCallback callback, gpointer user_data) { + GThreadedResolver *self = G_THREADED_RESOLVER (resolver); GTask *task; LookupData *data; - data = lookup_data_new (hostname, flags_to_family (flags)); + data = lookup_data_new_by_name (hostname, flags_to_family (flags)); task = g_task_new (resolver, cancellable, callback, user_data); + + g_debug ("%s: starting new lookup for %s with GTask %p, LookupData %p", + G_STRFUNC, hostname, task, data); + g_task_set_source_tag (task, lookup_by_name_with_flags_async); g_task_set_name (task, "[gio] resolver lookup"); - 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); + g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) lookup_data_free); + + run_task_in_thread_pool_async (self, task); + g_object_unref (task); } @@ -297,13 +474,11 @@ lookup_by_name_with_flags_finish (GResolver *resolver, return g_task_propagate_pointer (G_TASK (result), error); } -static void -do_lookup_by_address (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) +static gchar * +do_lookup_by_address (GInetAddress *address, + GCancellable *cancellable, + GError **error) { - GInetAddress *address = task_data; struct sockaddr_storage sockaddr_address; gsize sockaddr_address_size; GSocketAddress *gsockaddr; @@ -319,7 +494,7 @@ do_lookup_by_address (GTask *task, retval = getnameinfo ((struct sockaddr *) &sockaddr_address, sockaddr_address_size, name, sizeof (name), NULL, 0, NI_NAMEREQD); if (retval == 0) - g_task_return_pointer (task, g_strdup (name), g_free); + return g_strdup (name); else { gchar *phys; @@ -333,14 +508,16 @@ do_lookup_by_address (GTask *task, #endif phys = g_inet_address_to_string (address); - g_task_return_new_error (task, - G_RESOLVER_ERROR, - g_resolver_error_from_addrinfo_error (retval), - _("Error reverse-resolving “%s”: %s"), - phys ? phys : "(unknown)", - error_message); + g_set_error (error, + G_RESOLVER_ERROR, + g_resolver_error_from_addrinfo_error (retval), + _("Error reverse-resolving “%s”: %s"), + phys ? phys : "(unknown)", + error_message); g_free (phys); g_free (error_message); + + return NULL; } } @@ -350,15 +527,19 @@ lookup_by_address (GResolver *resolver, GCancellable *cancellable, GError **error) { + GThreadedResolver *self = G_THREADED_RESOLVER (resolver); + LookupData *data = NULL; GTask *task; gchar *name; + data = lookup_data_new_by_address (address); task = g_task_new (resolver, cancellable, NULL, NULL); g_task_set_source_tag (task, lookup_by_address); g_task_set_name (task, "[gio] resolver lookup"); - g_task_set_task_data (task, g_object_ref (address), g_object_unref); - g_task_set_return_on_cancel (task, TRUE); - g_task_run_in_thread_sync (task, do_lookup_by_address); + g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) lookup_data_free); + + run_task_in_thread_pool_sync (self, task); + name = g_task_propagate_pointer (task, error); g_object_unref (task); @@ -372,14 +553,18 @@ lookup_by_address_async (GResolver *resolver, GAsyncReadyCallback callback, gpointer user_data) { + GThreadedResolver *self = G_THREADED_RESOLVER (resolver); + LookupData *data = NULL; GTask *task; + data = lookup_data_new_by_address (address); task = g_task_new (resolver, cancellable, callback, user_data); g_task_set_source_tag (task, lookup_by_address_async); g_task_set_name (task, "[gio] resolver lookup"); - g_task_set_task_data (task, g_object_ref (address), g_object_unref); - g_task_set_return_on_cancel (task, TRUE); - g_task_run_in_thread (task, do_lookup_by_address); + g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) lookup_data_free); + + run_task_in_thread_pool_async (self, task); + g_object_unref (task); } @@ -1066,18 +1251,6 @@ g_resolver_records_from_DnsQuery (const gchar *rrname, #endif -typedef struct { - char *rrname; - GResolverRecordType record_type; -} LookupRecordsData; - -static void -free_lookup_records_data (LookupRecordsData *lrd) -{ - g_free (lrd->rrname); - g_slice_free (LookupRecordsData, lrd); -} - static void free_records (GList *records) { @@ -1093,15 +1266,13 @@ int res_query(const char *, int, int, u_char *, int); #endif #endif -static void -do_lookup_records (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) +static GList * +do_lookup_records (const gchar *rrname, + GResolverRecordType record_type, + GCancellable *cancellable, + GError **error) { - LookupRecordsData *lrd = task_data; GList *records; - GError *error = NULL; #if defined(G_OS_UNIX) gint len = 512; @@ -1125,21 +1296,21 @@ do_lookup_records (GTask *task, struct __res_state res = { 0, }; if (res_ninit (&res) != 0) { - g_task_return_new_error (task, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL, - _("Error resolving “%s”"), lrd->rrname); - return; + g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL, + _("Error resolving “%s”"), rrname); + return NULL; } #endif - rrtype = g_resolver_record_type_to_rrtype (lrd->record_type); + rrtype = g_resolver_record_type_to_rrtype (record_type); answer = g_byte_array_new (); for (;;) { g_byte_array_set_size (answer, len * 2); #if defined(HAVE_RES_NQUERY) - len = res_nquery (&res, lrd->rrname, C_IN, rrtype, answer->data, answer->len); + len = res_nquery (&res, rrname, C_IN, rrtype, answer->data, answer->len); #else - len = res_query (lrd->rrname, C_IN, rrtype, answer->data, answer->len); + len = res_query (rrname, C_IN, rrtype, answer->data, answer->len); #endif /* If answer fit in the buffer then we're done */ @@ -1153,7 +1324,7 @@ do_lookup_records (GTask *task, } herr = h_errno; - records = g_resolver_records_from_res_query (lrd->rrname, rrtype, answer->data, len, herr, &error); + records = g_resolver_records_from_res_query (rrname, rrtype, answer->data, len, herr, error); g_byte_array_free (answer, TRUE); #ifdef HAVE_RES_NQUERY @@ -1174,18 +1345,15 @@ do_lookup_records (GTask *task, DNS_RECORD *results = NULL; WORD dnstype; - dnstype = g_resolver_record_type_to_dnstype (lrd->record_type); - status = DnsQuery_A (lrd->rrname, dnstype, DNS_QUERY_STANDARD, NULL, &results, NULL); - records = g_resolver_records_from_DnsQuery (lrd->rrname, dnstype, status, results, &error); + dnstype = g_resolver_record_type_to_dnstype (record_type); + status = DnsQuery_A (rrname, dnstype, DNS_QUERY_STANDARD, NULL, &results, NULL); + records = g_resolver_records_from_DnsQuery (rrname, dnstype, status, results, error); if (results != NULL) DnsRecordListFree (results, DnsFreeRecordList); #endif - if (records) - g_task_return_pointer (task, records, (GDestroyNotify) free_records); - else - g_task_return_error (task, error); + return g_steal_pointer (&records); } static GList * @@ -1195,21 +1363,20 @@ lookup_records (GResolver *resolver, GCancellable *cancellable, GError **error) { + GThreadedResolver *self = G_THREADED_RESOLVER (resolver); GTask *task; GList *records; - LookupRecordsData *lrd; + LookupData *data = NULL; task = g_task_new (resolver, cancellable, NULL, NULL); g_task_set_source_tag (task, lookup_records); g_task_set_name (task, "[gio] resolver lookup records"); - lrd = g_slice_new (LookupRecordsData); - lrd->rrname = g_strdup (rrname); - lrd->record_type = record_type; - g_task_set_task_data (task, lrd, (GDestroyNotify) free_lookup_records_data); + data = lookup_data_new_records (rrname, record_type); + g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) lookup_data_free); + + run_task_in_thread_pool_sync (self, task); - g_task_set_return_on_cancel (task, TRUE); - g_task_run_in_thread_sync (task, do_lookup_records); records = g_task_propagate_pointer (task, error); g_object_unref (task); @@ -1224,20 +1391,19 @@ lookup_records_async (GResolver *resolver, GAsyncReadyCallback callback, gpointer user_data) { + GThreadedResolver *self = G_THREADED_RESOLVER (resolver); GTask *task; - LookupRecordsData *lrd; + LookupData *data = NULL; task = g_task_new (resolver, cancellable, callback, user_data); g_task_set_source_tag (task, lookup_records_async); g_task_set_name (task, "[gio] resolver lookup records"); - lrd = g_slice_new (LookupRecordsData); - lrd->rrname = g_strdup (rrname); - lrd->record_type = record_type; - g_task_set_task_data (task, lrd, (GDestroyNotify) free_lookup_records_data); + data = lookup_data_new_records (rrname, record_type); + g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) lookup_data_free); + + run_task_in_thread_pool_async (self, task); - g_task_set_return_on_cancel (task, TRUE); - g_task_run_in_thread (task, do_lookup_records); g_object_unref (task); } @@ -1251,12 +1417,210 @@ lookup_records_finish (GResolver *resolver, return g_task_propagate_pointer (G_TASK (result), error); } +/* Will be called in the GLib worker thread, so must lock all accesses to shared + * data. */ +static gboolean +timeout_cb (gpointer user_data) +{ + GTask *task = G_TASK (user_data); + LookupData *data = g_task_get_task_data (task); + gboolean should_return; + + g_mutex_lock (&data->lock); + + should_return = g_atomic_int_compare_and_exchange (&data->will_return, NOT_YET, TIMED_OUT); + g_clear_pointer (&data->timeout_source, g_source_unref); + + g_mutex_unlock (&data->lock); + + if (should_return) + g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, + _("Socket I/O timed out")); + + /* Signal completion of the task. */ + g_mutex_lock (&data->lock); + g_assert (!data->has_returned); + data->has_returned = TRUE; + g_cond_broadcast (&data->cond); + g_mutex_unlock (&data->lock); + + return G_SOURCE_REMOVE; +} + +/* Will be called in the GLib worker thread, so must lock all accesses to shared + * data. */ +static gboolean +cancelled_cb (GCancellable *cancellable, + gpointer user_data) +{ + GTask *task = G_TASK (user_data); + LookupData *data = g_task_get_task_data (task); + gboolean should_return; + + g_mutex_lock (&data->lock); + + g_assert (g_cancellable_is_cancelled (cancellable)); + should_return = g_atomic_int_compare_and_exchange (&data->will_return, NOT_YET, CANCELLED); + g_clear_pointer (&data->cancellable_source, g_source_unref); + + g_mutex_unlock (&data->lock); + + if (should_return) + g_task_return_error_if_cancelled (task); + + /* Signal completion of the task. */ + g_mutex_lock (&data->lock); + g_assert (!data->has_returned); + data->has_returned = TRUE; + g_cond_broadcast (&data->cond); + g_mutex_unlock (&data->lock); + + return G_SOURCE_REMOVE; +} + +static void +run_task_in_thread_pool_async (GThreadedResolver *self, + GTask *task) +{ + LookupData *data = g_task_get_task_data (task); + guint timeout_ms = g_resolver_get_timeout (G_RESOLVER (self)); + GCancellable *cancellable = g_task_get_cancellable (task); + + g_mutex_lock (&data->lock); + + g_thread_pool_push (self->thread_pool, g_object_ref (task), NULL); + + if (timeout_ms != 0) + { + data->timeout_source = g_timeout_source_new (timeout_ms); + g_source_set_static_name (data->timeout_source, "[gio] threaded resolver timeout"); + g_source_set_callback (data->timeout_source, G_SOURCE_FUNC (timeout_cb), task, NULL); + g_source_attach (data->timeout_source, GLIB_PRIVATE_CALL (g_get_worker_context) ()); + } + + if (cancellable != NULL) + { + data->cancellable_source = g_cancellable_source_new (cancellable); + g_source_set_static_name (data->cancellable_source, "[gio] threaded resolver cancellable"); + g_source_set_callback (data->cancellable_source, G_SOURCE_FUNC (cancelled_cb), task, NULL); + g_source_attach (data->cancellable_source, GLIB_PRIVATE_CALL (g_get_worker_context) ()); + } + + g_mutex_unlock (&data->lock); +} + +static void +run_task_in_thread_pool_sync (GThreadedResolver *self, + GTask *task) +{ + LookupData *data = g_task_get_task_data (task); + + run_task_in_thread_pool_async (self, task); + + g_mutex_lock (&data->lock); + while (!data->has_returned) + g_cond_wait (&data->cond, &data->lock); + g_mutex_unlock (&data->lock); +} + +static void +threaded_resolver_worker_cb (gpointer task_data, + gpointer user_data) +{ + GTask *task = G_TASK (g_steal_pointer (&task_data)); + LookupData *data = g_task_get_task_data (task); + GCancellable *cancellable = g_task_get_cancellable (task); + GError *local_error = NULL; + gboolean should_return; + + switch (data->lookup_type) { + case LOOKUP_BY_NAME: + { + GList *addresses = do_lookup_by_name (data->lookup_by_name.hostname, + data->lookup_by_name.address_family, + cancellable, + &local_error); + + g_mutex_lock (&data->lock); + should_return = g_atomic_int_compare_and_exchange (&data->will_return, NOT_YET, COMPLETED); + g_mutex_unlock (&data->lock); + + if (should_return) + { + if (addresses != NULL) + g_task_return_pointer (task, g_steal_pointer (&addresses), (GDestroyNotify) g_resolver_free_addresses); + else + g_task_return_error (task, g_steal_pointer (&local_error)); + } + + g_clear_pointer (&addresses, g_resolver_free_addresses); + } + break; + case LOOKUP_BY_ADDRESS: + { + gchar *name = do_lookup_by_address (data->lookup_by_address.address, + cancellable, + &local_error); + + g_mutex_lock (&data->lock); + should_return = g_atomic_int_compare_and_exchange (&data->will_return, NOT_YET, COMPLETED); + g_mutex_unlock (&data->lock); + + if (should_return) + { + if (name != NULL) + g_task_return_pointer (task, g_steal_pointer (&name), g_free); + else + g_task_return_error (task, g_steal_pointer (&local_error)); + } + + g_clear_pointer (&name, g_free); + } + break; + case LOOKUP_RECORDS: + { + GList *records = do_lookup_records (data->lookup_records.rrname, + data->lookup_records.record_type, + cancellable, + &local_error); + + g_mutex_lock (&data->lock); + should_return = g_atomic_int_compare_and_exchange (&data->will_return, NOT_YET, COMPLETED); + g_mutex_unlock (&data->lock); + + if (should_return) + { + if (records != NULL) + g_task_return_pointer (task, g_steal_pointer (&records), (GDestroyNotify) free_records); + else + g_task_return_error (task, g_steal_pointer (&local_error)); + } + + g_clear_pointer (&records, free_records); + } + break; + default: + g_assert_not_reached (); + } + + /* Signal completion of a task. */ + g_mutex_lock (&data->lock); + g_assert (!data->has_returned); + data->has_returned = TRUE; + g_cond_broadcast (&data->cond); + g_mutex_unlock (&data->lock); + + g_object_unref (task); +} static void g_threaded_resolver_class_init (GThreadedResolverClass *threaded_class) { + GObjectClass *object_class = G_OBJECT_CLASS (threaded_class); GResolverClass *resolver_class = G_RESOLVER_CLASS (threaded_class); + object_class->finalize = g_threaded_resolver_finalize; + 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; |