• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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