From 9a3da0c8cfaea6f99c17862096f5194eaffa9fc9 Mon Sep 17 00:00:00 2001 From: Greg Burd Date: Wed, 3 Apr 2024 15:40:39 -0400 Subject: [PATCH] cleanup, some still TODO --- Makefile | 4 +- examples/slm.c | 190 +++++++++++++++--------------- include/sl.h | 313 +++++++++++++++---------------------------------- 3 files changed, 192 insertions(+), 315 deletions(-) diff --git a/Makefile b/Makefile index 40ba5e6..586c532 100644 --- a/Makefile +++ b/Makefile @@ -5,8 +5,8 @@ SHARED_LIB = libskiplist.so # https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html #CFLAGS = -Wall -Wextra -Wpedantic -Of -std=c99 -Iinclude/ -fPIC -#CFLAGS = -Wall -Wextra -Wpedantic -Og -g -std=c99 -Iinclude/ -fPIC -CFLAGS = -Wall -Wextra -Wpedantic -Og -g -fsanitize=address,leak,object-size,pointer-compare,pointer-subtract,null,return,bounds,pointer-overflow,undefined -fsanitize-address-use-after-scope -std=c99 -Iinclude/ -fPIC +CFLAGS = -Wall -Wextra -Wpedantic -Og -g -std=c99 -Iinclude/ -fPIC +#CFLAGS = -Wall -Wextra -Wpedantic -Og -g -fsanitize=address,leak,object-size,pointer-compare,pointer-subtract,null,return,bounds,pointer-overflow,undefined -fsanitize-address-use-after-scope -std=c99 -Iinclude/ -fPIC #CFLAGS = -Wall -Wextra -Wpedantic -Og -g -fsanitize=all -fhardened -std=c99 -Iinclude/ -fPIC #env ASAN_OPTIONS=detect_leaks=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./examples/mls diff --git a/examples/slm.c b/examples/slm.c index 9fda119..a3db828 100644 --- a/examples/slm.c +++ b/examples/slm.c @@ -1,5 +1,5 @@ #pragma GCC push_options -#pragma GCC optimize ("O0") +#pragma GCC optimize("O0") // OPTIONS to set before including sl.h // --------------------------------------------------------------------------- @@ -18,23 +18,19 @@ // Local demo application OPTIONS: // --------------------------------------------------------------------------- -#define TEST_ARRAY_SIZE 1000 +#define TEST_ARRAY_SIZE 10 #define VALIDATE #define SNAPSHOTS +//define TODO_RESTORE_SNAPSHOTS #define DOT #ifdef DOT size_t gen = 0; FILE *of = 0; -typedef struct esempio esempio_t; -typedef struct esempio_node esempio_node_t; -static int __skip_integrity_check_esempio(esempio_t *slist, int flags); -int api_skip_dot_esempio(FILE *os, esempio_t *slist, size_t nsg, char *msg, void (*fn)(struct esempio_node *, char *)); -void sprintf_esempio_node(esempio_node_t *node, char *buf); #endif // --------------------------------------------------------------------------- #ifdef VALIDATE -#define CHECK __skip_integrity_check_esempio(list, 0) +#define CHECK __skip_integrity_check_ex(list, 0) #else #define CHECK ((void)0) #endif @@ -42,7 +38,7 @@ void sprintf_esempio_node(esempio_node_t *node, char *buf); /* * SKIPLIST EXAMPLE: * - * This example creates an "esempio" (example in Italian) Skiplist where keys + * This example creates an "ex" (example in Italian) Skiplist where keys * are integers, values are strings allocated on the heap. */ @@ -54,17 +50,17 @@ void sprintf_esempio_node(esempio_node_t *node, char *buf); * node against another, logic you'll provide in SKIP_DECL as a * block below. */ -struct esempio_node { +struct ex_node { int key; char *value; - SKIPLIST_ENTRY(esempio) entries; + SKIPLIST_ENTRY(ex) entries; }; /* * Generate all the access functions for our type of Skiplist. */ SKIPLIST_DECL( - esempio, api_, entries, + ex, api_, entries, /* compare entries: list, a, b, aux */ { (void)list; @@ -119,7 +115,7 @@ SKIPLIST_DECL( * list or when `a == b`. In those cases the comparison function * returns before using the code in your block, don't panic. :) int -__esempio_key_compare(esempio_t *list, esempio_node_t *a, esempio_node_t *b, void *aux) +__ex_key_compare(ex_t *list, ex_node_t *a, ex_node_t *b, void *aux) { (void)list; (void)aux; @@ -140,7 +136,7 @@ __esempio_key_compare(esempio_t *list, esempio_node_t *a, esempio_node_t *b, voi * extract data from within your nodes. */ SKIPLIST_DECL_ACCESS( - esempio, api_, key, int, value, char *, + ex, api_, key, int, value, char *, /* query blk */ { query.key = key; }, /* return blk */ { return node->value; }) @@ -150,7 +146,7 @@ SKIPLIST_DECL_ACCESS( * Enable functions that enable returning to an earlier point in * time when a snapshot was created. */ -SKIPLIST_DECL_SNAPSHOTS(esempio, api_, entries) +SKIPLIST_DECL_SNAPSHOTS(ex, api_, entries) /* * Optional: Archive to/from bytes @@ -158,33 +154,33 @@ SKIPLIST_DECL_SNAPSHOTS(esempio, api_, entries) * Enable functions that can write/read the content of your Skiplist * out/in to/from an array of bytes. */ -SKIPLIST_DECL_ARCHIVE(esempio, api_, entries) +// TODO SKIPLIST_DECL_ARCHIVE(ex, api_, entries) /* * Optional: As Hashtable * * Turn your Skiplist into a hash table. */ -//TODO SKIPLIST_DECL_HASHTABLE(esempio, api_, entries, snaps) +// TODO SKIPLIST_DECL_HASHTABLE(ex, api_, entries, snaps) /* * Optional: Check Skiplists at runtime * * Create a functions that validate the integrity of a Skiplist. */ -SKIPLIST_DECL_VALIDATE(esempio, api_, entries) +SKIPLIST_DECL_VALIDATE(ex, api_, entries) /* Optional: Visualize your Skiplist using DOT/Graphviz in PDF * * Create the functions used to annotate a visualization of a Skiplist. */ -SKIPLIST_DECL_DOT(esempio, api_, entries) +SKIPLIST_DECL_DOT(ex, api_, entries) void -sprintf_esempio_node(esempio_node_t *node, char *buf) +sprintf_ex_node(ex_node_t *node, char *buf) { - sprintf(buf, "%d:%s (hits: %lu)", node->key, node->value, node->entries.sle_levels[0].hits); -//TODO sprintf(buf, "%d:%s", node->key, node->value); + // sprintf(buf, "%d:%s (hits: %lu)", node->key, node->value, node->entries.sle_levels[0].hits); + sprintf(buf, "%d:%s", node->key, node->value); } // Function for this demo application. @@ -253,14 +249,22 @@ shuffle(int *array, size_t n) } } +#ifdef TODO_RESTORE_SNAPSHOTS +typedef struct { + size_t length; + size_t key; + size_t snap_id; +} snap_info_t; +#endif + // --------------------------------------------------------------------------- int main() { int rc; -#ifdef SNAPSHOTS - size_t snap_i = 0; - uint64_t snap_ids[2048]; +#ifdef TODO_RESTORE_SNAPSHOTS + size_t n_snaps = 0; + snap_info_t snaps[TEST_ARRAY_SIZE * 2 + 1]; #endif #ifdef DOT @@ -272,28 +276,23 @@ main() #endif /* Allocate and initialize a Skiplist. */ - esempio_t *list = (esempio_t *)malloc(sizeof(esempio_t)); + ex_t *list = (ex_t *)malloc(sizeof(ex_t)); if (list == NULL) return ENOMEM; - rc = api_skip_init_esempio(list, -12); + rc = api_skip_init_ex(list, -12); if (rc) return rc; - api_skip_snapshots_init_esempio(list); + api_skip_snapshots_init_ex(list); #ifdef DOT - api_skip_dot_esempio(of, list, gen++, "init", sprintf_esempio_node); + api_skip_dot_ex(of, list, gen++, "init", sprintf_ex_node); #endif - if (api_skip_get_esempio(list, 0) != NULL) + if (api_skip_get_ex(list, 0) != NULL) perror("found a non-existent item!"); - api_skip_del_esempio(list, 0); + api_skip_del_ex(list, 0); CHECK; -#ifdef SNAPSHOTS - /* Test creating a snapshot of an empty Skiplist */ - snap_ids[snap_i++] = api_skip_snapshot_esempio(list); -#endif - - /* Insert 7 key/value pairs into the list. */ + /* Insert TEST_ARRAY_SIZE key/value pairs into the list. */ int i, j; char *numeral; #ifdef DOT @@ -307,138 +306,143 @@ main() shuffle(array, asz); for (i = 0; i < asz; i++) { - numeral = to_lower(int_to_roman_numeral(array[i])); - rc = api_skip_put_esempio(list, array[i], numeral); - CHECK; #ifdef SNAPSHOTS - if (i > TEST_ARRAY_SIZE + 1) { - snap_ids[snap_i++] = api_skip_snapshot_esempio(list); + api_skip_snapshot_ex(list); +#elif defined(TODO_RESTORE_SNAPSHOTS) + /* Snapshot the first iteration, and then every 5th after that. */ + if (i % 5 == 0) { + snaps[i].length = api_skip_length_ex(list); + snaps[i].key = array[i]; + snaps[i].snap_id = api_skip_snapshot_ex(list); + n_snaps++; CHECK; } #endif + numeral = to_lower(int_to_roman_numeral(array[i])); + rc = api_skip_put_ex(list, array[i], numeral); + CHECK; #ifdef DOT sprintf(msg, "put key: %d value: %s", i, numeral); - api_skip_dot_esempio(of, list, gen++, msg, sprintf_esempio_node); - CHECK; + api_skip_dot_ex(of, list, gen++, msg, sprintf_ex_node); #endif - char *v = api_skip_get_esempio(list, array[i]); + char *v = api_skip_get_ex(list, array[i]); CHECK; char *upper_numeral = calloc(1, strlen(v) + 1); strncpy(upper_numeral, v, strlen(v)); + assert(strncmp(v, upper_numeral, strlen(upper_numeral)) == 0); to_upper(upper_numeral); - api_skip_set_esempio(list, array[i], upper_numeral); + api_skip_set_ex(list, array[i], upper_numeral); CHECK; - if (i == 8) { - api_skip_get_esempio(list, -2); - api_skip_get_esempio(list, -2); - api_skip_get_esempio(list, -2); - api_skip_get_esempio(list, -2); - } } numeral = int_to_roman_numeral(-1); - api_skip_dup_esempio(list, -1, numeral); + api_skip_dup_ex(list, -1, numeral); CHECK; #ifdef DOT sprintf(msg, "put dup key: %d value: %s", i, numeral); - api_skip_dot_esempio(of, list, gen++, msg, sprintf_esempio_node); - CHECK; + api_skip_dot_ex(of, list, gen++, msg, sprintf_ex_node); #endif numeral = int_to_roman_numeral(1); - api_skip_dup_esempio(list, 1, numeral); + api_skip_dup_ex(list, 1, numeral); CHECK; #ifdef DOT sprintf(msg, "put dup key: %d value: %s", i, numeral); - api_skip_dot_esempio(of, list, gen++, msg, sprintf_esempio_node); - CHECK; + api_skip_dot_ex(of, list, gen++, msg, sprintf_ex_node); #endif - api_skip_del_esempio(list, 0); + api_skip_del_ex(list, 0); CHECK; - if (api_skip_get_esempio(list, 0) != NULL) + if (api_skip_get_ex(list, 0) != NULL) perror("found a deleted item!"); - api_skip_del_esempio(list, 0); + api_skip_del_ex(list, 0); CHECK; - if (api_skip_get_esempio(list, 0) != NULL) + if (api_skip_get_ex(list, 0) != NULL) perror("found a deleted item!"); int key = TEST_ARRAY_SIZE + 1; - api_skip_del_esempio(list, key); + api_skip_del_ex(list, key); CHECK; key = -(TEST_ARRAY_SIZE)-1; - api_skip_del_esempio(list, key); + api_skip_del_ex(list, key); CHECK; #ifdef DOT sprintf(msg, "deleted key: %d, value: %s", 0, numeral); - api_skip_dot_esempio(of, list, gen++, msg, sprintf_esempio_node); - CHECK; -#endif - -#ifdef SNAPSHOTS - api_skip_restore_snapshot_esempio(list, snap_ids[snap_i - 1]); - api_skip_release_snapshots_esempio(list); + api_skip_dot_ex(of, list, gen++, msg, sprintf_ex_node); #endif numeral = int_to_roman_numeral(-(TEST_ARRAY_SIZE)); - assert(strcmp(api_skip_pos_esempio(list, SKIP_GTE, -(TEST_ARRAY_SIZE)-1)->value, numeral) == 0); + assert(strcmp(api_skip_pos_ex(list, SKIP_GTE, -(TEST_ARRAY_SIZE)-1)->value, numeral) == 0); free(numeral); numeral = int_to_roman_numeral(-2); - assert(strcmp(api_skip_pos_esempio(list, SKIP_GTE, -2)->value, numeral) == 0); + assert(strcmp(api_skip_pos_ex(list, SKIP_GTE, -2)->value, numeral) == 0); free(numeral); numeral = int_to_roman_numeral(1); - assert(strcmp(api_skip_pos_esempio(list, SKIP_GTE, 0)->value, numeral) == 0); + assert(strcmp(api_skip_pos_ex(list, SKIP_GTE, 0)->value, numeral) == 0); free(numeral); numeral = int_to_roman_numeral(2); - assert(strcmp(api_skip_pos_esempio(list, SKIP_GTE, 2)->value, numeral) == 0); + assert(strcmp(api_skip_pos_ex(list, SKIP_GTE, 2)->value, numeral) == 0); free(numeral); - assert(api_skip_pos_esempio(list, SKIP_GTE, (TEST_ARRAY_SIZE + 1)) == NULL); + assert(api_skip_pos_ex(list, SKIP_GTE, (TEST_ARRAY_SIZE + 1)) == NULL); numeral = int_to_roman_numeral(-(TEST_ARRAY_SIZE)); - assert(strcmp(api_skip_pos_esempio(list, SKIP_GT, -(TEST_ARRAY_SIZE)-1)->value, numeral) == 0); + assert(strcmp(api_skip_pos_ex(list, SKIP_GT, -(TEST_ARRAY_SIZE)-1)->value, numeral) == 0); free(numeral); numeral = int_to_roman_numeral(-1); - assert(strcmp(api_skip_pos_esempio(list, SKIP_GT, -2)->value, numeral) == 0); + assert(strcmp(api_skip_pos_ex(list, SKIP_GT, -2)->value, numeral) == 0); free(numeral); numeral = int_to_roman_numeral(1); - assert(strcmp(api_skip_pos_esempio(list, SKIP_GT, 0)->value, numeral) == 0); + assert(strcmp(api_skip_pos_ex(list, SKIP_GT, 0)->value, numeral) == 0); free(numeral); numeral = int_to_roman_numeral(2); - assert(strcmp(api_skip_pos_esempio(list, SKIP_GT, 1)->value, numeral) == 0); + assert(strcmp(api_skip_pos_ex(list, SKIP_GT, 1)->value, numeral) == 0); free(numeral); - assert(api_skip_pos_esempio(list, SKIP_GT, TEST_ARRAY_SIZE) == NULL); + assert(api_skip_pos_ex(list, SKIP_GT, TEST_ARRAY_SIZE) == NULL); - assert(api_skip_pos_esempio(list, SKIP_LT, -(TEST_ARRAY_SIZE)) == NULL); + assert(api_skip_pos_ex(list, SKIP_LT, -(TEST_ARRAY_SIZE)) == NULL); numeral = int_to_roman_numeral(-2); - assert(strcmp(api_skip_pos_esempio(list, SKIP_LT, -1)->value, numeral) == 0); + assert(strcmp(api_skip_pos_ex(list, SKIP_LT, -1)->value, numeral) == 0); free(numeral); numeral = int_to_roman_numeral(-1); - assert(strcmp(api_skip_pos_esempio(list, SKIP_LT, 0)->value, numeral) == 0); + assert(strcmp(api_skip_pos_ex(list, SKIP_LT, 0)->value, numeral) == 0); free(numeral); numeral = int_to_roman_numeral(1); - assert(strcmp(api_skip_pos_esempio(list, SKIP_LT, 2)->value, numeral) == 0); + assert(strcmp(api_skip_pos_ex(list, SKIP_LT, 2)->value, numeral) == 0); free(numeral); numeral = int_to_roman_numeral(TEST_ARRAY_SIZE); - assert(strcmp(api_skip_pos_esempio(list, SKIP_LT, (TEST_ARRAY_SIZE + 1))->value, numeral) == 0); + assert(strcmp(api_skip_pos_ex(list, SKIP_LT, (TEST_ARRAY_SIZE + 1))->value, numeral) == 0); free(numeral); - assert(api_skip_pos_esempio(list, SKIP_LTE, -(TEST_ARRAY_SIZE)-1) == NULL); + assert(api_skip_pos_ex(list, SKIP_LTE, -(TEST_ARRAY_SIZE)-1) == NULL); numeral = int_to_roman_numeral(-2); - assert(strcmp(api_skip_pos_esempio(list, SKIP_LTE, -2)->value, numeral) == 0); + assert(strcmp(api_skip_pos_ex(list, SKIP_LTE, -2)->value, numeral) == 0); free(numeral); numeral = int_to_roman_numeral(-1); - assert(strcmp(api_skip_pos_esempio(list, SKIP_LTE, 0)->value, numeral) == 0); + assert(strcmp(api_skip_pos_ex(list, SKIP_LTE, 0)->value, numeral) == 0); free(numeral); numeral = int_to_roman_numeral(2); - assert(strcmp(api_skip_pos_esempio(list, SKIP_LTE, 2)->value, numeral) == 0); + assert(strcmp(api_skip_pos_ex(list, SKIP_LTE, 2)->value, numeral) == 0); free(numeral); numeral = int_to_roman_numeral(TEST_ARRAY_SIZE); - assert(strcmp(api_skip_pos_esempio(list, SKIP_LTE, (TEST_ARRAY_SIZE + 1))->value, numeral) == 0); + assert(strcmp(api_skip_pos_ex(list, SKIP_LTE, (TEST_ARRAY_SIZE + 1))->value, numeral) == 0); free(numeral); +#ifdef TODO_RESTORE_SNAPSHOTS + // Walk backward by 2 and test snapshot restore. + for (i = n_snaps; i > 0; i -= 2) { + api_skip_restore_snapshot_ex(list, snaps[i].snap_id); + CHECK; + assert(api_skip_length_ex(list) == snaps[i].length); + numeral = int_to_roman_numeral(snaps[i].key); + assert(strncmp(api_skip_get_ex(list, snaps[i].key), numeral, strlen(numeral)) == 0); + free(numeral); + } + api_skip_release_snapshots_ex(list); +#endif + #ifdef DOT - api_skip_dot_end_esempio(of, gen); + api_skip_dot_end_ex(of, gen); fclose(of); #endif - api_skip_free_esempio(list); + api_skip_free_ex(list); free(list); return rc; } diff --git a/include/sl.h b/include/sl.h index dcc1502..3587b58 100644 --- a/include/sl.h +++ b/include/sl.h @@ -265,14 +265,12 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li int (*compare_entries)(decl##_t *, decl##_node_t *, decl##_node_t *, void *); \ \ /* Optional: Snapshots */ \ - int (*preserve_node)(decl##_t * slist, const decl##_node_t *src, decl##_node_t **preserved); \ - void (*release_snapshots)(decl##_t *); \ - size_t (*snapshot_current_era)(decl##_t *); \ - size_t (*snapshot_incr_era)(decl##_t *); \ - void (*snapshot_record_era)(decl##_t *, decl##_node_t *); \ + int (*snapshot_preserve_node)(decl##_t * slist, const decl##_node_t *src, decl##_node_t **preserved); \ + void (*snapshot_release)(decl##_t *); \ } slh_fns; \ struct { \ - size_t era; \ + size_t cur_era; \ + size_t pres_era; \ decl##_node_t *pres; \ } slh_snap; \ }; \ @@ -419,7 +417,8 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li \ slist->slh_length = 0; \ slist->slh_max_height = SKIPLIST_MAX_HEIGHT == 1 ? (size_t)(max < 0 ? -max : max) : SKIPLIST_MAX_HEIGHT; \ - slist->slh_snap.era = 0; \ + slist->slh_snap.cur_era = 0; \ + slist->slh_snap.pres_era = 0; \ slist->slh_snap.pres = 0; \ slist->slh_fns.free_entry = __skip_free_entry_fn_##decl; \ slist->slh_fns.update_entry = __skip_update_entry_fn_##decl; \ @@ -433,11 +432,13 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li if (rc) \ goto fail; \ \ + /* Initial height is 0 (meaning 1 level), all next point to tail */ \ slist->slh_head->field.sle_height = 0; \ for (i = 0; i < slist->slh_max_height; i++) \ slist->slh_head->field.sle_levels[i].next = slist->slh_tail; \ slist->slh_head->field.sle_prev = NULL; \ \ + /* Initial height is 0 (meaning 1 level), all next point to tail */ \ slist->slh_tail->field.sle_height = slist->slh_head->field.sle_height; \ for (i = 0; i < slist->slh_max_height; i++) \ slist->slh_tail->field.sle_levels[i].next = NULL; \ @@ -473,7 +474,7 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li * \ * Returns the current length of the list. \ */ \ - int prefix##skip_length_##decl(decl##_t *slist) \ + size_t prefix##skip_length_##decl(decl##_t *slist) \ { \ return slist->slh_length; \ } \ @@ -561,8 +562,8 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li prefix##skip_free_node_##decl(slist, node); \ node = next; \ } \ - if (slist->slh_fns.snapshot_incr_era) \ - slist->slh_fns.snapshot_incr_era(slist); \ + if (slist->slh_snap.pres_era > 0) \ + slist->slh_snap.cur_era++; \ return; \ } \ \ @@ -669,10 +670,6 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li } while (j-- > 1); \ /* 4) reduce height by one */ \ path[i].node->field.sle_height--; \ - /* TODO: remove me \ - __skip_integrity_check_##decl(slist, 0); \ - prefix##skip_dot_##decl(of, slist, gen, "dsc_cond", sprintf_##decl##_node); \ - */ \ } \ } \ /* (b) Check the ascent condition: \ @@ -694,10 +691,6 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li path[i - 1].node->field.sle_levels[path[i - 1].in].hits = 0; \ if (path[i - 1].in != 0) \ path[i - 1].node->field.sle_levels[path[i - 1].in].next->field.sle_levels[path[i - 1].in].next = path[i].node; \ - /* TODO: remove me \ - __skip_integrity_check_##decl(slist, 0); \ - prefix##skip_dot_##decl(of, slist, gen, "asc_cond", sprintf_##decl##_node); \ - */ \ } \ } \ } \ @@ -829,8 +822,10 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li slist->slh_tail->field.sle_height = new_height; \ } \ /* Record the era for this node to enable snapshots. */ \ - if (slist->slh_fns.snapshot_record_era) \ - slist->slh_fns.snapshot_record_era(slist, new); \ + if (slist->slh_snap.pres_era > 0) { \ + /* Increase the the list's era/age and record it. */ \ + new->field.sle_era = slist->slh_snap.cur_era++; \ + } \ /* Set hits for rebalencing to 1 when new born. */ \ new->field.sle_levels[new_height].hits = 1; \ /* Increase our list length (aka. size, count, etc.) by one. */ \ @@ -1102,20 +1097,20 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li return -1; \ \ /* If the optional snapshots feature is configured, use it now. \ - Snapshots preserve the node if it is younger than our snapshot \ - moment. */ \ - if (slist->slh_fns.preserve_node) { \ - np = slist->slh_fns.preserve_node(slist, node, NULL); \ + Snapshots preserve the node if it is older than our snapshot \ + and about to be mutated. */ \ + if (slist->slh_snap.pres_era > 0) { \ + /* Preserve the node. */ \ + np = slist->slh_fns.snapshot_preserve_node(slist, node, NULL); \ if (np > 0) \ return np; \ + \ + /* Increase the the list's era/age. */ \ + slist->slh_snap.cur_era++; \ } \ \ slist->slh_fns.update_entry(node, value); \ \ - /* Record the era for this node to enable snapshots. */ \ - if (slist->slh_fns.snapshot_record_era) \ - slist->slh_fns.snapshot_record_era(slist, node); \ - \ return rc; \ } \ \ @@ -1150,12 +1145,16 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li node = path[0].node; \ if (node) { \ /* If the optional snapshots feature is configured, use it now. \ - Snapshots preserve the node if it is younger than our snapshot \ - moment, this node is about to be removed. */ \ - if (slist->slh_fns.preserve_node) { \ - np = slist->slh_fns.preserve_node(slist, node, NULL); \ + Snapshots preserve the node if it is older than our snapshot \ + and about to be mutated. */ \ + if (slist->slh_snap.pres_era > 0) { \ + /* Preserve the node. */ \ + np = slist->slh_fns.snapshot_preserve_node(slist, node, NULL); \ if (np > 0) \ return np; \ + \ + /* Increase the the list's era/age. */ \ + slist->slh_snap.cur_era++; \ } \ /* We found it, set the next->prev to the node->prev keeping in mind \ that the next node might be the tail). */ \ @@ -1208,8 +1207,8 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li if (slist == NULL) \ return; \ \ - if (slist->slh_fns.release_snapshots) \ - slist->slh_fns.release_snapshots(slist); \ + if (slist->slh_snap.pres_era > 0 && slist->slh_fns.snapshot_release) \ + slist->slh_fns.snapshot_release(slist); \ \ prefix##skip_release_##decl(slist); \ \ @@ -1220,15 +1219,18 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li #define SKIPLIST_DECL_SNAPSHOTS(decl, prefix, field) \ \ /** \ - * -- __skip_snapshot_record_era_ \ + * -- skip_snapshot_ \ * \ + * A snapshot is a read-only view of a Skiplist at a point in time. Once \ + * taken, a snapshot must be restored or released. Any number of snapshots \ + * can be created. Return the `era` of the snapshot. \ */ \ - static void __skip_snapshot_record_era_##decl(decl##_t *slist, decl##_node_t *node) \ + uint64_t prefix##skip_snapshot_##decl(decl##_t *slist) \ { \ if (slist == NULL) \ - return; \ + return 0; \ \ - node->field.sle_era = slist->slh_fns.snapshot_current_era(slist); \ + return ++slist->slh_snap.pres_era; \ } \ \ /** \ @@ -1238,17 +1240,21 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li void prefix##skip_release_snapshots_##decl(decl##_t *slist) \ { \ decl##_node_t *node, *next; \ + \ if (slist == NULL) \ return; \ \ + if (slist->slh_snap.pres_era == 0) \ + return; \ + \ node = slist->slh_snap.pres; \ while (node) { \ next = node->field.sle_levels[0].next; \ prefix##skip_free_node_##decl(slist, node); \ node = next; \ } \ - slist->slh_snap.pres = NULL; \ - slist->slh_snap.era = 0; \ + slist->slh_snap.pres = NULL; \ + slist->slh_snap.pres_era = 0; \ } \ \ /** \ @@ -1276,7 +1282,15 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li if (slist == NULL || src == NULL) \ return 0; \ \ - if (src->field.sle_era > slist->slh_snap.era) \ + /* Never preserve the head or the tail. */ \ + if (src == slist->slh_head || src == slist->slh_tail) \ + return 0; \ + \ + /* If the era into which the node `src` was born preceeded the latest \ + snapshot era, then we need to preserve the older version of this \ + node. Said another way, we preserve anything with an era that is \ + less than the slh_snap.cur_era. */ \ + if (src->field.sle_era > slist->slh_snap.pres_era) \ return 0; \ \ /* (a) alloc, ... */ \ @@ -1285,18 +1299,16 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li if (rc) \ return rc; \ \ - /* (b) shallow copy, fix sle_levels pointer, ... */ \ + /* (b) shallow copy, copied sle_levels pointer is to the src list, so \ + update that to point to the offset in this heap object, ... */ \ memcpy(dest, src, sizeof(decl##_node_t) + sle_arr_sz); \ dest->field.sle_levels = (struct __skiplist_##decl##_level *)((uintptr_t)dest + sizeof(decl##_node_t)); \ \ - /* ... if we're not preserving the head or the tail, ... */ \ - if (!(src == slist->slh_head || src == slist->slh_tail)) { \ - /* (c) then user-supplied copy */ \ - slist->slh_fns.archive_entry(dest, src); \ - if (rc) { \ - prefix##skip_free_node_##decl(slist, dest); \ - return rc; \ - } \ + /* (c) then user-supplied copy */ \ + slist->slh_fns.archive_entry(dest, src); \ + if (rc) { \ + prefix##skip_free_node_##decl(slist, dest); \ + return rc; \ } \ \ /* (d) is this a duplicate? */ \ @@ -1312,7 +1324,7 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li } \ \ /* (f) set duplicate flag */ \ - dest->field.sle_levels[1].next = (decl##_node_t *)is_dup; \ + dest->field.sle_levels[1].next = is_dup; \ \ /* (g) insert node into slh_pres list at head */ \ if (slist->slh_snap.pres == NULL) { \ @@ -1333,29 +1345,14 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li } \ \ /** \ - * -- skip_snapshot_ \ - * \ - * A snapshot is a read-only view of a Skiplist at a point in time. Once \ - * taken, a snapshot must be restored or released. Any number of snapshots \ - * can be created. \ - */ \ - uint64_t prefix##skip_snapshot_##decl(decl##_t *slist) \ - { \ - if (slist == NULL) \ - return 0; \ - slist->slh_snap.era = slist->slh_fns.snapshot_current_era(slist); \ - slist->slh_fns.snapshot_incr_era(slist); \ - return slist->slh_snap.era; \ - } \ - \ - /** \ - * -- skip_restore_snapshot_ TODO test! \ + * -- skip_restore_snapshot_ TODO/WIP \ * \ * Restores the Skiplist to generation `era`. Once you restore `era` you \ - * can no longer access any generations > `era`. \ + * can no longer restore any [era, current era] only those earlier than \ + * era. \ * \ * ALGORITHM: \ - * iterate over the preserved nodes (slist->slh_snap.pres) \ + * iterate over the preserved nodes (slist->slh_snap.pres) \ * a) remove/free nodes with node->era > era from slist \ * b) remove/free nodes > era from slh_pres \ * c) restore nodes == era by... \ @@ -1376,31 +1373,35 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li if (slist == NULL) \ return NULL; \ \ - if (era >= slist->slh_snap.era || slist->slh_snap.pres == NULL) \ + if (slist->slh_snap.pres_era == 0) \ + return NULL; \ + \ + if (era >= slist->slh_snap.cur_era || slist->slh_snap.pres == NULL) \ return slist; \ \ - cur_era = slist->slh_fns.snapshot_current_era(slist); \ + cur_era = slist->slh_snap.cur_era; \ \ - /* (a) */ \ + /* (a) remove and free nodes from slist that are newer than era */ \ SKIPLIST_FOREACH_H2T(decl, prefix, field, slist, node, i) \ { \ ((void)i); \ - if (node->field.sle_era > era) \ + if (node->field.sle_era > era) { \ prefix##skip_remove_node_##decl(slist, node); \ + } \ } \ \ prev = NULL; \ node = slist->slh_snap.pres; \ while (node) { \ - /* (b) */ \ + /* (b) remove nodes from slh_pres that are newer than era */ \ if (node->field.sle_era > era) { \ /* remove node from slh_snap.pres list */ \ if (slist->slh_snap.pres == node) \ slist->slh_snap.pres = node->field.sle_levels[0].next; \ else { \ if (node->field.sle_levels[0].next == NULL) \ - if (node == slist->slh_snap.pres) \ - slist->slh_snap.pres = NULL; \ + if (node == slist->slh_snap.pres) \ + slist->slh_snap.pres = NULL; \ else \ prev->field.sle_levels[0].next = NULL; \ else \ @@ -1409,7 +1410,7 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li prefix##skip_free_node_##decl(slist, node); \ } \ \ - /* c */ \ + /* (c) restore nodes from slh_pres that match the era */ \ prev = NULL; \ if (node->field.sle_era == era) { \ /* remove node from slh_snap.pres list */ \ @@ -1417,7 +1418,10 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li slist->slh_snap.pres = node->field.sle_levels[0].next; \ else { \ if (node->field.sle_levels[0].next == NULL) \ - prev->field.sle_levels[0].next = NULL; \ + if (node == slist->slh_snap.pres) \ + slist->slh_snap.pres = NULL; \ + else \ + prev->field.sle_levels[0].next = NULL; \ else \ prev->field.sle_levels[0].next = node->field.sle_levels[0].next; \ } \ @@ -1434,32 +1438,12 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li node = node->field.sle_levels[0].next; \ } \ \ - /* (d) */ \ - slist->slh_snap.era = cur_era; \ + /* (d) set list's era */ \ + slist->slh_snap.pres_era = slist->slh_snap.pres == NULL ? 0 : cur_era; \ \ return slist; \ } \ \ - /** \ - * -- __skip_snapshot_current_era \ - * \ - * Returns the current era for snapshot purposes. \ - */ \ - static size_t __skip_snapshot_current_era_##decl(decl##_t *slist) \ - { \ - return slist->slh_snap.era; \ - } \ - \ - /** \ - * -- __skip_snapshot_incr_era \ - * \ - * Increments the snapshot era. \ - */ \ - static size_t __skip_snapshot_incr_era_##decl(decl##_t *slist) \ - { \ - return ++slist->slh_snap.era; \ - } \ - \ /** \ * -- skip_snapshots_init_ \ * \ @@ -1468,122 +1452,11 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li void prefix##skip_snapshots_init_##decl(decl##_t *slist) \ { \ if (slist != NULL) { \ - slist->slh_fns.preserve_node = __skip_preserve_node_##decl; \ - slist->slh_fns.release_snapshots = prefix##skip_release_snapshots_##decl; \ - slist->slh_fns.snapshot_current_era = __skip_snapshot_current_era_##decl; \ - slist->slh_fns.snapshot_incr_era = __skip_snapshot_incr_era_##decl; \ - slist->slh_fns.snapshot_record_era = __skip_snapshot_record_era_##decl; \ + slist->slh_fns.snapshot_preserve_node = __skip_preserve_node_##decl; \ + slist->slh_fns.snapshot_release = prefix##skip_release_snapshots_##decl; \ } \ } -#define SKIPLIST_DECL_ARCHIVE(decl, prefix, field) \ - \ - /* Archive of a Skip List */ \ - typedef struct decl##_archive { \ - decl##_t list; \ - decl##_node_t *nodes; \ - size_t bytes; \ - } decl##_archive_t; \ - \ - /** \ - * -- skip_to_bytes_ TODO/WIP \ - * \ - * Writes out list and node content to a portable array of bytes \ - * suitable for archiving to disk. \ - */ \ - decl##_archive_t *prefix##skip_to_bytes_##decl(decl##_t *slist) \ - { \ - int rc = 0; \ - size_t size = 0, bytes, i; \ - decl##_archive_t *archive; \ - decl##_node_t *src; \ - \ - if (slist == NULL) \ - return NULL; \ - \ - bytes = sizeof(decl##_archive_t) + (slist->slh_length * sizeof(decl##_node_t)); \ - src = prefix##skip_head_##decl(slist); \ - while (src) { \ - slist->slh_fns.sizeof_entry(src); \ - bytes += sizeof(size_t); \ - bytes += size; \ - src = prefix##skip_next_node_##decl(slist, src); \ - } \ - archive = (decl##_archive_t *)calloc(1, bytes); \ - if (archive == NULL) \ - return NULL; \ - \ - archive->bytes = bytes; \ - archive->list.slh_length = slist->slh_length; \ - archive->list.slh_max_height = slist->slh_max_height; \ - archive->nodes = (decl##_node_t *)(archive + sizeof(decl##_archive_t)); \ - \ - i = 0; \ - src = prefix##skip_head_##decl(slist); \ - while (src) { \ - decl##_node_t *n = (decl##_node_t *)archive->nodes + (i++ * sizeof(decl##_node_t)); \ - slist->slh_fns.archive_entry(n, src); \ - if (rc) \ - return NULL; \ - src = prefix##skip_next_node_##decl(slist, src); \ - } \ - return archive; \ - } \ - \ - /** \ - * -- skip_from_bytes_ TODO/WIP \ - * \ - */ \ - decl##_t *prefix##skip_from_bytes_##decl(decl##_archive_t *archive) \ - { \ - int rc; \ - size_t i; \ - decl##_t *slist; \ - decl##_node_t *src, *dest; \ - \ - if (archive == NULL) \ - return 0; \ - slist = (decl##_t *)calloc(1, sizeof(decl##_t)); \ - if (slist == NULL) \ - return NULL; \ - \ - slist->slh_max_height = archive->list.slh_max_height; \ - \ - rc = prefix##skip_alloc_node_##decl(slist, &slist->slh_head); \ - if (rc) \ - goto fail; \ - rc = prefix##skip_alloc_node_##decl(slist, &slist->slh_tail); \ - if (rc) \ - goto fail; \ - \ - slist->slh_head->field.sle_height = 0; \ - for (i = 0; i < slist->slh_max_height; i++) \ - slist->slh_head->field.sle_levels[i].next = slist->slh_tail; \ - slist->slh_head->field.sle_prev = NULL; \ - \ - slist->slh_tail->field.sle_height = slist->slh_max_height; \ - for (i = 0; i < slist->slh_max_height; i++) \ - slist->slh_tail->field.sle_levels[i].next = NULL; \ - slist->slh_tail->field.sle_prev = slist->slh_head; \ - \ - i = 0; \ - while (archive->list.slh_length > 0) { \ - decl##_node_t *n = (decl##_node_t *)archive->nodes + (i++ * sizeof(decl##_node_t)); \ - src = (decl##_node_t *)&n; \ - rc = prefix##skip_alloc_node_##decl(slist, &dest); \ - slist->slh_fns.archive_entry(dest, src); \ - __skip_insert_##decl(slist, dest, 1); \ - archive->list.slh_length--; \ - } \ - return slist; \ - fail:; \ - if (slist->slh_head) \ - free(slist->slh_head); \ - if (slist->slh_tail) \ - free(slist->slh_tail); \ - return NULL; \ - } - #define SKIPLIST_DECL_VALIDATE(decl, prefix, field) \ /** \ * -- __skip_integrity_failure_ \ @@ -1709,12 +1582,12 @@ void __attribute__((format(printf, 4, 5))) __skip_diag_(const char *file, int li return n_err; \ } \ /* TODO: really? \ - if (node->field.sle_levels[lvl].next != slist->slh_tail) { \ - __skip_integrity_failure_##decl("after internal nodes, the head's %lu next node should always be the tail\n", lvl); \ - n_err++; \ - if (flags) \ - return n_err; \ - } \ + if (node->field.sle_levels[lvl].next != slist->slh_tail) { \ + __skip_integrity_failure_##decl("after internal nodes, the head's %lu next node should always be the tail\n", lvl); \ + n_err++; \ + if (flags) \ + return n_err; \ + } \ */ \ } \ \