Lookup from ThreadsΒΆ

This example shows how to use libunbound from a threaded program. It is a modification of the example program from the Setup the Context section. It creates two threads and resolves from both threads.

This example uses pthreads, and assumes that libunbound was compiled with threading enabled (which is the default, if pthreads can be found). To compile the example pass the compiler the option -lpthread.

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include <unbound.h>

#include <pthread.h>

/* The main function of the first thread */
void* thread_one(void* threadarg)
{
        struct ub_ctx* ctx = (struct ub_ctx*)threadarg;
        struct ub_result* result;
        int retval;
        /* query for webserver */
        retval = ub_resolve(ctx, "www.nlnetlabs.nl",
                1 /* TYPE A (IPv4 address) */,
                1 /* CLASS IN (internet) */, &result);
        if(retval != 0) {
                printf("resolve error: %s\n", ub_strerror(retval));
                return NULL;
        }

        /* show first result */
        if(result->havedata)
                printf("Thread1: address of %s is %s\n", result->qname,
                        inet_ntoa(*(struct in_addr*)result->data[0]));

        /* exit thread */
        ub_resolve_free(result);
        return NULL;
}

/* The main function of the second thread */
void* thread_two(void* threadarg)
{
        struct ub_ctx* ctx = (struct ub_ctx*)threadarg;
        struct ub_result* result;
        int retval;
        /* query for webserver */
        retval = ub_resolve(ctx, "www.google.nl",
                1 /* TYPE A (IPv4 address) */,
                1 /* CLASS IN (internet) */, &result);
        if(retval != 0) {
                printf("resolve error: %s\n", ub_strerror(retval));
                return NULL;
        }

        /* show first result */
        if(result->havedata)
                printf("Thread2: address of %s is %s\n", result->qname,
                        inet_ntoa(*(struct in_addr*)result->data[0]));

        /* exit thread */
        ub_resolve_free(result);
        return NULL;
}

int main(void)
{
        struct ub_ctx* ctx;
        int retval;
        pthread_t t1, t2;

        /* create context */
        ctx = ub_ctx_create();
        if(!ctx) {
                printf("error: could not create unbound context\n");
                return 1;
        }
        /* read /etc/resolv.conf for DNS proxy settings (from DHCP) */
        if( (retval=ub_ctx_resolvconf(ctx, "/etc/resolv.conf")) != 0) {
                printf("error reading resolv.conf: %s. errno says: %s\n",
                        ub_strerror(retval), strerror(errno));
                return 1;
        }
        /* read /etc/hosts for locally supplied host addresses */
        if( (retval=ub_ctx_hosts(ctx, "/etc/hosts")) != 0) {
                printf("error reading hosts: %s. errno says: %s\n",
                        ub_strerror(retval), strerror(errno));
                return 1;
        }

        /* start two threads, uses pthreads */
        pthread_create(&t1, NULL, thread_one, ctx);
        pthread_create(&t2, NULL, thread_two, ctx);
        /* wait for both threads to complete */
        pthread_join(t1, NULL);
        pthread_join(t2, NULL);

        ub_ctx_delete(ctx);
        return 0;
}

Invocation of this program yields the following:

$ example_5
Thread1: address of www.nlnetlabs.nl is 213.154.224.1
Thread2: address of www.google.nl is 64.233.183.147

Sometimes, the result from thread 2 is printed first.

The example starts at the main program function. The unbound context is created and resolv.conf and /etc/hosts are read in. Then, two threads are started using pthread_create. The main program continues with waiting for those two threads to finish.

The first thread, thread_one, starts by obtaining a pointer to the unbound context from the thread argument. Then, www.nlnetlabs.nl is resolved, using the regular ub_resolve. The result is printed, and freed and the thread exits with return NULL.

The second thread, thread_two, does the same as the first thread, but looks up www.google.nl instead.

Using threads is easy when the context is created with ub_ctx_create. In this example, when both threads start resolving, they act as a 2-threaded resolver, and share results, validation outcomes and data. When one of the threads finishes its lookup, the other thread continues as a 1-threaded resolver. When the resolver is created with ub_ctx_create_event or ub_ctx_create_ub_event, with an event base, then it can only be accessed from one thread, usually the one that is running that event loop.

This example uses blocking resolution for both threads. You can use asynchronous resolution in threaded programs too. The function ub_resolve_async is used to perform a background lookup. The calling thread continues executing while the background lookup is in progress.

The application can decide if it wants the background lookup to be performed from a (forked) process or from a (newly created) thread, by setting ub_ctx_async. The default is to fork. The asynchronous resolution process or thread is deleted when ub_ctx_delete is called.

Callbacks from asynchronous lookups are performed when ub_process is called, just like in a single-threaded program. The thread from which the callbacks are called is the thread from which ub_process has been called. It is the responsibility of the application to signal other threads that lookup results are available.

It is possible to have a thread wait for the file descriptor from ub_ctx_fd (a pipe) to become readable, and process any pending lookup results with ub_process.