summaryrefslogtreecommitdiffstats
path: root/src/dict/dict-init-cache.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/dict/dict-init-cache.c')
-rw-r--r--src/dict/dict-init-cache.c164
1 files changed, 164 insertions, 0 deletions
diff --git a/src/dict/dict-init-cache.c b/src/dict/dict-init-cache.c
new file mode 100644
index 0000000..ed76940
--- /dev/null
+++ b/src/dict/dict-init-cache.c
@@ -0,0 +1,164 @@
+/* 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_wait_all(void)
+{
+ struct dict_init_cache_list *listp;
+
+ for (listp = dicts; listp != NULL; listp = listp->next)
+ dict_wait(listp->dict);
+}
+
+void dict_init_cache_destroy_all(void)
+{
+ timeout_remove(&to_dict);
+ while (dicts != NULL)
+ dict_init_cache_list_free(dicts);
+}