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    char *part_path = NULL;
22    unsigned int i;
23 
24    db->num_parts = debug_get_num_option("MESA_DISK_CACHE_DATABASE_NUM_PARTS", 50);
25 
26    db->parts = calloc(db->num_parts, sizeof(*db->parts));
27    if (!db->parts)
28       return false;
29 
30    for (i = 0; i < db->num_parts; i++) {
31       bool db_opened = false;
32 
33       if (asprintf(&part_path, "%s/part%u", cache_path, i) == -1)
34          goto close_db;
35 
36       if (mkdir(part_path, 0755) == -1 && errno != EEXIST)
37          goto free_path;
38 
39       /* DB opening may fail only in a case of a severe problem,
40        * like IO error.
41        */
42       db_opened = mesa_cache_db_open(&db->parts[i], part_path);
43       if (!db_opened)
44          goto free_path;
45 
46       free(part_path);
47    }
48 
49    /* remove old pre multi-part cache */
50    mesa_db_wipe_path(cache_path);
51 
52    return true;
53 
54 free_path:
55    free(part_path);
56 close_db:
57    while (i--)
58       mesa_cache_db_close(&db->parts[i]);
59 
60    free(db->parts);
61 
62    return false;
63 #endif
64 }
65 
66 void
mesa_cache_db_multipart_close(struct mesa_cache_db_multipart * db)67 mesa_cache_db_multipart_close(struct mesa_cache_db_multipart *db)
68 {
69    while (db->num_parts--)
70       mesa_cache_db_close(&db->parts[db->num_parts]);
71 
72    free(db->parts);
73 }
74 
75 void
mesa_cache_db_multipart_set_size_limit(struct mesa_cache_db_multipart * db,uint64_t max_cache_size)76 mesa_cache_db_multipart_set_size_limit(struct mesa_cache_db_multipart *db,
77                                        uint64_t max_cache_size)
78 {
79    for (unsigned int i = 0; i < db->num_parts; i++)
80       mesa_cache_db_set_size_limit(&db->parts[i],
81                                    max_cache_size / db->num_parts);
82 }
83 
84 void *
mesa_cache_db_multipart_read_entry(struct mesa_cache_db_multipart * db,const uint8_t * cache_key_160bit,size_t * size)85 mesa_cache_db_multipart_read_entry(struct mesa_cache_db_multipart *db,
86                                    const uint8_t *cache_key_160bit,
87                                    size_t *size)
88 {
89    unsigned last_read_part = db->last_read_part;
90 
91    for (unsigned int i = 0; i < db->num_parts; i++) {
92       unsigned int part = (last_read_part + i) % db->num_parts;
93 
94       void *cache_item = mesa_cache_db_read_entry(&db->parts[part],
95                                                   cache_key_160bit, size);
96       if (cache_item) {
97          /* Likely that the next entry lookup will hit the same DB part. */
98          db->last_read_part = part;
99          return cache_item;
100       }
101    }
102 
103    return NULL;
104 }
105 
106 static unsigned
mesa_cache_db_multipart_select_victim_part(struct mesa_cache_db_multipart * db)107 mesa_cache_db_multipart_select_victim_part(struct mesa_cache_db_multipart *db)
108 {
109    double best_score = 0, score;
110    unsigned victim = 0;
111 
112    for (unsigned int i = 0; i < db->num_parts; i++) {
113       score = mesa_cache_db_eviction_score(&db->parts[i]);
114       if (score > best_score) {
115          best_score = score;
116          victim = i;
117       }
118    }
119 
120    return victim;
121 }
122 
123 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)124 mesa_cache_db_multipart_entry_write(struct mesa_cache_db_multipart *db,
125                                     const uint8_t *cache_key_160bit,
126                                     const void *blob, size_t blob_size)
127 {
128    unsigned last_written_part = db->last_written_part;
129    int wpart = -1;
130 
131    for (unsigned int i = 0; i < db->num_parts; i++) {
132       unsigned int part = (last_written_part + i) % db->num_parts;
133 
134       /* Note that each DB part has own locking. */
135       if (mesa_cache_db_has_space(&db->parts[part], blob_size)) {
136          wpart = part;
137          break;
138       }
139    }
140 
141    /* All DB parts are full. Writing to a full DB part will auto-trigger
142     * eviction of LRU cache entries from the part. Select DB part that
143     * contains majority of LRU cache entries.
144     */
145    if (wpart < 0)
146       wpart = mesa_cache_db_multipart_select_victim_part(db);
147 
148    db->last_written_part = wpart;
149 
150    return mesa_cache_db_entry_write(&db->parts[wpart], cache_key_160bit,
151                                     blob, blob_size);
152 }
153 
154 void
mesa_cache_db_multipart_entry_remove(struct mesa_cache_db_multipart * db,const uint8_t * cache_key_160bit)155 mesa_cache_db_multipart_entry_remove(struct mesa_cache_db_multipart *db,
156                                      const uint8_t *cache_key_160bit)
157 {
158    for (unsigned int i = 0; i < db->num_parts; i++)
159       mesa_cache_db_entry_remove(&db->parts[i], cache_key_160bit);
160 }
161