summaryrefslogtreecommitdiffstats
path: root/src/lib-index/mail-index-alloc-cache.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib-index/mail-index-alloc-cache.c315
1 files changed, 315 insertions, 0 deletions
diff --git a/src/lib-index/mail-index-alloc-cache.c b/src/lib-index/mail-index-alloc-cache.c
new file mode 100644
index 0000000..fe52754
--- /dev/null
+++ b/src/lib-index/mail-index-alloc-cache.c
@@ -0,0 +1,315 @@
+/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "module-context.h"
+#include "eacces-error.h"
+#include "mail-index-private.h"
+#include "mail-index-alloc-cache.h"
+
+#define MAIL_INDEX_ALLOC_CACHE_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, mail_index_alloc_cache_index_module)
+
+/* How many seconds to keep index opened for reuse after it's been closed */
+#define INDEX_CACHE_TIMEOUT 10
+/* How many closed indexes to keep */
+#define INDEX_CACHE_MAX 3
+
+struct mail_index_alloc_cache_list {
+ union mail_index_module_context module_ctx;
+ struct mail_index_alloc_cache_list *next;
+
+ struct mail_index *index;
+ char *mailbox_path;
+ int refcount;
+ bool referenced;
+
+ dev_t index_dir_dev;
+ ino_t index_dir_ino;
+
+ time_t destroy_time;
+};
+
+static MODULE_CONTEXT_DEFINE_INIT(mail_index_alloc_cache_index_module,
+ &mail_index_module_register);
+static struct mail_index_alloc_cache_list *indexes = NULL;
+static unsigned int indexes_cache_references_count = 0;
+static struct timeout *to_index = NULL;
+
+static struct mail_index_alloc_cache_list *
+mail_index_alloc_cache_add(struct mail_index *index,
+ const char *mailbox_path, struct stat *st)
+{
+ struct mail_index_alloc_cache_list *list;
+
+ list = i_new(struct mail_index_alloc_cache_list, 1);
+ list->refcount = 1;
+ list->index = index;
+
+ list->mailbox_path = i_strdup(mailbox_path);
+ list->index_dir_dev = st->st_dev;
+ list->index_dir_ino = st->st_ino;
+
+ list->next = indexes;
+ indexes = list;
+
+ MODULE_CONTEXT_SET(index, mail_index_alloc_cache_index_module, list);
+ return list;
+}
+
+static void
+mail_index_alloc_cache_list_unref(struct mail_index_alloc_cache_list *list)
+{
+ i_assert(list->referenced);
+ i_assert(indexes_cache_references_count > 0);
+
+ indexes_cache_references_count--;
+ mail_index_close(list->index);
+ list->referenced = FALSE;
+}
+
+static void
+mail_index_alloc_cache_list_free(struct mail_index_alloc_cache_list *list)
+{
+ i_assert(list->refcount == 0);
+
+ if (list->referenced)
+ mail_index_alloc_cache_list_unref(list);
+ mail_index_free(&list->index);
+ i_free(list->mailbox_path);
+ i_free(list);
+}
+
+static struct mail_index_alloc_cache_list *
+mail_index_alloc_cache_find_and_expire(const char *mailbox_path,
+ const char *index_dir,
+ const struct stat *index_st)
+{
+ struct mail_index_alloc_cache_list **indexp, *rec, *match;
+ unsigned int destroy_count;
+ struct stat st;
+
+ destroy_count = 0; match = NULL;
+ for (indexp = &indexes; *indexp != NULL;) {
+ rec = *indexp;
+
+ if (match != NULL) {
+ /* already found the index. we're just going through
+ the rest of them to drop 0 refcounts */
+ } else if (rec->refcount == 0 && rec->index->open_count == 0) {
+ /* index is already closed. don't even try to
+ reuse it. */
+ } else if (index_dir != NULL && rec->index_dir_ino != 0) {
+ if (index_st->st_ino == rec->index_dir_ino &&
+ CMP_DEV_T(index_st->st_dev, rec->index_dir_dev)) {
+ /* make sure the directory still exists.
+ it might have been renamed and we're trying
+ to access it via its new path now. */
+ if (stat(rec->index->dir, &st) < 0 ||
+ st.st_ino != index_st->st_ino ||
+ !CMP_DEV_T(st.st_dev, index_st->st_dev))
+ rec->destroy_time = 0;
+ else
+ match = rec;
+ }
+ } else if (mailbox_path != NULL && rec->mailbox_path != NULL &&
+ index_dir == NULL && rec->index_dir_ino == 0) {
+ if (strcmp(mailbox_path, rec->mailbox_path) == 0)
+ match = rec;
+ }
+
+ if (rec->refcount == 0 && rec != match) {
+ if (rec->destroy_time <= ioloop_time ||
+ destroy_count >= INDEX_CACHE_MAX) {
+ *indexp = rec->next;
+ mail_index_alloc_cache_list_free(rec);
+ continue;
+ } else {
+ destroy_count++;
+ }
+ }
+
+ indexp = &(*indexp)->next;
+ }
+ return match;
+}
+
+struct mail_index *
+mail_index_alloc_cache_get(struct event *parent_event, const char *mailbox_path,
+ const char *index_dir, const char *prefix)
+{
+ struct mail_index_alloc_cache_list *match;
+ struct stat st;
+
+ /* compare index_dir inodes so we don't break even with symlinks.
+ if index_dir doesn't exist yet or if using in-memory indexes, just
+ compare mailbox paths */
+ i_zero(&st);
+ if (index_dir == NULL) {
+ /* in-memory indexes */
+ } else if (stat(index_dir, &st) < 0) {
+ if (errno == ENOENT) {
+ /* it'll be created later */
+ } else if (errno == EACCES) {
+ e_error(parent_event, "%s",
+ eacces_error_get("stat", index_dir));
+ } else {
+ e_error(parent_event, "stat(%s) failed: %m", index_dir);
+ }
+ }
+
+ match = mail_index_alloc_cache_find_and_expire(mailbox_path,
+ index_dir, &st);
+ if (match == NULL) {
+ struct mail_index *index =
+ mail_index_alloc(parent_event, index_dir, prefix);
+ match = mail_index_alloc_cache_add(index, mailbox_path, &st);
+ } else {
+ match->refcount++;
+ }
+ i_assert(match->index != NULL);
+ return match->index;
+}
+
+struct mail_index *
+mail_index_alloc_cache_find(const char *index_dir)
+{
+ struct mail_index_alloc_cache_list *rec;
+ struct stat st;
+
+ if (stat(index_dir, &st) < 0) {
+ if (errno != ENOENT)
+ i_error("stat(%s) failed: %m", index_dir);
+ return NULL;
+ }
+
+ for (rec = indexes; rec != NULL; rec = rec->next) {
+ if (st.st_ino == rec->index_dir_ino &&
+ CMP_DEV_T(st.st_dev, rec->index_dir_dev))
+ return rec->index;
+ }
+ return NULL;
+}
+
+static bool destroy_unrefed(unsigned int min_destroy_count)
+{
+ struct mail_index_alloc_cache_list **list, *rec;
+ bool destroyed = FALSE;
+ bool seen_ref0 = FALSE;
+
+ for (list = &indexes; *list != NULL;) {
+ rec = *list;
+
+ if (rec->refcount == 0 &&
+ (min_destroy_count > 0 || rec->destroy_time <= ioloop_time)) {
+ *list = rec->next;
+ destroyed = TRUE;
+ mail_index_alloc_cache_list_free(rec);
+ if (min_destroy_count > 0)
+ min_destroy_count--;
+ } else {
+ if (rec->refcount == 0)
+ seen_ref0 = TRUE;
+ if (min_destroy_count > 0 &&
+ rec->index->open_count == 1 &&
+ rec->referenced) {
+ /* we're the only one keeping this index open.
+ we might be here, because the caller is
+ deleting this mailbox and wants its indexes
+ to be closed. so close it. */
+ destroyed = TRUE;
+ mail_index_alloc_cache_list_unref(rec);
+ }
+ list = &(*list)->next;
+ }
+ }
+
+ if (!seen_ref0 && to_index != NULL)
+ timeout_remove(&to_index);
+ return destroyed;
+}
+
+static void ATTR_NULL(1)
+index_removal_timeout(void *context ATTR_UNUSED)
+{
+ destroy_unrefed(0);
+}
+
+void mail_index_alloc_cache_unref(struct mail_index **_index)
+{
+ struct mail_index *index = *_index;
+ struct mail_index_alloc_cache_list *list, **listp;
+
+ *_index = NULL;
+ list = NULL;
+ for (listp = &indexes; *listp != NULL; listp = &(*listp)->next) {
+ if ((*listp)->index == index) {
+ list = *listp;
+ break;
+ }
+ }
+
+ i_assert(list != NULL);
+ i_assert(list->refcount > 0);
+
+ list->refcount--;
+ list->destroy_time = ioloop_time + INDEX_CACHE_TIMEOUT;
+
+ if (list->refcount == 0 && index->open_count == 0) {
+ /* index was already closed. don't even try to cache it. */
+ *listp = list->next;
+ mail_index_alloc_cache_list_free(list);
+ } else if (to_index == NULL) {
+ /* Add to root ioloop in case we got here from an inner
+ ioloop which gets destroyed too early. */
+ to_index = timeout_add_to(io_loop_get_root(),
+ INDEX_CACHE_TIMEOUT*1000/2,
+ index_removal_timeout, NULL);
+ }
+}
+
+void mail_index_alloc_cache_destroy_unrefed(void)
+{
+ destroy_unrefed(UINT_MAX);
+}
+
+void mail_index_alloc_cache_index_opened(struct mail_index *index)
+{
+ struct mail_index_alloc_cache_list *list =
+ MAIL_INDEX_ALLOC_CACHE_CONTEXT(index);
+ struct stat st;
+
+ if (list != NULL && list->index_dir_ino == 0 &&
+ !MAIL_INDEX_IS_IN_MEMORY(index)) {
+ /* newly created index directory. update its stat. */
+ if (stat(index->dir, &st) == 0) {
+ list->index_dir_ino = st.st_ino;
+ list->index_dir_dev = st.st_dev;
+ }
+ }
+}
+
+void mail_index_alloc_cache_index_closing(struct mail_index *index)
+{
+ struct mail_index_alloc_cache_list *list =
+ MAIL_INDEX_ALLOC_CACHE_CONTEXT(index);
+
+ i_assert(index->open_count > 0);
+ if (index->open_count > 1 || list == NULL)
+ return;
+
+ if (list->referenced) {
+ /* we're closing our referenced index */
+ return;
+ }
+ while (indexes_cache_references_count > INDEX_CACHE_MAX) {
+ if (!destroy_unrefed(1)) {
+ /* our cache is full already, don't keep more */
+ return;
+ }
+ }
+ /* keep the index referenced for caching */
+ indexes_cache_references_count++;
+ list->referenced = TRUE;
+ index->open_count++;
+}