From 2758123530258ace83f8d4193831bad62a4ee884 Mon Sep 17 00:00:00 2001 From: IJN Akashi AR Date: Wed, 19 Mar 2025 20:24:01 -0700 Subject: [PATCH 1/6] implement chunk eviction when all zones are full --- include/cachemap.h | 8 ++++ include/zncache.h | 2 + include/zone_state_manager.h | 4 ++ src/cache.c | 16 +++++++ src/cachemap.c | 81 +++++++++++++++++++++++++++++++++--- src/eviction/chunk.c | 57 +++++++++++++++++++++++-- src/zncache.c | 2 +- src/zone_state_manager.c | 33 ++++++++++++++- 8 files changed, 191 insertions(+), 12 deletions(-) diff --git a/include/cachemap.h b/include/cachemap.h index 695e74d..91d9229 100644 --- a/include/cachemap.h +++ b/include/cachemap.h @@ -59,6 +59,14 @@ struct zone_map_result { struct zone_map_result zn_cachemap_find(struct zn_cachemap *map, const uint32_t data_id); +// Apply compaction to a zone. This temporarily renders all zones inactive/unavailable until compaction is finished. +void +zn_cachemap_compact_begin(struct zn_cachemap *map, const uint32_t zone_id, uint32_t** data_ids, struct zn_pair** locations, uint32_t* count); + +// Finish compaction. +void +zn_cachemap_compact_end(struct zn_cachemap *map, const uint32_t zone_id, const uint32_t* data_ids, struct zn_pair* locations, uint32_t count); + /** @brief Inserts a new mapping into the data structure. Called by * the thread when it's finished writing to the zone. * diff --git a/include/zncache.h b/include/zncache.h index 42d2230..cf33ab8 100644 --- a/include/zncache.h +++ b/include/zncache.h @@ -113,6 +113,8 @@ zn_init_cache(struct zn_cache *cache, struct zbd_info *info, size_t chunk_sz, ui void zn_destroy_cache(struct zn_cache *cache); +unsigned char * zn_read_from_disk_whole(struct zn_cache *cache, uint32_t zone_id, void *buffer); + /** * @brief Read a chunk from disk * diff --git a/include/zone_state_manager.h b/include/zone_state_manager.h index 29a4433..c24a2db 100644 --- a/include/zone_state_manager.h +++ b/include/zone_state_manager.h @@ -117,6 +117,10 @@ zsm_return_active_zone(struct zone_state_manager *state, struct zn_pair *pair); int zsm_evict(struct zone_state_manager *state, int zone_to_free); +// This function evicts the current zone and then allows the thread to write to it. Once the thread is finished writing, it should call zsm_return_active_zone. +void +zsm_evict_and_write(struct zone_state_manager *state, uint32_t zone_id, uint32_t count); + void zsm_failed_to_write(struct zone_state_manager *state, struct zn_pair pair); diff --git a/src/cache.c b/src/cache.c index a066ad4..95bc756 100644 --- a/src/cache.c +++ b/src/cache.c @@ -1,5 +1,6 @@ // For pread #define _XOPEN_SOURCE 500 +#include "cachemap.h" #include #include "znutil.h" @@ -169,6 +170,21 @@ zn_destroy_cache(struct zn_cache *cache) { /* g_mutex_clear(&cache->reader.lock); */ } +unsigned char * +zn_read_from_disk_whole(struct zn_cache *cache, uint32_t zone_id, void *buffer) { + assert(cache); + assert(buffer); + + long write_ptr = CHUNK_POINTER(cache->zone_cap, cache->chunk_sz, 0, zone_id); + size_t bytes = pread(cache->fd, buffer, cache->zone_cap, write_ptr); + if (bytes != cache->zone_cap) { + fprintf(stderr, "Couldn't read from fd\n"); + return nullptr; + } + + return buffer; +} + unsigned char * zn_read_from_disk(struct zn_cache *cache, struct zn_pair *zone_pair) { unsigned char *data = malloc(cache->chunk_sz); diff --git a/src/cachemap.c b/src/cachemap.c index 7cdd414..ff8cb83 100644 --- a/src/cachemap.c +++ b/src/cachemap.c @@ -1,4 +1,5 @@ #include "cachemap.h" +#include "znbackend.h" #include "zncache.h" #include "assert.h" @@ -75,7 +76,7 @@ zn_cachemap_find(struct zn_cachemap *map, const uint32_t data_id) { wait_cond->value.write_finished = g_atomic_rc_box_new(GCond); g_cond_init(wait_cond->value.write_finished); - g_hash_table_insert(map->zone_map, GINT_TO_POINTER(data_id), wait_cond); + g_hash_table_insert(map->zone_map, GUINT_TO_POINTER(data_id), wait_cond); g_mutex_unlock(&map->cache_map_mutex); return *wait_cond; @@ -101,13 +102,55 @@ zn_cachemap_find(struct zn_cachemap *map, const uint32_t data_id) { } void -zn_cachemap_insert(struct zn_cachemap *map, const uint32_t data_id, struct zn_pair location) { +zn_cachemap_compact_begin(struct zn_cachemap *map, const uint32_t zone_id, uint32_t** data_ids, struct zn_pair** locations, uint32_t* count) { assert(map); + assert(data_ids); + assert(locations); + assert(count); + + g_mutex_lock(&map->cache_map_mutex); + + *count = g_hash_table_size(map->data_map[zone_id]); + *locations = malloc(sizeof(struct zn_pair) * (*count)); + *data_ids = malloc(sizeof(uint32_t) * (*count)); + + // Gather all valid chunks in the zone and temporarily invalidate them + GHashTableIter iter; + gpointer key = NULL; + gpointer value = NULL; + int i = 0; + g_hash_table_iter_init (&iter, map->data_map[zone_id]); + while (g_hash_table_iter_next (&iter, &key, &value)) { + uint32_t chunk_offset = GPOINTER_TO_UINT(key); + uint32_t data_id = GPOINTER_TO_UINT(value); + + // Find the entry in the hash table + struct zone_map_result* res = g_hash_table_lookup(map->zone_map, GUINT_TO_POINTER(data_id)); + assert(res); + assert(res->type == RESULT_LOC); + assert(res->value.location.chunk_offset == chunk_offset); + assert(res->value.location.id == data_id); + + // Invalidate it, replace with a temporary condition variable + res->type = RESULT_COND; + res->value.write_finished = g_atomic_rc_box_new(GCond); + g_cond_init(res->value.write_finished); + + + // Write to the out variables + (*data_ids)[i] = data_id; + (*locations)[i] = (struct zn_pair){.chunk_offset = chunk_offset}; + + i += 1; + } - g_mutex_lock(&map->cache_map_mutex); - - dbg_print_g_hash_table("map->data_map[location.zone]", map->data_map[location.zone], PRINT_G_HASH_TABLE_GINT); + g_mutex_unlock(&map->cache_map_mutex); +} +static void +zn_cachemap_insert_nolock(struct zn_cachemap *map, const uint32_t data_id, + struct zn_pair location) { + // It must contain an entry if the thread called zn_cachemap_find beforehand assert(g_hash_table_contains(map->zone_map, GUINT_TO_POINTER(data_id))); @@ -118,13 +161,39 @@ zn_cachemap_insert(struct zn_cachemap *map, const uint32_t data_id, struct zn_pa result->value.location = location; // Does this mutate the entry in the hash table? result->type = RESULT_LOC; assert(map->data_map[location.zone]); - g_hash_table_insert(map->data_map[location.zone], GUINT_TO_POINTER(location.chunk_offset), GINT_TO_POINTER(data_id)); + g_hash_table_insert(map->data_map[location.zone], GUINT_TO_POINTER(location.chunk_offset), GUINT_TO_POINTER(data_id)); g_cond_broadcast(condition); // Wake up threads waiting for it g_atomic_rc_box_release_full(condition, // Decrement the writing thread's ref (GDestroyNotify) free_cond_var); dbg_print_g_hash_table("map->data_map[location.zone]", map->data_map[location.zone], PRINT_G_HASH_TABLE_GINT); +} + +void +zn_cachemap_compact_end(struct zn_cachemap *map, const uint32_t zone_id, const uint32_t* data_ids, struct zn_pair* locations, uint32_t count) { + assert(map); + assert(data_ids); + assert(locations); + + g_mutex_lock(&map->cache_map_mutex); + + // Replace with actual locations + for (uint32_t i = 0; i < count; i++) { + assert(zone_id == locations[i].zone); + zn_cachemap_insert_nolock(map, data_ids[i], locations[i]); + } + + g_mutex_unlock(&map->cache_map_mutex); +} + +void +zn_cachemap_insert(struct zn_cachemap *map, const uint32_t data_id, struct zn_pair location) { + assert(map); + g_mutex_lock(&map->cache_map_mutex); + dbg_print_g_hash_table("map->data_map[location.zone]", map->data_map[location.zone], PRINT_G_HASH_TABLE_GINT); + zn_cachemap_insert_nolock(map, data_id, location); + dbg_print_g_hash_table("map->data_map[location.zone]", map->data_map[location.zone], PRINT_G_HASH_TABLE_GINT); g_mutex_unlock(&map->cache_map_mutex); } diff --git a/src/eviction/chunk.c b/src/eviction/chunk.c index ae9f8b0..5eb1778 100644 --- a/src/eviction/chunk.c +++ b/src/eviction/chunk.c @@ -1,5 +1,7 @@ +#include "cachemap.h" #include "eviction_policy.h" #include "eviction_policy_chunk.h" +#include "zncache.h" #include "znutil.h" #include "minheap.h" #include "zone_state_manager.h" @@ -9,6 +11,7 @@ #include #include #include +#include void zn_policy_chunk_update(policy_data_t _policy, struct zn_pair location, @@ -27,7 +30,7 @@ zn_policy_chunk_update(policy_data_t _policy, struct zn_pair location, struct eviction_policy_chunk_zone * zpc = &p->zone_pool[location.zone]; struct zn_pair * zp = &zpc->chunks[location.chunk_offset]; - GList *node; + GList *node = NULL; // Should always be present (might be NULL) assert(g_hash_table_lookup_extended(p->chunk_to_lru_map, zp, NULL, (gpointer *)&node)); @@ -75,6 +78,50 @@ zn_policy_chunk_update(policy_data_t _policy, struct zn_pair location, g_mutex_unlock(&p->policy_mutex); } +static void +zn_policy_compact_zone(struct zn_policy_chunk *p, struct eviction_policy_chunk_zone * old_zone) { + unsigned char* buf = zn_read_from_disk_whole(p->cache, old_zone->zone_id, p->chunk_buf); + assert(buf); + + // These arrays are allocated inside the function + uint32_t* data_ids = NULL; + struct zn_pair *locations = NULL; + uint32_t count = 0; + + zn_cachemap_compact_begin(&p->cache->cache_map, old_zone->zone_id, &data_ids, &locations, &count); + + // Wait for all readers to end + while (g_atomic_int_get(&p->cache->active_readers[old_zone->zone_id]) > 0) {} + + // Reset the current zone state + zsm_evict_and_write(&p->cache->zone_state, old_zone->zone_id, count); + + // Copy all the old zones + unsigned char* random_buffer = generate_random_buffer(p->cache->chunk_sz); + for (uint32_t i = 0; i < count; i++) { + + unsigned char* data = zn_gen_write_buffer(p->cache, old_zone->zone_id, random_buffer); + + // Write buffer to disk, 4kb blocks at a time + unsigned long long wp = + CHUNK_POINTER(p->cache->zone_cap, p->cache->chunk_sz, locations[i].chunk_offset, locations[i].zone); + if (zn_write_out(p->cache->fd, p->cache->chunk_sz, data, WRITE_GRANULARITY, wp) != 0) { + dbg_printf("Couldn't write to fd at wp=%llu\n", wp); + } + } + + // Update the zsm + struct zn_pair end_pair = { + .zone = old_zone->zone_id, + .chunk_offset = count - 1, + }; + int ret = zsm_return_active_zone(&p->cache->zone_state, &end_pair); + assert(!ret); + + zn_cachemap_compact_end(&p->cache->cache_map, old_zone->zone_id, data_ids, + locations, count); +} + static void zn_policy_chunk_gc(policy_data_t policy) { // TODO: If later separated from evict, lock here @@ -102,11 +149,15 @@ zn_policy_chunk_gc(policy_data_t policy) { struct zn_pair new_location; enum zsm_get_active_zone_error ret = zsm_get_active_zone(&p->cache->zone_state, &new_location); + + // Not enough zones available. We are just going to compact the old zone if (ret != ZSM_GET_ACTIVE_ZONE_SUCCESS) { - assert(!"TODO"); - // TODO: ??? + zn_policy_compact_zone(p, old_zone); + return; } + // TODO: What if someone already wrote the existing data to a different location? We may need to check that first + // Read the chunk from the old zone unsigned char *data = zn_read_from_disk(p->cache, &old_zone->chunks[i]); assert(data); diff --git a/src/zncache.c b/src/zncache.c index 408f13f..834c4d1 100644 --- a/src/zncache.c +++ b/src/zncache.c @@ -223,7 +223,7 @@ main(int argc, char **argv) { "\tWorker threads: %u\n" "\tEviction threads: %u\n" "\tWorkload file: %s\n", - device, (device_type == ZE_BACKEND_ZNS) ? "ZNS" : "Block", chunk_sz, nr_threads, nr_eviction_threads, (argc == 5) ? argv[5] : "Simple generator"); + device, (device_type == ZE_BACKEND_ZNS) ? "ZNS" : "Block", chunk_sz, BLOCK_ZONE_CAPACITY, nr_threads, nr_eviction_threads, (argc == 5) ? argv[5] : "Simple generator"); #ifdef DEBUG printf("\tDEBUG=on\n"); diff --git a/src/zone_state_manager.c b/src/zone_state_manager.c index f97dc6d..3d718ed 100644 --- a/src/zone_state_manager.c +++ b/src/zone_state_manager.c @@ -234,6 +234,33 @@ zsm_get_active_zone_batch(int chunks) { return (GArray) {}; } +void +zsm_evict_and_write(struct zone_state_manager *state, uint32_t zone_id, uint32_t count) { + assert(state); + + g_mutex_lock(&state->state_mutex); + + struct zn_zone *zone = &state->state[zone_id]; + assert(zone->state == ZN_ZONE_FULL); + + int ret = reset_zone(state, zone); + assert(ret == 0); + assert(g_queue_pop_tail(state->free) == zone); + + ret = open_zone(state, zone); + assert(ret == 0); + assert(g_queue_pop_tail(state->active) == zone); + + zone->state = ZN_ZONE_WRITE_OCCURING; + zone->zone_id = zone_id; + zone->chunk_offset = count - 1; // The last index that is active + g_queue_clear(zone->invalid); + + state->writes_occurring++; + + g_mutex_unlock(&state->state_mutex); +} + int zsm_return_active_zone(struct zone_state_manager *state, struct zn_pair *pair) { assert(state); @@ -274,7 +301,9 @@ zsm_evict(struct zone_state_manager *state, int zone_to_free) { struct zn_zone *zone = &state->state[zone_to_free]; assert(zone->state == ZN_ZONE_FULL); - + + // TODO: I think we need to free the invalid queue + g_queue_clear(zone->invalid); // This should do it... Right? int ret = reset_zone(state, zone); if (!ret) { g_mutex_unlock(&state->state_mutex); @@ -362,4 +391,4 @@ zsm_mark_chunk_invalid(struct zone_state_manager *state, struct zn_pair *locatio PRINT_G_QUEUE_GINT ); g_mutex_unlock(&state->state_mutex); -} \ No newline at end of file +} From 43c93f0f6ad0af0fcafbeadded64b8fb3adb3af4 Mon Sep 17 00:00:00 2001 From: IJN Akashi AR Date: Fri, 21 Mar 2025 02:23:15 -0700 Subject: [PATCH 2/6] add documentation --- include/cachemap.h | 23 +++++++++++-- include/zncache.h | 8 +++++ include/zone_state_manager.h | 10 ++++-- src/cachemap.c | 65 +++++++++++++++++++++--------------- src/eviction/chunk.c | 42 ++++++++++++++--------- src/zone_state_manager.c | 10 +++--- 6 files changed, 106 insertions(+), 52 deletions(-) diff --git a/include/cachemap.h b/include/cachemap.h index 91d9229..7660d1a 100644 --- a/include/cachemap.h +++ b/include/cachemap.h @@ -59,11 +59,28 @@ struct zone_map_result { struct zone_map_result zn_cachemap_find(struct zn_cachemap *map, const uint32_t data_id); -// Apply compaction to a zone. This temporarily renders all zones inactive/unavailable until compaction is finished. +/** @brief Begin compaction of a zone. This temporarily renders all chunks in that zone unavailable + until the compaction operation is finished, after which zn_cachemap_compact_end should be +called. + * @param[in] map the cachemap + * @param[in] zone_id the zone that will be compacted + * @param[out] data_ids an out pointer for an array. This outputs the list of data IDs that were associated with the zone. + * @param[out] locations an out pointer for an array. This outputs the list of locations (most importantly are the chunk locations) that were associated with the zone. + * @param[out] count an out pointer for the number of chunks that are still valid in the zone. + */ void -zn_cachemap_compact_begin(struct zn_cachemap *map, const uint32_t zone_id, uint32_t** data_ids, struct zn_pair** locations, uint32_t* count); +zn_cachemap_compact_begin(struct zn_cachemap *map, const uint32_t zone_id, uint32_t **data_ids, + struct zn_pair **locations, uint32_t *count); -// Finish compaction. +/** @brief Finish compaction of a zone. This temporarily renders all chunks in that zone unavailable + until the compaction operation is finished, after which zn_cachemap_compact_end should be +called. + * @param[in] map the cachemap + * @param[in] zone_id the zone that was compacted + * @param[in] data_ids An array that stores the list of data IDs that were associated with the zone. + * @param[in] locations An array that stores the list of locations that were associated with the zone. + * @param[in] count the number of chunks that were still valid in the zone. + */ void zn_cachemap_compact_end(struct zn_cachemap *map, const uint32_t zone_id, const uint32_t* data_ids, struct zn_pair* locations, uint32_t count); diff --git a/include/zncache.h b/include/zncache.h index a0bc2bb..10f6dbd 100644 --- a/include/zncache.h +++ b/include/zncache.h @@ -124,6 +124,14 @@ zn_init_cache(struct zn_cache *cache, struct zbd_info *info, size_t chunk_sz, ui void zn_destroy_cache(struct zn_cache *cache); +/** + * @brief Read the entire zone into the buffer. This is used to compact a zone. + * + * @param cache Pointer to the `zn_cache` structure + * @param zone_id The zone to read + * @param buffer the buffer to write to. It must have been allocated by the caller and big enough to store + * @return Buffer read from disk. It's the same buffer as the input + */ unsigned char * zn_read_from_disk_whole(struct zn_cache *cache, uint32_t zone_id, void *buffer); /** diff --git a/include/zone_state_manager.h b/include/zone_state_manager.h index fa788ea..4619ee9 100644 --- a/include/zone_state_manager.h +++ b/include/zone_state_manager.h @@ -106,7 +106,10 @@ zsm_get_active_zone(struct zone_state_manager *state, struct zn_pair *pair); GArray zsm_get_active_zone_batch(int chunks); -// Returns the active zone after it's written to + +/** @brief Returns the active zone after it's written to. + * @param pair the chunk that was written to. + */ int zsm_return_active_zone(struct zone_state_manager *state, struct zn_pair *pair); @@ -120,7 +123,10 @@ zsm_return_active_zone(struct zone_state_manager *state, struct zn_pair *pair); int zsm_evict(struct zone_state_manager *state, int zone_to_free); -// This function evicts the current zone and then allows the thread to write to it. Once the thread is finished writing, it should call zsm_return_active_zone. +/** @brief Resets the zone pointer, making it writeable again from the start. Once the thread is finished updating the zone, it should call zsm_return_active_zone. + * @param zone_id the zone to make free again + * @param count the number of chunks that are still valid. + */ void zsm_evict_and_write(struct zone_state_manager *state, uint32_t zone_id, uint32_t count); diff --git a/src/cachemap.c b/src/cachemap.c index 9187381..c97d5bf 100644 --- a/src/cachemap.c +++ b/src/cachemap.c @@ -104,45 +104,52 @@ zn_cachemap_find(struct zn_cachemap *map, const uint32_t data_id) { void zn_cachemap_compact_begin(struct zn_cachemap *map, const uint32_t zone_id, uint32_t** data_ids, struct zn_pair** locations, uint32_t* count) { assert(map); - assert(data_ids); - assert(locations); - assert(count); + assert(data_ids); + assert(locations); + assert(count); + + // This function gathers all valid chunks in the zone and informs + // the calling thread of them. + // It also removes all entries in the hash map in the meantime, + // replacing them with condition variables until the thread is + // finished compacting the zone. - g_mutex_lock(&map->cache_map_mutex); + g_mutex_lock(&map->cache_map_mutex); *count = g_hash_table_size(map->data_map[zone_id]); *locations = malloc(sizeof(struct zn_pair) * (*count)); *data_ids = malloc(sizeof(uint32_t) * (*count)); - // Gather all valid chunks in the zone and temporarily invalidate them - GHashTableIter iter; + // Gather all valid chunks in the zone and temporarily invalidate them + GHashTableIter iter; gpointer key = NULL; gpointer value = NULL; int i = 0; - g_hash_table_iter_init (&iter, map->data_map[zone_id]); - while (g_hash_table_iter_next (&iter, &key, &value)) { - uint32_t chunk_offset = GPOINTER_TO_UINT(key); - uint32_t data_id = GPOINTER_TO_UINT(value); - // Find the entry in the hash table - struct zone_map_result* res = g_hash_table_lookup(map->zone_map, GUINT_TO_POINTER(data_id)); - assert(res); - assert(res->type == RESULT_LOC); - assert(res->value.location.chunk_offset == chunk_offset); - assert(res->value.location.id == data_id); + // Iterate through all valid chunks. Invalid chunks should not be in the data map. + g_hash_table_iter_init (&iter, map->data_map[zone_id]); + while (g_hash_table_iter_next (&iter, &key, &value)) { + uint32_t chunk_offset = GPOINTER_TO_UINT(key); + uint32_t data_id = GPOINTER_TO_UINT(value); - // Invalidate it, replace with a temporary condition variable - res->type = RESULT_COND; - res->value.write_finished = g_atomic_rc_box_new(GCond); - g_cond_init(res->value.write_finished); + // Find the entry in the hash table + struct zone_map_result* res = g_hash_table_lookup(map->zone_map, GUINT_TO_POINTER(data_id)); + assert(res); + assert(res->type == RESULT_LOC); + assert(res->value.location.chunk_offset == chunk_offset); + assert(res->value.location.id == data_id); + // Invalidate it, replace with a temporary condition variable + res->type = RESULT_COND; + res->value.write_finished = g_atomic_rc_box_new(GCond); + g_cond_init(res->value.write_finished); // Write to the out variables (*data_ids)[i] = data_id; (*locations)[i] = (struct zn_pair){.chunk_offset = chunk_offset}; - i += 1; - } + i += 1; + } g_mutex_unlock(&map->cache_map_mutex); } @@ -173,18 +180,22 @@ zn_cachemap_insert_nolock(struct zn_cachemap *map, const uint32_t data_id, void zn_cachemap_compact_end(struct zn_cachemap *map, const uint32_t zone_id, const uint32_t* data_ids, struct zn_pair* locations, uint32_t count) { assert(map); - assert(data_ids); + assert(data_ids); assert(locations); - g_mutex_lock(&map->cache_map_mutex); + // This function finishes compaction. It reinserts all valid + // chunks back into the cachemap, replacing the condition + // variables with actual locations. + + g_mutex_lock(&map->cache_map_mutex); // Replace with actual locations for (uint32_t i = 0; i < count; i++) { - assert(zone_id == locations[i].zone); - zn_cachemap_insert_nolock(map, data_ids[i], locations[i]); + assert(zone_id == locations[i].zone); + zn_cachemap_insert_nolock(map, data_ids[i], locations[i]); } - g_mutex_unlock(&map->cache_map_mutex); + g_mutex_unlock(&map->cache_map_mutex); } void diff --git a/src/eviction/chunk.c b/src/eviction/chunk.c index 633c34a..acb9487 100644 --- a/src/eviction/chunk.c +++ b/src/eviction/chunk.c @@ -79,39 +79,48 @@ zn_policy_chunk_update(policy_data_t _policy, struct zn_pair location, g_mutex_unlock(&p->policy_mutex); } +/** @brief This function takes a full zone containing invalid chunks, + and compacts it so that only valid chunks remain. + * + * At a high level, this function copies all data from the zone, + removes all invalid chunks, and makes the zone available again as + an active zone. + */ static void zn_policy_compact_zone(struct zn_policy_chunk *p, struct eviction_policy_chunk_zone * old_zone) { - unsigned char* buf = zn_read_from_disk_whole(p->cache, old_zone->zone_id, p->chunk_buf); - assert(buf); + unsigned char* buf = zn_read_from_disk_whole(p->cache, old_zone->zone_id, p->chunk_buf); + assert(buf); - // These arrays are allocated inside the function - uint32_t* data_ids = NULL; + // These arrays are allocated inside the function + uint32_t* data_ids = NULL; struct zn_pair *locations = NULL; uint32_t count = 0; - + + // Get information about the valid chunks zn_cachemap_compact_begin(&p->cache->cache_map, old_zone->zone_id, &data_ids, &locations, &count); - // Wait for all readers to end + // Wait for all readers to finish while (g_atomic_int_get(&p->cache->active_readers[old_zone->zone_id]) > 0) {} - // Reset the current zone state - zsm_evict_and_write(&p->cache->zone_state, old_zone->zone_id, count); + // Reset the current zone, making it writeable again + zsm_evict_and_write(&p->cache->zone_state, old_zone->zone_id, count); // Copy all the old zones - unsigned char* random_buffer = generate_random_buffer(p->cache->chunk_sz); for (uint32_t i = 0; i < count; i++) { + // Read from the correct chunk offset indicated by the location + unsigned char* read_ptr = &buf[p->cache->chunk_sz * locations[i].chunk_offset]; - unsigned char* data = zn_gen_write_buffer(p->cache, old_zone->zone_id, random_buffer); - - // Write buffer to disk, 4kb blocks at a time - unsigned long long wp = - CHUNK_POINTER(p->cache->zone_cap, p->cache->chunk_sz, locations[i].chunk_offset, locations[i].zone); - if (zn_write_out(p->cache->fd, p->cache->chunk_sz, data, WRITE_GRANULARITY, wp) != 0) { + // Write to the ith sequential chunk + unsigned long long wp = + CHUNK_POINTER(p->cache->zone_cap, p->cache->chunk_sz, i, locations[i].zone); + if (zn_write_out(p->cache->fd, p->cache->chunk_sz, read_ptr, WRITE_GRANULARITY, wp) != 0) { dbg_printf("Couldn't write to fd at wp=%llu\n", wp); } } - // Update the zsm + // Update the zsm. The latest pair that we wrote to was to the + // (count - 1)th chunk (0 indexed), So indicate it as such in the + // chunk_offset field. struct zn_pair end_pair = { .zone = old_zone->zone_id, .chunk_offset = count - 1, @@ -119,6 +128,7 @@ zn_policy_compact_zone(struct zn_policy_chunk *p, struct eviction_policy_chunk_z int ret = zsm_return_active_zone(&p->cache->zone_state, &end_pair); assert(!ret); + // Finish the compaction, updating the cachemap zn_cachemap_compact_end(&p->cache->cache_map, old_zone->zone_id, data_ids, locations, count); } diff --git a/src/zone_state_manager.c b/src/zone_state_manager.c index bf200e7..4055895 100644 --- a/src/zone_state_manager.c +++ b/src/zone_state_manager.c @@ -239,7 +239,9 @@ void zsm_evict_and_write(struct zone_state_manager *state, uint32_t zone_id, uint32_t count) { assert(state); - g_mutex_lock(&state->state_mutex); + // Reset and open the zone. + + g_mutex_lock(&state->state_mutex); struct zn_zone *zone = &state->state[zone_id]; assert(zone->state == ZN_ZONE_FULL); @@ -250,16 +252,16 @@ zsm_evict_and_write(struct zone_state_manager *state, uint32_t zone_id, uint32_t ret = open_zone(state, zone); assert(ret == 0); - assert(g_queue_pop_tail(state->active) == zone); + assert(g_queue_pop_tail(state->active) == zone); - zone->state = ZN_ZONE_WRITE_OCCURING; + zone->state = ZN_ZONE_WRITE_OCCURING; zone->zone_id = zone_id; zone->chunk_offset = count - 1; // The last index that is active g_queue_clear(zone->invalid); state->writes_occurring++; - g_mutex_unlock(&state->state_mutex); + g_mutex_unlock(&state->state_mutex); } int From 04a939cfa307d4159e1fa4d860746194ecaca3e5 Mon Sep 17 00:00:00 2001 From: IJN Akashi AR Date: Fri, 21 Mar 2025 13:55:09 -0700 Subject: [PATCH 3/6] use NULL --- src/cache.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cache.c b/src/cache.c index 0ada3d5..759a7e3 100644 --- a/src/cache.c +++ b/src/cache.c @@ -225,7 +225,7 @@ zn_read_from_disk_whole(struct zn_cache *cache, uint32_t zone_id, void *buffer) size_t bytes = pread(cache->fd, buffer, cache->zone_cap, write_ptr); if (bytes != cache->zone_cap) { fprintf(stderr, "Couldn't read from fd\n"); - return nullptr; + return NULL; } return buffer; From 08360562a269c60d9c5cbb49f0ab50619abc599e Mon Sep 17 00:00:00 2001 From: John Ramsden Date: Wed, 2 Apr 2025 19:42:10 -0700 Subject: [PATCH 4/6] Fix missing id, add zone_map print --- include/znutil.h | 1 + src/cache.c | 1 + src/cachemap.c | 36 +++++++++++++++++++++--------------- src/znutil.c | 20 ++++++++++++++++++++ 4 files changed, 43 insertions(+), 15 deletions(-) diff --git a/include/znutil.h b/include/znutil.h index b9a2c9b..d210af5 100644 --- a/include/znutil.h +++ b/include/znutil.h @@ -32,6 +32,7 @@ enum print_g_hash_table_type { PRINT_G_HASH_TABLE_PROM_LRU_NODE = 1, PRINT_G_HASH_TABLE_ZN_PAIR = 2, PRINT_G_HASH_TABLE_ZN_PAIR_NODE = 3, + PRINT_G_HASH_TABLE_ZONE_MAP_RESULT = 4, }; /** diff --git a/src/cache.c b/src/cache.c index 8da813c..10fe0d0 100644 --- a/src/cache.c +++ b/src/cache.c @@ -60,6 +60,7 @@ zn_cache_get(struct zn_cache *cache, const uint32_t id, unsigned char *random_bu struct timespec start_time, end_time; TIME_NOW(&start_time); unsigned char *data = zn_read_from_disk(cache, &result.value.location); + result.value.location.id = id; TIME_NOW(&end_time); double t = TIME_DIFFERENCE_NSEC(start_time, end_time); ZN_PROFILER_UPDATE(cache->profiler, ZN_PROFILER_METRIC_READ_LATENCY, t); diff --git a/src/cachemap.c b/src/cachemap.c index c97d5bf..33eec99 100644 --- a/src/cachemap.c +++ b/src/cachemap.c @@ -119,6 +119,8 @@ zn_cachemap_compact_begin(struct zn_cachemap *map, const uint32_t zone_id, uint3 *count = g_hash_table_size(map->data_map[zone_id]); *locations = malloc(sizeof(struct zn_pair) * (*count)); *data_ids = malloc(sizeof(uint32_t) * (*count)); + assert(*locations); + assert(*data_ids); // Gather all valid chunks in the zone and temporarily invalidate them GHashTableIter iter; @@ -129,26 +131,27 @@ zn_cachemap_compact_begin(struct zn_cachemap *map, const uint32_t zone_id, uint3 // Iterate through all valid chunks. Invalid chunks should not be in the data map. g_hash_table_iter_init (&iter, map->data_map[zone_id]); while (g_hash_table_iter_next (&iter, &key, &value)) { - uint32_t chunk_offset = GPOINTER_TO_UINT(key); + uint32_t chunk_offset = GPOINTER_TO_UINT(key); uint32_t data_id = GPOINTER_TO_UINT(value); - // Find the entry in the hash table - struct zone_map_result* res = g_hash_table_lookup(map->zone_map, GUINT_TO_POINTER(data_id)); - assert(res); - assert(res->type == RESULT_LOC); - assert(res->value.location.chunk_offset == chunk_offset); - assert(res->value.location.id == data_id); + // Find the entry in the hash table + dbg_print_g_hash_table("zone_map", map->zone_map, PRINT_G_HASH_TABLE_ZONE_MAP_RESULT); + struct zone_map_result* res = g_hash_table_lookup(map->zone_map, GUINT_TO_POINTER(data_id)); + assert(res); + assert(res->type == RESULT_LOC); + assert(res->value.location.chunk_offset == chunk_offset); + assert(res->value.location.id == data_id); - // Invalidate it, replace with a temporary condition variable - res->type = RESULT_COND; - res->value.write_finished = g_atomic_rc_box_new(GCond); - g_cond_init(res->value.write_finished); + // Invalidate it, replace with a temporary condition variable + res->type = RESULT_COND; + res->value.write_finished = g_atomic_rc_box_new(GCond); + g_cond_init(res->value.write_finished); // Write to the out variables (*data_ids)[i] = data_id; - (*locations)[i] = (struct zn_pair){.chunk_offset = chunk_offset}; + (*locations)[i] = (struct zn_pair){.chunk_offset = chunk_offset, .zone = zone_id}; - i += 1; + i += 1; } g_mutex_unlock(&map->cache_map_mutex); @@ -164,9 +167,11 @@ zn_cachemap_insert_nolock(struct zn_cachemap *map, const uint32_t data_id, struct zone_map_result *result = g_hash_table_lookup(map->zone_map, GUINT_TO_POINTER(data_id)); assert(result->type == RESULT_COND); + dbg_print_g_hash_table("zone_map", map->zone_map, PRINT_G_HASH_TABLE_ZONE_MAP_RESULT); GCond *condition = result->value.write_finished; result->value.location = location; // Does this mutate the entry in the hash table? result->type = RESULT_LOC; + dbg_print_g_hash_table("zone_map", map->zone_map, PRINT_G_HASH_TABLE_ZONE_MAP_RESULT); assert(map->data_map[location.zone]); g_hash_table_insert(map->data_map[location.zone], GUINT_TO_POINTER(location.chunk_offset), GUINT_TO_POINTER(data_id)); g_cond_broadcast(condition); // Wake up threads waiting for it @@ -191,8 +196,9 @@ zn_cachemap_compact_end(struct zn_cachemap *map, const uint32_t zone_id, const u // Replace with actual locations for (uint32_t i = 0; i < count; i++) { - assert(zone_id == locations[i].zone); - zn_cachemap_insert_nolock(map, data_ids[i], locations[i]); + uint32_t zone = locations[i].zone; + assert(zone_id == zone); + zn_cachemap_insert_nolock(map, data_ids[i], locations[i]); } g_mutex_unlock(&map->cache_map_mutex); diff --git a/src/znutil.c b/src/znutil.c index 4bb25c4..363265f 100644 --- a/src/znutil.c +++ b/src/znutil.c @@ -38,6 +38,24 @@ print_g_hash_table_zn_pair_node(gpointer key, gpointer value) { } } +inline static void +print_g_hash_table_zone_map_result_node(gpointer key, gpointer value) { + struct zone_map_result *res = value; + if (res) { + if (res->type == RESULT_LOC) { + struct zn_pair *zp = &res->value.location; + printf("[%d: (zone=%u, chunk=%u, id=%u, in_use=%s)], ", + GPOINTER_TO_INT(key), zp->zone, zp->chunk_offset, zp->id, zp->in_use ? "true" : "false"); + } else if (res->type == RESULT_COND) { + printf("[%d: (cond var at %p)], ", GPOINTER_TO_INT(key), (void *)res->value.write_finished); + } else { + printf("[%d: (unknown type %d)], ", GPOINTER_TO_INT(key), res->type); + } + } else { + printf("[%d: (%s)], ", GPOINTER_TO_INT(key), "NULL"); + } +} + inline static void print_g_hash_table_g_int(gpointer key, gpointer value) { printf("[%d: %d], ", GPOINTER_TO_INT(key), GPOINTER_TO_INT(value)); @@ -61,6 +79,8 @@ print_g_hash_table(char *name, GHashTable *hash_table, enum print_g_hash_table_t print_g_hash_table_zn_pair_node(key, value); break; case PRINT_G_HASH_TABLE_PROM_LRU_NODE: print_g_hash_table_prom_lru_node(key, value); break; + case PRINT_G_HASH_TABLE_ZONE_MAP_RESULT: + print_g_hash_table_zone_map_result_node(key, value); break; default: printf("Unimplemented hash table print type"); break; } From 178c1ff510c3609b68664a537d9c3c7e807b18fc Mon Sep 17 00:00:00 2001 From: samc Date: Wed, 9 Apr 2025 19:19:22 -0700 Subject: [PATCH 5/6] use posix_memalign --- include/znutil.h | 1 + src/cache.c | 2 -- src/eviction_policy.c | 7 ++++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/include/znutil.h b/include/znutil.h index d210af5..8f3b301 100644 --- a/include/znutil.h +++ b/include/znutil.h @@ -2,6 +2,7 @@ #define UTIL_H #define SEED 42 +#define ZN_DIRECT_ALIGNMENT 4096 #include "libzbd/zbd.h" diff --git a/src/cache.c b/src/cache.c index fe5aaab..a05279a 100644 --- a/src/cache.c +++ b/src/cache.c @@ -15,8 +15,6 @@ #include "libzbd/zbd.h" #include -#define ZN_DIRECT_ALIGNMENT 4096 - void zn_fg_evict(struct zn_cache *cache) { uint32_t free_zones = zsm_get_num_free_zones(&cache->zone_state); diff --git a/src/eviction_policy.c b/src/eviction_policy.c index bd21db9..0c56971 100644 --- a/src/eviction_policy.c +++ b/src/eviction_policy.c @@ -3,10 +3,12 @@ #include "eviction_policy_promotional.h" #include "eviction_policy_chunk.h" #include "zncache.h" +#include "znutil.h" #include #include #include +#include #ifdef UNUSED /** @@ -79,7 +81,10 @@ zn_evict_policy_init(struct zn_evict_policy *policy, enum zn_evict_policy_type t data->cache = cache; - data->chunk_buf = malloc(cache->max_zone_chunks * cache->chunk_sz); + if (posix_memalign((void**)&data->chunk_buf, ZN_DIRECT_ALIGNMENT, + cache->max_zone_chunks * cache->chunk_sz) != 0) { + nomem(); + } assert(data->chunk_buf); data->total_chunks = cache->nr_zones * cache->max_zone_chunks; From c4e60c40d4a4fe48ca4b66fd858bb453a9f9532a Mon Sep 17 00:00:00 2001 From: samc Date: Wed, 9 Apr 2025 22:54:18 -0700 Subject: [PATCH 6/6] enforce only one thread evicting at a time This is done by returning immediately when the lock fails. Also fix a small bug with Chunk --- src/eviction/chunk.c | 10 ++++++++-- src/eviction/promotional.c | 5 ++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/eviction/chunk.c b/src/eviction/chunk.c index d67a43b..239ba24 100644 --- a/src/eviction/chunk.c +++ b/src/eviction/chunk.c @@ -145,6 +145,10 @@ zn_policy_chunk_gc(policy_data_t policy) { while (free_zones < EVICT_LOW_THRESH_ZONES) { struct zn_minheap_entry *ent = zn_minheap_extract_min(p->invalid_pqueue); + if (!ent) { + return; + } + struct eviction_policy_chunk_zone * old_zone = ent->data; assert(old_zone); dbg_printf("Found minheap_entry priority=%u, chunks_in_use=%u, zone=%u\n", @@ -210,8 +214,10 @@ int zn_policy_chunk_evict(policy_data_t policy) { struct zn_policy_chunk *p = policy; - g_mutex_lock(&p->policy_mutex); - + gboolean locked_by_us = g_mutex_trylock(&p->policy_mutex); + if (!locked_by_us) { + return -1; + } uint32_t in_lru = g_queue_get_length(&p->lru_queue); uint32_t free_chunks = p->total_chunks - in_lru; diff --git a/src/eviction/promotional.c b/src/eviction/promotional.c index db4f5db..4c4cd26 100644 --- a/src/eviction/promotional.c +++ b/src/eviction/promotional.c @@ -58,7 +58,10 @@ int zn_policy_promotional_get_zone_to_evict(policy_data_t policy) { struct zn_policy_promotional *promote_policy = policy; - g_mutex_lock(&promote_policy->policy_mutex); + gboolean locked_by_us = g_mutex_trylock(&promote_policy->policy_mutex); + if (!locked_by_us) { + return -1; + } dbg_print_g_queue("lru_queue", &promote_policy->lru_queue, PRINT_G_QUEUE_GINT);