| /* |
| * Copyright © 2022 Collabora, Ltd. |
| * |
| * SPDX-License-Identifier: MIT |
| */ |
| |
| #include <sys/stat.h> |
| |
| #include "detect_os.h" |
| #include "string.h" |
| #include "mesa_cache_db_multipart.h" |
| #include "u_debug.h" |
| |
| bool |
| mesa_cache_db_multipart_open(struct mesa_cache_db_multipart *db, |
| const char *cache_path) |
| { |
| #if DETECT_OS_WINDOWS |
| return false; |
| #else |
| db->num_parts = debug_get_num_option("MESA_DISK_CACHE_DATABASE_NUM_PARTS", 50); |
| db->cache_path = cache_path; |
| db->parts = calloc(db->num_parts, sizeof(*db->parts)); |
| if (!db->parts) |
| return false; |
| |
| simple_mtx_init(&db->lock, mtx_plain); |
| |
| return true; |
| #endif |
| } |
| |
| static bool |
| mesa_cache_db_multipart_init_part_locked(struct mesa_cache_db_multipart *db, |
| unsigned int part) |
| { |
| #if DETECT_OS_WINDOWS |
| return false; |
| #else |
| struct mesa_cache_db *db_part; |
| bool db_opened = false; |
| char *part_path = NULL; |
| |
| if (db->parts[part]) |
| return true; |
| |
| if (asprintf(&part_path, "%s/part%u", db->cache_path, part) == -1) |
| return false; |
| |
| if (mkdir(part_path, 0755) == -1 && errno != EEXIST) |
| goto free_path; |
| |
| db_part = calloc(1, sizeof(*db_part)); |
| if (!db_part) |
| goto free_path; |
| |
| /* DB opening may fail only in a case of a severe problem, |
| * like IO error. |
| */ |
| db_opened = mesa_cache_db_open(db_part, part_path); |
| if (!db_opened) { |
| free(db_part); |
| goto free_path; |
| } |
| |
| if (db->max_cache_size) |
| mesa_cache_db_set_size_limit(db_part, db->max_cache_size / db->num_parts); |
| |
| /* remove old pre multi-part cache */ |
| mesa_db_wipe_path(db->cache_path); |
| |
| __sync_synchronize(); |
| |
| db->parts[part] = db_part; |
| |
| free_path: |
| free(part_path); |
| |
| return db_opened; |
| #endif |
| } |
| |
| static bool |
| mesa_cache_db_multipart_init_part(struct mesa_cache_db_multipart *db, |
| unsigned int part) |
| { |
| bool ret; |
| |
| if (db->parts[part]) |
| return true; |
| |
| simple_mtx_lock(&db->lock); |
| ret = mesa_cache_db_multipart_init_part_locked(db, part); |
| simple_mtx_unlock(&db->lock); |
| |
| return ret; |
| } |
| |
| void |
| mesa_cache_db_multipart_close(struct mesa_cache_db_multipart *db) |
| { |
| while (db->num_parts--) { |
| if (db->parts[db->num_parts]) { |
| mesa_cache_db_close(db->parts[db->num_parts]); |
| free(db->parts[db->num_parts]); |
| } |
| } |
| |
| free(db->parts); |
| simple_mtx_destroy(&db->lock); |
| } |
| |
| void |
| mesa_cache_db_multipart_set_size_limit(struct mesa_cache_db_multipart *db, |
| uint64_t max_cache_size) |
| { |
| for (unsigned int part = 0; part < db->num_parts; part++) { |
| if (db->parts[part]) |
| mesa_cache_db_set_size_limit(db->parts[part], |
| max_cache_size / db->num_parts); |
| } |
| |
| db->max_cache_size = max_cache_size; |
| } |
| |
| void * |
| mesa_cache_db_multipart_read_entry(struct mesa_cache_db_multipart *db, |
| const uint8_t *cache_key_160bit, |
| size_t *size) |
| { |
| unsigned last_read_part = db->last_read_part; |
| |
| for (unsigned int i = 0; i < db->num_parts; i++) { |
| unsigned int part = (last_read_part + i) % db->num_parts; |
| |
| if (!mesa_cache_db_multipart_init_part(db, part)) |
| break; |
| |
| void *cache_item = mesa_cache_db_read_entry(db->parts[part], |
| cache_key_160bit, size); |
| if (cache_item) { |
| /* Likely that the next entry lookup will hit the same DB part. */ |
| db->last_read_part = part; |
| return cache_item; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static unsigned |
| mesa_cache_db_multipart_select_victim_part(struct mesa_cache_db_multipart *db) |
| { |
| double best_score = 0, score; |
| unsigned victim = 0; |
| |
| for (unsigned int i = 0; i < db->num_parts; i++) { |
| if (!mesa_cache_db_multipart_init_part(db, i)) |
| continue; |
| |
| score = mesa_cache_db_eviction_score(db->parts[i]); |
| if (score > best_score) { |
| best_score = score; |
| victim = i; |
| } |
| } |
| |
| return victim; |
| } |
| |
| 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) |
| { |
| unsigned last_written_part = db->last_written_part; |
| int wpart = -1; |
| |
| for (unsigned int i = 0; i < db->num_parts; i++) { |
| unsigned int part = (last_written_part + i) % db->num_parts; |
| |
| if (!mesa_cache_db_multipart_init_part(db, part)) |
| break; |
| |
| /* Note that each DB part has own locking. */ |
| if (mesa_cache_db_has_space(db->parts[part], blob_size)) { |
| wpart = part; |
| break; |
| } |
| } |
| |
| /* All DB parts are full. Writing to a full DB part will auto-trigger |
| * eviction of LRU cache entries from the part. Select DB part that |
| * contains majority of LRU cache entries. |
| */ |
| if (wpart < 0) |
| wpart = mesa_cache_db_multipart_select_victim_part(db); |
| |
| if (!mesa_cache_db_multipart_init_part(db, wpart)) |
| return false; |
| |
| db->last_written_part = wpart; |
| |
| return mesa_cache_db_entry_write(db->parts[wpart], cache_key_160bit, |
| blob, blob_size); |
| } |
| |
| void |
| mesa_cache_db_multipart_entry_remove(struct mesa_cache_db_multipart *db, |
| const uint8_t *cache_key_160bit) |
| { |
| for (unsigned int i = 0; i < db->num_parts; i++) { |
| if (!mesa_cache_db_multipart_init_part(db, i)) |
| continue; |
| |
| mesa_cache_db_entry_remove(db->parts[i], cache_key_160bit); |
| } |
| } |