diff options
Diffstat (limited to '')
-rw-r--r-- | src/lib-index/mail-index-alloc-cache.c | 315 |
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++; +} |