netdb: non-blocking compat getaddrinfo_a()
authorMarko Kreen <markokr@gmail.com>
Fri, 25 Mar 2011 07:13:56 +0000 (09:13 +0200)
committerMarko Kreen <markokr@gmail.com>
Fri, 25 Mar 2011 20:33:57 +0000 (22:33 +0200)
Lauch single thread that does lookups.  It must not multi-thread
as thread-safety of libc getaddrinfo() is uncertain.

usual/netdb.c

index 7a719cc2c44ba49a36c596be209b3746244b0346..d7bb4a2fe26da2352e7f9bc933fb1571b4bdd135 100644 (file)
 
 #include <usual/socket.h>
 
+/* is compat function needed? */
 #ifndef HAVE_GETADDRINFO_A
 
-int getaddrinfo_a(int mode, struct gaicb *list[], int nitems, struct sigevent *sevp)
+/* full compat if threads are available */
+#ifdef HAVE_PTHREAD_H
+
+#include <pthread.h>
+#include <usual/list.h>
+
+/*
+ * Basic blocking lookup
+ */
+
+static void gaia_lookup(pthread_t origin, struct gaicb *list[], int nitems, struct sigevent *sevp)
 {
        struct gaicb *g;
        int i, res;
 
-       if (nitems <= 0)
-               return 0;
-       if (mode != GAI_WAIT && mode != GAI_NOWAIT)
-               goto einval;
-
        for (i = 0; i < nitems; i++) {
                g = list[i];
                res = getaddrinfo(g->ar_name, g->ar_service, g->ar_request, &g->ar_result);
                g->_state = res;
        }
 
-       if (!sevp || sevp->sigev_notify == SIGEV_NONE)
-               return 0;
-
-       if (sevp->sigev_notify == SIGEV_SIGNAL) {
-               raise(sevp->sigev_signo);
+       if (!sevp || sevp->sigev_notify == SIGEV_NONE) {
+               /* do nothing */
+       } else if (sevp->sigev_notify == SIGEV_SIGNAL) {
+               /* send signal */
+               pthread_kill(origin, sevp->sigev_signo);
        } else if (sevp->sigev_notify == SIGEV_THREAD) {
+               /* call function */
                union sigval sv;
                sevp->sigev_notify_function(sv);
-       } else
-               goto einval;
+       }
+}
+
+/*
+ * Thread to run blocking lookup in
+ */
+
+struct GAIAContext {
+       struct List req_list;
+       pthread_cond_t cond;
+       pthread_mutex_t lock;
+       pthread_t thread;
+};
+
+struct GAIARequest {
+       struct List node;
+       pthread_t origin;
+       int nitems;
+       struct sigevent sev;
+       struct gaicb *list[FLEX_ARRAY];
+};
+
+#define RQ_SIZE(n) (offsetof(struct GAIARequest,list) + (n)*(sizeof(struct gaicb *)))
+
+static void gaia_lock_reqs(struct GAIAContext *ctx)
+{
+       pthread_mutex_lock(&ctx->lock);
+}
+
+static void gaia_unlock_reqs(struct GAIAContext *ctx)
+{
+       pthread_mutex_unlock(&ctx->lock);
+}
+
+static void *gaia_lookup_thread(void *arg)
+{
+       struct GAIAContext *ctx = arg;
+       struct GAIARequest *rq;
+       struct List *el;
+
+       gaia_lock_reqs(ctx);
+loop:
+       while (1) {
+               el = list_pop(&ctx->req_list);
+               if (!el)
+                       break;
+               gaia_unlock_reqs(ctx);
+
+               rq = container_of(el, struct GAIARequest, node);
+               gaia_lookup(rq->origin, rq->list, rq->nitems, &rq->sev);
+               free(rq);
+
+               gaia_lock_reqs(ctx);
+       }
+       pthread_cond_wait(&ctx->cond, &ctx->lock);
+       goto loop;
+
+       return NULL;
+}
+
+/*
+ * Functions run in user thread
+ */
+
+static int gaia_post_request(struct GAIAContext *ctx, struct gaicb *list[], int nitems, struct sigevent *sevp)
+{
+       struct GAIARequest *rq;
+       
+       rq = malloc(RQ_SIZE(nitems));
+       if (!rq)
+               return EAI_MEMORY;
+
+       list_init(&rq->node);
+       rq->origin = pthread_self();
+       rq->nitems = nitems;
+       if (sevp)
+               rq->sev = *sevp;
+       else
+               rq->sev.sigev_notify = SIGEV_NONE;
+       memcpy(rq->list, list, sizeof(struct gaicb *));
+
+       gaia_lock_reqs(ctx);
+       list_append(&ctx->req_list, &rq->node);
+       gaia_unlock_reqs(ctx);
+
+       pthread_cond_signal(&ctx->cond);
+
        return 0;
+}
+
+static struct GAIAContext *gaia_create_context(void)
+{
+       struct GAIAContext *ctx;
+       int err;
+
+       ctx = malloc(sizeof(*ctx));
+       if (!ctx)
+               return NULL;
+
+       list_init(&ctx->req_list);
+       err = pthread_cond_init(&ctx->cond, NULL);
+       if (err)
+               goto failed;
+
+       err = pthread_mutex_init(&ctx->lock, NULL);
+       if (err)
+               goto failed;
+
+       err = pthread_create(&ctx->thread, NULL, gaia_lookup_thread, ctx);
+       if (err)
+               goto failed;
+
+       return ctx;
+
+failed:
+       free(ctx);
+       errno = err;
+       return NULL;
+}
+
+/*
+ * Final interface
+ */
+
+int getaddrinfo_a(int mode, struct gaicb *list[], int nitems, struct sigevent *sevp)
+{
+       static struct GAIAContext *ctx;
+
+       if (nitems <= 0)
+               return 0;
+
+       if (sevp && sevp->sigev_notify != SIGEV_NONE
+           && sevp->sigev_notify != SIGEV_SIGNAL
+           && sevp->sigev_notify != SIGEV_THREAD)
+               goto einval;
+
+       if (mode == GAI_WAIT) {
+               gaia_lookup(pthread_self(), list, nitems, sevp);
+               return 0;
+       } else if (mode == GAI_NOWAIT) {
+               if (!ctx) {
+                       ctx = gaia_create_context();
+                       if (!ctx)
+                               return EAI_MEMORY;
+               }
+               return gaia_post_request(ctx, list, nitems, sevp);
+       }
 einval:
        errno = EINVAL;
        return EAI_SYSTEM;
 }
 
-#endif
+#else /* without threads not much to do */
+
+int getaddrinfo_a(int mode, struct gaicb *list[], int nitems, struct sigevent *sevp)
+{
+       errno = ENOSYS;
+       return EAI_SYSTEM;
+}
+
+#endif /* !HAVE_PTHREAD_H */
+#endif /* !HAVE_GETADDRINFO_A */