1 /*
2 * Copyright © 2022 Collabora, Ltd.
3 *
4 * SPDX-License-Identifier: MIT
5 */
6
7 #include <sys/stat.h>
8
9 #include "detect_os.h"
10 #include "string.h"
11 #include "mesa_cache_db_multipart.h"
12 #include "u_debug.h"
13
14 bool
mesa_cache_db_multipart_open(struct mesa_cache_db_multipart * db,const char * cache_path)15 mesa_cache_db_multipart_open(struct mesa_cache_db_multipart *db,
16 const char *cache_path)
17 {
18 #if DETECT_OS_WINDOWS
19 return false;
20 #else
21 db->num_parts = debug_get_num_option("MESA_DISK_CACHE_DATABASE_NUM_PARTS", 50);
22 db->cache_path = cache_path;
23 db->parts = calloc(db->num_parts, sizeof(*db->parts));
24 if (!db->parts)
25 return false;
26
27 simple_mtx_init(&db->lock, mtx_plain);
28
29 return true;
30 #endif
31 }
32
33 static bool
mesa_cache_db_multipart_init_part_locked(struct mesa_cache_db_multipart * db,unsigned int part)34 mesa_cache_db_multipart_init_part_locked(struct mesa_cache_db_multipart *db,
35 unsigned int part)
36 {
37 #if DETECT_OS_WINDOWS
38 return false;
39 #else
40 struct mesa_cache_db *db_part;
41 bool db_opened = false;
42 char *part_path = NULL;
43
44 if (db->parts[part])
45 return true;
46
47 if (asprintf(&part_path, "%s/part%u", db->cache_path, part) == -1)
48 return false;
49
50 if (mkdir(part_path, 0755) == -1 && errno != EEXIST)
51 goto free_path;
52
53 db_part = calloc(1, sizeof(*db_part));
54 if (!db_part)
55 goto free_path;
56
57 /* DB opening may fail only in a case of a severe problem,
58 * like IO error.
59 */
60 db_opened = mesa_cache_db_open(db_part, part_path);
61 if (!db_opened) {
62 free(db_part);
63 goto free_path;
64 }
65
66 if (db->max_cache_size)
67 mesa_cache_db_set_size_limit(db_part, db->max_cache_size / db->num_parts);
68
69 /* remove old pre multi-part cache */
70 mesa_db_wipe_path(db->cache_path);
71
72 __sync_synchronize();
73
74 db->parts[part] = db_part;
75
76 free_path:
77 free(part_path);
78
79 return db_opened;
80 #endif
81 }
82
83 static bool
mesa_cache_db_multipart_init_part(struct mesa_cache_db_multipart * db,unsigned int part)84 mesa_cache_db_multipart_init_part(struct mesa_cache_db_multipart *db,
85 unsigned int part)
86 {
87 bool ret;
88
89 if (db->parts[part])
90 return true;
91
92 simple_mtx_lock(&db->lock);
93 ret = mesa_cache_db_multipart_init_part_locked(db, part);
94 simple_mtx_unlock(&db->lock);
95
96 return ret;
97 }
98
99 void
mesa_cache_db_multipart_close(struct mesa_cache_db_multipart * db)100 mesa_cache_db_multipart_close(struct mesa_cache_db_multipart *db)
101 {
102 while (db->num_parts--) {
103 if (db->parts[db->num_parts]) {
104 mesa_cache_db_close(db->parts[db->num_parts]);
105 free(db->parts[db->num_parts]);
106 }
107 }
108
109 free(db->parts);
110 simple_mtx_destroy(&db->lock);
111 }
112
113 void
mesa_cache_db_multipart_set_size_limit(struct mesa_cache_db_multipart * db,uint64_t max_cache_size)114 mesa_cache_db_multipart_set_size_limit(struct mesa_cache_db_multipart *db,
115 uint64_t max_cache_size)
116 {
117 for (unsigned int part = 0; part < db->num_parts; part++) {
118 if (db->parts[part])
119 mesa_cache_db_set_size_limit(db->parts[part],
120 max_cache_size / db->num_parts);
121 }
122
123 db->max_cache_size = max_cache_size;
124 }
125
126 void *
mesa_cache_db_multipart_read_entry(struct mesa_cache_db_multipart * db,const uint8_t * cache_key_160bit,size_t * size)127 mesa_cache_db_multipart_read_entry(struct mesa_cache_db_multipart *db,
128 const uint8_t *cache_key_160bit,
129 size_t *size)
130 {
131 unsigned last_read_part = db->last_read_part;
132
133 for (unsigned int i = 0; i < db->num_parts; i++) {
134 unsigned int part = (last_read_part + i) % db->num_parts;
135
136 if (!mesa_cache_db_multipart_init_part(db, part))
137 break;
138
139 void *cache_item = mesa_cache_db_read_entry(db->parts[part],
140 cache_key_160bit, size);
141 if (cache_item) {
142 /* Likely that the next entry lookup will hit the same DB part. */
143 db->last_read_part = part;
144 return cache_item;
145 }
146 }
147
148 return NULL;
149 }
150
151 static unsigned
mesa_cache_db_multipart_select_victim_part(struct mesa_cache_db_multipart * db)152 mesa_cache_db_multipart_select_victim_part(struct mesa_cache_db_multipart *db)
153 {
154 double best_score = 0, score;
155 unsigned victim = 0;
156
157 for (unsigned int i = 0; i < db->num_parts; i++) {
158 if (!mesa_cache_db_multipart_init_part(db, i))
159 continue;
160
161 score = mesa_cache_db_eviction_score(db->parts[i]);
162 if (score > best_score) {
163 best_score = score;
164 victim = i;
165 }
166 }
167
168 return victim;
169 }
170
171 bool
mesa_cache_db_multipart_entry_write(struct mesa_cache_db_multipart * db,const uint8_t * cache_key_160bit,const void * blob,size_t blob_size)172 mesa_cache_db_multipart_entry_write(struct mesa_cache_db_multipart *db,
173 const uint8_t *cache_key_160bit,
174 const void *blob, size_t blob_size)
175 {
176 unsigned last_written_part = db->last_written_part;
177 int wpart = -1;
178
179 for (unsigned int i = 0; i < db->num_parts; i++) {
180 unsigned int part = (last_written_part + i) % db->num_parts;
181
182 if (!mesa_cache_db_multipart_init_part(db, part))
183 break;
184
185 /* Note that each DB part has own locking. */
186 if (mesa_cache_db_has_space(db->parts[part], blob_size)) {
187 wpart = part;
188 break;
189 }
190 }
191
192 /* All DB parts are full. Writing to a full DB part will auto-trigger
193 * eviction of LRU cache entries from the part. Select DB part that
194 * contains majority of LRU cache entries.
195 */
196 if (wpart < 0)
197 wpart = mesa_cache_db_multipart_select_victim_part(db);
198
199 if (!mesa_cache_db_multipart_init_part(db, wpart))
200 return false;
201
202 db->last_written_part = wpart;
203
204 return mesa_cache_db_entry_write(db->parts[wpart], cache_key_160bit,
205 blob, blob_size);
206 }
207
208 void
mesa_cache_db_multipart_entry_remove(struct mesa_cache_db_multipart * db,const uint8_t * cache_key_160bit)209 mesa_cache_db_multipart_entry_remove(struct mesa_cache_db_multipart *db,
210 const uint8_t *cache_key_160bit)
211 {
212 for (unsigned int i = 0; i < db->num_parts; i++) {
213 if (!mesa_cache_db_multipart_init_part(db, i))
214 continue;
215
216 mesa_cache_db_entry_remove(db->parts[i], cache_key_160bit);
217 }
218 }
219