]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
libunbound work.
authorWouter Wijngaards <wouter@nlnetlabs.nl>
Tue, 4 Dec 2007 16:14:09 +0000 (16:14 +0000)
committerWouter Wijngaards <wouter@nlnetlabs.nl>
Tue, 4 Dec 2007 16:14:09 +0000 (16:14 +0000)
git-svn-id: file:///svn/unbound/trunk@802 be551aaa-1e26-0410-a405-d3ace91eadb9

doc/Changelog
libunbound/context.c [new file with mode: 0644]
libunbound/context.h [new file with mode: 0644]
libunbound/unbound.c

index dc4f349d79552e6186c27ef07435232be1e097fd..d26a94dced85f9ce83805f5aabcc7a9194e9e516 100644 (file)
@@ -2,6 +2,8 @@
        - minor Makefile fixup.
        - moved module-stack code out of daemon/daemon into services/modstack,
          preparing for code-reuse.
+       - move context into own header file.
+       - context query structure.
 
 3 December 2007: Wouter
        - changed checkconf/ to smallapp/ to make room for more support tools.
diff --git a/libunbound/context.c b/libunbound/context.c
new file mode 100644 (file)
index 0000000..90a3849
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * libunbound/context.c - validating context for unbound internal use
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file
+ *
+ * This file contains the validator context structure.
+ */
+#include "config.h"
+#include "libunbound/context.h"
+#include "util/module.h"
+#include "util/config_file.h"
+#include "services/modstack.h"
+#include "services/localzone.h"
+#include "services/cache/rrset.h"
+#include "services/cache/infra.h"
+#include "util/data/msgreply.h"
+#include "util/storage/slabhash.h"
+
+int 
+context_finalize(struct ub_val_ctx* ctx)
+{
+       struct config_file* cfg = ctx->env->cfg;
+       verbosity = cfg->verbosity;
+       log_init(cfg->logfile, cfg->use_syslog, NULL);
+       config_apply(cfg);
+       if(!modstack_setup(&ctx->mods, cfg->module_conf, ctx->env))
+               return UB_INITFAIL;
+       ctx->local_zones = local_zones_create();
+       if(!ctx->local_zones)
+               return UB_NOMEM;
+       if(!local_zones_apply_cfg(ctx->local_zones, cfg))
+               return UB_INITFAIL;
+       if(!ctx->env->msg_cache ||
+          cfg->msg_cache_size != slabhash_get_size(ctx->env->msg_cache) || 
+          cfg->msg_cache_slabs != ctx->env->msg_cache->size) {
+               slabhash_delete(ctx->env->msg_cache);
+               ctx->env->msg_cache = slabhash_create(cfg->msg_cache_slabs,
+                       HASH_DEFAULT_STARTARRAY, cfg->msg_cache_size,
+                       msgreply_sizefunc, query_info_compare,
+                       query_entry_delete, reply_info_delete, NULL);
+               if(!ctx->env->msg_cache)
+                       return UB_NOMEM;
+       }
+       ctx->env->rrset_cache = rrset_cache_adjust(ctx->env->rrset_cache,
+               ctx->env->cfg, ctx->env->alloc);
+       if(!ctx->env->rrset_cache)
+               return UB_NOMEM;
+       ctx->env->infra_cache = infra_adjust(ctx->env->infra_cache, cfg);
+       if(!ctx->env->infra_cache)
+               return UB_NOMEM;
+       return UB_NOERROR;
+}
+
+int context_query_cmp(const void* a, const void* b)
+{
+       if( *(int*)a < *(int*)b )
+               return -1;
+       if( *(int*)a > *(int*)b )
+               return 1;
+       return 0;
+}
+
+/** How many times to try to find an unused query-id-number for async */
+#define NUM_ID_TRIES 100000
+/** find next useful id number of 0 on error */
+static int
+find_id(struct ub_val_ctx* ctx, int* id)
+{
+       size_t tries = 0;
+       while(rbtree_search(&ctx->queries, &ctx->next_querynum)) {
+               ctx->next_querynum++; /* numerical wraparound is fine */
+               if(tries++ > NUM_ID_TRIES)
+                       return 0;
+       }
+       *id = ctx->next_querynum;
+       ctx->next_querynum++;
+       return 1;
+}
+
+struct ctx_query* 
+context_new(struct ub_val_ctx* ctx, char* name, int rrtype, int rrclass, 
+       ub_val_callback_t cb, void* cbarg)
+{
+       struct ctx_query* q = (struct ctx_query*)calloc(1, sizeof(*q));
+       if(!q) return NULL;
+       if(!find_id(ctx, &q->querynum)) {
+               free(q);
+               return NULL;
+       }
+       q->node.key = &q->querynum;
+       q->async = (cb != NULL);
+       q->cb = cb;
+       q->cb_arg = cbarg;
+       q->res = (struct ub_val_result*)calloc(1, sizeof(*q->res));
+       if(!q->res) {
+               free(q);
+               return NULL;
+       }
+       q->res->qname = strdup(name);
+       if(!q->res->qname) {
+               free(q->res);
+               free(q);
+               return NULL;
+       }
+       q->res->qtype = rrtype;
+       q->res->qclass = rrclass;
+
+       /* add to query list */
+       if(q->async)
+               ctx->num_async ++;
+       (void)rbtree_insert(&ctx->queries, &q->node);
+       return q;
+}
diff --git a/libunbound/context.h b/libunbound/context.h
new file mode 100644 (file)
index 0000000..1a6dfcf
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * libunbound/context.h - validating context for unbound internal use
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file
+ *
+ * This file contains the validator context structure.
+ */
+#ifndef LIBUNBOUND_CONTEXT_H
+#define LIBUNBOUND_CONTEXT_H
+#include "util/locks.h"
+#include "util/alloc.h"
+#include "util/rbtree.h"
+#include "services/modstack.h"
+#include "libunbound/unbound.h"
+
+/**
+ * The context structure
+ *
+ * Contains two pipes for async service
+ *     qq : write queries to the async service pid/tid.
+ *     rr : read results from the async service pid/tid.
+ */
+struct ub_val_ctx {
+       /* --- pipes --- */
+       /** mutex on query write pipe */
+       lock_basic_t qqpipe_lock;
+       /** the query write pipe, [0] read from, [1] write on */
+       int qqpipe[2];
+       /** mutex on result read pipe */
+       lock_basic_t rrpipe_lock;
+       /** the result read pipe, [0] read from, [1] write on */
+       int rrpipe[2];
+
+       /* --- shared data --- */
+       /** mutex for access to env.cfg, finalized and dothread */
+       lock_basic_t cfglock;
+       /** 
+        * The context has been finalized 
+        * This is after config when the first resolve is done.
+        * The modules are inited (module-init()) and shared caches created.
+        */
+       int finalized;
+
+       /** do threading (instead of forking) for async resolution */
+       int dothread;
+       /** next thread number for new threads */
+       int thr_next_num;
+       /** 
+        * List of alloc-cache-id points per threadnum for notinuse threads.
+        * Simply the entire struct alloc_cache with the 'super' member used
+        * to link a simply linked list. Reset super member to the superalloc
+        * before use.
+        */
+       struct alloc_cache* alloc_list;
+
+       /** shared caches, and so on */
+       struct alloc_cache superalloc;
+       /** module env master value */
+       struct module_env* env;
+       /** module stack */
+       struct module_stack mods;
+       /** local authority zones */
+       struct local_zones* local_zones;
+
+       /** next query number (to try) to use */
+       int next_querynum;
+       /** number of async queries outstanding */
+       size_t num_async;
+       /** 
+        * Tree of outstanding queries. Indexed by querynum 
+        * Used when results come in for async to lookup.
+        * Used when cancel is done for lookup (and delete).
+        * Used to see if querynum is free for use.
+        * Content of type ctx_query.
+        */ 
+       rbtree_t queries;
+};
+
+/**
+ * The queries outstanding for the libunbound resolver.
+ * These are outstanding for async resolution.
+ * But also, outstanding for sync resolution by one of the threads that
+ * has joined the threadpool.
+ */
+struct ctx_query {
+       /** node in rbtree, must be first entry, key is ptr to the querynum */
+       struct rbnode_t node;
+       /** query id number, key for node */
+       int querynum;
+       /** was this an async query? */
+       int async;
+
+       /** for async query, the callback function */
+       ub_val_callback_t cb;
+       /** for async query, the callback user arg */
+       void* cb_arg;
+
+       /** result structure, also contains original query, type, class.
+        * malloced ptr ready to hand to the client. */
+       struct ub_val_result* res;
+};
+
+/**
+ * The error constants
+ */
+enum ub_ctx_err {
+       /** no error */
+       UB_NOERROR = 0,
+       /** alloc failure */
+       UB_NOMEM,
+       /** socket operation */
+       UB_SOCKET,
+       /** syntax error */
+       UB_SYNTAX,
+       /** DNS service failed */
+       UB_SERVFAIL,
+       /** initialization failed (bad settings) */
+       UB_INITFAIL
+};
+
+/** 
+ * finalize a context.
+ * @param ctx: context to finalize. creates shared data.
+ * @return 0 if OK, or errcode.
+ */
+int context_finalize(struct ub_val_ctx* ctx);
+
+/** compare two ctx_query elements */
+int context_query_cmp(const void* a, const void* b);
+
+/**
+ * Create new query in context, add to querynum list.
+ * @param ctx: context
+ * @param name: query name
+ * @param rrtype: type
+ * @param rrclass: class
+ * @param cb: callback for async, or NULL for sync.
+ * @param cbarg: user arg for async queries.
+ * @return new ctx_query or NULL for malloc failure.
+ */
+struct ctx_query* context_new(struct ub_val_ctx* ctx, char* name, int rrtype,
+        int rrclass, ub_val_callback_t cb, void* cbarg);
+
+#endif /* LIBUNBOUND_CONTEXT_H */
index bf9d236b02c8cd419060ee2ff2eb09cb4a75d643..198d42c5470a2e7fbe71a34ef5382b2910c54522 100644 (file)
 /* include the public api first, it should be able to stand alone */
 #include "libunbound/unbound.h"
 #include "config.h"
+#include "libunbound/context.h"
 #include "util/locks.h"
 #include "util/config_file.h"
 #include "util/alloc.h"
-
-/**
- * The context structure
- *
- * Contains two pipes for async service
- *     qq : write queries to the async service pid/tid.
- *     rr : read results from the async service pid/tid.
- */
-struct ub_val_ctx {
-       /** mutex on query write pipe */
-       lock_basic_t qqpipe_lock;
-       /** the query write pipe, [0] read from, [1] write on */
-       int qqpipe[2];
-       /** mutex on result read pipe */
-       lock_basic_t rrpipe_lock;
-       /** the result read pipe, [0] read from, [1] write on */
-       int rrpipe[2];
-
-       /** configuration options */
-       struct config_file* cfg;
-       /** do threading (instead of forking) for async resolution */
-       int dothread;
-
-       /** shared data */
-       /* list of alloc-cache-id points and nextthreadnum */
-       /*struct shareddata* shared;*/
-       /** outstanding querylist and next querynum (to try) */
-
-       /** shared caches, and so on */
-       struct alloc_cache superalloc;
-       /** module env master value */
-       struct module_env* env;
-       /** number of modules active, ids from 0 to num-1. */
-       int num_modules;
-       /** the module callbacks, array of num_modules length */
-       struct module_func_block** modfunc;
-       /** local authority zones */
-       struct local_zones* local_zones;
-
-       /** TODO list of outstanding queries */
-};
-
-/**
- * The error constants
- */
-enum ub_ctx_err {
-       /** no error */
-       UB_NOERROR = 0,
-       /** alloc failure */
-       UB_NOMEM,
-       /** socket operation */
-       UB_SOCKET,
-       /** syntax error */
-       UB_SYNTAX,
-       /** DNS service failed */
-       UB_SERVFAIL
-};
-
+#include "util/module.h"
+#include "util/log.h"
+#include "services/modstack.h"
+#include "services/localzone.h"
+#include "services/cache/infra.h"
+#include "services/cache/rrset.h"
 
 struct ub_val_ctx* 
 ub_val_ctx_create()
