FreeBSD: Mostly reentrant resolver introduced in FreeBSD

Submitted by neveripe
on February 16, 2004 - 8:10pm

Brian Feldman introduced new versions of the resolver and getaddrinfo DNS functions that are mostly* reentrant**.

The getaddrinfo(3) function is defined for protocol-independent nodename-to-address translation. It performs the functionality of gethostbyname(3) and getservbyname(3), but in a more sophisticated manner.

* mostly - because it is still need to be polished and tested to be fully reentrant.

** reentrant function - a function that can be used at the same time from multiple threads, so long as they do it with different data.


From: "Brian F. Feldman" [email blocked]
Date: Mon, 09 Feb 2004 21:52:51 -0500
Subject: Re: mostly-reentrant resolver/getaddrinfo(3) 
List-Id: Technical Discussions relating to FreeBSD <freebsd-hackers.freebsd.org>
List-Archive: http://lists.freebsd.org/pipermail/freebsd-hackers
List-Subscribe: http://lists.freebsd.org/mailman/listinfo/freebsd-hackers

Alright, here we go!  I simplified some things out a bit and used 
pthread_once(3) to make things look a little cleaner.  The RES_BOGUS flag 
was unnecessary, and now single-threaded programs and the first thread of 
multi-threaded programs do not incur the allocation of per-thread resolver 
storage.  I also allocated all the per-thread storage at once because the 
user is not privy to that, anyway.  I've gotten good feedback so far, and 
the latest round of changes at the least "works for me" :)  Try it in your 
multi-tab Mozilla!

Index: include/resolv.h
===================================================================
RCS file: /usr/ncvs/src/include/resolv.h,v
retrieving revision 1.23
diff -u -r1.23 resolv.h
--- include/resolv.h	7 Dec 2003 12:32:23 -0000	1.23
+++ include/resolv.h	10 Feb 2004 00:55:35 -0000
@@ -200,7 +200,12 @@
 	char *	humanname;	/* Its fun name, like "mail exchanger" */
 };
 
-extern struct __res_state _res;
+__BEGIN_DECLS
+extern struct __res_state *___res(void);
+extern struct __res_state_ext *___res_ext(void);
+__END_DECLS
+#define	_res		(*___res())
+#define	_res_ext	(*___res_ext())
 /* for INET6 */
 extern struct __res_state_ext _res_ext;
 
Index: lib/libc/include/reentrant.h
===================================================================
RCS file: /usr/ncvs/src/lib/libc/include/reentrant.h,v
retrieving revision 1.2
diff -u -r1.2 reentrant.h
--- lib/libc/include/reentrant.h	1 Nov 2002 09:37:17 -0000	1.2
+++ lib/libc/include/reentrant.h	10 Feb 2004 01:11:45 -0000
@@ -94,10 +94,12 @@
 #define mutex_t			pthread_mutex_t
 #define cond_t			pthread_cond_t
 #define rwlock_t		pthread_rwlock_t
+#define once_t			pthread_once_t
 
 #define thread_key_t		pthread_key_t
 #define MUTEX_INITIALIZER	PTHREAD_MUTEX_INITIALIZER
 #define RWLOCK_INITIALIZER	PTHREAD_RWLOCK_INITIALIZER
+#define ONCE_INITIALIZER	PTHREAD_ONCE_INIT
 
 #define mutex_init(m, a)	_pthread_mutex_init(m, a)
 #define mutex_lock(m)		if (__isthreaded) \
@@ -127,6 +129,7 @@
 #define thr_getspecific(k)	_pthread_getspecific(k)
 #define thr_sigsetmask(f, n, o)	_pthread_sigmask(f, n, o)
 
+#define thr_once(o, i)		_pthread_once(o, i)
 #define thr_self()		_pthread_self()
 #define thr_exit(x)		_pthread_exit(x)
 #define thr_main()		_pthread_main_np()
Index: lib/libc/net/getaddrinfo.c
===================================================================
RCS file: /usr/ncvs/src/lib/libc/net/getaddrinfo.c,v
retrieving revision 1.48
diff -u -r1.48 getaddrinfo.c
--- lib/libc/net/getaddrinfo.c	30 Oct 2003 17:36:53 -0000	1.48
+++ lib/libc/net/getaddrinfo.c	6 Feb 2004 06:20:23 -0000
@@ -1511,6 +1511,7 @@
 		return 0;
 	}
 
+	THREAD_UNLOCK();
 	switch (_nsdispatch(&result, dtab, NSDB_HOSTS, "getaddrinfo",
 			default_dns_files, hostname, pai)) {
 	case NS_TRYAGAIN:
@@ -1524,20 +1525,20 @@
 		goto free;
 	case NS_SUCCESS:
 		error = 0;
+		THREAD_LOCK();
 		for (cur = result; cur; cur = cur->ai_next) {
 			GET_PORT(cur, servname);
 			/* canonname should be filled already */
 		}
+		THREAD_UNLOCK();
 		break;
 	}
-	THREAD_UNLOCK();
 
 	*res = result;
 
 	return 0;
 
 free:
-	THREAD_UNLOCK();
 	if (result)
 		freeaddrinfo(result);
 	return error;
@@ -2037,6 +2038,7 @@
 	memset(&sentinel, 0, sizeof(sentinel));
 	cur = &sentinel;
 
+	THREAD_LOCK();
 	_sethtent();
 	while ((p = _gethtent(name, pai)) != NULL) {
 		cur->ai_next = p;
@@ -2044,6 +2046,7 @@
 			cur = cur->ai_next;
 	}
 	_endhtent();
