[glib] gio: Port GThreadedResolver to use res_nquery() to fix thread-safety
- From: Philip Withnall <pwithnall src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib] gio: Port GThreadedResolver to use res_nquery() to fix thread-safety
- Date: Fri, 2 Feb 2018 17:11:34 +0000 (UTC)
commit 40be86bb0e422247673ccc36fc3c493c882b4390
Author: Philip Withnall <withnall endlessm com>
Date: Fri Jan 5 14:26:35 2018 +0000
gio: Port GThreadedResolver to use res_nquery() to fix thread-safety
res_query() uses global state in the form of the struct __res_state
which contains the contents of resolv.conf (and other things). On Linux,
this state seems to be thread-local, so there is no problem. On OS X,
however, it is not, and hence multiple res_query() calls from parallel
threads will compete and return bogus results.
The fix for this is to use res_nquery(), introduced in BIND 8.2, which
takes an explicit state argument. This allows us to manually store the
state thread-locally. If res_nquery() isn’t available, we fall back to
res_query(). It should be available on OS X though. As a data point,
it’s available on Fedora 27.
There’s a slight complication in the fact that OS X requires the state
to be freed using res_ndestroy() rather than res_nclose(). Linux uses
res_nclose().
(See, for example, the NetBSD man page:
https://www.unix.com/man-page/netbsd/3/res_ninit/. The Linux one is
incomplete and not so useful:
http://man7.org/linux/man-pages/man3/resolver.3.html.)
The new code will call res_ninit() once per res_nquery() task. This is
not optimal, but no worse than before — since res_query() was being
called in a worker thread, on Linux, it would implicitly initialise the
thread-local struct __res_state when it was called. We’ve essentially
just made that explicit. In practical terms, this means a
stat("/etc/resolv.conf") call per res_nquery() task.
In future, we could improve this by using an explicit thread pool with
some manually-created worker threads, each of which initialises a struct
__res_state on spawning, and only updates it on receiving
the #GResolver::reload signal.
Signed-off-by: Philip Withnall <withnall endlessm com>
https://bugzilla.gnome.org/show_bug.cgi?id=792050
config.h.meson | 12 +++++++++++
config.h.win32.in | 12 +++++++++++
configure.ac | 49 +++++++++++++++++++++++++++++++++++++++++++++++
gio/gthreadedresolver.c | 32 ++++++++++++++++++++++++++++++
gio/meson.build | 48 ++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 153 insertions(+), 0 deletions(-)
---
diff --git a/config.h.meson b/config.h.meson
index 2350fd3..d540cb8 100644
--- a/config.h.meson
+++ b/config.h.meson
@@ -346,6 +346,18 @@
/* Define to 1 if you have the 'res_init' function. */
#mesondefine HAVE_RES_INIT
+/* Define to 1 if you have the 'res_nclose' function. */
+#mesondefine HAVE_RES_NCLOSE
+
+/* Define to 1 if you have the 'res_ndestroy' function. */
+#mesondefine HAVE_RES_NDESTROY
+
+/* Define to 1 if you have the 'res_ninit' function. */
+#mesondefine HAVE_RES_NINIT
+
+/* Define to 1 if you have the 'res_nquery' function. */
+#mesondefine HAVE_RES_NQUERY
+
/* Define to 1 if you have the <sched.h> header file. */
#mesondefine HAVE_SCHED_H
diff --git a/config.h.win32.in b/config.h.win32.in
index b607cca..b50d814 100644
--- a/config.h.win32.in
+++ b/config.h.win32.in
@@ -356,6 +356,18 @@
/* Define to 1 if you have the 'res_init' function. */
/* #undef HAVE_RES_INIT */
+/* Define to 1 if you have the 'res_nclose' function. */
+/* #undef HAVE_RES_NCLOSE */
+
+/* Define to 1 if you have the 'res_ndestroy' function. */
+/* #undef HAVE_RES_NDESTROY */
+
+/* Define to 1 if you have the 'res_ninit' function. */
+/* #undef HAVE_RES_NINIT */
+
+/* Define to 1 if you have the 'res_nquery' function. */
+/* #undef HAVE_RES_NQUERY */
+
/* Define to 1 if you have the <sched.h> header file. */
/* #undef HAVE_SCHED_H */
diff --git a/configure.ac b/configure.ac
index 2d9a4e8..a4ceedb 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1016,6 +1016,7 @@ AS_IF([test $glib_native_win32 = yes], [
[AC_MSG_ERROR(Could not find socket())]))
save_libs="$LIBS"
LIBS="$LIBS $NETWORK_LIBS"
+
AC_MSG_CHECKING([for res_init])
AC_TRY_LINK([#include <sys/types.h>
#include <netinet/in.h>
@@ -1026,6 +1027,54 @@ AS_IF([test $glib_native_win32 = yes], [
],[AC_MSG_RESULT([yes])
AC_DEFINE(HAVE_RES_INIT, 1, [Define to 1 if you have the 'res_init' function.])
],[AC_MSG_RESULT([no])])
+
+ AC_MSG_CHECKING([for res_nclose])
+ AC_TRY_LINK([#include <sys/types.h>
+ #include <netinet/in.h>
+ #include <arpa/nameser.h>
+ #include <resolv.h>
+ ],[
+ struct __res_state res;
+ res_nclose(&res);
+ ],[AC_MSG_RESULT([yes])
+ AC_DEFINE(HAVE_RES_NCLOSE, 1, [Define to 1 if you have the 'res_nclose' function.])
+ ],[AC_MSG_RESULT([no])])
+
+ AC_MSG_CHECKING([for res_ndestroy])
+ AC_TRY_LINK([#include <sys/types.h>
+ #include <netinet/in.h>
+ #include <arpa/nameser.h>
+ #include <resolv.h>
+ ],[
+ struct __res_state res;
+ res_ndestroy(&res);
+ ],[AC_MSG_RESULT([yes])
+ AC_DEFINE(HAVE_RES_NDESTROY, 1, [Define to 1 if you have the 'res_ndestroy' function.])
+ ],[AC_MSG_RESULT([no])])
+
+ AC_MSG_CHECKING([for res_ninit])
+ AC_TRY_LINK([#include <sys/types.h>
+ #include <netinet/in.h>
+ #include <arpa/nameser.h>
+ #include <resolv.h>
+ ],[
+ struct __res_state res;
+ res_ninit(&res);
+ ],[AC_MSG_RESULT([yes])
+ AC_DEFINE(HAVE_RES_NINIT, 1, [Define to 1 if you have the 'res_ninit' function.])
+ ],[AC_MSG_RESULT([no])])
+
+ AC_MSG_CHECKING([for res_nquery])
+ AC_TRY_LINK([#include <sys/types.h>
+ #include <netinet/in.h>
+ #include <arpa/nameser.h>
+ #include <resolv.h>
+ ],[
+ struct __res_state res;
+ res_nquery(&res, "test", 0, 0, (void *)0, 0);
+ ],[AC_MSG_RESULT([yes])
+ AC_DEFINE(HAVE_RES_NQUERY, 1, [Define to 1 if you have the 'res_nquery' function.])
+ ],[AC_MSG_RESULT([no])])
LIBS="$save_libs"
])
AC_SUBST(NETWORK_LIBS)
diff --git a/gio/gthreadedresolver.c b/gio/gthreadedresolver.c
index 7941d95..fc5c1bb 100644
--- a/gio/gthreadedresolver.c
+++ b/gio/gthreadedresolver.c
@@ -824,12 +824,36 @@ do_lookup_records (GTask *task,
GByteArray *answer;
gint rrtype;
+#ifdef HAVE_RES_NQUERY
+ /* Load the resolver state. This is done once per worker thread, and the
+ * #GResolver::reload signal is ignored (since we always reload). This could
+ * be improved by having an explicit worker thread pool, with each thread
+ * containing some state which is initialised at thread creation time and
+ * updated in response to #GResolver::reload.
+ *
+ * What we have currently is not particularly worse than using res_query() in
+ * worker threads, since it would transparently call res_init() for each new
+ * worker thread. (Although the workers would get reused by the
+ * #GThreadPool.) */
+ struct __res_state res;
+ if (res_ninit (&res) != 0)
+ {
+ g_task_return_new_error (task, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL,
+ _("Error resolving “%s”"), lrd->rrname);
+ return;
+ }
+#endif
+
rrtype = g_resolver_record_type_to_rrtype (lrd->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);
+#else
len = res_query (lrd->rrname, C_IN, rrtype, answer->data, answer->len);
+#endif
/* If answer fit in the buffer then we're done */
if (len < 0 || len < (gint)answer->len)
@@ -845,6 +869,14 @@ do_lookup_records (GTask *task,
records = g_resolver_records_from_res_query (lrd->rrname, rrtype, answer->data, len, herr, &error);
g_byte_array_free (answer, TRUE);
+#if defined(HAVE_RES_NDESTROY)
+ res_ndestroy (&res);
+#elif defined(HAVE_RES_NCLOSE)
+ res_nclose (&res);
+#elif defined(HAVE_RES_NINIT)
+#error "Your platform has res_ninit() but not res_nclose() or res_ndestroy(). Please file a bug at
https://bugzilla.gnome.org/enter_bug.cgi?product=glib"
+#endif
+
#else
DNS_STATUS status;
diff --git a/gio/meson.build b/gio/meson.build
index a72baad..1d61d23 100644
--- a/gio/meson.build
+++ b/gio/meson.build
@@ -87,6 +87,54 @@ if host_system != 'windows'
glib_conf.set('HAVE_RES_INIT', 1)
endif
+ # res_nclose()
+ if cc.links('''#include <sys/types.h>
+ #include <netinet/in.h>
+ #include <arpa/nameser.h>
+ #include <resolv.h>
+ int main (int argc, char ** argv) {
+ struct __res_state res;
+ return res_nclose(&res);
+ }''', args : network_args, name : 'res_nclose()')
+ glib_conf.set('HAVE_RES_NCLOSE', 1)
+ endif
+
+ # res_ndestroy()
+ if cc.links('''#include <sys/types.h>
+ #include <netinet/in.h>
+ #include <arpa/nameser.h>
+ #include <resolv.h>
+ int main (int argc, char ** argv) {
+ struct __res_state res;
+ return res_ndestroy(&res);
+ }''', args : network_args, name : 'res_ndestroy()')
+ glib_conf.set('HAVE_RES_NDESTROY', 1)
+ endif
+
+ # res_ninit()
+ if cc.links('''#include <sys/types.h>
+ #include <netinet/in.h>
+ #include <arpa/nameser.h>
+ #include <resolv.h>
+ int main (int argc, char ** argv) {
+ struct __res_state res;
+ return res_ninit(&res);
+ }''', args : network_args, name : 'res_ninit()')
+ glib_conf.set('HAVE_RES_NINIT', 1)
+ endif
+
+ # res_nquery()
+ if cc.links('''#include <sys/types.h>
+ #include <netinet/in.h>
+ #include <arpa/nameser.h>
+ #include <resolv.h>
+ int main (int argc, char ** argv) {
+ struct __res_state res;
+ return res_nquery(&res, "test", 0, 0, (void *)0, 0);
+ }''', args : network_args, name : 'res_nquery()')
+ glib_conf.set('HAVE_RES_NQUERY', 1)
+ endif
+
if cc.compiles('''#include <netinet/in.h>
struct ip_mreqn foo;''',
name : 'struct ip_mreqn')
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]