master
c 873 lines 29.4 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "dictionary-internals.h"
4
5 ARAL *dict_items_aral = NULL;
6 ARAL *dict_shared_items_aral = NULL;
7
8 struct dictionary_stats dictionary_stats_category_other = {
9 .name = "other",
10 };
11
12 // ----------------------------------------------------------------------------
13 // public locks API
14
15 inline void dictionary_write_lock(DICTIONARY *dict) {
16 ll_recursive_lock(dict, DICTIONARY_LOCK_WRITE);
17 }
18
19 inline void dictionary_write_unlock(DICTIONARY *dict) {
20 ll_recursive_unlock(dict, DICTIONARY_LOCK_WRITE);
21 }
22
23 // ----------------------------------------------------------------------------
24 // ARAL for dict and hooks
25
26 static ARAL *ar_dict = NULL;
27 static ARAL *ar_hooks = NULL;
28
29 static void dictionary_init_aral(void) {
30 if(ar_dict && ar_hooks) return;
31
32 static SPINLOCK spinlock = SPINLOCK_INITIALIZER;
33 spinlock_lock(&spinlock);
34
35 if(!ar_dict)
36 ar_dict = aral_by_size_acquire(sizeof(DICTIONARY));
37
38 if(!ar_hooks)
39 ar_hooks = aral_by_size_acquire(sizeof(struct dictionary_hooks));
40
41 spinlock_unlock(&spinlock);
42 }
43
44 // ----------------------------------------------------------------------------
45 // callbacks registration
46
47 static inline void dictionary_hooks_allocate(DICTIONARY *dict) {
48 if(dict->hooks) return;
49
50 dictionary_init_aral();
51
52 dict->hooks = aral_callocz(ar_hooks);
53 dict->hooks->links = 1;
54
55 DICTIONARY_STATS_PLUS_MEMORY(dict, 0, sizeof(struct dictionary_hooks), 0);
56 }
57
58 static inline size_t dictionary_hooks_free(DICTIONARY *dict) {
59 if(!dict->hooks) return 0;
60
61 REFCOUNT links = __atomic_sub_fetch(&dict->hooks->links, 1, __ATOMIC_ACQUIRE);
62 if(links == 0) {
63 aral_freez(ar_hooks, dict->hooks);
64 dict->hooks = NULL;
65
66 DICTIONARY_STATS_MINUS_MEMORY(dict, 0, sizeof(struct dictionary_hooks), 0);
67 return sizeof(struct dictionary_hooks);
68 }
69
70 return 0;
71 }
72
73 void dictionary_register_insert_callback(DICTIONARY *dict, dict_cb_insert_t insert_callback, void *data) {
74 if(unlikely(is_view_dictionary(dict)))
75 fatal("DICTIONARY: called %s() on a view.", __FUNCTION__ );
76
77 dictionary_hooks_allocate(dict);
78 dict->hooks->insert_callback = insert_callback;
79 dict->hooks->insert_callback_data = data;
80 }
81
82 void dictionary_register_conflict_callback(DICTIONARY *dict, dict_cb_conflict_t conflict_callback, void *data) {
83 if(unlikely(is_view_dictionary(dict)))
84 fatal("DICTIONARY: called %s() on a view.", __FUNCTION__ );
85
86 internal_error(!(dict->options & DICT_OPTION_DONT_OVERWRITE_VALUE), "DICTIONARY: registering conflict callback without DICT_OPTION_DONT_OVERWRITE_VALUE");
87 dict->options |= DICT_OPTION_DONT_OVERWRITE_VALUE;
88
89 dictionary_hooks_allocate(dict);
90 dict->hooks->conflict_callback = conflict_callback;
91 dict->hooks->conflict_callback_data = data;
92 }
93
94 void dictionary_register_react_callback(DICTIONARY *dict, dict_cb_react_t react_callback, void *data) {
95 if(unlikely(is_view_dictionary(dict)))
96 fatal("DICTIONARY: called %s() on a view.", __FUNCTION__ );
97
98 dictionary_hooks_allocate(dict);
99 dict->hooks->react_callback = react_callback;
100 dict->hooks->react_callback_data = data;
101 }
102
103 void dictionary_register_delete_callback(DICTIONARY *dict, dict_cb_delete_t delete_callback, void *data) {
104 if(unlikely(is_view_dictionary(dict)))
105 fatal("DICTIONARY: called %s() on a view.", __FUNCTION__ );
106
107 dictionary_hooks_allocate(dict);
108 dict->hooks->delete_callback = delete_callback;
109 dict->hooks->delelte_callback_data = data;
110 }
111
112 // ----------------------------------------------------------------------------
113 // dictionary statistics API
114
115 ALWAYS_INLINE
116 size_t dictionary_version(DICTIONARY *dict) {
117 if(unlikely(!dict)) return 0;
118
119 // this is required for views to return the right number
120 // garbage_collect_pending_deletes(dict);
121
122 return __atomic_load_n(&dict->version, __ATOMIC_RELAXED);
123 }
124
125 ALWAYS_INLINE
126 size_t dictionary_entries(DICTIONARY *dict) {
127 if(unlikely(!dict)) return 0;
128
129 // this is required for views to return the right number
130 // garbage_collect_pending_deletes(dict);
131
132 long int entries = __atomic_load_n(&dict->entries, __ATOMIC_RELAXED);
133 internal_fatal(entries < 0, "DICTIONARY: entries is negative: %ld", entries);
134
135 return entries;
136 }
137
138 size_t dictionary_referenced_items(DICTIONARY *dict) {
139 if(unlikely(!dict)) return 0;
140
141 long int referenced_items = __atomic_load_n(&dict->referenced_items, __ATOMIC_RELAXED);
142 if(referenced_items < 0)
143 fatal("DICTIONARY: referenced items is negative: %ld", referenced_items);
144
145 return referenced_items;
146 }
147
148 void dictionary_version_increment(DICTIONARY *dict) {
149 __atomic_fetch_add(&dict->version, 1, __ATOMIC_RELAXED);
150 }
151
152 // ----------------------------------------------------------------------------
153 // tracking allocated dictionaries
154
155 #include "dictionary-debug.h"
156
157 // ----------------------------------------------------------------------------
158 // items garbage collector
159
160 void garbage_collect_pending_deletes(DICTIONARY *dict) {
161 usec_t last_master_deletion_us = dict->hooks?__atomic_load_n(&dict->hooks->last_master_deletion_us, __ATOMIC_RELAXED):0;
162 usec_t last_gc_run_us = __atomic_load_n(&dict->last_gc_run_us, __ATOMIC_RELAXED);
163
164 bool is_view = is_view_dictionary(dict);
165
166 if(likely(!(
167 DICTIONARY_PENDING_DELETES_GET(dict) > 0 ||
168 (is_view && last_master_deletion_us > last_gc_run_us)
169 )))
170 return;
171
172 ll_recursive_lock(dict, DICTIONARY_LOCK_WRITE);
173
174 __atomic_store_n(&dict->last_gc_run_us, now_realtime_usec(), __ATOMIC_RELAXED);
175
176 if(is_view)
177 dictionary_index_lock_wrlock(dict);
178
179 DICTIONARY_STATS_GARBAGE_COLLECTIONS_PLUS1(dict);
180
181 size_t deleted = 0, pending = 0, examined = 0;
182 DICTIONARY_ITEM *item = dict->items.list, *item_next;
183 while(item) {
184 examined++;
185
186 // this will clean up
187 item_next = item->next;
188 int rc = item_check_and_acquire_advanced(dict, item, is_view);
189
190 if(rc == RC_ITEM_MARKED_FOR_DELETION) {
191 // we didn't get a reference
192
193 if(item_is_not_referenced_and_can_be_removed(dict, item)) {
194 DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(dict->items.list, item, prev, next);
195 dict_item_free_with_hooks(dict, item);
196 deleted++;
197
198 pending = DICTIONARY_PENDING_DELETES_MINUS1(dict);
199 if (!pending)
200 break;
201 }
202 }
203 else if(rc == RC_ITEM_IS_CURRENTLY_BEING_DELETED)
204 ; // do not touch this item (we didn't get a reference)
205
206 else if(rc == RC_ITEM_OK)
207 item_release(dict, item);
208
209 item = item_next;
210 }
211
212 if(is_view)
213 dictionary_index_wrlock_unlock(dict);
214
215 ll_recursive_unlock(dict, DICTIONARY_LOCK_WRITE);
216
217 (void)deleted;
218 (void)examined;
219
220 dictionary_internal_error(false, dict, "DICTIONARY: garbage collected dictionary, "
221 "examined %zu items, deleted %zu items, still pending %zu items",
222 examined, deleted, pending);
223 }
224
225 void dictionary_garbage_collect(DICTIONARY *dict) {
226 if(!dict) return;
227 garbage_collect_pending_deletes(dict);
228 }
229
230 // ----------------------------------------------------------------------------
231
232 void dictionary_static_items_aral_init(void) {
233 static SPINLOCK spinlock;
234
235 if(unlikely(!dict_items_aral || !dict_shared_items_aral)) {
236 spinlock_lock(&spinlock);
237
238 if(!dict_items_aral)
239 dict_items_aral = aral_by_size_acquire(sizeof(DICTIONARY_ITEM));
240
241 if(!dict_shared_items_aral)
242 dict_shared_items_aral = aral_by_size_acquire(sizeof(DICTIONARY_ITEM_SHARED));
243
244 spinlock_unlock(&spinlock);
245 }
246 }
247
248 // ----------------------------------------------------------------------------
249 // delayed destruction of dictionaries
250
251 static bool dictionary_free_all_resources(DICTIONARY *dict, size_t *mem, bool force) {
252 if(mem)
253 *mem = 0;
254
255 if(!force && dictionary_referenced_items(dict))
256 return false;
257
258 size_t dict_size = 0, counted_items = 0, item_size = 0, index_size = 0;
259 (void)counted_items;
260
261 #ifdef NETDATA_INTERNAL_CHECKS
262 long int entries = dict->entries;
263 long int referenced_items = dict->referenced_items;
264 long int pending_deletion_items = dict->pending_deletion_items;
265 #endif
266
267 // destroy the index
268 dictionary_index_lock_wrlock(dict);
269 index_size += hashtable_destroy_unsafe(dict);
270 dictionary_index_wrlock_unlock(dict);
271
272 ll_recursive_lock(dict, DICTIONARY_LOCK_WRITE);
273 DICTIONARY_ITEM *item = dict->items.list;
274 while (item) {
275 // cache item->next
276 // because we are going to free item
277 DICTIONARY_ITEM *item_next = item->next;
278
279 item_size += dict_item_free_with_hooks(dict, item);
280 item = item_next;
281
282 // to speed up destruction, we don't unlink the item
283 // from the linked-list here
284
285 counted_items++;
286 }
287 dict->items.list = NULL;
288 ll_recursive_unlock(dict, DICTIONARY_LOCK_WRITE);
289
290 dict_size += dictionary_locks_destroy(dict);
291 dict_size += reference_counter_free(dict);
292 dict_size += dictionary_hooks_free(dict);
293 dict_size += sizeof(DICTIONARY);
294 DICTIONARY_STATS_MINUS_MEMORY(dict, 0, sizeof(DICTIONARY), 0);
295
296 if(dict->value_aral)
297 aral_by_size_release(dict->value_aral);
298
299 dictionary_debug_untrack_dict(dict);
300 aral_freez(ar_dict, dict);
301
302 internal_error(
303 false,
304 "DICTIONARY: Freed dictionary having %ld (counted %zu) entries, %ld referenced, %ld pending deletion, total freed memory: %zu bytes (sizeof(dict) = %zu, sizeof(item) = %zu).",
305 entries, counted_items, referenced_items, pending_deletion_items,
306 dict_size + item_size, sizeof(DICTIONARY), sizeof(DICTIONARY_ITEM) + sizeof(DICTIONARY_ITEM_SHARED));
307
308 if(mem)
309 *mem = dict_size + item_size + index_size;
310
311 return true;
312 }
313
314 netdata_mutex_t dictionaries_waiting_to_be_destroyed_mutex;
315 static DICTIONARY *dictionaries_waiting_to_be_destroyed = NULL;
316
317 static void __attribute__((constructor)) init_mutex(void) {
318 netdata_mutex_init(&dictionaries_waiting_to_be_destroyed_mutex);
319 }
320
321 static void __attribute__((destructor)) destroy_mutex(void) {
322 netdata_mutex_destroy(&dictionaries_waiting_to_be_destroyed_mutex);
323 }
324
325 #ifdef FSANITIZE_ADDRESS
326 DEFINE_JUDYL_TYPED(STACKTRACE, size_t);
327 #endif
328
329 static void dictionary_queue_for_destruction(DICTIONARY *dict) {
330 netdata_mutex_lock(&dictionaries_waiting_to_be_destroyed_mutex);
331
332 if(dict_flag_check(dict, DICT_FLAG_QUEUED_FOR_DESTRUCTION))
333 goto cleanup;
334
335 DICTIONARY_STATS_DICT_DESTROY_QUEUED_PLUS1(dict);
336 dict_flag_set(dict, DICT_FLAG_DESTROYED);
337 dict_flag_set(dict, DICT_FLAG_QUEUED_FOR_DESTRUCTION);
338
339 dict->next = dictionaries_waiting_to_be_destroyed;
340 dictionaries_waiting_to_be_destroyed = dict;
341
342 cleanup:
343 netdata_mutex_unlock(&dictionaries_waiting_to_be_destroyed_mutex);
344 }
345
346 size_t dictionary_destroy_delayed_count(void) {
347 netdata_mutex_lock(&dictionaries_waiting_to_be_destroyed_mutex);
348
349 size_t count = 0;
350 for(DICTIONARY *dict = dictionaries_waiting_to_be_destroyed, *next; dict ; dict = next) {
351 next = dict->next;
352 count++;
353 }
354
355 netdata_mutex_unlock(&dictionaries_waiting_to_be_destroyed_mutex);
356 return count;
357 }
358
359 size_t cleanup_destroyed_dictionaries(bool shutdown __maybe_unused)
360 {
361 if (netdata_mutex_trylock(&dictionaries_waiting_to_be_destroyed_mutex) != 0)
362 return 0;
363
364 if (!dictionaries_waiting_to_be_destroyed) {
365 netdata_mutex_unlock(&dictionaries_waiting_to_be_destroyed_mutex);
366 return 0;
367 }
368
369 size_t remaining = 0;
370
371 #ifdef FSANITIZE_ADDRESS
372 // Create Judy arrays for tracking stats by stacktrace
373 STACKTRACE_JudyLSet dict_counts = { 0 }; // Count of dictionaries per stacktrace
374 STACKTRACE_JudyLSet item_counts = { 0 }; // Count of items per stacktrace
375 STACKTRACE_JudyLSet item_stacktrace_counts = { 0 }; // Count of items by their creation stacktrace
376
377 STACKTRACE_INIT(&dict_counts);
378 STACKTRACE_INIT(&item_counts);
379 STACKTRACE_INIT(&item_stacktrace_counts);
380 #endif
381
382 DICTIONARY *dict, *last = NULL, *next = NULL;
383 for(dict = dictionaries_waiting_to_be_destroyed; dict ; dict = next) {
384 next = dict->next;
385
386 DICTIONARY_STATS_DICT_DESTROY_QUEUED_MINUS1(dict);
387 if(dictionary_free_all_resources(dict, NULL, false)) {
388 if(last) last->next = next;
389 else dictionaries_waiting_to_be_destroyed = next;
390 }
391 else {
392 #ifdef FSANITIZE_ADDRESS
393 size_t ref_items = dictionary_referenced_items(dict);
394
395 // Track this dictionary for deduplication reporting
396 if (shutdown) {
397 // Process all stacktraces from this dictionary
398 for (int i = 0; i < dict->stacktraces.num_stacktraces; i++) {
399 if (dict->stacktraces.stacktraces[i]) {
400 // Update dictionary count
401 uintptr_t key = (uintptr_t)dict->stacktraces.stacktraces[i];
402 size_t dict_count = STACKTRACE_GET(&dict_counts, key);
403 dict_count++;
404 STACKTRACE_SET(&dict_counts, key, dict_count);
405
406 // Update item count
407 size_t items_count = STACKTRACE_GET(&item_counts, key);
408 items_count += ref_items;
409 STACKTRACE_SET(&item_counts, key, items_count);
410 }
411 }
412 }
413 #endif
414
415 DICTIONARY_STATS_DICT_DESTROY_QUEUED_PLUS1(dict);
416 last = dict;
417 remaining++;
418 }
419 }
420
421 #ifdef FSANITIZE_ADDRESS
422 if (remaining > 0 && shutdown) {
423 // Print deduplicated report
424 fprintf(stderr, "WARNING: There are %zu dictionaries with references in them, that cannot be destroyed.\n",
425 remaining);
426
427 // Buffer for formatting stack traces
428 BUFFER *wb = buffer_create(16384, NULL);
429
430 // Print each unique stacktrace group
431 Word_t stacktrace_idx = 0;
432 size_t i = 0;
433
434 // Get the first key from dict_counts (both Judy arrays have the same keys)
435 for(size_t key_index = STACKTRACE_FIRST(&dict_counts, &stacktrace_idx);
436 key_index;
437 key_index = STACKTRACE_NEXT(&dict_counts, &stacktrace_idx)) {
438
439 i++;
440 STACKTRACE st = (STACKTRACE)stacktrace_idx;
441 size_t dict_count = STACKTRACE_GET(&dict_counts, stacktrace_idx);
442 size_t item_count = STACKTRACE_GET(&item_counts, stacktrace_idx);
443
444 // Format stacktrace to buffer
445 buffer_flush(wb);
446 stacktrace_to_buffer(st, wb);
447
448 fprintf(stderr, "\n > DICTIONARY DELAYED %zu: %zu items in %zu dictionaries accessed from:\n%s\n\n",
449 i, item_count, dict_count, buffer_tostring(wb));
450 }
451
452 // Clean up
453 // Now collect and report information about dictionary items grouped by their creation stacktrace
454 fprintf(stderr, "\n========= DICTIONARY ITEMS GROUPED BY CREATION STACKTRACE =========\n");
455
456 // Loop through dictionaries to collect item stacktraces
457 for(dict = dictionaries_waiting_to_be_destroyed; dict; dict = dict->next) {
458 // Iterate through all items and count by stacktrace
459 DICTIONARY_ITEM *item;
460 for(item = dict->items.list; item; item = item->next) {
461 // Count each unique stacktrace in the item's array
462 for (int i = 0; i < item->stacktraces.num_stacktraces; i++) {
463 if (item->stacktraces.stacktraces[i]) {
464 uintptr_t item_key = (uintptr_t)item->stacktraces.stacktraces[i];
465 size_t count = STACKTRACE_GET(&item_stacktrace_counts, item_key);
466 count++;
467 STACKTRACE_SET(&item_stacktrace_counts, item_key, count);
468 }
469 }
470 }
471 }
472
473 // Print report of items by stacktrace
474 Word_t item_st_idx = 0;
475 size_t j = 0;
476
477 for(size_t key_index = STACKTRACE_FIRST(&item_stacktrace_counts, &item_st_idx);
478 key_index;
479 key_index = STACKTRACE_NEXT(&item_stacktrace_counts, &item_st_idx)) {
480
481 j++;
482 STACKTRACE st = (STACKTRACE)item_st_idx;
483 size_t count = STACKTRACE_GET(&item_stacktrace_counts, item_st_idx);
484
485 // Format stacktrace to buffer
486 buffer_flush(wb);
487 stacktrace_to_buffer(st, wb);
488
489 fprintf(stderr, "\n > DICTIONARY ITEMS DELAYED %zu: %zu items accessed from:\n%s\n\n",
490 j, count, buffer_tostring(wb));
491 }
492
493 fprintf(stderr, "Total: %zu dictionaries\n", j);
494 fprintf(stderr, "==================================================================\n");
495
496 STACKTRACE_FREE(&dict_counts, NULL, NULL);
497 STACKTRACE_FREE(&item_counts, NULL, NULL);
498 STACKTRACE_FREE(&item_stacktrace_counts, NULL, NULL);
499
500 buffer_free(wb);
501 }
502 #endif
503
504 netdata_mutex_unlock(&dictionaries_waiting_to_be_destroyed_mutex);
505
506 return remaining;
507 }
508
509 // ----------------------------------------------------------------------------
510 // API internal checks
511
512 // Use the debug version from dictionary-debug.h
513 #define api_internal_check(dict, item, allow_null_dict, allow_null_item) dictionary_debug_internal_check(dict, item, allow_null_dict, allow_null_item)
514
515 #define api_is_name_good(dict, name, name_len) api_is_name_good_with_trace(dict, name, name_len, __FUNCTION__)
516 static bool api_is_name_good_with_trace(DICTIONARY *dict __maybe_unused, const char *name, ssize_t name_len __maybe_unused, const char *function __maybe_unused) {
517 if(unlikely(!name)) {
518 dictionary_internal_error(true, dict,
519 "DICTIONARY: attempted to %s() with name = NULL on a dictionary",
520 function);
521 return false;
522 }
523
524 if(unlikely(!*name)) {
525 dictionary_internal_error(true, dict,
526 "DICTIONARY: attempted to %s() with empty name on a dictionary",
527 function);
528 return false;
529 }
530
531 dictionary_internal_error(
532 name_len > 0 && name_len != (ssize_t)strlen(name), dict,
533 "DICTIONARY: attempted to %s() with a name of '%s', having length of %zu, "
534 "but the supplied name_len = %ld",
535 function,
536 name,
537 strlen(name),
538 (long int) name_len);
539
540 dictionary_internal_error(
541 name_len <= 0 && name_len != -1, dict,
542 "DICTIONARY: attempted to %s() with a name of '%s', having length of %zu, "
543 "but the supplied name_len = %ld",
544 function,
545 name,
546 strlen(name),
547 (long int) name_len);
548
549 return true;
550 }
551
552 // ----------------------------------------------------------------------------
553 // API - dictionary management
554
555 static DICTIONARY *dictionary_create_internal(DICT_OPTIONS options, struct dictionary_stats *stats, size_t fixed_size) {
556 dictionary_init_aral();
557 cleanup_destroyed_dictionaries(false);
558
559 DICTIONARY *dict = aral_callocz(ar_dict);
560 dict->options = options;
561 dict->stats = stats;
562
563 if((dict->options & DICT_OPTION_FIXED_SIZE) && !fixed_size) {
564 dict->options &= ~DICT_OPTION_FIXED_SIZE;
565 internal_fatal(true, "DICTIONARY: requested fixed size dictionary, without setting the size");
566 }
567 if(!(dict->options & DICT_OPTION_FIXED_SIZE) && fixed_size) {
568 dict->options |= DICT_OPTION_FIXED_SIZE;
569 internal_fatal(true, "DICTIONARY: set a fixed size for the items, without setting DICT_OPTION_FIXED_SIZE flag");
570 }
571
572 if(dict->options & DICT_OPTION_FIXED_SIZE)
573 dict->value_aral = aral_by_size_acquire(fixed_size);
574 else
575 dict->value_aral = NULL;
576
577 // if(!(dict->options & (DICT_OPTION_INDEX_JUDY|DICT_OPTION_INDEX_HASHTABLE)))
578 dict->options |= DICT_OPTION_INDEX_JUDY;
579
580 size_t dict_size = 0;
581 dict_size += sizeof(DICTIONARY);
582 dict_size += dictionary_locks_init(dict);
583 dict_size += reference_counter_init(dict);
584 dict_size += hashtable_init_unsafe(dict);
585
586 dictionary_static_items_aral_init();
587 pointer_index_init(dict);
588
589 DICTIONARY_STATS_PLUS_MEMORY(dict, 0, dict_size, 0);
590
591 return dict;
592 }
593
594 // Helper function to add a stacktrace to a dictionary
595 #ifdef FSANITIZE_ADDRESS
596 static inline void dict_add_stacktrace(DICTIONARY *dict) {
597 stacktrace_array_add(&dict->stacktraces, 1);
598 }
599 #endif
600
601 DICTIONARY *dictionary_create_advanced(DICT_OPTIONS options, struct dictionary_stats *stats, size_t fixed_size) {
602 DICTIONARY *dict = dictionary_create_internal(options, stats?stats:&dictionary_stats_category_other, fixed_size);
603
604 #ifdef FSANITIZE_ADDRESS
605 // Initialize stacktrace tracking
606 stacktrace_array_init(&dict->stacktraces);
607
608 // Add the first stack trace at creation time
609 dict_add_stacktrace(dict);
610 #endif
611
612 DICTIONARY_STATS_DICT_CREATIONS_PLUS1(dict);
613 dictionary_debug_track_dict(dict);
614 return dict;
615 }
616
617 DICTIONARY *dictionary_create_view(DICTIONARY *master) {
618 DICTIONARY *dict = dictionary_create_internal(master->options, master->stats,
619 master->value_aral ? aral_requested_element_size(master->value_aral) : 0);
620
621 dict->master = master;
622
623 dictionary_hooks_allocate(master);
624
625 if(unlikely(__atomic_load_n(&master->hooks->links, __ATOMIC_RELAXED)) < 1)
626 fatal("DICTIONARY: attempted to create a view that has %d links", master->hooks->links);
627
628 dict->hooks = master->hooks;
629 __atomic_add_fetch(&master->hooks->links, 1, __ATOMIC_ACQUIRE);
630
631 #ifdef FSANITIZE_ADDRESS
632 // Initialize stacktrace tracking
633 stacktrace_array_init(&dict->stacktraces);
634
635 // Add the first stack trace at creation time
636 dict_add_stacktrace(dict);
637 #endif
638
639 DICTIONARY_STATS_DICT_CREATIONS_PLUS1(dict);
640 dictionary_debug_track_dict(dict);
641 return dict;
642 }
643
644 void dictionary_flush(DICTIONARY *dict) {
645 if(unlikely(!dict))
646 return;
647
648 ll_recursive_lock(dict, DICTIONARY_LOCK_WRITE);
649
650 DICTIONARY_ITEM *item, *next = NULL;
651 for(item = dict->items.list; item ;item = next) {
652 next = item->next;
653 dict_item_del(dict, item_get_name(item), (ssize_t)item_get_name_len(item));
654 }
655
656 ll_recursive_unlock(dict, DICTIONARY_LOCK_WRITE);
657
658 DICTIONARY_STATS_DICT_FLUSHES_PLUS1(dict);
659
660 dictionary_garbage_collect(dict);
661 }
662
663 size_t dictionary_destroy(DICTIONARY *dict) {
664 cleanup_destroyed_dictionaries(false);
665
666 if(!dict || unlikely(is_dictionary_destroyed(dict)))
667 return 0;
668
669 ll_recursive_lock(dict, DICTIONARY_LOCK_WRITE);
670
671 DICTIONARY_STATS_DICT_DESTRUCTIONS_PLUS1(dict);
672
673 size_t referenced_items = dictionary_referenced_items(dict);
674 if(referenced_items) {
675 dictionary_flush(dict);
676 dictionary_queue_for_destruction(dict);
677
678 dictionary_internal_error(
679 true, dict,
680 "DICTIONARY: delaying destruction of dictionary, because it has %d referenced items in it (%d total).",
681 dict->referenced_items, dict->entries);
682
683 ll_recursive_unlock(dict, DICTIONARY_LOCK_WRITE);
684 return 0;
685 }
686
687 dict_flag_set(dict, DICT_FLAG_DESTROYED);
688
689 // Destroy the index while holding the items write lock.
690 // This prevents a TOCTOU race: without this, a reader could pass the
691 // is_dictionary_destroyed() check, then acquire an item via the index,
692 // after we've decided to force-free all items.
693 // By destroying the index here, any concurrent dictionary_get_and_acquire_item()
694 // that acquires the index lock after this point will find an empty index.
695 // This uses hashtable_destroy_unsafe(); the later cleanup path in
696 // dictionary_free_all_resources() may invoke the same index teardown
697 // again, so this relies on that full destruction flow being safe to repeat.
698 dictionary_index_lock_wrlock(dict);
699 hashtable_destroy_unsafe(dict);
700 dictionary_index_wrlock_unlock(dict);
701
702 // Re-check: a reader that held the index read lock during the destroy
703 // above may have acquired an item before we got the index write lock.
704 // If so, fall back to the deferred destruction path.
705 if(dictionary_referenced_items(dict)) {
706 dictionary_queue_for_destruction(dict);
707
708 ll_recursive_unlock(dict, DICTIONARY_LOCK_WRITE);
709 return 0;
710 }
711
712 ll_recursive_unlock(dict, DICTIONARY_LOCK_WRITE);
713
714 size_t freed;
715 dictionary_free_all_resources(dict, &freed, true);
716
717 return freed;
718 }
719
720 // ----------------------------------------------------------------------------
721 // SET an item to the dictionary
722
723 DICT_ITEM_CONST DICTIONARY_ITEM *dictionary_set_and_acquire_item_advanced(DICTIONARY *dict, const char *name, ssize_t name_len, void *value, size_t value_len, void *constructor_data) {
724 if(unlikely(!api_is_name_good(dict, name, name_len)))
725 return NULL;
726
727 api_internal_check(dict, NULL, false, true);
728
729 if(unlikely(is_view_dictionary(dict)))
730 fatal("DICTIONARY: this dictionary is a view, you cannot add items other than the ones from the master dictionary.");
731
732 DICTIONARY_ITEM *item =
733 dict_item_add_or_reset_value_and_acquire(dict, name, name_len, value, value_len, constructor_data, NULL);
734 api_internal_check(dict, item, false, false);
735 return item;
736 }
737
738 void *dictionary_set_advanced(DICTIONARY *dict, const char *name, ssize_t name_len, void *value, size_t value_len, void *constructor_data) {
739 DICTIONARY_ITEM *item = dictionary_set_and_acquire_item_advanced(dict, name, name_len, value, value_len, constructor_data);
740
741 if(likely(item)) {
742 void *v = item->shared->value;
743 item_release(dict, item);
744 return v;
745 }
746
747 return NULL;
748 }
749
750 DICT_ITEM_CONST DICTIONARY_ITEM *dictionary_view_set_and_acquire_item_advanced(DICTIONARY *dict, const char *name, ssize_t name_len, DICTIONARY_ITEM *master_item) {
751 if(unlikely(!api_is_name_good(dict, name, name_len)))
752 return NULL;
753
754 api_internal_check(dict, NULL, false, true);
755
756 if(unlikely(is_master_dictionary(dict)))
757 fatal("DICTIONARY: this dictionary is a master, you cannot add items from other dictionaries.");
758
759 garbage_collect_pending_deletes(dict);
760
761 dictionary_acquired_item_dup(dict->master, master_item);
762 DICTIONARY_ITEM *item = dict_item_add_or_reset_value_and_acquire(dict, name, name_len, NULL, 0, NULL, master_item);
763 dictionary_acquired_item_release(dict->master, master_item);
764
765 api_internal_check(dict, item, false, false);
766 return item;
767 }
768
769 void *dictionary_view_set_advanced(DICTIONARY *dict, const char *name, ssize_t name_len, DICTIONARY_ITEM *master_item) {
770 DICTIONARY_ITEM *item = dictionary_view_set_and_acquire_item_advanced(dict, name, name_len, master_item);
771
772 if(likely(item)) {
773 void *v = item->shared->value;
774 item_release(dict, item);
775 return v;
776 }
777
778 return NULL;
779 }
780
781 // ----------------------------------------------------------------------------
782 // GET an item from the dictionary
783
784 DICT_ITEM_CONST DICTIONARY_ITEM *dictionary_get_and_acquire_item_advanced(DICTIONARY *dict, const char *name, ssize_t name_len) {
785 if(unlikely(!api_is_name_good(dict, name, name_len)))
786 return NULL;
787
788 api_internal_check(dict, NULL, false, true);
789 DICTIONARY_ITEM *item = dict_item_find_and_acquire(dict, name, name_len);
790 api_internal_check(dict, item, false, true);
791 return item;
792 }
793
794 void *dictionary_get_advanced(DICTIONARY *dict, const char *name, ssize_t name_len) {
795 DICTIONARY_ITEM *item = dictionary_get_and_acquire_item_advanced(dict, name, name_len);
796
797 if(likely(item)) {
798 void *v = item->shared->value;
799 item_release(dict, item);
800 return v;
801 }
802
803 return NULL;
804 }
805
806 // ----------------------------------------------------------------------------
807 // DUP/REL an item (increase/decrease its reference counter)
808
809 ALWAYS_INLINE
810 DICT_ITEM_CONST DICTIONARY_ITEM *dictionary_acquired_item_dup(DICTIONARY *dict, DICT_ITEM_CONST DICTIONARY_ITEM *item) {
811 // we allow the item to be NULL here
812 api_internal_check(dict, item, false, true);
813
814 if(likely(item)) {
815 item_acquire(dict, item);
816 api_internal_check(dict, item, false, false);
817 }
818
819 return item;
820 }
821
822 ALWAYS_INLINE
823 void dictionary_acquired_item_release(DICTIONARY *dict, DICT_ITEM_CONST DICTIONARY_ITEM *item) {
824 // we allow the item to be NULL here
825 api_internal_check(dict, item, false, true);
826
827 // no need to get a lock here
828 // we pass the last parameter to reference_counter_release() as true
829 // so that the release may get a write-lock if required to clean up
830
831 if(likely(item))
832 item_release(dict, item);
833 }
834
835 // ----------------------------------------------------------------------------
836 // get the name/value of an item
837
838 ALWAYS_INLINE
839 const char *dictionary_acquired_item_name(DICT_ITEM_CONST DICTIONARY_ITEM *item) {
840 return item_get_name(item);
841 }
842
843 ALWAYS_INLINE
844 void *dictionary_acquired_item_value(DICT_ITEM_CONST DICTIONARY_ITEM *item) {
845 if(likely(item))
846 return item->shared->value;
847
848 return NULL;
849 }
850
851 size_t dictionary_acquired_item_references(DICT_ITEM_CONST DICTIONARY_ITEM *item) {
852 if(likely(item))
853 return DICTIONARY_ITEM_REFCOUNT_GET_SOLE(item);
854
855 return 0;
856 }
857
858 // ----------------------------------------------------------------------------
859 // DEL an item
860
861 bool dictionary_del_advanced(DICTIONARY *dict, const char *name, ssize_t name_len) {
862 if(unlikely(!api_is_name_good(dict, name, name_len)))
863 return false;
864
865 api_internal_check(dict, NULL, false, true);
866
867 if(unlikely(is_dictionary_destroyed(dict))) {
868 internal_error(true, "DICTIONARY: attempted to delete item on a destroyed dictionary");
869 return false;
870 }
871
872 return dict_item_del(dict, name, name_len);
873 }