]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
dict: Add caching mechanism for initializing dicts
authorSiavash Tavakoli <siavash.tavakoli@open-xchange.com>
Fri, 25 Jun 2021 13:11:52 +0000 (14:11 +0100)
committersiavash.tavakoli <siavash.tavakoli@open-xchange.com>
Mon, 16 Aug 2021 08:58:47 +0000 (08:58 +0000)
Add a pool for dict instances. Each dict is refcounted and given a grace period
of 30 seconds for deletion. If refcount drops to 0 and no new dict
operation uses the instance in that period, it will be freed. A maximum
of 10 dicts are kept in the cache.

src/dict/Makefile.am
src/dict/dict-init-cache.c [new file with mode: 0644]
src/dict/dict-init-cache.h [new file with mode: 0644]

index 279f0d285ad3902d7d64b916ea7081ed050900dd..e408046361e4dbb657ddf310d2da3451735bb6ac 100644 (file)
@@ -32,10 +32,12 @@ dict_SOURCES = \
        dict-connection.c \
        dict-commands.c \
        dict-settings.c \
+       dict-init-cache.c \
        main.c
 
 noinst_HEADERS = \
        dict-connection.h \
        dict-commands.h \
        dict-settings.h \
+       dict-init-cache.h \
        main.h
diff --git a/src/dict/dict-init-cache.c b/src/dict/dict-init-cache.c
new file mode 100644 (file)
index 0000000..0e47e2b
--- /dev/null
@@ -0,0 +1,156 @@
+/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "dict.h"
+#include "dict-private.h"
+#include "dict-init-cache.h"
+#include "llist.h"
+
+/* How many seconds to keep dict opened for reuse after it's been closed */
+#define DICT_CACHE_TIMEOUT_SECS 30
+/* How many closed dicts to keep */
+#define DICT_CACHE_MAX_COUNT 10
+
+struct dict_init_cache_list {
+       struct dict_init_cache_list *prev, *next;
+
+       struct dict *dict;
+       char *dict_name;
+       int refcount;
+
+       time_t destroy_time;
+};
+
+static struct dict_init_cache_list *dicts = NULL;
+static struct timeout *to_dict = NULL;
+
+static struct dict_init_cache_list *
+dict_init_cache_add(const char *dict_name, struct dict *dict)
+{
+       struct dict_init_cache_list *list;
+
+       list = i_new(struct dict_init_cache_list, 1);
+       list->refcount = 1;
+       list->dict = dict;
+       list->dict_name = i_strdup(dict_name);
+
+       DLLIST_PREPEND(&dicts, list);
+
+       return list;
+}
+
+static void dict_init_cache_list_free(struct dict_init_cache_list *list)
+{
+       i_assert(list->refcount == 0);
+
+       DLLIST_REMOVE(&dicts, list);
+       dict_deinit(&list->dict);
+       i_free(list->dict_name);
+       i_free(list);
+}
+
+static struct dict_init_cache_list *dict_init_cache_find(const char *dict_name)
+{
+       struct dict_init_cache_list *listp = dicts, *next = NULL, *match = NULL;
+       unsigned int ref0_count = 0;
+
+       while (listp != NULL) {
+                next = listp->next;
+               if (match != NULL) {
+                       /* already found the dict. we're just going through
+                          the rest of them to drop 0 refcounts */
+               } else if (strcmp(dict_name, listp->dict_name) == 0)
+                       match = listp;
+
+               if (listp->refcount == 0 && listp != match) {
+                       if (listp->destroy_time <= ioloop_time ||
+                           ref0_count >= DICT_CACHE_MAX_COUNT - 1)
+                               dict_init_cache_list_free(listp);
+                       else
+                               ref0_count++;
+               }
+                listp = next;
+       }
+       return match;
+}
+
+int dict_init_cache_get(const char *dict_name, const char *uri,
+                       const struct dict_settings *set,
+                       struct dict **dict_r, const char **error_r)
+{
+       struct dict_init_cache_list *match;
+       int ret = 0;
+
+       match = dict_init_cache_find(dict_name);
+       if (match == NULL) {
+               if (dict_init(uri, set, dict_r, error_r) < 0)
+                       return -1;
+               match = dict_init_cache_add(dict_name, *dict_r);
+       } else {
+               match->refcount++;
+               *dict_r = match->dict;
+       }
+       i_assert(match->dict != NULL);
+       return ret;
+}
+
+static void destroy_unrefed(void)
+{
+       struct dict_init_cache_list *listp, *next = NULL;
+       bool seen_ref0 = FALSE;
+
+       for (listp = dicts; listp != NULL; listp = next) {
+               next = listp->next;
+
+               i_assert(listp->refcount >= 0);
+               if (listp->refcount > 0)
+                       ;
+               else if (listp->destroy_time <= ioloop_time)
+                       dict_init_cache_list_free(listp);
+               else
+                       seen_ref0 = TRUE;
+       }
+
+       if (!seen_ref0 && to_dict != NULL)
+               timeout_remove(&to_dict);
+}
+
+static void dict_removal_timeout(void *context ATTR_UNUSED)
+{
+       destroy_unrefed();
+}
+
+void dict_init_cache_unref(struct dict **_dict)
+{
+       struct dict *dict = *_dict;
+       struct dict_init_cache_list *listp;
+
+       if (dict == NULL)
+               return;
+
+       *_dict = NULL;
+       for (listp = dicts; listp != NULL; listp = listp->next) {
+               if (listp->dict == dict)
+                       break;
+       }
+
+       i_assert(listp != NULL && listp->dict == dict);
+       i_assert(listp->refcount > 0);
+
+       listp->refcount--;
+       listp->destroy_time = ioloop_time + DICT_CACHE_TIMEOUT_SECS;
+
+       if (to_dict == NULL) {
+               to_dict = timeout_add_to(io_loop_get_root(),
+                                        DICT_CACHE_TIMEOUT_SECS*1000/2,
+                                        dict_removal_timeout, NULL);
+       }
+}
+
+void dict_init_cache_destroy_all(void)
+{
+       timeout_remove(&to_dict);
+       while (dicts != NULL)
+               dict_init_cache_list_free(dicts);
+}
diff --git a/src/dict/dict-init-cache.h b/src/dict/dict-init-cache.h
new file mode 100644 (file)
index 0000000..e26944a
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef DICT_INIT_CACHE_H
+#define DICT_INIT_CACHE_H
+
+int dict_init_cache_get(const char *dict_name, const char *uri,
+                       const struct dict_settings *set,
+                       struct dict **dict_r, const char **error_r);
+void dict_init_cache_unref(struct dict **dict);
+void dict_init_cache_destroy_all(void);
+
+#endif