context UPDATE use hash table instead of pkey for errors

There was a limitaion of max 1024 (Linux) pthread
keys per process so it was not possible to create more.
Fixes #2000
diff --git a/src/common.h b/src/common.h
index ad7a040..bb4e49d 100644
--- a/src/common.h
+++ b/src/common.h
@@ -311,6 +311,14 @@
  *****************************************************************************/
 
 /**
+ * @brief Context error hash table record.
+ */
+struct ly_ctx_err_rec {
+    struct ly_err_item *err;          /** pointer to the error items, if any */
+    pthread_t tid;                    /** pthread thread ID */
+};
+
+/**
  * @brief Context of the YANG schemas
  */
 struct ly_ctx {
@@ -327,7 +335,7 @@
 
     ly_ext_data_clb ext_clb;          /**< optional callback for providing extension-specific run-time data for extensions */
     void *ext_clb_data;               /**< optional private data for ::ly_ctx.ext_clb */
-    pthread_key_t errlist_key;        /**< key for the thread-specific list of errors related to the context */
+    struct ly_ht *err_ht;             /**< hash table of thread-specific list of errors related to the context */
     pthread_mutex_t lyb_hash_lock;    /**< lock for storing LYB schema hashes in schema nodes */
 };
 
diff --git a/src/context.c b/src/context.c
index 54c2fc5..c2c5bab 100644
--- a/src/context.c
+++ b/src/context.c
@@ -4,7 +4,7 @@
  * @author Michal Vasko <mvasko@cesnet.cz>
  * @brief Context implementations
  *
- * Copyright (c) 2015 - 2021 CESNET, z.s.p.o.
+ * Copyright (c) 2015 - 2023 CESNET, z.s.p.o.
  *
  * This source code is licensed under BSD 3-Clause License (the "License").
  * You may not use this file except in compliance with the License.
@@ -228,6 +228,17 @@
     return mod;
 }
 
+/**
+ * @brief Hash table value-equal callback for comparing context error hash table record.
+ */
+static ly_bool
+ly_ctx_ht_err_equal_cb(void *val1_p, void *val2_p, ly_bool UNUSED(mod), void *UNUSED(cb_data))
+{
+    struct ly_ctx_err_rec *err1 = val1_p, *err2 = val2_p;
+
+    return err1->tid == err2->tid;
+}
+
 LIBYANG_API_DEF LY_ERR
 ly_ctx_new(const char *search_dir, uint16_t options, struct ly_ctx **new_ctx)
 {
@@ -251,8 +262,9 @@
     /* plugins */
     LY_CHECK_ERR_GOTO(lyplg_init(), LOGINT(NULL); rc = LY_EINT, cleanup);
 
-    /* initialize thread-specific keys */
-    while ((pthread_key_create(&ctx->errlist_key, ly_err_free)) == EAGAIN) {}
+    /* initialize thread-specific error hash table */
+    ctx->err_ht = lyht_new(1, sizeof(struct ly_ctx_err_rec), ly_ctx_ht_err_equal_cb, NULL, 1);
+    LY_CHECK_ERR_GOTO(!ctx->err_ht, rc = LY_EMEM, cleanup);
 
     /* init LYB hash lock */
     pthread_mutex_init(&ctx->lyb_hash_lock, NULL);
@@ -1247,6 +1259,19 @@
     return ret;
 }
 
+/**
+ * @brief Callback for freeing context error hash table values.
+ *
+ * @param[in] val_p Pointer to a pointer to an error item to free with all the siblings.
+ */
+static void
+ly_ctx_ht_err_rec_free(void *val_p)
+{
+    struct ly_ctx_err_rec *err = val_p;
+
+    ly_err_free(err->err);
+}
+
 LIBYANG_API_DEF void
 ly_ctx_destroy(struct ly_ctx *ctx)
 {
@@ -1279,9 +1304,8 @@
     /* leftover unres */
     lys_unres_glob_erase(&ctx->unres);
 
-    /* clean the error list */
-    ly_err_clean(ctx, 0);
-    pthread_key_delete(ctx->errlist_key);
+    /* clean the error hash table */
+    lyht_free(ctx->err_ht, ly_ctx_ht_err_rec_free);
 
     /* dictionary */
     lydict_clean(&ctx->dict);
diff --git a/src/hash_table.h b/src/hash_table.h
index bec7c86..170f5b9 100644
--- a/src/hash_table.h
+++ b/src/hash_table.h
@@ -167,7 +167,7 @@
  * @brief Free a hash table.
  *
  * @param[in] ht Hash table to be freed.
- * @param[in] val_free Optional callback for freeing allthe stored values, @p val_p is a pointer to a stored value.
+ * @param[in] val_free Optional callback for freeing all the stored values, @p val_p is a pointer to a stored value.
  */
 void lyht_free(struct ly_ht *ht, void (*val_free)(void *val_p));
 
diff --git a/src/log.c b/src/log.c
index 74407d8..691741b 100644
--- a/src/log.c
+++ b/src/log.c
@@ -173,77 +173,112 @@
     return e->no;
 }
 