@@ -115,6 +64,10 @@ ub_val_ctx_create()
                return NULL;
        }
        checklock_start();
+       log_ident_set("libunbound");
+       verbosity = 0; /* errors only */
+       log_init(NULL, 0, NULL); /* logs to stderr */
+       alloc_init(&ctx->superalloc, NULL, 0);
        if(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx->qqpipe) == -1) {
                free(ctx);
                return NULL;
@@ -129,35 +82,80 @@ ub_val_ctx_create()
        }
        lock_basic_init(&ctx->qqpipe_lock);
        lock_basic_init(&ctx->rrpipe_lock);
-       ctx->cfg = config_create();
-       if(!ctx->cfg) {
+       lock_basic_init(&ctx->cfglock);
+       ctx->env = (struct module_env*)calloc(1, sizeof(*ctx->env));
+       if(!ctx->env) {
+               ub_val_ctx_delete(ctx);
+               errno = ENOMEM;
+               return NULL;
+       }
+       ctx->env->cfg = config_create();
+       if(!ctx->env->cfg) {
                ub_val_ctx_delete(ctx);
                errno = ENOMEM;
                return NULL;
        }
+       ctx->env->alloc = &ctx->superalloc;
+       ctx->env->worker = NULL;
+       ctx->env->need_to_validate = 0;
+       modstack_init(&ctx->mods);
+       rbtree_init(&ctx->queries, &context_query_cmp);
        return ctx;
 }
 
+/** delete q */
+static void
+delq(rbnode_t* n, void* ATTR_UNUSED(arg))
+{
+       struct ctx_query* q = (struct ctx_query*)n;
+       if(!q) return;
+       ub_val_result_free(q->res);
+       free(q);
+}
+
 void 
 ub_val_ctx_delete(struct ub_val_ctx* ctx)
 {
+       struct alloc_cache* a, *na;
        if(!ctx) return;
+       modstack_desetup(&ctx->mods, ctx->env);
+       a = ctx->alloc_list;
+       while(a) {
+               na = a->super;
+               a->super = &ctx->superalloc;
+               alloc_clear(a);
+               a = na;
+       }
+       alloc_clear(&ctx->superalloc);
+       local_zones_delete(ctx->local_zones);
        lock_basic_destroy(&ctx->qqpipe_lock);
        lock_basic_destroy(&ctx->rrpipe_lock);
+       lock_basic_destroy(&ctx->cfglock);
        close(ctx->qqpipe[0]);
        close(ctx->qqpipe[1]);
        close(ctx->rrpipe[0]);
        close(ctx->rrpipe[1]);
-       config_delete(ctx->cfg);
+       if(ctx->env) {
+               slabhash_delete(ctx->env->msg_cache);
+               rrset_cache_delete(ctx->env->rrset_cache);
+               infra_delete(ctx->env->infra_cache);
+               config_delete(ctx->env->cfg);
+               free(ctx->env);
+       }
+       traverse_postorder(&ctx->queries, delq, NULL);
        free(ctx);
 }
 
 int 
 ub_val_ctx_config(struct ub_val_ctx* ctx, char* fname)
 {
-       if(!config_read(ctx->cfg, fname)) {
+       lock_basic_lock(&ctx->cfglock);
+       ctx->finalized = 0;
+       if(!config_read(ctx->env->cfg, fname)) {
+               lock_basic_unlock(&ctx->cfglock);
                return UB_SYNTAX;
        }
+       lock_basic_unlock(&ctx->cfglock);
        return UB_NOERROR;
 }
 
@@ -166,10 +164,14 @@ ub_val_ctx_add_ta(struct ub_val_ctx* ctx, char* ta)
 {
        char* dup = strdup(ta);
        if(!dup) return UB_NOMEM;
-       if(!cfg_strlist_insert(&ctx->cfg->trust_anchor_list, dup)) {
+       lock_basic_lock(&ctx->cfglock);
+       ctx->finalized = 0;
+       if(!cfg_strlist_insert(&ctx->env->cfg->trust_anchor_list, dup)) {
+               lock_basic_unlock(&ctx->cfglock);
                free(dup);
                return UB_NOMEM;
        }
+       lock_basic_unlock(&ctx->cfglock);
        return UB_NOERROR;
 }
 
@@ -178,17 +180,24 @@ ub_val_ctx_trustedkeys(struct ub_val_ctx* ctx, char* fname)
 {
        char* dup = strdup(fname);
        if(!dup) return UB_NOMEM;
-       if(!cfg_strlist_insert(&ctx->cfg->trusted_keys_file_list, dup)) {
+       lock_basic_lock(&ctx->cfglock);
+       ctx->finalized = 0;
+       if(!cfg_strlist_insert(&ctx->env->cfg->trusted_keys_file_list, dup)) {
+               lock_basic_unlock(&ctx->cfglock);
                free(dup);
                return UB_NOMEM;
        }
+       lock_basic_unlock(&ctx->cfglock);
        return UB_NOERROR;
 }
 
 int 
 ub_val_ctx_async(struct ub_val_ctx* ctx, int dothread)
 {
+       lock_basic_lock(&ctx->cfglock);
+       ctx->finalized = 0;
        ctx->dothread = dothread;
+       lock_basic_unlock(&ctx->cfglock);
        return UB_NOERROR;
 }
 
@@ -223,13 +232,17 @@ ub_val_ctx_poll(struct ub_val_ctx* ctx)
 int 
 ub_val_ctx_wait(struct ub_val_ctx* ctx)
 {
+       lock_basic_lock(&ctx->cfglock);
        /* TODO until no more queries outstanding */
-       while(1) {
+       while(ctx->num_async > 0) {
+               lock_basic_unlock(&ctx->cfglock);
                lock_basic_lock(&ctx->rrpipe_lock);
                (void)pollit(ctx, NULL);
                lock_basic_unlock(&ctx->rrpipe_lock);
                ub_val_ctx_process(ctx);
+               lock_basic_lock(&ctx->cfglock);
        }
+       lock_basic_unlock(&ctx->cfglock);
        return UB_NOERROR;
 }
 
@@ -243,6 +256,7 @@ int
 ub_val_ctx_process(struct ub_val_ctx* ctx)
 {
        /* TODO */
+       /* ctx->num_asynx-- when handled; */
        return UB_NOMEM;
 }
 
@@ -250,7 +264,25 @@ int
 ub_val_resolve(struct ub_val_ctx* ctx, char* name, int rrtype, 
        int rrclass, int* secure, int* data, struct ub_val_result** result)
 {
+       struct ctx_query* q;
+
+       lock_basic_lock(&ctx->cfglock);
+       if(!ctx->finalized) {
+               int r = context_finalize(ctx);
+               if(r) {
+                       lock_basic_unlock(&ctx->cfglock);
+                       return r;
+               }
+       }
+       /* create new ctx_query and attempt to add to the list */
+       q = context_new(ctx, name, rrtype, rrclass, NULL, NULL);
+       lock_basic_unlock(&ctx->cfglock);
+       if(!q)
+               return UB_NOMEM;
        /* become a resolver thread for a bit */
+       *secure = 0;
+       *data = 0;
+       *result = NULL;
 
        /* TODO */
        return UB_NOMEM;
@@ -260,14 +292,41 @@ int
 ub_val_resolve_async(struct ub_val_ctx* ctx, char* name, int rrtype, 
        int rrclass, void* mydata, ub_val_callback_t callback, int* async_id)
 {
-       /* TODO */
+       struct ctx_query* q;
+
+       lock_basic_lock(&ctx->cfglock);
+       if(!ctx->finalized) {
+               int r = context_finalize(ctx);
+               if(r) {
+                       lock_basic_unlock(&ctx->cfglock);
+                       return r;
+               }
+       }
+       /* create new ctx_query and attempt to add to the list */
+       q = context_new(ctx, name, rrtype, rrclass, callback, mydata);
+       lock_basic_unlock(&ctx->cfglock);
+       if(!q)
+               return UB_NOMEM;
+       /* TODO write over pipe to background worker */
+       *async_id = q->querynum;
        return UB_NOMEM;
 }
 
 int 
 ub_val_cancel(struct ub_val_ctx* ctx, int async_id)
 {
-       /* TODO */
+       struct ctx_query* q;
+       lock_basic_lock(&ctx->cfglock);
+       q = (struct ctx_query*)rbtree_search(&ctx->queries, &async_id);
+       lock_basic_unlock(&ctx->cfglock);
+       if(!q || !q->async) /* it is not there, so nothing to do */
+               return UB_NOERROR;
+       /* TODO ; send cancel to background worker */
+
+       lock_basic_lock(&ctx->cfglock);
+       (void)rbtree_delete(&ctx->queries, &async_id);
+       ctx->num_async--;
+       lock_basic_unlock(&ctx->cfglock);
        return UB_NOMEM;
 }
 
@@ -294,6 +353,7 @@ ub_val_strerror(int err)
                case UB_SOCKET: return "socket io error";
                case UB_SYNTAX: return "syntax error";
                case UB_SERVFAIL: return "server failure";
+               case UB_INITFAIL: return "initialization failure";
                default: return "unknown error";
        }
 }