summaryrefslogtreecommitdiffstats
path: root/src/dict/dict-init-cache.c
blob: ed76940b04df2de58eb31c12761f201cf5dfbb85 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
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);
}