+/**
+ * @brief Get error record from error hash table of a context for the current thread.
+ *
+ * @param[in] ctx Context to use.
+ * @return Thread error record, if any.
+ */
+static struct ly_ctx_err_rec *
+ly_err_get_rec(const struct ly_ctx *ctx)
+{
+    struct ly_ctx_err_rec rec, *match = NULL;
+
+    /* prepare record */
+    rec.tid = pthread_self();
+
+    /* get the pointer to the matching record */
+    lyht_find(ctx->err_ht, &rec, dict_hash((void *)&rec.tid, sizeof rec.tid), (void **)&match);
+
+    return match;
+}
+
 LIBYANG_API_DEF struct ly_err_item *
 ly_err_first(const struct ly_ctx *ctx)
 {
+    struct ly_ctx_err_rec *rec;
+
     LY_CHECK_ARG_RET(NULL, ctx, NULL);
 
-    return pthread_getspecific(ctx->errlist_key);
+    /* get the pointer to the matching record */
+    rec = ly_err_get_rec(ctx);
+
+    return rec ? rec->err : NULL;
 }
 
 LIBYANG_API_DEF struct ly_err_item *
 ly_err_last(const struct ly_ctx *ctx)
 {
-    const struct ly_err_item *e;
+    struct ly_ctx_err_rec *rec;
 
     LY_CHECK_ARG_RET(NULL, ctx, NULL);
 
-    e = pthread_getspecific(ctx->errlist_key);
-    return e ? e->prev : NULL;
+    /* get the pointer to the matching record */
+    if (!(rec = ly_err_get_rec(ctx))) {
+        return NULL;
+    }
+
+    return rec->err ? rec->err->prev : NULL;
 }
 
 void
 ly_err_move(struct ly_ctx *src_ctx, struct ly_ctx *trg_ctx)
 {
-    const struct ly_err_item *e;
+    struct ly_ctx_err_rec *rec;
+    struct ly_err_item *err = NULL;
 
-    /* clear any current errors */
-    ly_err_clean(trg_ctx, NULL);
-
-    /* get the errors in src */
-    e = pthread_getspecific(src_ctx->errlist_key);
-    pthread_setspecific(src_ctx->errlist_key, NULL);
+    /* get and remove the errors from src */
+    rec = ly_err_get_rec(src_ctx);
+    if (rec) {
+        err = rec->err;
+        rec->err = NULL;
+    }
 
     /* set them for trg */
-    pthread_setspecific(trg_ctx->errlist_key, e);
+    rec = ly_err_get_rec(trg_ctx);
+    ly_err_free(rec->err);
+    rec->err = err;
 }
 
 LIBYANG_API_DEF void
 ly_err_free(void *ptr)
 {
-    struct ly_err_item *i, *next;
+    struct ly_err_item *e, *next;
 
     /* clean the error list */
-    for (i = (struct ly_err_item *)ptr; i; i = next) {
-        next = i->next;
-        free(i->msg);
-        free(i->path);
-        free(i->apptag);
-        free(i);
+    LY_LIST_FOR_SAFE(ptr, next, e) {
+        free(e->msg);
+        free(e->path);
+        free(e->apptag);
+        free(e);
     }
 }
 
 LIBYANG_API_DEF void
 ly_err_clean(struct ly_ctx *ctx, struct ly_err_item *eitem)
 {
-    struct ly_err_item *i, *first;
+    struct ly_ctx_err_rec *rec;
+    struct ly_err_item *e;
 
-    first = ly_err_first(ctx);
-    if (first == eitem) {
+    if (!(rec = ly_err_get_rec(ctx))) {
+        return;
+    }
+    if (rec->err == eitem) {
         eitem = NULL;
     }
-    if (eitem) {
+
+    if (!eitem) {
+        /* free all err */
+        ly_err_free(rec->err);
+        rec->err = NULL;
+    } else {
         /* disconnect the error */
-        for (i = first; i && (i->next != eitem); i = i->next) {}
-        assert(i);
-        i->next = NULL;
-        first->prev = i;
+        for (e = rec->err; e && (e->next != eitem); e = e->next) {}
+        assert(e);
+        e->next = NULL;
+        rec->err->prev = e;
         /* free this err and newer */
         ly_err_free(eitem);
-    } else {
-        /* free all err */
-        ly_err_free(first);
-        pthread_setspecific(ctx->errlist_key, NULL);
     }
 }
 
@@ -356,64 +391,89 @@
     }
 }
 
+/**
+ * @brief Store generated error in a context.
+ *
+ * @param[in] ctx Context to use.
+ * @param[in] level Message log level.
+ * @param[in] no Error number.
+ * @param[in] vecode Error validation error code.
+ * @param[in] msg Error message, always spent.
+ * @param[in] path Error path, always spent.
+ * @param[in] apptag Error app tag, always spent.
+ * @return LY_ERR value.
+ */
 static LY_ERR
 log_store(const struct ly_ctx *ctx, LY_LOG_LEVEL level, LY_ERR no, LY_VECODE vecode, char *msg, char *path, char *apptag)
 {
-    struct ly_err_item *eitem, *last;
+    struct ly_ctx_err_rec *rec, new;
+    struct ly_err_item *e, *last;
+    LY_ERR r;
 
     assert(ctx && (level < LY_LLVRB));
 
-    eitem = pthread_getspecific(ctx->errlist_key);
-    if (!eitem) {
+    if (!(rec = ly_err_get_rec(ctx))) {
+        /* insert a new record */
+        new.err = NULL;
+        new.tid = pthread_self();
+        r = lyht_insert(ctx->err_ht, &new, dict_hash((void *)&new.tid, sizeof new.tid), (void **)&rec);
+        if (r) {
+            /* should never happen */
+            return r;
+        }
+    }
+
+    e = rec->err;
+    if (!e) {
         /* if we are only to fill in path, there must have been an error stored */
         assert(msg);
-        eitem = malloc(sizeof *eitem);
-        LY_CHECK_GOTO(!eitem, mem_fail);
-        eitem->prev = eitem;
-        eitem->next = NULL;
+        e = malloc(sizeof *e);
+        LY_CHECK_GOTO(!e, mem_fail);
+        e->prev = e;
+        e->next = NULL;
 
-        pthread_setspecific(ctx->errlist_key, eitem);
+        rec->err = e;
     } else if (!msg) {
         /* only filling the path */
         assert(path);
 
         /* find last error */
-        eitem = eitem->prev;
+        e = e->prev;
         do {
-            if (eitem->level == LY_LLERR) {
+            if (e->level == LY_LLERR) {
                 /* fill the path */
-                free(eitem->path);
-                eitem->path = path;
+                free(e->path);
+                e->path = path;
                 return LY_SUCCESS;
             }
-            eitem = eitem->prev;
-        } while (eitem->prev->next);
+            e = e->prev;
+        } while (e->prev->next);
         /* last error was not found */
         assert(0);
     } else if ((temp_ly_log_opts && ((*temp_ly_log_opts & LY_LOSTORE_LAST) == LY_LOSTORE_LAST)) ||
             (!temp_ly_log_opts && ((ATOMIC_LOAD_RELAXED(ly_log_opts) & LY_LOSTORE_LAST) == LY_LOSTORE_LAST))) {
         /* overwrite last message */
-        free(eitem->msg);
-        free(eitem->path);
-        free(eitem->apptag);
+        free(e->msg);
+        free(e->path);
+        free(e->apptag);
     } else {
         /* store new message */
-        last = eitem->prev;
-        eitem->prev = malloc(sizeof *eitem);
-        LY_CHECK_GOTO(!eitem->prev, mem_fail);
-        eitem = eitem->prev;
-        eitem->prev = last;
-        eitem->next = NULL;
-        last->next = eitem;
+        last = e->prev;
+        e->prev = malloc(sizeof *e);
+        LY_CHECK_GOTO(!e->prev, mem_fail);
+        e = e->prev;
+        e->prev = last;
+        e->next = NULL;
+        last->next = e;
     }
 
     /* fill in the information */
-    eitem->level = level;
-    eitem->no = no;
-    eitem->vecode = vecode;
-    eitem->msg = msg;
-    eitem->path = path;
-    eitem->apptag = apptag;
+    e->level = level;
+    e->no = no;
+    e->vecode = vecode;
+    e->msg = msg;
+    e->path = path;
+    e->apptag = apptag;
     return LY_SUCCESS;
 
 mem_fail: