| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "dictionary-internals.h" |
| 4 | |
| 5 | // ---------------------------------------------------------------------------- |
| 6 | // unit test |
| 7 | |
| 8 | static void dictionary_unittest_free_char_pp(char **pp, size_t entries) { |
| 9 | for(size_t i = 0; i < entries ;i++) |
| 10 | freez(pp[i]); |
| 11 | |
| 12 | freez(pp); |
| 13 | } |
| 14 | |
| 15 | static char **dictionary_unittest_generate_names(size_t entries) { |
| 16 | char **names = mallocz(sizeof(char *) * entries); |
| 17 | for(size_t i = 0; i < entries ;i++) { |
| 18 | char buf[25 + 1] = ""; |
| 19 | snprintfz(buf, sizeof(buf), "name.%zu.0123456789.%zu!@#$%%^&*(),./[]{}\\|~`", i, entries / 2 + i); |
| 20 | names[i] = strdupz(buf); |
| 21 | } |
| 22 | return names; |
| 23 | } |
| 24 | |
| 25 | static char **dictionary_unittest_generate_values(size_t entries) { |
| 26 | char **values = mallocz(sizeof(char *) * entries); |
| 27 | for(size_t i = 0; i < entries ;i++) { |
| 28 | char buf[25 + 1] = ""; |
| 29 | snprintfz(buf, sizeof(buf), "value-%zu-0987654321.%zu%%^&*(),. \t !@#$/[]{}\\|~`", i, entries / 2 + i); |
| 30 | values[i] = strdupz(buf); |
| 31 | } |
| 32 | return values; |
| 33 | } |
| 34 | |
| 35 | static size_t dictionary_unittest_set_clone(DICTIONARY *dict, char **names, char **values, size_t entries) { |
| 36 | size_t errors = 0; |
| 37 | for(size_t i = 0; i < entries ;i++) { |
| 38 | size_t vallen = strlen(values[i]); |
| 39 | char *val = (char *)dictionary_set(dict, names[i], values[i], vallen); |
| 40 | if(val == values[i]) { fprintf(stderr, ">>> %s() returns reference to value\n", __FUNCTION__); errors++; } |
| 41 | if(!val || memcmp(val, values[i], vallen) != 0) { fprintf(stderr, ">>> %s() returns invalid value\n", __FUNCTION__); errors++; } |
| 42 | } |
| 43 | return errors; |
| 44 | } |
| 45 | |
| 46 | static size_t dictionary_unittest_set_null(DICTIONARY *dict, char **names, char **values, size_t entries) { |
| 47 | (void)values; |
| 48 | size_t errors = 0; |
| 49 | size_t i = 0; |
| 50 | for(; i < entries ;i++) { |
| 51 | void *val = dictionary_set(dict, names[i], NULL, 0); |
| 52 | if(val != NULL) { fprintf(stderr, ">>> %s() returns a non NULL value\n", __FUNCTION__); errors++; } |
| 53 | } |
| 54 | if(dictionary_entries(dict) != i) { |
| 55 | fprintf(stderr, ">>> %s() dictionary items do not match\n", __FUNCTION__); |
| 56 | errors++; |
| 57 | } |
| 58 | return errors; |
| 59 | } |
| 60 | |
| 61 | |
| 62 | static size_t dictionary_unittest_set_nonclone(DICTIONARY *dict, char **names, char **values, size_t entries) { |
| 63 | size_t errors = 0; |
| 64 | for(size_t i = 0; i < entries ;i++) { |
| 65 | size_t vallen = strlen(values[i]); |
| 66 | char *val = (char *)dictionary_set(dict, names[i], values[i], vallen); |
| 67 | if(val != values[i]) { fprintf(stderr, ">>> %s() returns invalid pointer to value\n", __FUNCTION__); errors++; } |
| 68 | } |
| 69 | return errors; |
| 70 | } |
| 71 | |
| 72 | static size_t dictionary_unittest_get_clone(DICTIONARY *dict, char **names, char **values, size_t entries) { |
| 73 | size_t errors = 0; |
| 74 | for(size_t i = 0; i < entries ;i++) { |
| 75 | size_t vallen = strlen(values[i]); |
| 76 | char *val = (char *)dictionary_get(dict, names[i]); |
| 77 | if(val == values[i]) { fprintf(stderr, ">>> %s() returns reference to value\n", __FUNCTION__); errors++; } |
| 78 | if(!val || memcmp(val, values[i], vallen) != 0) { fprintf(stderr, ">>> %s() returns invalid value\n", __FUNCTION__); errors++; } |
| 79 | } |
| 80 | return errors; |
| 81 | } |
| 82 | |
| 83 | static size_t dictionary_unittest_get_nonclone(DICTIONARY *dict, char **names, char **values, size_t entries) { |
| 84 | size_t errors = 0; |
| 85 | for(size_t i = 0; i < entries ;i++) { |
| 86 | char *val = (char *)dictionary_get(dict, names[i]); |
| 87 | if(val != values[i]) { fprintf(stderr, ">>> %s() returns invalid pointer to value\n", __FUNCTION__); errors++; } |
| 88 | } |
| 89 | return errors; |
| 90 | } |
| 91 | |
| 92 | static size_t dictionary_unittest_get_nonexisting(DICTIONARY *dict, char **names, char **values, size_t entries) { |
| 93 | (void)names; |
| 94 | size_t errors = 0; |
| 95 | for(size_t i = 0; i < entries ;i++) { |
| 96 | char *val = (char *)dictionary_get(dict, values[i]); |
| 97 | if(val) { fprintf(stderr, ">>> %s() returns non-existing item\n", __FUNCTION__); errors++; } |
| 98 | } |
| 99 | return errors; |
| 100 | } |
| 101 | |
| 102 | static size_t dictionary_unittest_del_nonexisting(DICTIONARY *dict, char **names, char **values, size_t entries) { |
| 103 | (void)names; |
| 104 | size_t errors = 0; |
| 105 | for(size_t i = 0; i < entries ;i++) { |
| 106 | bool ret = dictionary_del(dict, values[i]); |
| 107 | if(ret) { fprintf(stderr, ">>> %s() deleted non-existing item\n", __FUNCTION__); errors++; } |
| 108 | } |
| 109 | return errors; |
| 110 | } |
| 111 | |
| 112 | static size_t dictionary_unittest_del_existing(DICTIONARY *dict, char **names, char **values, size_t entries) { |
| 113 | (void)values; |
| 114 | size_t errors = 0; |
| 115 | |
| 116 | size_t forward_from = 0, forward_to = entries / 3; |
| 117 | size_t middle_from = forward_to, middle_to = entries * 2 / 3; |
| 118 | size_t backward_from = middle_to, backward_to = entries; |
| 119 | |
| 120 | for(size_t i = forward_from; i < forward_to ;i++) { |
| 121 | bool ret = dictionary_del(dict, names[i]); |
| 122 | if(!ret) { fprintf(stderr, ">>> %s() didn't delete (forward) existing item\n", __FUNCTION__); errors++; } |
| 123 | } |
| 124 | |
| 125 | for(size_t i = middle_to - 1; i >= middle_from ;i--) { |
| 126 | bool ret = dictionary_del(dict, names[i]); |
| 127 | if(!ret) { fprintf(stderr, ">>> %s() didn't delete (middle) existing item\n", __FUNCTION__); errors++; } |
| 128 | } |
| 129 | |
| 130 | for(size_t i = backward_to - 1; i >= backward_from ;i--) { |
| 131 | bool ret = dictionary_del(dict, names[i]); |
| 132 | if(!ret) { fprintf(stderr, ">>> %s() didn't delete (backward) existing item\n", __FUNCTION__); errors++; } |
| 133 | } |
| 134 | |
| 135 | return errors; |
| 136 | } |
| 137 | |
| 138 | static size_t dictionary_unittest_reset_clone(DICTIONARY *dict, char **names, char **values, size_t entries) { |
| 139 | (void)values; |
| 140 | // set the name as value too |
| 141 | size_t errors = 0; |
| 142 | for(size_t i = 0; i < entries ;i++) { |
| 143 | size_t vallen = strlen(names[i]); |
| 144 | char *val = (char *)dictionary_set(dict, names[i], names[i], vallen); |
| 145 | if(val == names[i]) { fprintf(stderr, ">>> %s() returns reference to value\n", __FUNCTION__); errors++; } |
| 146 | if(!val || memcmp(val, names[i], vallen) != 0) { fprintf(stderr, ">>> %s() returns invalid value\n", __FUNCTION__); errors++; } |
| 147 | } |
| 148 | return errors; |
| 149 | } |
| 150 | |
| 151 | static size_t dictionary_unittest_reset_nonclone(DICTIONARY *dict, char **names, char **values, size_t entries) { |
| 152 | (void)values; |
| 153 | // set the name as value too |
| 154 | size_t errors = 0; |
| 155 | for(size_t i = 0; i < entries ;i++) { |
| 156 | size_t vallen = strlen(names[i]); |
| 157 | char *val = (char *)dictionary_set(dict, names[i], names[i], vallen); |
| 158 | if(val != names[i]) { fprintf(stderr, ">>> %s() returns invalid pointer to value\n", __FUNCTION__); errors++; } |
| 159 | if(!val) { fprintf(stderr, ">>> %s() returns invalid value\n", __FUNCTION__); errors++; } |
| 160 | } |
| 161 | return errors; |
| 162 | } |
| 163 | |
| 164 | static size_t dictionary_unittest_reset_dont_overwrite_nonclone(DICTIONARY *dict, char **names, char **values, size_t entries) { |
| 165 | // set the name as value too |
| 166 | size_t errors = 0; |
| 167 | for(size_t i = 0; i < entries ;i++) { |
| 168 | size_t vallen = strlen(names[i]); |
| 169 | char *val = (char *)dictionary_set(dict, names[i], names[i], vallen); |
| 170 | if(val != values[i]) { fprintf(stderr, ">>> %s() returns invalid pointer to value\n", __FUNCTION__); errors++; } |
| 171 | } |
| 172 | return errors; |
| 173 | } |
| 174 | |
| 175 | static int dictionary_unittest_walkthrough_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value __maybe_unused, void *data __maybe_unused) { |
| 176 | return 1; |
| 177 | } |
| 178 | |
| 179 | static size_t dictionary_unittest_walkthrough(DICTIONARY *dict, char **names, char **values, size_t entries) { |
| 180 | (void)names; |
| 181 | (void)values; |
| 182 | int sum = dictionary_walkthrough_read(dict, dictionary_unittest_walkthrough_callback, NULL); |
| 183 | if(sum < (int)entries) return entries - sum; |
| 184 | else return sum - entries; |
| 185 | } |
| 186 | |
| 187 | static int dictionary_unittest_walkthrough_delete_this_callback(const DICTIONARY_ITEM *item, void *value __maybe_unused, void *data) { |
| 188 | const char *name = dictionary_acquired_item_name((DICTIONARY_ITEM *)item); |
| 189 | |
| 190 | if(!dictionary_del((DICTIONARY *)data, name)) |
| 191 | return 0; |
| 192 | |
| 193 | return 1; |
| 194 | } |
| 195 | |
| 196 | static size_t dictionary_unittest_walkthrough_delete_this(DICTIONARY *dict, char **names, char **values, size_t entries) { |
| 197 | (void)names; |
| 198 | (void)values; |
| 199 | int sum = dictionary_walkthrough_write(dict, dictionary_unittest_walkthrough_delete_this_callback, dict); |
| 200 | if(sum < (int)entries) return entries - sum; |
| 201 | else return sum - entries; |
| 202 | } |
| 203 | |
| 204 | static int dictionary_unittest_walkthrough_stop_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value __maybe_unused, void *data __maybe_unused) { |
| 205 | return -1; |
| 206 | } |
| 207 | |
| 208 | static size_t dictionary_unittest_walkthrough_stop(DICTIONARY *dict, char **names, char **values, size_t entries) { |
| 209 | (void)names; |
| 210 | (void)values; |
| 211 | (void)entries; |
| 212 | int sum = dictionary_walkthrough_read(dict, dictionary_unittest_walkthrough_stop_callback, NULL); |
| 213 | if(sum != -1) return 1; |
| 214 | return 0; |
| 215 | } |
| 216 | |
| 217 | static size_t dictionary_unittest_foreach(DICTIONARY *dict, char **names, char **values, size_t entries) { |
| 218 | (void)names; |
| 219 | (void)values; |
| 220 | (void)entries; |
| 221 | size_t count = 0; |
| 222 | char *item; |
| 223 | dfe_start_read(dict, item) |
| 224 | count++; |
| 225 | dfe_done(item); |
| 226 | |
| 227 | if(count > entries) return count - entries; |
| 228 | return entries - count; |
| 229 | } |
| 230 | |
| 231 | static size_t dictionary_unittest_foreach_delete_this(DICTIONARY *dict, char **names, char **values, size_t entries) { |
| 232 | (void)names; |
| 233 | (void)values; |
| 234 | (void)entries; |
| 235 | size_t count = 0; |
| 236 | char *item; |
| 237 | dfe_start_write(dict, item) |
| 238 | if(dictionary_del(dict, item_dfe.name)) count++; |
| 239 | dfe_done(item); |
| 240 | |
| 241 | if(count > entries) return count - entries; |
| 242 | return entries - count; |
| 243 | } |
| 244 | |
| 245 | static size_t dictionary_unittest_destroy(DICTIONARY *dict, char **names, char **values, size_t entries) { |
| 246 | (void)names; |
| 247 | (void)values; |
| 248 | (void)entries; |
| 249 | size_t bytes = dictionary_destroy(dict); |
| 250 | fprintf(stderr, " %s() freed %zu bytes,", __FUNCTION__, bytes); |
| 251 | return 0; |
| 252 | } |
| 253 | |
| 254 | static usec_t dictionary_unittest_run_and_measure_time(DICTIONARY *dict, char *message, char **names, char **values, size_t entries, size_t *errors, size_t (*callback)(DICTIONARY *dict, char **names, char **values, size_t entries)) { |
| 255 | fprintf(stderr, "%40s ... ", message); |
| 256 | |
| 257 | usec_t started = now_realtime_usec(); |
| 258 | size_t errs = callback(dict, names, values, entries); |
| 259 | usec_t ended = now_realtime_usec(); |
| 260 | usec_t dt = ended - started; |
| 261 | |
| 262 | if(callback == dictionary_unittest_destroy) dict = NULL; |
| 263 | |
| 264 | long int found_ok = 0, found_deleted = 0, found_referenced = 0; |
| 265 | if(dict) { |
| 266 | DICTIONARY_ITEM *item; |
| 267 | DOUBLE_LINKED_LIST_FOREACH_FORWARD(dict->items.list, item, prev, next) { |
| 268 | if(item->refcount >= 0 && !(item ->flags & ITEM_FLAG_DELETED)) |
| 269 | found_ok++; |
| 270 | else |
| 271 | found_deleted++; |
| 272 | |
| 273 | if(item->refcount > 0) |
| 274 | found_referenced++; |
| 275 | } |
| 276 | } |
| 277 | |
| 278 | fprintf(stderr, " %zu errors, %d (found %ld) items in dictionary, %d (found %ld) referenced, %d (found %ld) deleted, %"PRIu64" usec \n", |
| 279 | errs, dict?dict->entries:0, found_ok, dict?dict->referenced_items:0, found_referenced, dict?dict->pending_deletion_items:0, found_deleted, dt); |
| 280 | *errors += errs; |
| 281 | return dt; |
| 282 | } |
| 283 | |
| 284 | static void dictionary_unittest_clone(DICTIONARY *dict, char **names, char **values, size_t entries, size_t *errors) { |
| 285 | dictionary_unittest_run_and_measure_time(dict, "adding entries", names, values, entries, errors, dictionary_unittest_set_clone); |
| 286 | dictionary_unittest_run_and_measure_time(dict, "getting entries", names, values, entries, errors, dictionary_unittest_get_clone); |
| 287 | dictionary_unittest_run_and_measure_time(dict, "getting non-existing entries", names, values, entries, errors, dictionary_unittest_get_nonexisting); |
| 288 | dictionary_unittest_run_and_measure_time(dict, "resetting entries", names, values, entries, errors, dictionary_unittest_reset_clone); |
| 289 | dictionary_unittest_run_and_measure_time(dict, "deleting non-existing entries", names, values, entries, errors, dictionary_unittest_del_nonexisting); |
| 290 | dictionary_unittest_run_and_measure_time(dict, "traverse foreach read loop", names, values, entries, errors, dictionary_unittest_foreach); |
| 291 | dictionary_unittest_run_and_measure_time(dict, "walkthrough read callback", names, values, entries, errors, dictionary_unittest_walkthrough); |
| 292 | dictionary_unittest_run_and_measure_time(dict, "walkthrough read callback stop", names, values, entries, errors, dictionary_unittest_walkthrough_stop); |
| 293 | dictionary_unittest_run_and_measure_time(dict, "deleting existing entries", names, values, entries, errors, dictionary_unittest_del_existing); |
| 294 | dictionary_unittest_run_and_measure_time(dict, "walking through empty", names, values, 0, errors, dictionary_unittest_walkthrough); |
| 295 | dictionary_unittest_run_and_measure_time(dict, "traverse foreach empty", names, values, 0, errors, dictionary_unittest_foreach); |
| 296 | dictionary_unittest_run_and_measure_time(dict, "destroying empty dictionary", names, values, entries, errors, dictionary_unittest_destroy); |
| 297 | } |
| 298 | |
| 299 | static void dictionary_unittest_nonclone(DICTIONARY *dict, char **names, char **values, size_t entries, size_t *errors) { |
| 300 | dictionary_unittest_run_and_measure_time(dict, "adding entries", names, values, entries, errors, dictionary_unittest_set_nonclone); |
| 301 | dictionary_unittest_run_and_measure_time(dict, "getting entries", names, values, entries, errors, dictionary_unittest_get_nonclone); |
| 302 | dictionary_unittest_run_and_measure_time(dict, "getting non-existing entries", names, values, entries, errors, dictionary_unittest_get_nonexisting); |
| 303 | dictionary_unittest_run_and_measure_time(dict, "resetting entries", names, values, entries, errors, dictionary_unittest_reset_nonclone); |
| 304 | dictionary_unittest_run_and_measure_time(dict, "deleting non-existing entries", names, values, entries, errors, dictionary_unittest_del_nonexisting); |
| 305 | dictionary_unittest_run_and_measure_time(dict, "traverse foreach read loop", names, values, entries, errors, dictionary_unittest_foreach); |
| 306 | dictionary_unittest_run_and_measure_time(dict, "walkthrough read callback", names, values, entries, errors, dictionary_unittest_walkthrough); |
| 307 | dictionary_unittest_run_and_measure_time(dict, "walkthrough read callback stop", names, values, entries, errors, dictionary_unittest_walkthrough_stop); |
| 308 | dictionary_unittest_run_and_measure_time(dict, "deleting existing entries", names, values, entries, errors, dictionary_unittest_del_existing); |
| 309 | dictionary_unittest_run_and_measure_time(dict, "walking through empty", names, values, 0, errors, dictionary_unittest_walkthrough); |
| 310 | dictionary_unittest_run_and_measure_time(dict, "traverse foreach empty", names, values, 0, errors, dictionary_unittest_foreach); |
| 311 | dictionary_unittest_run_and_measure_time(dict, "destroying empty dictionary", names, values, entries, errors, dictionary_unittest_destroy); |
| 312 | } |
| 313 | |
| 314 | struct dictionary_unittest_sorting { |
| 315 | const char *old_name; |
| 316 | const char *old_value; |
| 317 | size_t count; |
| 318 | }; |
| 319 | |
| 320 | static int dictionary_unittest_sorting_callback(const DICTIONARY_ITEM *item, void *value, void *data) { |
| 321 | const char *name = dictionary_acquired_item_name((DICTIONARY_ITEM *)item); |
| 322 | struct dictionary_unittest_sorting *t = (struct dictionary_unittest_sorting *)data; |
| 323 | const char *v = (const char *)value; |
| 324 | |
| 325 | int ret = 0; |
| 326 | if(t->old_name && strcmp(t->old_name, name) > 0) { |
| 327 | fprintf(stderr, "name '%s' should be after '%s'\n", t->old_name, name); |
| 328 | ret = 1; |
| 329 | } |
| 330 | t->count++; |
| 331 | t->old_name = name; |
| 332 | t->old_value = v; |
| 333 | |
| 334 | return ret; |
| 335 | } |
| 336 | |
| 337 | static size_t dictionary_unittest_sorted_walkthrough(DICTIONARY *dict, char **names, char **values, size_t entries) { |
| 338 | (void)names; |
| 339 | (void)values; |
| 340 | struct dictionary_unittest_sorting tmp = { .old_name = NULL, .old_value = NULL, .count = 0 }; |
| 341 | size_t errors; |
| 342 | errors = dictionary_sorted_walkthrough_read(dict, dictionary_unittest_sorting_callback, &tmp); |
| 343 | |
| 344 | if(tmp.count != entries) { |
| 345 | fprintf(stderr, "Expected %zu entries, counted %zu\n", entries, tmp.count); |
| 346 | errors++; |
| 347 | } |
| 348 | return errors; |
| 349 | } |
| 350 | |
| 351 | static void dictionary_unittest_sorting(DICTIONARY *dict, char **names, char **values, size_t entries, size_t *errors) { |
| 352 | dictionary_unittest_run_and_measure_time(dict, "adding entries", names, values, entries, errors, dictionary_unittest_set_clone); |
| 353 | dictionary_unittest_run_and_measure_time(dict, "sorted walkthrough", names, values, entries, errors, dictionary_unittest_sorted_walkthrough); |
| 354 | } |
| 355 | |
| 356 | static void dictionary_unittest_null_dfe(DICTIONARY *dict, char **names, char **values, size_t entries, size_t *errors) { |
| 357 | dictionary_unittest_run_and_measure_time(dict, "adding null value entries", names, values, entries, errors, dictionary_unittest_set_null); |
| 358 | dictionary_unittest_run_and_measure_time(dict, "traverse foreach read loop", names, values, entries, errors, dictionary_unittest_foreach); |
| 359 | } |
| 360 | |
| 361 | |
| 362 | static int unittest_check_dictionary_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value __maybe_unused, void *data __maybe_unused) { |
| 363 | return 1; |
| 364 | } |
| 365 | |
| 366 | static size_t unittest_check_dictionary(const char *label, DICTIONARY *dict, size_t traversable, size_t active_items, size_t deleted_items, size_t referenced_items, size_t pending_deletion) { |
| 367 | size_t errors = 0; |
| 368 | |
| 369 | size_t ll = 0; |
| 370 | void *t; |
| 371 | dfe_start_read(dict, t) |
| 372 | ll++; |
| 373 | dfe_done(t); |
| 374 | |
| 375 | fprintf(stderr, "DICT %-20s: dictionary foreach entries %zu, expected %zu...\t\t\t\t\t", |
| 376 | label, ll, traversable); |
| 377 | if(ll != traversable) { |
| 378 | fprintf(stderr, "FAILED\n"); |
| 379 | errors++; |
| 380 | } |
| 381 | else |
| 382 | fprintf(stderr, "OK\n"); |
| 383 | |
| 384 | ll = dictionary_walkthrough_read(dict, unittest_check_dictionary_callback, NULL); |
| 385 | fprintf(stderr, "DICT %-20s: dictionary walkthrough entries %zu, expected %zu...\t\t\t\t", |
| 386 | label, ll, traversable); |
| 387 | if(ll != traversable) { |
| 388 | fprintf(stderr, "FAILED\n"); |
| 389 | errors++; |
| 390 | } |
| 391 | else |
| 392 | fprintf(stderr, "OK\n"); |
| 393 | |
| 394 | ll = dictionary_sorted_walkthrough_read(dict, unittest_check_dictionary_callback, NULL); |
| 395 | fprintf(stderr, "DICT %-20s: dictionary sorted walkthrough entries %zu, expected %zu...\t\t\t", |
| 396 | label, ll, traversable); |
| 397 | if(ll != traversable) { |
| 398 | fprintf(stderr, "FAILED\n"); |
| 399 | errors++; |
| 400 | } |
| 401 | else |
| 402 | fprintf(stderr, "OK\n"); |
| 403 | |
| 404 | DICTIONARY_ITEM *item; |
| 405 | size_t active = 0, deleted = 0, referenced = 0, pending = 0; |
| 406 | for(item = dict->items.list; item; item = item->next) { |
| 407 | if(!(item->flags & ITEM_FLAG_DELETED) && !(item->shared->flags & ITEM_FLAG_DELETED)) |
| 408 | active++; |
| 409 | else { |
| 410 | deleted++; |
| 411 | |
| 412 | if(item->refcount == 0) |
| 413 | pending++; |
| 414 | } |
| 415 | |
| 416 | if(item->refcount > 0) |
| 417 | referenced++; |
| 418 | } |
| 419 | |
| 420 | fprintf(stderr, "DICT %-20s: dictionary active items reported %d, counted %zu, expected %zu...\t\t\t", |
| 421 | label, dict->entries, active, active_items); |
| 422 | if(active != active_items || active != (size_t)dict->entries) { |
| 423 | fprintf(stderr, "FAILED\n"); |
| 424 | errors++; |
| 425 | } |
| 426 | else |
| 427 | fprintf(stderr, "OK\n"); |
| 428 | |
| 429 | fprintf(stderr, "DICT %-20s: dictionary deleted items counted %zu, expected %zu...\t\t\t\t", |
| 430 | label, deleted, deleted_items); |
| 431 | if(deleted != deleted_items) { |
| 432 | fprintf(stderr, "FAILED\n"); |
| 433 | errors++; |
| 434 | } |
| 435 | else |
| 436 | fprintf(stderr, "OK\n"); |
| 437 | |
| 438 | fprintf(stderr, "DICT %-20s: dictionary referenced items reported %d, counted %zu, expected %zu...\t\t", |
| 439 | label, dict->referenced_items, referenced, referenced_items); |
| 440 | if(referenced != referenced_items || dict->referenced_items != (long int)referenced) { |
| 441 | fprintf(stderr, "FAILED\n"); |
| 442 | errors++; |
| 443 | } |
| 444 | else |
| 445 | fprintf(stderr, "OK\n"); |
| 446 | |
| 447 | fprintf(stderr, "DICT %-20s: dictionary pending deletion items reported %d, counted %zu, expected %zu...\t", |
| 448 | label, dict->pending_deletion_items, pending, pending_deletion); |
| 449 | if(pending != pending_deletion || pending != (size_t)dict->pending_deletion_items) { |
| 450 | fprintf(stderr, "FAILED\n"); |
| 451 | errors++; |
| 452 | } |
| 453 | else |
| 454 | fprintf(stderr, "OK\n"); |
| 455 | |
| 456 | return errors; |
| 457 | } |
| 458 | |
| 459 | static int check_item_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data) { |
| 460 | return value == data; |
| 461 | } |
| 462 | |
| 463 | static size_t unittest_check_item(const char *label, DICTIONARY *dict, |
| 464 | DICTIONARY_ITEM *item, const char *name, const char *value, int refcount, |
| 465 | ITEM_FLAGS deleted_flags, bool searchable, bool browsable, bool linked) { |
| 466 | size_t errors = 0; |
| 467 | |
| 468 | fprintf(stderr, "ITEM %-20s: name is '%s', expected '%s'...\t\t\t\t\t\t", label, item_get_name(item), name); |
| 469 | if(strcmp(item_get_name(item), name) != 0) { |
| 470 | fprintf(stderr, "FAILED\n"); |
| 471 | errors++; |
| 472 | } |
| 473 | else |
| 474 | fprintf(stderr, "OK\n"); |
| 475 | |
| 476 | fprintf(stderr, "ITEM %-20s: value is '%s', expected '%s'...\t\t\t\t\t", label, (const char *)item->shared->value, value); |
| 477 | if(strcmp((const char *)item->shared->value, value) != 0) { |
| 478 | fprintf(stderr, "FAILED\n"); |
| 479 | errors++; |
| 480 | } |
| 481 | else |
| 482 | fprintf(stderr, "OK\n"); |
| 483 | |
| 484 | fprintf(stderr, "ITEM %-20s: refcount is %d, expected %d...\t\t\t\t\t\t\t", label, item->refcount, refcount); |
| 485 | if (item->refcount != refcount) { |
| 486 | fprintf(stderr, "FAILED\n"); |
| 487 | errors++; |
| 488 | } |
| 489 | else |
| 490 | fprintf(stderr, "OK\n"); |
| 491 | |
| 492 | fprintf(stderr, "ITEM %-20s: deleted flag is %s, expected %s...\t\t\t\t\t", label, |
| 493 | (item->flags & ITEM_FLAG_DELETED || item->shared->flags & ITEM_FLAG_DELETED)?"true":"false", |
| 494 | (deleted_flags & ITEM_FLAG_DELETED)?"true":"false"); |
| 495 | |
| 496 | if ((item->flags & ITEM_FLAG_DELETED || item->shared->flags & ITEM_FLAG_DELETED) != (deleted_flags & ITEM_FLAG_DELETED)) { |
| 497 | fprintf(stderr, "FAILED\n"); |
| 498 | errors++; |
| 499 | } |
| 500 | else |
| 501 | fprintf(stderr, "OK\n"); |
| 502 | |
| 503 | void *v = dictionary_get(dict, name); |
| 504 | bool found = v == item->shared->value; |
| 505 | fprintf(stderr, "ITEM %-20s: searchable %5s, expected %5s...\t\t\t\t\t\t", label, |
| 506 | found?"true":"false", searchable?"true":"false"); |
| 507 | if(found != searchable) { |
| 508 | fprintf(stderr, "FAILED\n"); |
| 509 | errors++; |
| 510 | } |
| 511 | else |
| 512 | fprintf(stderr, "OK\n"); |
| 513 | |
| 514 | found = false; |
| 515 | void *t; |
| 516 | dfe_start_read(dict, t) { |
| 517 | if(t == item->shared->value) found = true; |
| 518 | } |
| 519 | dfe_done(t); |
| 520 | |
| 521 | fprintf(stderr, "ITEM %-20s: dfe browsable %5s, expected %5s...\t\t\t\t\t", label, |
| 522 | found?"true":"false", browsable?"true":"false"); |
| 523 | if(found != browsable) { |
| 524 | fprintf(stderr, "FAILED\n"); |
| 525 | errors++; |
| 526 | } |
| 527 | else |
| 528 | fprintf(stderr, "OK\n"); |
| 529 | |
| 530 | found = dictionary_walkthrough_read(dict, check_item_callback, item->shared->value); |
| 531 | fprintf(stderr, "ITEM %-20s: walkthrough browsable %5s, expected %5s...\t\t\t\t", label, |
| 532 | found?"true":"false", browsable?"true":"false"); |
| 533 | if(found != browsable) { |
| 534 | fprintf(stderr, "FAILED\n"); |
| 535 | errors++; |
| 536 | } |
| 537 | else |
| 538 | fprintf(stderr, "OK\n"); |
| 539 | |
| 540 | found = dictionary_sorted_walkthrough_read(dict, check_item_callback, item->shared->value); |
| 541 | fprintf(stderr, "ITEM %-20s: sorted walkthrough browsable %5s, expected %5s...\t\t\t", label, |
| 542 | found?"true":"false", browsable?"true":"false"); |
| 543 | if(found != browsable) { |
| 544 | fprintf(stderr, "FAILED\n"); |
| 545 | errors++; |
| 546 | } |
| 547 | else |
| 548 | fprintf(stderr, "OK\n"); |
| 549 | |
| 550 | found = false; |
| 551 | DICTIONARY_ITEM *n; |
| 552 | for(n = dict->items.list; n ;n = n->next) |
| 553 | if(n == item) found = true; |
| 554 | |
| 555 | fprintf(stderr, "ITEM %-20s: linked %5s, expected %5s...\t\t\t\t\t\t", label, |
| 556 | found?"true":"false", linked?"true":"false"); |
| 557 | if(found != linked) { |
| 558 | fprintf(stderr, "FAILED\n"); |
| 559 | errors++; |
| 560 | } |
| 561 | else |
| 562 | fprintf(stderr, "OK\n"); |
| 563 | |
| 564 | return errors; |
| 565 | } |
| 566 | |
| 567 | struct thread_unittest { |
| 568 | int join; |
| 569 | DICTIONARY *dict; |
| 570 | int dups; |
| 571 | |
| 572 | ND_THREAD *thread; |
| 573 | struct dictionary_stats stats; |
| 574 | }; |
| 575 | |
| 576 | static void unittest_dict_thread(void *arg) { |
| 577 | struct thread_unittest *tu = arg; |
| 578 | for(; 1 ;) { |
| 579 | if(__atomic_load_n(&tu->join, __ATOMIC_RELAXED)) |
| 580 | break; |
| 581 | |
| 582 | DICT_ITEM_CONST DICTIONARY_ITEM *item = |
| 583 | dictionary_set_and_acquire_item_advanced(tu->dict, "dict thread checking 1234567890", |
| 584 | -1, NULL, 0, NULL); |
| 585 | tu->stats.ops.inserts++; |
| 586 | |
| 587 | dictionary_get(tu->dict, dictionary_acquired_item_name(item)); |
| 588 | tu->stats.ops.searches++; |
| 589 | |
| 590 | void *t1; |
| 591 | dfe_start_write(tu->dict, t1) { |
| 592 | |
| 593 | // this should delete the referenced item |
| 594 | dictionary_del(tu->dict, t1_dfe.name); |
| 595 | tu->stats.ops.deletes++; |
| 596 | |
| 597 | void *t2; |
| 598 | dfe_start_write(tu->dict, t2) { |
| 599 | // this should add another |
| 600 | dictionary_set(tu->dict, t2_dfe.name, NULL, 0); |
| 601 | tu->stats.ops.inserts++; |
| 602 | |
| 603 | dictionary_get(tu->dict, dictionary_acquired_item_name(item)); |
| 604 | tu->stats.ops.searches++; |
| 605 | |
| 606 | // and this should delete it again |
| 607 | dictionary_del(tu->dict, t2_dfe.name); |
| 608 | tu->stats.ops.deletes++; |
| 609 | } |
| 610 | dfe_done(t2); |
| 611 | tu->stats.ops.traversals++; |
| 612 | |
| 613 | // this should fail to add it |
| 614 | dictionary_set(tu->dict, t1_dfe.name, NULL, 0); |
| 615 | tu->stats.ops.inserts++; |
| 616 | |
| 617 | dictionary_del(tu->dict, t1_dfe.name); |
| 618 | tu->stats.ops.deletes++; |
| 619 | } |
| 620 | dfe_done(t1); |
| 621 | tu->stats.ops.traversals++; |
| 622 | |
| 623 | for(int i = 0; i < tu->dups ; i++) { |
| 624 | dictionary_acquired_item_dup(tu->dict, item); |
| 625 | dictionary_get(tu->dict, dictionary_acquired_item_name(item)); |
| 626 | tu->stats.ops.searches++; |
| 627 | } |
| 628 | |
| 629 | for(int i = 0; i < tu->dups ; i++) { |
| 630 | dictionary_acquired_item_release(tu->dict, item); |
| 631 | dictionary_del(tu->dict, dictionary_acquired_item_name(item)); |
| 632 | tu->stats.ops.deletes++; |
| 633 | } |
| 634 | |
| 635 | dictionary_acquired_item_release(tu->dict, item); |
| 636 | dictionary_del(tu->dict, "dict thread checking 1234567890"); |
| 637 | tu->stats.ops.deletes++; |
| 638 | |
| 639 | // test concurrent deletions and flushes |
| 640 | { |
| 641 | if(gettid_cached() % 2) { |
| 642 | char buf [256 + 1]; |
| 643 | |
| 644 | for (int i = 0; i < 1000; i++) { |
| 645 | snprintfz(buf, sizeof(buf), "del/flush test %d", i); |
| 646 | dictionary_set(tu->dict, buf, NULL, 0); |
| 647 | tu->stats.ops.inserts++; |
| 648 | } |
| 649 | |
| 650 | for (int i = 0; i < 1000; i++) { |
| 651 | snprintfz(buf, sizeof(buf), "del/flush test %d", i); |
| 652 | dictionary_del(tu->dict, buf); |
| 653 | tu->stats.ops.deletes++; |
| 654 | } |
| 655 | } |
| 656 | else { |
| 657 | for (int i = 0; i < 10; i++) { |
| 658 | dictionary_flush(tu->dict); |
| 659 | tu->stats.ops.flushes++; |
| 660 | } |
| 661 | } |
| 662 | } |
| 663 | } |
| 664 | } |
| 665 | |
| 666 | static int dictionary_unittest_threads() { |
| 667 | time_t seconds_to_run = 5; |
| 668 | enum { DICTIONARY_UNITTEST_THREADS = 2 }; |
| 669 | |
| 670 | struct thread_unittest tu[DICTIONARY_UNITTEST_THREADS]; |
| 671 | memset(tu, 0, sizeof(struct thread_unittest) * DICTIONARY_UNITTEST_THREADS); |
| 672 | |
| 673 | fprintf( |
| 674 | stderr, |
| 675 | "\nChecking dictionary concurrency with %d threads for %lld seconds...\n", |
| 676 | DICTIONARY_UNITTEST_THREADS, |
| 677 | (long long)seconds_to_run); |
| 678 | |
| 679 | // threads testing of dictionary |
| 680 | struct dictionary_stats stats = {}; |
| 681 | tu[0].join = 0; |
| 682 | tu[0].dups = 1; |
| 683 | tu[0].dict = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE, &stats, 0); |
| 684 | |
| 685 | for (int i = 0; i < DICTIONARY_UNITTEST_THREADS; i++) { |
| 686 | if(i) |
| 687 | tu[i] = tu[0]; |
| 688 | |
| 689 | char buf[100 + 1]; |
| 690 | snprintf(buf, 100, "dict%d", i); |
| 691 | tu[i].thread = nd_thread_create(buf, NETDATA_THREAD_OPTION_DONT_LOG, unittest_dict_thread, &tu[i]); |
| 692 | } |
| 693 | |
| 694 | sleep_usec(seconds_to_run * USEC_PER_SEC); |
| 695 | |
| 696 | for (int i = 0; i < DICTIONARY_UNITTEST_THREADS; i++) { |
| 697 | __atomic_store_n(&tu[i].join, 1, __ATOMIC_RELAXED); |
| 698 | |
| 699 | nd_thread_join(tu[i].thread); |
| 700 | |
| 701 | if(i) { |
| 702 | tu[0].stats.ops.inserts += tu[i].stats.ops.inserts; |
| 703 | tu[0].stats.ops.deletes += tu[i].stats.ops.deletes; |
| 704 | tu[0].stats.ops.searches += tu[i].stats.ops.searches; |
| 705 | tu[0].stats.ops.flushes += tu[i].stats.ops.flushes; |
| 706 | tu[0].stats.ops.traversals += tu[i].stats.ops.traversals; |
| 707 | } |
| 708 | } |
| 709 | |
| 710 | fprintf(stderr, |
| 711 | "CALLS : inserts %zu" |
| 712 | ", deletes %zu" |
| 713 | ", searches %zu" |
| 714 | ", traversals %zu" |
| 715 | ", flushes %zu" |
| 716 | "\n", |
| 717 | tu[0].stats.ops.inserts, |
| 718 | tu[0].stats.ops.deletes, |
| 719 | tu[0].stats.ops.searches, |
| 720 | tu[0].stats.ops.traversals, |
| 721 | tu[0].stats.ops.flushes |
| 722 | ); |
| 723 | |
| 724 | #ifdef DICT_WITH_STATS |
| 725 | fprintf(stderr, |
| 726 | "ACTUAL: inserts %zu" |
| 727 | ", deletes %zu" |
| 728 | ", searches %zu" |
| 729 | ", traversals %zu" |
| 730 | ", resets %zu" |
| 731 | ", flushes %zu" |
| 732 | ", entries %d" |
| 733 | ", referenced_items %d" |
| 734 | ", pending deletions %d" |
| 735 | ", check spins %zu" |
| 736 | ", insert spins %zu" |
| 737 | ", delete spins %zu" |
| 738 | ", search ignores %zu" |
| 739 | "\n", |
| 740 | stats.ops.inserts, |
| 741 | stats.ops.deletes, |
| 742 | stats.ops.searches, |
| 743 | stats.ops.traversals, |
| 744 | stats.ops.resets, |
| 745 | stats.ops.flushes, |
| 746 | tu[0].dict->entries, |
| 747 | tu[0].dict->referenced_items, |
| 748 | tu[0].dict->pending_deletion_items, |
| 749 | stats.spin_locks.use_spins, |
| 750 | stats.spin_locks.insert_spins, |
| 751 | stats.spin_locks.delete_spins, |
| 752 | stats.spin_locks.search_spins |
| 753 | ); |
| 754 | #endif |
| 755 | |
| 756 | dictionary_destroy(tu[0].dict); |
| 757 | return 0; |
| 758 | } |
| 759 | |
| 760 | struct thread_view_unittest { |
| 761 | int join; |
| 762 | DICTIONARY *master; |
| 763 | DICTIONARY *view; |
| 764 | DICTIONARY_ITEM *item_master; |
| 765 | int dups; |
| 766 | }; |
| 767 | |
| 768 | static void unittest_dict_master_thread(void *arg) { |
| 769 | struct thread_view_unittest *tv = arg; |
| 770 | |
| 771 | DICTIONARY_ITEM *item = NULL; |
| 772 | int loops = 0; |
| 773 | while(!__atomic_load_n(&tv->join, __ATOMIC_RELAXED)) { |
| 774 | |
| 775 | if(!item) |
| 776 | item = dictionary_set_and_acquire_item(tv->master, "ITEM1", "123", strlen("123")); |
| 777 | |
| 778 | if(__atomic_load_n(&tv->item_master, __ATOMIC_RELAXED) != NULL) { |
| 779 | dictionary_acquired_item_release(tv->master, item); |
| 780 | dictionary_del(tv->master, "ITEM1"); |
| 781 | item = NULL; |
| 782 | loops++; |
| 783 | continue; |
| 784 | } |
| 785 | |
| 786 | dictionary_acquired_item_dup(tv->master, item); // for the view thread |
| 787 | __atomic_store_n(&tv->item_master, item, __ATOMIC_RELAXED); |
| 788 | dictionary_del(tv->master, "ITEM1"); |
| 789 | |
| 790 | |
| 791 | for(int i = 0; i < tv->dups + loops ; i++) { |
| 792 | dictionary_acquired_item_dup(tv->master, item); |
| 793 | } |
| 794 | |
| 795 | for(int i = 0; i < tv->dups + loops ; i++) { |
| 796 | dictionary_acquired_item_release(tv->master, item); |
| 797 | } |
| 798 | |
| 799 | dictionary_acquired_item_release(tv->master, item); |
| 800 | |
| 801 | item = NULL; |
| 802 | loops = 0; |
| 803 | } |
| 804 | } |
| 805 | |
| 806 | static void unittest_dict_view_thread(void *arg) { |
| 807 | struct thread_view_unittest *tv = arg; |
| 808 | |
| 809 | DICTIONARY_ITEM *m_item = NULL; |
| 810 | |
| 811 | while(!__atomic_load_n(&tv->join, __ATOMIC_RELAXED)) { |
| 812 | if(!(m_item = __atomic_load_n(&tv->item_master, __ATOMIC_RELAXED))) |
| 813 | continue; |
| 814 | |
| 815 | DICTIONARY_ITEM *v_item = dictionary_view_set_and_acquire_item(tv->view, "ITEM2", m_item); |
| 816 | dictionary_acquired_item_release(tv->master, m_item); |
| 817 | __atomic_store_n(&tv->item_master, NULL, __ATOMIC_RELAXED); |
| 818 | |
| 819 | for(int i = 0; i < tv->dups ; i++) { |
| 820 | dictionary_acquired_item_dup(tv->view, v_item); |
| 821 | } |
| 822 | |
| 823 | for(int i = 0; i < tv->dups ; i++) { |
| 824 | dictionary_acquired_item_release(tv->view, v_item); |
| 825 | } |
| 826 | |
| 827 | dictionary_del(tv->view, "ITEM2"); |
| 828 | |
| 829 | while(!__atomic_load_n(&tv->join, __ATOMIC_RELAXED) && !(m_item = __atomic_load_n(&tv->item_master, __ATOMIC_RELAXED))) { |
| 830 | dictionary_acquired_item_dup(tv->view, v_item); |
| 831 | dictionary_acquired_item_release(tv->view, v_item); |
| 832 | } |
| 833 | |
| 834 | dictionary_acquired_item_release(tv->view, v_item); |
| 835 | } |
| 836 | } |
| 837 | |
| 838 | static struct dictionary_stats stats_master = { 0 }; |
| 839 | static struct dictionary_stats stats_view = { 0 }; |
| 840 | |
| 841 | static int dictionary_unittest_view_threads() { |
| 842 | struct thread_view_unittest tv = { |
| 843 | .join = 0, |
| 844 | .master = NULL, |
| 845 | .view = NULL, |
| 846 | .item_master = NULL, |
| 847 | .dups = 1, |
| 848 | }; |
| 849 | |
| 850 | // threads testing of dictionary |
| 851 | tv.master = dictionary_create_advanced(DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_DONT_OVERWRITE_VALUE, &stats_master, 0); |
| 852 | tv.view = dictionary_create_view(tv.master); |
| 853 | tv.view->stats = &stats_view; |
| 854 | |
| 855 | time_t seconds_to_run = 5; |
| 856 | fprintf( |
| 857 | stderr, |
| 858 | "\nChecking dictionary concurrency with 1 master and 1 view threads for %lld seconds...\n", |
| 859 | (long long)seconds_to_run); |
| 860 | |
| 861 | ND_THREAD *master_thread, *view_thread; |
| 862 | tv.join = 0; |
| 863 | |
| 864 | master_thread = nd_thread_create("master", NETDATA_THREAD_OPTION_DONT_LOG, unittest_dict_master_thread, &tv); |
| 865 | |
| 866 | view_thread = nd_thread_create("view", NETDATA_THREAD_OPTION_DONT_LOG, unittest_dict_view_thread, &tv); |
| 867 | |
| 868 | sleep_usec(seconds_to_run * USEC_PER_SEC); |
| 869 | |
| 870 | __atomic_store_n(&tv.join, 1, __ATOMIC_RELAXED); |
| 871 | nd_thread_join(view_thread); |
| 872 | nd_thread_join(master_thread); |
| 873 | |
| 874 | #ifdef DICT_WITH_STATS |
| 875 | fprintf(stderr, |
| 876 | "MASTER: inserts %zu" |
| 877 | ", deletes %zu" |
| 878 | ", searches %zu" |
| 879 | ", resets %zu" |
| 880 | ", entries %d" |
| 881 | ", referenced_items %d" |
| 882 | ", pending deletions %d" |
| 883 | ", check spins %zu" |
| 884 | ", insert spins %zu" |
| 885 | ", delete spins %zu" |
| 886 | ", search ignores %zu" |
| 887 | "\n", |
| 888 | stats_master.ops.inserts, |
| 889 | stats_master.ops.deletes, |
| 890 | stats_master.ops.searches, |
| 891 | stats_master.ops.resets, |
| 892 | tv.master->entries, |
| 893 | tv.master->referenced_items, |
| 894 | tv.master->pending_deletion_items, |
| 895 | stats_master.spin_locks.use_spins, |
| 896 | stats_master.spin_locks.insert_spins, |
| 897 | stats_master.spin_locks.delete_spins, |
| 898 | stats_master.spin_locks.search_spins |
| 899 | ); |
| 900 | fprintf(stderr, |
| 901 | "VIEW : inserts %zu" |
| 902 | ", deletes %zu" |
| 903 | ", searches %zu" |
| 904 | ", resets %zu" |
| 905 | ", entries %d" |
| 906 | ", referenced_items %d" |
| 907 | ", pending deletions %d" |
| 908 | ", check spins %zu" |
| 909 | ", insert spins %zu" |
| 910 | ", delete spins %zu" |
| 911 | ", search ignores %zu" |
| 912 | "\n", |
| 913 | stats_view.ops.inserts, |
| 914 | stats_view.ops.deletes, |
| 915 | stats_view.ops.searches, |
| 916 | stats_view.ops.resets, |
| 917 | tv.view->entries, |
| 918 | tv.view->referenced_items, |
| 919 | tv.view->pending_deletion_items, |
| 920 | stats_view.spin_locks.use_spins, |
| 921 | stats_view.spin_locks.insert_spins, |
| 922 | stats_view.spin_locks.delete_spins, |
| 923 | stats_view.spin_locks.search_spins |
| 924 | ); |
| 925 | #endif |
| 926 | |
| 927 | dictionary_destroy(tv.master); |
| 928 | dictionary_destroy(tv.view); |
| 929 | |
| 930 | return 0; |
| 931 | } |
| 932 | |
| 933 | size_t dictionary_unittest_views(void) { |
| 934 | size_t errors = 0; |
| 935 | struct dictionary_stats stats = {}; |
| 936 | DICTIONARY *master = dictionary_create_advanced(DICT_OPTION_NONE, &stats, 0); |
| 937 | DICTIONARY *view = dictionary_create_view(master); |
| 938 | DICTIONARY_ITEM *master_item2 = NULL; |
| 939 | DICTIONARY_ITEM *view_item2 = NULL; |
| 940 | DICTIONARY_ITEM *lookup = NULL; |
| 941 | |
| 942 | fprintf(stderr, "\n\nChecking dictionary views...\n"); |
| 943 | |
| 944 | // Add an item to both master and view, then remove the view first and the master second |
| 945 | fprintf(stderr, "\nPASS 1: Adding 1 item to master:\n"); |
| 946 | DICTIONARY_ITEM *item1_on_master = dictionary_set_and_acquire_item(master, "KEY 1", "VALUE1", strlen("VALUE1") + 1); |
| 947 | errors += unittest_check_dictionary("master", master, 1, 1, 0, 1, 0); |
| 948 | errors += unittest_check_item("master", master, item1_on_master, "KEY 1", item1_on_master->shared->value, 1, ITEM_FLAG_NONE, true, true, true); |
| 949 | |
| 950 | fprintf(stderr, "\nPASS 1: Adding master item to view:\n"); |
| 951 | DICTIONARY_ITEM *item1_on_view = dictionary_view_set_and_acquire_item(view, "KEY 1 ON VIEW", item1_on_master); |
| 952 | errors += unittest_check_dictionary("view", view, 1, 1, 0, 1, 0); |
| 953 | errors += unittest_check_item("view", view, item1_on_view, "KEY 1 ON VIEW", item1_on_master->shared->value, 1, ITEM_FLAG_NONE, true, true, true); |
| 954 | |
| 955 | fprintf(stderr, "\nPASS 1: Deleting view item:\n"); |
| 956 | dictionary_del(view, "KEY 1 ON VIEW"); |
| 957 | errors += unittest_check_dictionary("master", master, 1, 1, 0, 1, 0); |
| 958 | errors += unittest_check_dictionary("view", view, 0, 0, 1, 1, 0); |
| 959 | errors += unittest_check_item("master", master, item1_on_master, "KEY 1", item1_on_master->shared->value, 1, ITEM_FLAG_NONE, true, true, true); |
| 960 | errors += unittest_check_item("view", view, item1_on_view, "KEY 1 ON VIEW", item1_on_master->shared->value, 1, ITEM_FLAG_DELETED, false, false, true); |
| 961 | |
| 962 | fprintf(stderr, "\nPASS 1: Releasing the deleted view item:\n"); |
| 963 | dictionary_acquired_item_release(view, item1_on_view); |
| 964 | errors += unittest_check_dictionary("master", master, 1, 1, 0, 1, 0); |
| 965 | errors += unittest_check_dictionary("view", view, 0, 0, 1, 0, 1); |
| 966 | errors += unittest_check_item("master", master, item1_on_master, "KEY 1", item1_on_master->shared->value, 1, ITEM_FLAG_NONE, true, true, true); |
| 967 | |
| 968 | fprintf(stderr, "\nPASS 1: Releasing the acquired master item:\n"); |
| 969 | dictionary_acquired_item_release(master, item1_on_master); |
| 970 | errors += unittest_check_dictionary("master", master, 1, 1, 0, 0, 0); |
| 971 | errors += unittest_check_dictionary("view", view, 0, 0, 1, 0, 1); |
| 972 | errors += unittest_check_item("master", master, item1_on_master, "KEY 1", item1_on_master->shared->value, 0, ITEM_FLAG_NONE, true, true, true); |
| 973 | |
| 974 | fprintf(stderr, "\nPASS 1: Deleting the released master item:\n"); |
| 975 | dictionary_del(master, "KEY 1"); |
| 976 | errors += unittest_check_dictionary("master", master, 0, 0, 0, 0, 0); |
| 977 | errors += unittest_check_dictionary("view", view, 0, 0, 1, 0, 1); |
| 978 | |
| 979 | // The other way now: |
| 980 | // Add an item to both master and view, then remove the master first and verify it is deleted on the view also |
| 981 | fprintf(stderr, "\nPASS 2: Adding 1 item to master:\n"); |
| 982 | item1_on_master = dictionary_set_and_acquire_item(master, "KEY 1", "VALUE1", strlen("VALUE1") + 1); |
| 983 | errors += unittest_check_dictionary("master", master, 1, 1, 0, 1, 0); |
| 984 | errors += unittest_check_item("master", master, item1_on_master, "KEY 1", item1_on_master->shared->value, 1, ITEM_FLAG_NONE, true, true, true); |
| 985 | |
| 986 | fprintf(stderr, "\nPASS 2: Adding master item to view:\n"); |
| 987 | item1_on_view = dictionary_view_set_and_acquire_item(view, "KEY 1 ON VIEW", item1_on_master); |
| 988 | errors += unittest_check_dictionary("view", view, 1, 1, 0, 1, 0); |
| 989 | errors += unittest_check_item("view", view, item1_on_view, "KEY 1 ON VIEW", item1_on_master->shared->value, 1, ITEM_FLAG_NONE, true, true, true); |
| 990 | |
| 991 | fprintf(stderr, "\nPASS 2: Deleting master item:\n"); |
| 992 | dictionary_del(master, "KEY 1"); |
| 993 | garbage_collect_pending_deletes(view); |
| 994 | errors += unittest_check_dictionary("master", master, 0, 0, 1, 1, 0); |
| 995 | errors += unittest_check_dictionary("view", view, 0, 0, 1, 1, 0); |
| 996 | errors += unittest_check_item("master", master, item1_on_master, "KEY 1", item1_on_master->shared->value, 1, ITEM_FLAG_DELETED, false, false, true); |
| 997 | errors += unittest_check_item("view", view, item1_on_view, "KEY 1 ON VIEW", item1_on_master->shared->value, 1, ITEM_FLAG_DELETED, false, false, true); |
| 998 | |
| 999 | fprintf(stderr, "\nPASS 2: Releasing the acquired master item:\n"); |
| 1000 | dictionary_acquired_item_release(master, item1_on_master); |
| 1001 | errors += unittest_check_dictionary("master", master, 0, 0, 1, 0, 1); |
| 1002 | errors += unittest_check_dictionary("view", view, 0, 0, 1, 1, 0); |
| 1003 | errors += unittest_check_item("view", view, item1_on_view, "KEY 1 ON VIEW", item1_on_master->shared->value, 1, ITEM_FLAG_DELETED, false, false, true); |
| 1004 | |
| 1005 | fprintf(stderr, "\nPASS 2: Releasing the deleted view item:\n"); |
| 1006 | dictionary_acquired_item_release(view, item1_on_view); |
| 1007 | errors += unittest_check_dictionary("master", master, 0, 0, 1, 0, 1); |
| 1008 | errors += unittest_check_dictionary("view", view, 0, 0, 1, 0, 1); |
| 1009 | |
| 1010 | fprintf(stderr, "\nPASS 3: Replacing a stale view item after master deletion:\n"); |
| 1011 | item1_on_master = dictionary_set_and_acquire_item(master, "KEY 1", "VALUE1", strlen("VALUE1") + 1); |
| 1012 | item1_on_view = dictionary_view_set_and_acquire_item(view, "KEY 1 ON VIEW", item1_on_master); |
| 1013 | dictionary_acquired_item_release(view, item1_on_view); |
| 1014 | dictionary_del(master, "KEY 1"); |
| 1015 | |
| 1016 | // Suppress the preflight garbage collection once so view_set() exercises |
| 1017 | // the stale-entry cleanup path inside dict_item_add_or_reset_value_and_acquire(). |
| 1018 | __atomic_store_n(&view->last_gc_run_us, now_realtime_usec(), __ATOMIC_RELAXED); |
| 1019 | |
| 1020 | master_item2 = dictionary_set_and_acquire_item(master, "KEY 1", "VALUE2", strlen("VALUE2") + 1); |
| 1021 | view_item2 = dictionary_view_set_and_acquire_item(view, "KEY 1 ON VIEW", master_item2); |
| 1022 | |
| 1023 | if(!view_item2) { |
| 1024 | fprintf(stderr, "View replacement returned NULL\n"); |
| 1025 | errors++; |
| 1026 | } |
| 1027 | else if(item_flag_check(view_item2, ITEM_FLAG_DELETED) || view_item2->shared != master_item2->shared) { |
| 1028 | fprintf(stderr, "View replacement returned stale/deleted item\n"); |
| 1029 | dictionary_acquired_item_release(view, view_item2); |
| 1030 | errors++; |
| 1031 | } |
| 1032 | else |
| 1033 | dictionary_acquired_item_release(view, view_item2); |
| 1034 | |
| 1035 | lookup = (DICTIONARY_ITEM *)dictionary_get_and_acquire_item(view, "KEY 1 ON VIEW"); |
| 1036 | if(!lookup || item_flag_check(lookup, ITEM_FLAG_DELETED) || lookup->shared != master_item2->shared) { |
| 1037 | fprintf(stderr, "View lookup did not resolve to the replacement item\n"); |
| 1038 | errors++; |
| 1039 | } |
| 1040 | if(lookup) |
| 1041 | dictionary_acquired_item_release(view, lookup); |
| 1042 | |
| 1043 | dictionary_acquired_item_release(master, master_item2); |
| 1044 | dictionary_acquired_item_release(master, item1_on_master); |
| 1045 | |
| 1046 | dictionary_destroy(master); |
| 1047 | dictionary_destroy(view); |
| 1048 | return errors; |
| 1049 | } |
| 1050 | |
| 1051 | // ---------------------------------------------------------------------------- |
| 1052 | // Test: dictionary_destroy() TOCTOU race |
| 1053 | // |
| 1054 | // Stress test for the race where dictionary_destroy() could start force-freeing |
| 1055 | // a dictionary while concurrent get/set/traversal operations were still able |
| 1056 | // to enter through stale pre-lock destroyed-state checks. |
| 1057 | // |
| 1058 | // The test runs the racy workload in a forked child process so that a crash |
| 1059 | // is detected as a child signal instead of bringing down the test harness. |
| 1060 | |
| 1061 | #ifndef OS_WINDOWS |
| 1062 | #include <sys/wait.h> |
| 1063 | #endif |
| 1064 | |
| 1065 | #ifndef OS_WINDOWS |
| 1066 | |
| 1067 | struct dict_destroy_race_data { |
| 1068 | DICTIONARY *dict; |
| 1069 | int ready; // atomic: worker signals it is looping |
| 1070 | int stop; // atomic: main tells worker to stop |
| 1071 | }; |
| 1072 | |
| 1073 | // Worker that continuously acquires and releases an item. |
| 1074 | // Keep the reference briefly so destroy() can observe an in-flight access in |
| 1075 | // the post-index-teardown recheck without forcing the old "already referenced" |
| 1076 | // path up front. |
| 1077 | static void dict_destroy_race_getter_thread(void *arg) { |
| 1078 | struct dict_destroy_race_data *d = arg; |
| 1079 | |
| 1080 | __atomic_store_n(&d->ready, 1, __ATOMIC_RELEASE); |
| 1081 | |
| 1082 | while(!__atomic_load_n(&d->stop, __ATOMIC_RELAXED)) { |
| 1083 | DICTIONARY_ITEM *item = (DICTIONARY_ITEM *)dictionary_get_and_acquire_item(d->dict, "key"); |
| 1084 | if(item) { |
| 1085 | const char *val = dictionary_acquired_item_value(item); |
| 1086 | if(val) { |
| 1087 | volatile char c __attribute__((unused)) = val[0]; |
| 1088 | } |
| 1089 | tinysleep(); |
| 1090 | dictionary_acquired_item_release(d->dict, item); |
| 1091 | } |
| 1092 | } |
| 1093 | } |
| 1094 | |
| 1095 | // Worker that continuously sets (inserts/updates) items. |
| 1096 | static void dict_destroy_race_setter_thread(void *arg) { |
| 1097 | struct dict_destroy_race_data *d = arg; |
| 1098 | |
| 1099 | __atomic_store_n(&d->ready, 1, __ATOMIC_RELEASE); |
| 1100 | |
| 1101 | int counter = 0; |
| 1102 | while(!__atomic_load_n(&d->stop, __ATOMIC_RELAXED)) { |
| 1103 | char key[32], val[32]; |
| 1104 | // Use unique keys so the test exercises concurrent inserts during |
| 1105 | // destruction without racing on value replacement semantics. |
| 1106 | snprintfz(key, sizeof(key), "key-%d", counter); |
| 1107 | snprintfz(val, sizeof(val), "val-%d", counter); |
| 1108 | dictionary_set(d->dict, key, val, strlen(val) + 1); |
| 1109 | counter++; |
| 1110 | } |
| 1111 | } |
| 1112 | |
| 1113 | // Worker that continuously traverses (dfe_start_read / dfe_done). |
| 1114 | static void dict_destroy_race_traverser_thread(void *arg) { |
| 1115 | struct dict_destroy_race_data *d = arg; |
| 1116 | |
| 1117 | __atomic_store_n(&d->ready, 1, __ATOMIC_RELEASE); |
| 1118 | |
| 1119 | while(!__atomic_load_n(&d->stop, __ATOMIC_RELAXED)) { |
| 1120 | void *val; |
| 1121 | dfe_start_read(d->dict, val) { |
| 1122 | if(val) { |
| 1123 | volatile char c __attribute__((unused)) = ((const char *)val)[0]; |
| 1124 | } |
| 1125 | } |
| 1126 | dfe_done(val); |
| 1127 | } |
| 1128 | } |
| 1129 | |
| 1130 | // Run the racy workload in a child process: concurrent get/set/traverse |
| 1131 | // while the main thread destroys the dictionary. Without the fix this may |
| 1132 | // crash or trip internal consistency checks, depending on timing. |
| 1133 | static void dict_destroy_race_child(int iterations) { |
| 1134 | for(int i = 0; i < iterations; i++) { |
| 1135 | DICTIONARY *dict = dictionary_create(DICT_OPTION_NONE); |
| 1136 | dictionary_set(dict, "key", "value", 6); |
| 1137 | |
| 1138 | struct dict_destroy_race_data getter_data = { .dict = dict, .ready = 0, .stop = 0 }; |
| 1139 | struct dict_destroy_race_data setter_data = { .dict = dict, .ready = 0, .stop = 0 }; |
| 1140 | struct dict_destroy_race_data traverser_data = { .dict = dict, .ready = 0, .stop = 0 }; |
| 1141 | |
| 1142 | ND_THREAD *getter = nd_thread_create( |
| 1143 | "race-getter", NETDATA_THREAD_OPTION_DONT_LOG, |
| 1144 | dict_destroy_race_getter_thread, &getter_data); |
| 1145 | |
| 1146 | ND_THREAD *setter = nd_thread_create( |
| 1147 | "race-setter", NETDATA_THREAD_OPTION_DONT_LOG, |
| 1148 | dict_destroy_race_setter_thread, &setter_data); |
| 1149 | |
| 1150 | ND_THREAD *traverser = nd_thread_create( |
| 1151 | "race-trav", NETDATA_THREAD_OPTION_DONT_LOG, |
| 1152 | dict_destroy_race_traverser_thread, &traverser_data); |
| 1153 | |
| 1154 | if(!getter || !setter || !traverser) { |
| 1155 | // Thread creation failed — stop any that did start and clean up. |
| 1156 | __atomic_store_n(&getter_data.stop, 1, __ATOMIC_RELEASE); |
| 1157 | __atomic_store_n(&setter_data.stop, 1, __ATOMIC_RELEASE); |
| 1158 | __atomic_store_n(&traverser_data.stop, 1, __ATOMIC_RELEASE); |
| 1159 | if(getter) nd_thread_join(getter); |
| 1160 | if(setter) nd_thread_join(setter); |
| 1161 | if(traverser) nd_thread_join(traverser); |
| 1162 | dictionary_destroy(dict); |
| 1163 | cleanup_destroyed_dictionaries(false); |
| 1164 | _exit(2); |
| 1165 | } |
| 1166 | |
| 1167 | // wait for all workers to be running |
| 1168 | while(!__atomic_load_n(&getter_data.ready, __ATOMIC_ACQUIRE) || |
| 1169 | !__atomic_load_n(&setter_data.ready, __ATOMIC_ACQUIRE) || |
| 1170 | !__atomic_load_n(&traverser_data.ready, __ATOMIC_ACQUIRE)) |
| 1171 | tinysleep(); |
| 1172 | |
| 1173 | tinysleep(); |
| 1174 | |
| 1175 | // Do not hold a permanent acquired item here: that would force the old |
| 1176 | // "already referenced" delayed-destroy path before destroy() reaches |
| 1177 | // the new destroyed-flag + index-teardown synchronization. Instead, |
| 1178 | // rely on the active workers to create transient in-flight accesses |
| 1179 | // while destroy() races with get/set/traversal. |
| 1180 | dictionary_destroy(dict); |
| 1181 | |
| 1182 | __atomic_store_n(&getter_data.stop, 1, __ATOMIC_RELEASE); |
| 1183 | __atomic_store_n(&setter_data.stop, 1, __ATOMIC_RELEASE); |
| 1184 | __atomic_store_n(&traverser_data.stop, 1, __ATOMIC_RELEASE); |
| 1185 | nd_thread_join(getter); |
| 1186 | nd_thread_join(setter); |
| 1187 | nd_thread_join(traverser); |
| 1188 | |
| 1189 | cleanup_destroyed_dictionaries(false); |
| 1190 | } |
| 1191 | } |
| 1192 | |
| 1193 | static int dictionary_destroy_race_unittest(void) { |
| 1194 | const int iterations = nd_is_running_under_ci() ? 10 : 100; |
| 1195 | |
| 1196 | fprintf(stderr, |
| 1197 | "\nTesting dictionary_destroy() TOCTOU race (%d iterations in child process)...\n", |
| 1198 | iterations); |
| 1199 | |
| 1200 | fflush(stderr); |
| 1201 | fflush(stdout); |
| 1202 | |
| 1203 | pid_t pid = fork(); |
| 1204 | if(pid == 0) { |
| 1205 | // child — run the racy workload |
| 1206 | dict_destroy_race_child(iterations); |
| 1207 | _exit(0); |
| 1208 | } |
| 1209 | |
| 1210 | if(pid < 0) { |
| 1211 | fprintf(stderr, "dictionary_destroy() TOCTOU race test: fork() failed: %s\n", |
| 1212 | strerror(errno)); |
| 1213 | return 1; |
| 1214 | } |
| 1215 | |
| 1216 | // Give the child a generous timeout so a hang doesn't stall the suite. |
| 1217 | int timeout_sec = 120; |
| 1218 | int status = 0; |
| 1219 | bool reaped = false; |
| 1220 | for(int elapsed = 0; elapsed < timeout_sec; elapsed++) { |
| 1221 | pid_t rc = waitpid(pid, &status, WNOHANG); |
| 1222 | if(rc > 0) { reaped = true; break; } |
| 1223 | if(rc < 0) { |
| 1224 | if(errno == EINTR) |
| 1225 | continue; |
| 1226 | fprintf(stderr, "dictionary_destroy() TOCTOU race test: waitpid() failed: %s\n", |
| 1227 | strerror(errno)); |
| 1228 | return 1; |
| 1229 | } |
| 1230 | sleep_usec(USEC_PER_SEC); |
| 1231 | } |
| 1232 | if(!reaped) { |
| 1233 | kill(pid, SIGKILL); |
| 1234 | while(waitpid(pid, &status, 0) < 0) { |
| 1235 | if(errno != EINTR) { |
| 1236 | fprintf(stderr, "dictionary_destroy() TOCTOU race test: waitpid() failed after SIGKILL: %s\n", |
| 1237 | strerror(errno)); |
| 1238 | return 1; |
| 1239 | } |
| 1240 | } |
| 1241 | fprintf(stderr, "dictionary_destroy() TOCTOU race test: FAILED — " |
| 1242 | "child hung (killed after %d seconds)\n", timeout_sec); |
| 1243 | return 1; |
| 1244 | } |
| 1245 | |
| 1246 | if(WIFSIGNALED(status)) { |
| 1247 | int sig = WTERMSIG(status); |
| 1248 | fprintf(stderr, |
| 1249 | "dictionary_destroy() TOCTOU race test: FAILED — " |
| 1250 | "child killed by signal %d (%s) — " |
| 1251 | "dictionary_destroy() still has a TOCTOU in its destroy/access " |
| 1252 | "synchronization path\n", |
| 1253 | sig, strsignal(sig)); |
| 1254 | return 1; |
| 1255 | } |
| 1256 | |
| 1257 | if(WIFEXITED(status) && WEXITSTATUS(status) != 0) { |
| 1258 | fprintf(stderr, |
| 1259 | "dictionary_destroy() TOCTOU race test: FAILED — " |
| 1260 | "child exited with status %d\n", |
| 1261 | WEXITSTATUS(status)); |
| 1262 | return 1; |
| 1263 | } |
| 1264 | |
| 1265 | fprintf(stderr, "dictionary_destroy() TOCTOU race test: OK\n"); |
| 1266 | return 0; |
| 1267 | } |
| 1268 | |
| 1269 | #else |
| 1270 | |
| 1271 | static int dictionary_destroy_race_unittest(void) { |
| 1272 | fprintf(stderr, |
| 1273 | "\nTesting dictionary_destroy() TOCTOU race: SKIPPED " |
| 1274 | "(fork-based test is unsupported on this platform)\n"); |
| 1275 | return 0; |
| 1276 | } |
| 1277 | |
| 1278 | #endif |
| 1279 | |
| 1280 | bool dictionary_traverse_or_destroy_unittest(void) { |
| 1281 | DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED); |
| 1282 | dictionary_set(dict, "KEY 1", "VALUE1", strlen("VALUE1") + 1); |
| 1283 | dictionary_set(dict, "KEY 2", "VALUE2", strlen("VALUE2") + 1); |
| 1284 | dictionary_set(dict, "KEY 3", "VALUE3", strlen("VALUE3") + 1); |
| 1285 | |
| 1286 | size_t counted = 0; |
| 1287 | const char *s; |
| 1288 | dfe_start_read(dict, s) { |
| 1289 | if(!counted) |
| 1290 | dictionary_destroy(dict); |
| 1291 | |
| 1292 | counted++; |
| 1293 | } |
| 1294 | dfe_done(s); |
| 1295 | |
| 1296 | return counted == 1; |
| 1297 | } |
| 1298 | |
| 1299 | /* |
| 1300 | * FIXME: a dictionary-related leak is reported when running the address |
| 1301 | * sanitizer. Need to investigate if it's introduced by the unit-test itself, |
| 1302 | * or the dictionary implementation. |
| 1303 | */ |
| 1304 | // ============================================================================ |
| 1305 | // Dictionary benchmark harness for before/after concurrency work (for example |
| 1306 | // RCU internals). Keep the workloads stable and the output compact. |
| 1307 | // ============================================================================ |
| 1308 | |
| 1309 | #define DICT_BENCH_MAX_SAMPLES 4096 |
| 1310 | #define DICT_BENCH_SAMPLE_EVERY 64 |
| 1311 | |
| 1312 | typedef enum { |
| 1313 | DICT_BENCH_READ_NONE = 0, |
| 1314 | DICT_BENCH_READ_TRAVERSAL, |
| 1315 | DICT_BENCH_READ_LOOKUP_HOT, |
| 1316 | DICT_BENCH_READ_LOOKUP_RANDOM, |
| 1317 | } dict_bench_read_mode_t; |
| 1318 | |
| 1319 | typedef enum { |
| 1320 | DICT_BENCH_WRITE_NONE = 0, |
| 1321 | DICT_BENCH_WRITE_CHURN, |
| 1322 | DICT_BENCH_WRITE_UPDATE, |
| 1323 | } dict_bench_write_mode_t; |
| 1324 | |
| 1325 | struct dict_bench_config { |
| 1326 | const char *workload; |
| 1327 | size_t entries; |
| 1328 | int readers; |
| 1329 | int writers; |
| 1330 | time_t seconds_to_run; |
| 1331 | dict_bench_read_mode_t read_mode; |
| 1332 | dict_bench_write_mode_t write_mode; |
| 1333 | }; |
| 1334 | |
| 1335 | struct dict_bench_stats { |
| 1336 | uint64_t ops; |
| 1337 | uint64_t items_seen; |
| 1338 | uint64_t latency_total_ut; |
| 1339 | size_t latency_samples_used; |
| 1340 | uint64_t latency_samples_seen; |
| 1341 | usec_t latency_samples[DICT_BENCH_MAX_SAMPLES]; |
| 1342 | }; |
| 1343 | |
| 1344 | struct dict_bench_thread { |
| 1345 | int id; |
| 1346 | int *join; |
| 1347 | DICTIONARY *dict; |
| 1348 | const struct dict_bench_config *cfg; |
| 1349 | struct dict_bench_stats stats; |
| 1350 | bool is_writer; |
| 1351 | uint32_t rng_state; |
| 1352 | ND_THREAD *thread; |
| 1353 | }; |
| 1354 | |
| 1355 | struct dict_bench_summary { |
| 1356 | uint64_t read_ops; |
| 1357 | uint64_t read_items_seen; |
| 1358 | uint64_t write_ops; |
| 1359 | double read_avg_ut; |
| 1360 | double read_p99_ut; |
| 1361 | double write_avg_ut; |
| 1362 | double write_p99_ut; |
| 1363 | }; |
| 1364 | |
| 1365 | static int dict_bench_usec_cmp(const void *a, const void *b) { |
| 1366 | const usec_t ua = *(const usec_t *)a; |
| 1367 | const usec_t ub = *(const usec_t *)b; |
| 1368 | return (ua > ub) - (ua < ub); |
| 1369 | } |
| 1370 | |
| 1371 | static inline uint32_t dict_bench_rand(uint32_t *state) { |
| 1372 | if(!*state) *state = 1; |
| 1373 | *state ^= *state << 13; |
| 1374 | *state ^= *state >> 17; |
| 1375 | *state ^= *state << 5; |
| 1376 | return *state; |
| 1377 | } |
| 1378 | |
| 1379 | static inline void dict_bench_record_latency(struct dict_bench_stats *stats, usec_t latency_ut, uint32_t *rng) { |
| 1380 | stats->latency_total_ut += latency_ut; |
| 1381 | stats->latency_samples_seen++; |
| 1382 | |
| 1383 | if(stats->latency_samples_used < DICT_BENCH_MAX_SAMPLES) |
| 1384 | stats->latency_samples[stats->latency_samples_used++] = latency_ut; |
| 1385 | else { |
| 1386 | // reservoir sampling: replace a random slot with probability N/total_seen |
| 1387 | uint32_t idx = dict_bench_rand(rng) % stats->latency_samples_seen; |
| 1388 | if(idx < DICT_BENCH_MAX_SAMPLES) |
| 1389 | stats->latency_samples[idx] = latency_ut; |
| 1390 | } |
| 1391 | } |
| 1392 | |
| 1393 | static void dict_bench_lookup_key(char *buf, size_t len, size_t key_idx) { |
| 1394 | snprintfz(buf, len, "bench-item-%zu", key_idx); |
| 1395 | } |
| 1396 | |
| 1397 | static void dict_bench_reader_thread(void *arg) { |
| 1398 | struct dict_bench_thread *ctx = arg; |
| 1399 | const size_t hot_keys = MIN(ctx->cfg->entries, (size_t)64); |
| 1400 | |
| 1401 | while(!__atomic_load_n(ctx->join, __ATOMIC_RELAXED)) { |
| 1402 | usec_t started_ut = 0; |
| 1403 | bool sample_latency = ((ctx->stats.ops & (DICT_BENCH_SAMPLE_EVERY - 1)) == 0); |
| 1404 | |
| 1405 | if(sample_latency) |
| 1406 | started_ut = now_monotonic_usec(); |
| 1407 | |
| 1408 | if(ctx->cfg->read_mode == DICT_BENCH_READ_TRAVERSAL) { |
| 1409 | void *v; |
| 1410 | dfe_start_read(ctx->dict, v) { |
| 1411 | (void)v; |
| 1412 | ctx->stats.items_seen++; |
| 1413 | } |
| 1414 | dfe_done(v); |
| 1415 | } |
| 1416 | else { |
| 1417 | char name[64]; |
| 1418 | size_t key_idx; |
| 1419 | |
| 1420 | if(ctx->cfg->read_mode == DICT_BENCH_READ_LOOKUP_HOT) |
| 1421 | key_idx = dict_bench_rand(&ctx->rng_state) % hot_keys; |
| 1422 | else |
| 1423 | key_idx = dict_bench_rand(&ctx->rng_state) % ctx->cfg->entries; |
| 1424 | |
| 1425 | dict_bench_lookup_key(name, sizeof(name), key_idx); |
| 1426 | (void)dictionary_get(ctx->dict, name); |
| 1427 | } |
| 1428 | |
| 1429 | ctx->stats.ops++; |
| 1430 | |
| 1431 | if(sample_latency) |
| 1432 | dict_bench_record_latency(&ctx->stats, now_monotonic_usec() - started_ut, &ctx->rng_state); |
| 1433 | } |
| 1434 | } |
| 1435 | |
| 1436 | static void dict_bench_writer_thread(void *arg) { |
| 1437 | struct dict_bench_thread *ctx = arg; |
| 1438 | uint64_t counter = 0; |
| 1439 | |
| 1440 | while(!__atomic_load_n(ctx->join, __ATOMIC_RELAXED)) { |
| 1441 | usec_t started_ut = 0; |
| 1442 | bool sample_latency = ((ctx->stats.ops & (DICT_BENCH_SAMPLE_EVERY - 1)) == 0); |
| 1443 | |
| 1444 | if(sample_latency) |
| 1445 | started_ut = now_monotonic_usec(); |
| 1446 | |
| 1447 | if(ctx->cfg->write_mode == DICT_BENCH_WRITE_CHURN) { |
| 1448 | char buf[64]; |
| 1449 | snprintfz(buf, sizeof(buf), "writer-key-%d-%"PRIu64, ctx->id, counter); |
| 1450 | dictionary_set(ctx->dict, buf, NULL, 0); |
| 1451 | dictionary_del(ctx->dict, buf); |
| 1452 | } |
| 1453 | else { |
| 1454 | char buf[64]; |
| 1455 | size_t key_idx = dict_bench_rand(&ctx->rng_state) % ctx->cfg->entries; |
| 1456 | uint64_t value = counter; |
| 1457 | |
| 1458 | dict_bench_lookup_key(buf, sizeof(buf), key_idx); |
| 1459 | dictionary_set(ctx->dict, buf, &value, sizeof(value)); |
| 1460 | } |
| 1461 | |
| 1462 | counter++; |
| 1463 | ctx->stats.ops++; |
| 1464 | |
| 1465 | if(sample_latency) |
| 1466 | dict_bench_record_latency(&ctx->stats, now_monotonic_usec() - started_ut, &ctx->rng_state); |
| 1467 | } |
| 1468 | } |
| 1469 | |
| 1470 | static void dict_bench_prepopulate(DICTIONARY *dict, size_t entries) { |
| 1471 | char name[64]; |
| 1472 | |
| 1473 | for(size_t i = 0; i < entries; i++) { |
| 1474 | uint64_t value = i; |
| 1475 | dict_bench_lookup_key(name, sizeof(name), i); |
| 1476 | dictionary_set(dict, name, &value, sizeof(value)); |
| 1477 | } |
| 1478 | } |
| 1479 | |
| 1480 | static double dict_bench_percentile_ut(usec_t *samples, size_t samples_used, size_t percentile) { |
| 1481 | if(!samples_used) |
| 1482 | return 0.0; |
| 1483 | |
| 1484 | qsort(samples, samples_used, sizeof(*samples), dict_bench_usec_cmp); |
| 1485 | |
| 1486 | size_t idx = ((samples_used - 1) * percentile) / 100; |
| 1487 | return (double)samples[idx]; |
| 1488 | } |
| 1489 | |
| 1490 | static void dict_bench_aggregate_latency( |
| 1491 | struct dict_bench_thread *threads, |
| 1492 | int count, |
| 1493 | bool writers, |
| 1494 | double *avg_ut, |
| 1495 | double *p99_ut |
| 1496 | ) { |
| 1497 | size_t max_samples = (size_t)DICT_BENCH_MAX_SAMPLES * count; |
| 1498 | usec_t *samples = callocz(max_samples, sizeof(usec_t)); |
| 1499 | size_t samples_used = 0; |
| 1500 | uint64_t total_latency_ut = 0; |
| 1501 | uint64_t total_ops = 0; |
| 1502 | |
| 1503 | for(int i = 0; i < count; i++) { |
| 1504 | if(threads[i].is_writer != writers) |
| 1505 | continue; |
| 1506 | |
| 1507 | total_latency_ut += threads[i].stats.latency_total_ut; |
| 1508 | total_ops += threads[i].stats.latency_samples_seen; |
| 1509 | |
| 1510 | size_t available = max_samples - samples_used; |
| 1511 | size_t copy = MIN(available, threads[i].stats.latency_samples_used); |
| 1512 | if(copy) { |
| 1513 | memcpy(&samples[samples_used], threads[i].stats.latency_samples, copy * sizeof(usec_t)); |
| 1514 | samples_used += copy; |
| 1515 | } |
| 1516 | } |
| 1517 | |
| 1518 | *avg_ut = total_ops ? (double)total_latency_ut / (double)total_ops : 0.0; |
| 1519 | *p99_ut = dict_bench_percentile_ut(samples, samples_used, 99); |
| 1520 | freez(samples); |
| 1521 | } |
| 1522 | |
| 1523 | static void dict_bench_run_case(const struct dict_bench_config *cfg) { |
| 1524 | int total_threads = cfg->readers + cfg->writers; |
| 1525 | int join = 0; |
| 1526 | struct dict_bench_thread *threads = callocz(total_threads, sizeof(*threads)); |
| 1527 | struct dict_bench_summary summary = {0}; |
| 1528 | DICTIONARY *dict = dictionary_create(DICT_OPTION_NONE); |
| 1529 | dict_bench_prepopulate(dict, cfg->entries); |
| 1530 | |
| 1531 | for(int i = 0; i < cfg->readers; i++) { |
| 1532 | char tname[32]; |
| 1533 | threads[i] = (struct dict_bench_thread){ |
| 1534 | .id = i, |
| 1535 | .join = &join, |
| 1536 | .dict = dict, |
| 1537 | .cfg = cfg, |
| 1538 | .is_writer = false, |
| 1539 | .rng_state = (uint32_t)(i + 1) * 2654435761U, |
| 1540 | }; |
| 1541 | snprintfz(tname, sizeof(tname), "dbread%d", i); |
| 1542 | threads[i].thread = nd_thread_create(tname, NETDATA_THREAD_OPTION_DONT_LOG, |
| 1543 | dict_bench_reader_thread, &threads[i]); |
| 1544 | } |
| 1545 | |
| 1546 | for(int i = 0; i < cfg->writers; i++) { |
| 1547 | int idx = cfg->readers + i; |
| 1548 | char tname[32]; |
| 1549 | threads[idx] = (struct dict_bench_thread){ |
| 1550 | .id = idx, |
| 1551 | .join = &join, |
| 1552 | .dict = dict, |
| 1553 | .cfg = cfg, |
| 1554 | .is_writer = true, |
| 1555 | .rng_state = (uint32_t)(idx + 1) * 2246822519U, |
| 1556 | }; |
| 1557 | snprintfz(tname, sizeof(tname), "dbwrite%d", i); |
| 1558 | threads[idx].thread = nd_thread_create(tname, NETDATA_THREAD_OPTION_DONT_LOG, |
| 1559 | dict_bench_writer_thread, &threads[idx]); |
| 1560 | } |
| 1561 | |
| 1562 | sleep_usec(cfg->seconds_to_run * USEC_PER_SEC); |
| 1563 | __atomic_store_n(&join, 1, __ATOMIC_RELAXED); |
| 1564 | |
| 1565 | for(int i = 0; i < total_threads; i++) { |
| 1566 | nd_thread_join(threads[i].thread); |
| 1567 | if(threads[i].is_writer) |
| 1568 | summary.write_ops += threads[i].stats.ops; |
| 1569 | else { |
| 1570 | summary.read_ops += threads[i].stats.ops; |
| 1571 | summary.read_items_seen += threads[i].stats.items_seen; |
| 1572 | } |
| 1573 | } |
| 1574 | |
| 1575 | dict_bench_aggregate_latency(threads, total_threads, false, &summary.read_avg_ut, &summary.read_p99_ut); |
| 1576 | dict_bench_aggregate_latency(threads, total_threads, true, &summary.write_avg_ut, &summary.write_p99_ut); |
| 1577 | dictionary_destroy(dict); |
| 1578 | cleanup_destroyed_dictionaries(false); |
| 1579 | freez(threads); |
| 1580 | |
| 1581 | if(cfg->read_mode == DICT_BENCH_READ_TRAVERSAL) { |
| 1582 | fprintf(stderr, "%-14s %8zu %8d %8d %14.0f %14.0f %14.0f %14.2f %14.2f %14.2f %14.2f\n", |
| 1583 | cfg->workload, |
| 1584 | cfg->entries, |
| 1585 | cfg->readers, |
| 1586 | cfg->writers, |
| 1587 | cfg->seconds_to_run ? (double)summary.read_ops / cfg->seconds_to_run : 0.0, |
| 1588 | cfg->seconds_to_run ? (double)summary.read_items_seen / cfg->seconds_to_run : 0.0, |
| 1589 | cfg->seconds_to_run ? (double)summary.write_ops / cfg->seconds_to_run : 0.0, |
| 1590 | summary.read_avg_ut, |
| 1591 | summary.read_p99_ut, |
| 1592 | summary.write_avg_ut, |
| 1593 | summary.write_p99_ut); |
| 1594 | } |
| 1595 | else { |
| 1596 | fprintf(stderr, "%-14s %8zu %8d %8d %14.0f %14.0f %14.2f %14.2f %14.2f %14.2f\n", |
| 1597 | cfg->workload, |
| 1598 | cfg->entries, |
| 1599 | cfg->readers, |
| 1600 | cfg->writers, |
| 1601 | cfg->seconds_to_run ? (double)summary.read_ops / cfg->seconds_to_run : 0.0, |
| 1602 | cfg->seconds_to_run ? (double)summary.write_ops / cfg->seconds_to_run : 0.0, |
| 1603 | summary.read_avg_ut, |
| 1604 | summary.read_p99_ut, |
| 1605 | summary.write_avg_ut, |
| 1606 | summary.write_p99_ut); |
| 1607 | } |
| 1608 | } |
| 1609 | |
| 1610 | static void dict_bench_print_separator(size_t width) { |
| 1611 | for(size_t i = 0; i < width; i++) |
| 1612 | fputc('-', stderr); |
| 1613 | fputc('\n', stderr); |
| 1614 | } |
| 1615 | |
| 1616 | static void dict_bench_print_header_line(const char *line) { |
| 1617 | fprintf(stderr, "%s\n", line); |
| 1618 | dict_bench_print_separator(strlen(line)); |
| 1619 | } |
| 1620 | |
| 1621 | static void dict_bench_print_suite_header( |
| 1622 | const char *suite, |
| 1623 | const char *read_ops_label, |
| 1624 | const char *read_avg_label, |
| 1625 | const char *read_slow_label, |
| 1626 | const char *writer_desc |
| 1627 | ) { |
| 1628 | char header[512]; |
| 1629 | |
| 1630 | fprintf(stderr, "\n=== %s ===\n", suite); |
| 1631 | fprintf(stderr, "%s\n", writer_desc); |
| 1632 | snprintfz(header, sizeof(header), |
| 1633 | "%-14s %8s %8s %8s %14s %14s %14s %14s %14s %14s", |
| 1634 | "workload", "entries", "readers", "writers", |
| 1635 | read_ops_label, "write ops/s", |
| 1636 | read_avg_label, read_slow_label, "w avg us", "w slow us"); |
| 1637 | dict_bench_print_header_line(header); |
| 1638 | } |
| 1639 | |
| 1640 | static void dict_bench_print_traversal_header(void) { |
| 1641 | char header[512]; |
| 1642 | |
| 1643 | fprintf(stderr, "\n=== Dictionary Traversal Benchmark ===\n"); |
| 1644 | fprintf(stderr, "Reader workload: full dictionary scan. Writer workload: temporary-key insert followed by delete.\n"); |
| 1645 | snprintfz(header, sizeof(header), |
| 1646 | "%-14s %8s %8s %8s %14s %14s %14s %14s %14s %14s %14s", |
| 1647 | "workload", "entries", "readers", "writers", |
| 1648 | "full scans/s", "items visited/s", "write ops/s", |
| 1649 | "avg scan us", "slow scan us", "w avg us", "w slow us"); |
| 1650 | dict_bench_print_header_line(header); |
| 1651 | } |
| 1652 | |
| 1653 | int dictionary_unittest_benchmark(void) { |
| 1654 | const time_t seconds_to_run = 2; |
| 1655 | const size_t sizes[] = {100, 10000}; |
| 1656 | const int readers[] = {1, 4, 8}; |
| 1657 | const int writers[] = {0, 1, 2}; |
| 1658 | |
| 1659 | dict_bench_print_traversal_header(); |
| 1660 | for(size_t i = 0; i < sizeof(readers) / sizeof(readers[0]); i++) { |
| 1661 | for(size_t j = 0; j < sizeof(writers) / sizeof(writers[0]); j++) { |
| 1662 | struct dict_bench_config cfg = { |
| 1663 | .workload = "traversal", |
| 1664 | .entries = 10000, |
| 1665 | .readers = readers[i], |
| 1666 | .writers = writers[j], |
| 1667 | .seconds_to_run = seconds_to_run, |
| 1668 | .read_mode = DICT_BENCH_READ_TRAVERSAL, |
| 1669 | .write_mode = DICT_BENCH_WRITE_CHURN, |
| 1670 | }; |
| 1671 | dict_bench_run_case(&cfg); |
| 1672 | } |
| 1673 | } |
| 1674 | |
| 1675 | dict_bench_print_suite_header( |
| 1676 | "Dictionary Lookup Benchmark", |
| 1677 | "lookups/s", |
| 1678 | "avg lookup us", |
| 1679 | "slow lookup us", |
| 1680 | "Reader workload: dictionary_get(). Writer workload: overwrite an existing dictionary entry." |
| 1681 | ); |
| 1682 | for(size_t s = 0; s < sizeof(sizes) / sizeof(sizes[0]); s++) { |
| 1683 | for(size_t i = 0; i < sizeof(readers) / sizeof(readers[0]); i++) { |
| 1684 | for(size_t j = 0; j < sizeof(writers) / sizeof(writers[0]); j++) { |
| 1685 | struct dict_bench_config hot_cfg = { |
| 1686 | .workload = "lookup-hot", |
| 1687 | .entries = sizes[s], |
| 1688 | .readers = readers[i], |
| 1689 | .writers = writers[j], |
| 1690 | .seconds_to_run = seconds_to_run, |
| 1691 | .read_mode = DICT_BENCH_READ_LOOKUP_HOT, |
| 1692 | .write_mode = DICT_BENCH_WRITE_UPDATE, |
| 1693 | }; |
| 1694 | struct dict_bench_config random_cfg = hot_cfg; |
| 1695 | random_cfg.workload = "lookup-random"; |
| 1696 | random_cfg.read_mode = DICT_BENCH_READ_LOOKUP_RANDOM; |
| 1697 | |
| 1698 | dict_bench_run_case(&hot_cfg); |
| 1699 | dict_bench_run_case(&random_cfg); |
| 1700 | } |
| 1701 | } |
| 1702 | } |
| 1703 | |
| 1704 | dict_bench_print_suite_header( |
| 1705 | "Dictionary Mixed RW Benchmark", |
| 1706 | "read ops/s", |
| 1707 | "read avg us", |
| 1708 | "slow read us", |
| 1709 | "Reader workload: random lookups. Writer workload depends on the row: update rewrites existing keys, churn inserts then deletes temporary keys." |
| 1710 | ); |
| 1711 | { |
| 1712 | const struct dict_bench_config configs[] = { |
| 1713 | {.workload = "mixed-8r1w", .entries = 10000, .readers = 8, .writers = 1, .seconds_to_run = seconds_to_run, .read_mode = DICT_BENCH_READ_LOOKUP_RANDOM, .write_mode = DICT_BENCH_WRITE_UPDATE}, |
| 1714 | {.workload = "mixed-8r2w", .entries = 10000, .readers = 8, .writers = 2, .seconds_to_run = seconds_to_run, .read_mode = DICT_BENCH_READ_LOOKUP_RANDOM, .write_mode = DICT_BENCH_WRITE_UPDATE}, |
| 1715 | {.workload = "mixed-4r1w", .entries = 10000, .readers = 4, .writers = 1, .seconds_to_run = seconds_to_run, .read_mode = DICT_BENCH_READ_LOOKUP_RANDOM, .write_mode = DICT_BENCH_WRITE_CHURN}, |
| 1716 | {.workload = "mixed-4r2w", .entries = 10000, .readers = 4, .writers = 2, .seconds_to_run = seconds_to_run, .read_mode = DICT_BENCH_READ_LOOKUP_RANDOM, .write_mode = DICT_BENCH_WRITE_CHURN}, |
| 1717 | }; |
| 1718 | |
| 1719 | for(size_t i = 0; i < sizeof(configs) / sizeof(configs[0]); i++) |
| 1720 | dict_bench_run_case(&configs[i]); |
| 1721 | } |
| 1722 | |
| 1723 | dict_bench_print_suite_header( |
| 1724 | "Dictionary Writer Cost Benchmark", |
| 1725 | "read ops/s", |
| 1726 | "read avg us", |
| 1727 | "slow read us", |
| 1728 | "Writer workload: 'update' overwrites existing keys, 'churn' inserts then deletes temporary keys; '+r' rows include background readers." |
| 1729 | ); |
| 1730 | { |
| 1731 | const struct dict_bench_config configs[] = { |
| 1732 | {.workload = "update-1w", .entries = 10000, .readers = 0, .writers = 1, .seconds_to_run = seconds_to_run, .read_mode = DICT_BENCH_READ_NONE, .write_mode = DICT_BENCH_WRITE_UPDATE}, |
| 1733 | {.workload = "update-2w", .entries = 10000, .readers = 0, .writers = 2, .seconds_to_run = seconds_to_run, .read_mode = DICT_BENCH_READ_NONE, .write_mode = DICT_BENCH_WRITE_UPDATE}, |
| 1734 | {.workload = "churn-1w", .entries = 10000, .readers = 0, .writers = 1, .seconds_to_run = seconds_to_run, .read_mode = DICT_BENCH_READ_NONE, .write_mode = DICT_BENCH_WRITE_CHURN}, |
| 1735 | {.workload = "churn-2w", .entries = 10000, .readers = 0, .writers = 2, .seconds_to_run = seconds_to_run, .read_mode = DICT_BENCH_READ_NONE, .write_mode = DICT_BENCH_WRITE_CHURN}, |
| 1736 | {.workload = "update+r", .entries = 10000, .readers = 8, .writers = 1, .seconds_to_run = seconds_to_run, .read_mode = DICT_BENCH_READ_LOOKUP_RANDOM, .write_mode = DICT_BENCH_WRITE_UPDATE}, |
| 1737 | {.workload = "churn+r", .entries = 10000, .readers = 8, .writers = 2, .seconds_to_run = seconds_to_run, .read_mode = DICT_BENCH_READ_LOOKUP_RANDOM, .write_mode = DICT_BENCH_WRITE_CHURN}, |
| 1738 | }; |
| 1739 | |
| 1740 | for(size_t i = 0; i < sizeof(configs) / sizeof(configs[0]); i++) |
| 1741 | dict_bench_run_case(&configs[i]); |
| 1742 | } |
| 1743 | |
| 1744 | fprintf(stderr, "\n"); |
| 1745 | return 0; |
| 1746 | } |
| 1747 | |
| 1748 | int dictionary_unittest(size_t entries) { |
| 1749 | if(entries < 10) entries = 10; |
| 1750 | |
| 1751 | DICTIONARY *dict; |
| 1752 | size_t errors = 0; |
| 1753 | |
| 1754 | fprintf(stderr, "Generating %zu names and values...\n", entries); |
| 1755 | char **names = dictionary_unittest_generate_names(entries); |
| 1756 | char **values = dictionary_unittest_generate_values(entries); |
| 1757 | |
| 1758 | fprintf(stderr, "\nCreating dictionary single threaded, clone, %zu items\n", entries); |
| 1759 | dict = dictionary_create(DICT_OPTION_SINGLE_THREADED); |
| 1760 | dictionary_unittest_clone(dict, names, values, entries, &errors); |
| 1761 | |
| 1762 | fprintf(stderr, "\nCreating dictionary multi threaded, clone, %zu items\n", entries); |
| 1763 | dict = dictionary_create(DICT_OPTION_NONE); |
| 1764 | dictionary_unittest_clone(dict, names, values, entries, &errors); |
| 1765 | |
| 1766 | fprintf(stderr, "\nCreating dictionary single threaded, non-clone, add-in-front options, %zu items\n", entries); |
| 1767 | dict = dictionary_create( |
| 1768 | DICT_OPTION_SINGLE_THREADED | DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_VALUE_LINK_DONT_CLONE | |
| 1769 | DICT_OPTION_ADD_IN_FRONT); |
| 1770 | dictionary_unittest_nonclone(dict, names, values, entries, &errors); |
| 1771 | |
| 1772 | fprintf(stderr, "\nCreating dictionary multi threaded, non-clone, add-in-front options, %zu items\n", entries); |
| 1773 | dict = dictionary_create( |
| 1774 | DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_VALUE_LINK_DONT_CLONE | DICT_OPTION_ADD_IN_FRONT); |
| 1775 | dictionary_unittest_nonclone(dict, names, values, entries, &errors); |
| 1776 | |
| 1777 | fprintf(stderr, "\nCreating dictionary single-threaded, non-clone, don't overwrite options, %zu items\n", entries); |
| 1778 | dict = dictionary_create( |
| 1779 | DICT_OPTION_SINGLE_THREADED | DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_VALUE_LINK_DONT_CLONE | |
| 1780 | DICT_OPTION_DONT_OVERWRITE_VALUE); |
| 1781 | dictionary_unittest_run_and_measure_time(dict, "adding entries", names, values, entries, &errors, dictionary_unittest_set_nonclone); |
| 1782 | dictionary_unittest_run_and_measure_time(dict, "resetting non-overwrite entries", names, values, entries, &errors, dictionary_unittest_reset_dont_overwrite_nonclone); |
| 1783 | dictionary_unittest_run_and_measure_time(dict, "traverse foreach read loop", names, values, entries, &errors, dictionary_unittest_foreach); |
| 1784 | dictionary_unittest_run_and_measure_time(dict, "walkthrough read callback", names, values, entries, &errors, dictionary_unittest_walkthrough); |
| 1785 | dictionary_unittest_run_and_measure_time(dict, "walkthrough read callback stop", names, values, entries, &errors, dictionary_unittest_walkthrough_stop); |
| 1786 | dictionary_unittest_run_and_measure_time(dict, "destroying full dictionary", names, values, entries, &errors, dictionary_unittest_destroy); |
| 1787 | |
| 1788 | fprintf(stderr, "\nCreating dictionary multi-threaded, non-clone, don't overwrite options, %zu items\n", entries); |
| 1789 | dict = dictionary_create( |
| 1790 | DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_VALUE_LINK_DONT_CLONE | DICT_OPTION_DONT_OVERWRITE_VALUE); |
| 1791 | dictionary_unittest_run_and_measure_time(dict, "adding entries", names, values, entries, &errors, dictionary_unittest_set_nonclone); |
| 1792 | dictionary_unittest_run_and_measure_time(dict, "walkthrough write delete this", names, values, entries, &errors, dictionary_unittest_walkthrough_delete_this); |
| 1793 | dictionary_unittest_run_and_measure_time(dict, "destroying empty dictionary", names, values, entries, &errors, dictionary_unittest_destroy); |
| 1794 | |
| 1795 | fprintf(stderr, "\nCreating dictionary multi-threaded, non-clone, don't overwrite options, %zu items\n", entries); |
| 1796 | dict = dictionary_create( |
| 1797 | DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_VALUE_LINK_DONT_CLONE | DICT_OPTION_DONT_OVERWRITE_VALUE); |
| 1798 | dictionary_unittest_run_and_measure_time(dict, "adding entries", names, values, entries, &errors, dictionary_unittest_set_nonclone); |
| 1799 | dictionary_unittest_run_and_measure_time(dict, "foreach write delete this", names, values, entries, &errors, dictionary_unittest_foreach_delete_this); |
| 1800 | dictionary_unittest_run_and_measure_time(dict, "traverse foreach read loop empty", names, values, 0, &errors, dictionary_unittest_foreach); |
| 1801 | dictionary_unittest_run_and_measure_time(dict, "walkthrough read callback empty", names, values, 0, &errors, dictionary_unittest_walkthrough); |
| 1802 | dictionary_unittest_run_and_measure_time(dict, "destroying empty dictionary", names, values, entries, &errors, dictionary_unittest_destroy); |
| 1803 | |
| 1804 | fprintf(stderr, "\nCreating dictionary single threaded, clone, %zu items\n", entries); |
| 1805 | dict = dictionary_create(DICT_OPTION_SINGLE_THREADED); |
| 1806 | dictionary_unittest_sorting(dict, names, values, entries, &errors); |
| 1807 | dictionary_unittest_run_and_measure_time(dict, "destroying full dictionary", names, values, entries, &errors, dictionary_unittest_destroy); |
| 1808 | |
| 1809 | fprintf(stderr, "\nCreating dictionary single threaded, clone, %zu items\n", entries); |
| 1810 | dict = dictionary_create(DICT_OPTION_SINGLE_THREADED); |
| 1811 | dictionary_unittest_null_dfe(dict, names, values, entries, &errors); |
| 1812 | dictionary_unittest_run_and_measure_time(dict, "destroying full dictionary", names, values, entries, &errors, dictionary_unittest_destroy); |
| 1813 | |
| 1814 | fprintf(stderr, "\nCreating dictionary single threaded, noclone, %zu items\n", entries); |
| 1815 | dict = dictionary_create(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_VALUE_LINK_DONT_CLONE); |
| 1816 | dictionary_unittest_null_dfe(dict, names, values, entries, &errors); |
| 1817 | dictionary_unittest_run_and_measure_time(dict, "destroying full dictionary", names, values, entries, &errors, dictionary_unittest_destroy); |
| 1818 | |
| 1819 | // check reference counters |
| 1820 | { |
| 1821 | fprintf(stderr, "\nTesting reference counters:\n"); |
| 1822 | dict = dictionary_create(DICT_OPTION_NONE | DICT_OPTION_NAME_LINK_DONT_CLONE); |
| 1823 | errors += unittest_check_dictionary("", dict, 0, 0, 0, 0, 0); |
| 1824 | |
| 1825 | fprintf(stderr, "\nAdding test item to dictionary and acquiring it\n"); |
| 1826 | dictionary_set(dict, "test", "ITEM1", 6); |
| 1827 | DICTIONARY_ITEM *item = (DICTIONARY_ITEM *)dictionary_get_and_acquire_item(dict, "test"); |
| 1828 | |
| 1829 | errors += unittest_check_dictionary("", dict, 1, 1, 0, 1, 0); |
| 1830 | errors += unittest_check_item("ACQUIRED", dict, item, "test", "ITEM1", 1, ITEM_FLAG_NONE, true, true, true); |
| 1831 | |
| 1832 | fprintf(stderr, "\nChecking that reference counters are increased:\n"); |
| 1833 | void *t; |
| 1834 | dfe_start_read(dict, t) { |
| 1835 | errors += unittest_check_dictionary("", dict, 1, 1, 0, 1, 0); |
| 1836 | errors += unittest_check_item("ACQUIRED TRAVERSAL", dict, item, "test", "ITEM1", 2, ITEM_FLAG_NONE, true, true, true); |
| 1837 | } |
| 1838 | dfe_done(t); |
| 1839 | |
| 1840 | fprintf(stderr, "\nChecking that reference counters are decreased:\n"); |
| 1841 | errors += unittest_check_dictionary("", dict, 1, 1, 0, 1, 0); |
| 1842 | errors += unittest_check_item("ACQUIRED TRAVERSAL 2", dict, item, "test", "ITEM1", 1, ITEM_FLAG_NONE, true, true, true); |
| 1843 | |
| 1844 | fprintf(stderr, "\nDeleting the item we have acquired:\n"); |
| 1845 | dictionary_del(dict, "test"); |
| 1846 | |
| 1847 | errors += unittest_check_dictionary("", dict, 0, 0, 1, 1, 0); |
| 1848 | errors += unittest_check_item("DELETED", dict, item, "test", "ITEM1", 1, ITEM_FLAG_DELETED, false, false, true); |
| 1849 | |
| 1850 | fprintf(stderr, "\nAdding another item with the same name of the item we deleted, while being acquired:\n"); |
| 1851 | dictionary_set(dict, "test", "ITEM2", 6); |
| 1852 | errors += unittest_check_dictionary("", dict, 1, 1, 1, 1, 0); |
| 1853 | |
| 1854 | fprintf(stderr, "\nAcquiring the second item:\n"); |
| 1855 | DICTIONARY_ITEM *item2 = (DICTIONARY_ITEM *)dictionary_get_and_acquire_item(dict, "test"); |
| 1856 | errors += unittest_check_item("FIRST", dict, item, "test", "ITEM1", 1, ITEM_FLAG_DELETED, false, false, true); |
| 1857 | errors += unittest_check_item("SECOND", dict, item2, "test", "ITEM2", 1, ITEM_FLAG_NONE, true, true, true); |
| 1858 | errors += unittest_check_dictionary("", dict, 1, 1, 1, 2, 0); |
| 1859 | |
| 1860 | fprintf(stderr, "\nReleasing the second item (the first is still acquired):\n"); |
| 1861 | dictionary_acquired_item_release(dict, (DICTIONARY_ITEM *)item2); |
| 1862 | errors += unittest_check_dictionary("", dict, 1, 1, 1, 1, 0); |
| 1863 | errors += unittest_check_item("FIRST", dict, item, "test", "ITEM1", 1, ITEM_FLAG_DELETED, false, false, true); |
| 1864 | errors += unittest_check_item("SECOND RELEASED", dict, item2, "test", "ITEM2", 0, ITEM_FLAG_NONE, true, true, true); |
| 1865 | |
| 1866 | fprintf(stderr, "\nDeleting the second item (the first is still acquired):\n"); |
| 1867 | dictionary_del(dict, "test"); |
| 1868 | errors += unittest_check_dictionary("", dict, 0, 0, 1, 1, 0); |
| 1869 | errors += unittest_check_item("ACQUIRED DELETED", dict, item, "test", "ITEM1", 1, ITEM_FLAG_DELETED, false, false, true); |
| 1870 | |
| 1871 | fprintf(stderr, "\nReleasing the first item (which we have already deleted):\n"); |
| 1872 | dictionary_acquired_item_release(dict, (DICTIONARY_ITEM *)item); |
| 1873 | dfe_start_write(dict, item) ; dfe_done(item); |
| 1874 | errors += unittest_check_dictionary("", dict, 0, 0, 1, 0, 1); |
| 1875 | |
| 1876 | fprintf(stderr, "\nAdding again the test item to dictionary and acquiring it\n"); |
| 1877 | dictionary_set(dict, "test", "ITEM1", 6); |
| 1878 | item = (DICTIONARY_ITEM *)dictionary_get_and_acquire_item(dict, "test"); |
| 1879 | |
| 1880 | errors += unittest_check_dictionary("", dict, 1, 1, 0, 1, 0); |
| 1881 | errors += unittest_check_item("RE-ADDITION", dict, item, "test", "ITEM1", 1, ITEM_FLAG_NONE, true, true, true); |
| 1882 | |
| 1883 | fprintf(stderr, "\nDestroying the dictionary while we have acquired an item\n"); |
| 1884 | dictionary_destroy(dict); |
| 1885 | |
| 1886 | fprintf(stderr, "Releasing the item (on a destroyed dictionary)\n"); |
| 1887 | dictionary_acquired_item_release(dict, (DICTIONARY_ITEM *)item); |
| 1888 | item = NULL; |
| 1889 | dict = NULL; |
| 1890 | } |
| 1891 | |
| 1892 | dictionary_unittest_free_char_pp(names, entries); |
| 1893 | dictionary_unittest_free_char_pp(values, entries); |
| 1894 | |
| 1895 | errors += dictionary_unittest_views(); |
| 1896 | errors += dictionary_unittest_threads(); |
| 1897 | errors += dictionary_unittest_view_threads(); |
| 1898 | |
| 1899 | if(!dictionary_traverse_or_destroy_unittest()) { |
| 1900 | fprintf(stderr, "Destroy on traversal test failed\n"); |
| 1901 | errors++; |
| 1902 | } |
| 1903 | else |
| 1904 | fprintf(stderr, "Destroy on traversal test OK\n"); |
| 1905 | |
| 1906 | errors += dictionary_destroy_race_unittest(); |
| 1907 | |
| 1908 | cleanup_destroyed_dictionaries(false); |
| 1909 | |
| 1910 | size_t delayed = dictionary_destroy_delayed_count(); |
| 1911 | if(delayed != 0) { |
| 1912 | fprintf(stderr, "WARNING: There are %zu dictionaries that cannot be destroyed\n", delayed); |
| 1913 | } |
| 1914 | else |
| 1915 | fprintf(stderr, "All dictionaries have been freed: OK\n"); |
| 1916 | |
| 1917 | fprintf(stderr, "\n%zu errors found\n", errors); |
| 1918 | return errors ? 1 : 0; |
| 1919 | } |