+	THREAD_UNLOCK();
 
 	*((struct addrinfo **)rv) = sentinel.ai_next;
 	if (sentinel.ai_next == NULL)
@@ -2152,9 +2155,12 @@
 	memset(&sentinel, 0, sizeof(sentinel));
 	cur = &sentinel;
 
+	THREAD_LOCK();
 	if (!__ypdomain) {
-		if (_yp_check(&__ypdomain) == 0)
+		if (_yp_check(&__ypdomain) == 0) {
+			THREAD_UNLOCK();
 			return NS_UNAVAIL;
+		}
 	}
 	if (__ypcurrent)
 		free(__ypcurrent);
@@ -2189,6 +2195,7 @@
 				cur = cur->ai_next;
 		}
 	}
+	THREAD_UNLOCK();
 
 	if (sentinel.ai_next == NULL) {
 		h_errno = HOST_NOT_FOUND;
Index: lib/libc/net/res_init.c
===================================================================
RCS file: /usr/ncvs/src/lib/libc/net/res_init.c,v
retrieving revision 1.31
diff -u -r1.31 res_init.c
--- lib/libc/net/res_init.c	7 Dec 2003 12:32:24 -0000	1.31
+++ lib/libc/net/res_init.c	10 Feb 2004 01:01:04 -0000
@@ -91,7 +91,11 @@
 #include <unistd.h>
 #include <netdb.h>
 
+#include "namespace.h"
+#include "reentrant.h"
+#include "un-namespace.h"
 #include "res_config.h"
+#include "res_send_private.h"
 
 static void res_setoptions(char *, char *);
 
@@ -106,16 +110,13 @@
 #endif
 
 /*
- * Resolver state default settings.
+ * Check structure for failed per-thread allocations.
  */
-
-struct __res_state _res
-# if defined(__BIND_RES_TEXT)
-	= { RES_TIMEOUT, }	/* Motorola, et al. */
-# endif
-	;
-
-struct __res_state_ext _res_ext;
+static struct res_per_thread {
+	struct __res_state res_state;
+	struct __res_state_ext res_state_ext;
+	struct __res_send_private res_send_private;
+} _res_per_thread_bogus;
 
 /*
  * Set up default settings.  If the configuration file exist, the values
@@ -142,6 +143,7 @@
 res_init()
 {
 	FILE *fp;
+	struct __res_send_private *rsp;
 	char *cp, **pp;
 	int n;
 	char buf[MAXDNAME];
@@ -157,6 +159,19 @@
 #endif
 
 	/*
+	 * If allocation of memory for this thread's resolver has failed,
+	 * return the error to the user.
+	 */
+	if (&_res == &_res_per_thread_bogus.res_state)
+		return (-1);
+	rsp = ___res_send_private();
+	rsp->s = -1;
+	rsp->connected = 0;
+	rsp->vc = 0;
+	rsp->af = 0;
+	rsp->Qhook = NULL;
+	rsp->Rhook = NULL;
+	/*
 	 * These three fields used to be statically initialized.  This made
 	 * it hard to use this code in a shared library.  It is necessary,
 	 * now that we're doing dynamic initialization here, that we preserve
@@ -595,6 +610,88 @@
 
 	gettimeofday(&now, NULL);
 	return (0xffff & (now.tv_sec ^ now.tv_usec ^ getpid()));
+}
+
+/*
+ * Resolver state default settings.
+ */
+
+#undef _res
+#undef _res_ext
+#ifdef __BIND_RES_TEXT
+struct __res_state _res = { RES_TIMEOUT };	/* Motorola, et al. */
+#else
+struct __res_state _res;
+#endif
+struct __res_state_ext _res_ext;
+static struct __res_send_private _res_send_private;
+
+static thread_key_t res_key;
+static once_t res_init_once = ONCE_INITIALIZER;
+static int res_thr_keycreated = 0;
+
+static void
+free_res(void *ptr)
+{
+	struct res_per_thread *myrsp = ptr;
+
+	if (myrsp->res_state.options & RES_INIT)
+		res_close();
+	free(myrsp);
+}
+
+static void
+res_keycreate(void)
+{
+	res_thr_keycreated = thr_keycreate(&res_key, free_res) == 0;
+}
+
+static struct res_per_thread *
+allocate_res(void)
+{
+	struct res_per_thread *myrsp;
+	
+	if (thr_once(&res_init_once, res_keycreate) != 0 ||
+	    !res_thr_keycreated)
+		return (&_res_per_thread_bogus);
+
+	myrsp = thr_getspecific(res_key);
+	if (myrsp != NULL)
+		return (myrsp);
+	myrsp = calloc(1, sizeof(*myrsp));
+	if (myrsp == NULL)
+		return (&_res_per_thread_bogus);
+#ifdef __BIND_RES_TEXT
+	myrsp->res_state.options = RES_TIMEOUT;		/* Motorola, et al. */
+#endif
+	if (thr_setspecific(res_key, myrsp) == 0)
+		return (myrsp);
+	free(myrsp);
+	return (&_res_per_thread_bogus);
+}
+
+struct __res_state *
+___res(void)
+{
+	if (thr_main() != 0)
+		return (&_res);
+	return (&allocate_res()->res_state);
+}
+
+struct __res_state_ext *
+___res_ext(void)
+{
+	if (thr_main() != 0)
+		return (&_res_ext);
+	return (&allocate_res()->res_state_ext);
+}
+
+struct __res_send_private *
+___res_send_private(void)
+{
+	if (thr_main() != 0)
+		return (&_res_send_private);
+	return (&allocate_res()->res_send_private);
 }
 
 /*
Index: lib/libc/net/res_send.c
===================================================================
RCS file: /usr/ncvs/src/lib/libc/net