@cryptotaxi247 / netdata-1 / commits / 43c4a8a83

split dictionary into multiple files (#16920)

* split dictionary into multiple files * rename to hashtable * dictionaries now support SIMPLE_HASHTABLE indexing, with user selection via a dictionary option

Costa Tsaousis committed Feb 3, 2024 at 20:09 UTC 43c4a8a83df4f223023de79069e82fcca5e25e7b
15 files changed +3733 -3472
CMakeLists.txt
+11
@@ -698,6 +698,17 @@ set(LIBNETDATA_FILES
698 src/libnetdata/config/dyncfg.h
699 src/libnetdata/json/json-c-parser-inline.h
700 src/libnetdata/template-enum.h
701 + src/libnetdata/dictionary/dictionary-internals.h
702 + src/libnetdata/dictionary/dictionary-unittest.c
703 + src/libnetdata/dictionary/thread-cache.c
704 + src/libnetdata/dictionary/thread-cache.h
705 + src/libnetdata/dictionary/dictionary-traversal.c
706 + src/libnetdata/dictionary/dictionary-statistics.h
707 + src/libnetdata/dictionary/dictionary-locks.h
708 + src/libnetdata/dictionary/dictionary-refcount.h
709 + src/libnetdata/dictionary/dictionary-hashtable.h
710 + src/libnetdata/dictionary/dictionary-item.h
711 + src/libnetdata/dictionary/dictionary-callbacks.h
712 )
713
714 if(ENABLE_PLUGIN_EBPF)
src/libnetdata/dictionary/dictionary-callbacks.h new
+93
@@ -0,0 +1,93 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_DICTIONARY_CALLBACKS_H
4 +#define NETDATA_DICTIONARY_CALLBACKS_H
5 +
6 +#include "dictionary-internals.h"
7 +
8 +// ----------------------------------------------------------------------------
9 +// callbacks execution
10 +
11 +static inline void dictionary_execute_insert_callback(DICTIONARY *dict, DICTIONARY_ITEM *item, void *constructor_data) {
12 + if(likely(!dict->hooks || !dict->hooks->insert_callback))
13 + return;
14 +
15 + if(unlikely(is_view_dictionary(dict)))
16 + fatal("DICTIONARY: called %s() on a view.", __FUNCTION__ );
17 +
18 + internal_error(false,
19 + "DICTIONARY: Running insert callback on item '%s' of dictionary created from %s() %zu@%s.",
20 + item_get_name(item),
21 + dict->creation_function,
22 + dict->creation_line,
23 + dict->creation_file);
24 +
25 + dict->hooks->insert_callback(item, item->shared->value, constructor_data?constructor_data:dict->hooks->insert_callback_data);
26 + DICTIONARY_STATS_CALLBACK_INSERTS_PLUS1(dict);
27 +}
28 +
29 +static inline bool dictionary_execute_conflict_callback(DICTIONARY *dict, DICTIONARY_ITEM *item, void *new_value, void *constructor_data) {
30 + if(likely(!dict->hooks || !dict->hooks->conflict_callback))
31 + return false;
32 +
33 + if(unlikely(is_view_dictionary(dict)))
34 + fatal("DICTIONARY: called %s() on a view.", __FUNCTION__ );
35 +
36 + internal_error(false,
37 + "DICTIONARY: Running conflict callback on item '%s' of dictionary created from %s() %zu@%s.",
38 + item_get_name(item),
39 + dict->creation_function,
40 + dict->creation_line,
41 + dict->creation_file);
42 +
43 + bool ret = dict->hooks->conflict_callback(
44 + item, item->shared->value, new_value,
45 + constructor_data ? constructor_data : dict->hooks->conflict_callback_data);
46 +
47 + DICTIONARY_STATS_CALLBACK_CONFLICTS_PLUS1(dict);
48 +
49 + return ret;
50 +}
51 +
52 +static inline void dictionary_execute_react_callback(DICTIONARY *dict, DICTIONARY_ITEM *item, void *constructor_data) {
53 + if(likely(!dict->hooks || !dict->hooks->react_callback))
54 + return;
55 +
56 + if(unlikely(is_view_dictionary(dict)))
57 + fatal("DICTIONARY: called %s() on a view.", __FUNCTION__ );
58 +
59 + internal_error(false,
60 + "DICTIONARY: Running react callback on item '%s' of dictionary created from %s() %zu@%s.",
61 + item_get_name(item),
62 + dict->creation_function,
63 + dict->creation_line,
64 + dict->creation_file);
65 +
66 + dict->hooks->react_callback(item, item->shared->value,
67 + constructor_data?constructor_data:dict->hooks->react_callback_data);
68 +
69 + DICTIONARY_STATS_CALLBACK_REACTS_PLUS1(dict);
70 +}
71 +
72 +static inline void dictionary_execute_delete_callback(DICTIONARY *dict, DICTIONARY_ITEM *item) {
73 + if(likely(!dict->hooks || !dict->hooks->delete_callback))
74 + return;
75 +
76 + // We may execute delete callback on items deleted from a view,
77 + // because we may have references to it, after the master is gone
78 + // so, the shared structure will remain until the last reference is released.
79 +
80 + internal_error(false,
81 + "DICTIONARY: Running delete callback on item '%s' of dictionary created from %s() %zu@%s.",
82 + item_get_name(item),
83 + dict->creation_function,
84 + dict->creation_line,
85 + dict->creation_file);
86 +
87 + dict->hooks->delete_callback(item, item->shared->value, dict->hooks->delelte_callback_data);
88 +
89 + DICTIONARY_STATS_CALLBACK_DELETES_PLUS1(dict);
90 +}
91 +
92 +
93 +#endif //NETDATA_DICTIONARY_CALLBACKS_H
src/libnetdata/dictionary/dictionary-hashtable.h new
+263
@@ -0,0 +1,263 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_DICTIONARY_HASHTABLE_H
4 +#define NETDATA_DICTIONARY_HASHTABLE_H
5 +
6 +#include "dictionary-internals.h"
7 +
8 +// ----------------------------------------------------------------------------
9 +// hashtable operations with simple hashtable
10 +
11 +static inline bool compare_keys(void *key1, void *key2) {
12 + const char *k1 = key1;
13 + const char *k2 = key2;
14 + return strcmp(k1, k2) == 0;
15 +}
16 +
17 +static inline void *item_to_key(DICTIONARY_ITEM *item) {
18 + return (void *)item_get_name(item);
19 +}
20 +
21 +#define SIMPLE_HASHTABLE_VALUE_TYPE DICTIONARY_ITEM
22 +#define SIMPLE_HASHTABLE_NAME _DICTIONARY
23 +#define SIMPLE_HASHTABLE_VALUE2KEY_FUNCTION item_to_key
24 +#define SIMPLE_HASHTABLE_COMPARE_KEYS_FUNCTION compare_keys
25 +#include "..//simple_hashtable.h"
26 +
27 +static inline size_t hashtable_init_hashtable(DICTIONARY *dict) {
28 + SIMPLE_HASHTABLE_DICTIONARY *ht = callocz(1, sizeof(*ht));
29 + simple_hashtable_init_DICTIONARY(ht, 4);
30 + dict->index.JudyHSArray = ht;
31 + return 0;
32 +}
33 +
34 +static inline size_t hashtable_destroy_hashtable(DICTIONARY *dict) {
35 + SIMPLE_HASHTABLE_DICTIONARY *ht = dict->index.JudyHSArray;
36 + if(unlikely(!ht)) return 0;
37 +
38 + size_t mem = sizeof(*ht) + ht->size * sizeof(SIMPLE_HASHTABLE_SLOT_DICTIONARY);
39 + simple_hashtable_destroy_DICTIONARY(ht);
40 + freez(ht);
41 + dict->index.JudyHSArray = NULL;
42 +
43 + return mem;
44 +}
45 +
46 +static inline void *hashtable_insert_hashtable(DICTIONARY *dict, const char *name, size_t name_len) {
47 + SIMPLE_HASHTABLE_DICTIONARY *ht = dict->index.JudyHSArray;
48 +
49 + char key[name_len+1];
50 + memcpy(key, name, name_len);
51 + key[name_len] = '\0';
52 +
53 + XXH64_hash_t hash = XXH3_64bits(name, name_len);
54 + SIMPLE_HASHTABLE_SLOT_DICTIONARY *sl = simple_hashtable_get_slot_DICTIONARY(ht, hash, key, true);
55 + sl->hash = hash; // we will need it in insert later - it is ok to overwrite - it is the same already
56 + return sl;
57 +}
58 +
59 +static inline DICTIONARY_ITEM *hashtable_insert_handle_to_item_hashtable(DICTIONARY *dict, void *handle) {
60 + (void)dict;
61 + SIMPLE_HASHTABLE_SLOT_DICTIONARY *sl = handle;
62 + DICTIONARY_ITEM *item = SIMPLE_HASHTABLE_SLOT_DATA(sl);
63 + return item;
64 +}
65 +
66 +static inline void hashtable_set_item_hashtable(DICTIONARY *dict, void *handle, DICTIONARY_ITEM *item) {
67 + SIMPLE_HASHTABLE_DICTIONARY *ht = dict->index.JudyHSArray;
68 + SIMPLE_HASHTABLE_SLOT_DICTIONARY *sl = handle;
69 + simple_hashtable_set_slot_DICTIONARY(ht, sl, sl->hash, item);
70 +}
71 +
72 +static inline int hashtable_delete_hashtable(DICTIONARY *dict, const char *name, size_t name_len, DICTIONARY_ITEM *item_to_delete) {
73 + (void)item_to_delete;
74 + SIMPLE_HASHTABLE_DICTIONARY *ht = dict->index.JudyHSArray;
75 +
76 + char key[name_len+1];
77 + memcpy(key, name, name_len);
78 + key[name_len] = '\0';
79 +
80 + XXH64_hash_t hash = XXH3_64bits(name, name_len);
81 + SIMPLE_HASHTABLE_SLOT_DICTIONARY *sl = simple_hashtable_get_slot_DICTIONARY(ht, hash, key, false);
82 + DICTIONARY_ITEM *item = SIMPLE_HASHTABLE_SLOT_DATA(sl);
83 + if(!item) return 0; // return not-found
84 +
85 + simple_hashtable_del_slot_DICTIONARY(ht, sl);
86 + return 1; // return deleted
87 +}
88 +
89 +static inline DICTIONARY_ITEM *hashtable_get_hashtable(DICTIONARY *dict, const char *name, size_t name_len) {
90 + SIMPLE_HASHTABLE_DICTIONARY *ht = dict->index.JudyHSArray;
91 + if(unlikely(!ht)) return NULL;
92 +
93 + char key[name_len+1];
94 + memcpy(key, name, name_len);
95 + key[name_len] = '\0';
96 +
97 + XXH64_hash_t hash = XXH3_64bits(name, name_len);
98 + SIMPLE_HASHTABLE_SLOT_DICTIONARY *sl = simple_hashtable_get_slot_DICTIONARY(ht, hash, key, false);
99 + return SIMPLE_HASHTABLE_SLOT_DATA(sl);
100 +}
101 +
102 +// ----------------------------------------------------------------------------
103 +// hashtable operations with Judy
104 +
105 +static inline size_t hashtable_init_judy(DICTIONARY *dict) {
106 + dict->index.JudyHSArray = NULL;
107 + return 0;
108 +}
109 +
110 +static inline size_t hashtable_destroy_judy(DICTIONARY *dict) {
111 + if(unlikely(!dict->index.JudyHSArray)) return 0;
112 +
113 + pointer_destroy_index(dict);
114 +
115 + JError_t J_Error;
116 + Word_t ret = JudyHSFreeArray(&dict->index.JudyHSArray, &J_Error);
117 + if(unlikely(ret == (Word_t) JERR)) {
118 + netdata_log_error("DICTIONARY: Cannot destroy JudyHS, JU_ERRNO_* == %u, ID == %d",
119 + JU_ERRNO(&J_Error), JU_ERRID(&J_Error));
120 + }
121 +
122 + netdata_log_debug(D_DICTIONARY, "Dictionary: hash table freed %lu bytes", ret);
123 +
124 + dict->index.JudyHSArray = NULL;
125 + return (size_t)ret;
126 +}
127 +
128 +static inline void *hashtable_insert_judy(DICTIONARY *dict, const char *name, size_t name_len) {
129 + JError_t J_Error;
130 + Pvoid_t *Rc = JudyHSIns(&dict->index.JudyHSArray, (void *)name, name_len, &J_Error);
131 + if (unlikely(Rc == PJERR)) {
132 + netdata_log_error("DICTIONARY: Cannot insert entry with name '%s' to JudyHS, JU_ERRNO_* == %u, ID == %d",
133 + name, JU_ERRNO(&J_Error), JU_ERRID(&J_Error));
134 + }
135 +
136 + // if *Rc == 0, new item added to the array
137 + // otherwise the existing item value is returned in *Rc
138 +
139 + // we return a pointer to a pointer, so that the caller can
140 + // put anything needed at the value of the index.
141 + // The pointer to pointer we return has to be used before
142 + // any other operation that may change the index (insert/delete).
143 + return (void *)Rc;
144 +}
145 +
146 +static inline DICTIONARY_ITEM *hashtable_insert_handle_to_item_judy(DICTIONARY *dict, void *handle) {
147 + (void)dict;
148 + DICTIONARY_ITEM **item_pptr = handle;
149 + return *item_pptr;
150 +}
151 +
152 +static inline void hashtable_set_item_judy(DICTIONARY *dict, void *handle, DICTIONARY_ITEM *item) {
153 + (void)dict;
154 + DICTIONARY_ITEM **item_pptr = handle;
155 + *item_pptr = item;
156 +}
157 +
158 +static inline int hashtable_delete_judy(DICTIONARY *dict, const char *name, size_t name_len, DICTIONARY_ITEM *item) {
159 + (void)item;
160 + if(unlikely(!dict->index.JudyHSArray)) return 0;
161 +
162 + JError_t J_Error;
163 + int ret = JudyHSDel(&dict->index.JudyHSArray, (void *)name, name_len, &J_Error);
164 + if(unlikely(ret == JERR)) {
165 + netdata_log_error("DICTIONARY: Cannot delete entry with name '%s' from JudyHS, JU_ERRNO_* == %u, ID == %d",
166 + name,
167 + JU_ERRNO(&J_Error), JU_ERRID(&J_Error));
168 + return 0;
169 + }
170 +
171 + // Hey, this is problematic! We need the value back, not just an int with a status!
172 + // https://sourceforge.net/p/judy/feature-requests/23/
173 +
174 + if(unlikely(ret == 0)) {
175 + // not found in the dictionary
176 + return 0;
177 + }
178 + else {
179 + // found and deleted from the dictionary
180 + return 1;
181 + }
182 +}
183 +
184 +static inline DICTIONARY_ITEM *hashtable_get_judy(DICTIONARY *dict, const char *name, size_t name_len) {
185 + if(unlikely(!dict->index.JudyHSArray)) return NULL;
186 +
187 + Pvoid_t *Rc;
188 + Rc = JudyHSGet(dict->index.JudyHSArray, (void *)name, name_len);
189 + if(likely(Rc)) {
190 + // found in the hash table
191 + pointer_check(dict, (DICTIONARY_ITEM *)*Rc);
192 + return (DICTIONARY_ITEM *)*Rc;
193 + }
194 + else {
195 + // not found in the hash table
196 + return NULL;
197 + }
198 +}
199 +
200 +// --------------------------------------------------------------------------------------------------------------------
201 +// select the right hashtable
202 +
203 +static inline size_t hashtable_init_unsafe(DICTIONARY *dict) {
204 + if(dict->options & DICT_OPTION_INDEX_JUDY)
205 + return hashtable_init_judy(dict);
206 + else
207 + return hashtable_init_hashtable(dict);
208 +}
209 +
210 +static inline size_t hashtable_destroy_unsafe(DICTIONARY *dict) {
211 + pointer_destroy_index(dict);
212 +
213 + if(dict->options & DICT_OPTION_INDEX_JUDY)
214 + return hashtable_destroy_judy(dict);
215 + else
216 + return hashtable_destroy_hashtable(dict);
217 +}
218 +
219 +static inline void *hashtable_insert_unsafe(DICTIONARY *dict, const char *name, size_t name_len) {
220 + if(dict->options & DICT_OPTION_INDEX_JUDY)
221 + return hashtable_insert_judy(dict, name, name_len);
222 + else
223 + return hashtable_insert_hashtable(dict, name, name_len);
224 +}
225 +
226 +static inline DICTIONARY_ITEM *hashtable_insert_handle_to_item_unsafe(DICTIONARY *dict, void *handle) {
227 + if(dict->options & DICT_OPTION_INDEX_JUDY)
228 + return hashtable_insert_handle_to_item_judy(dict, handle);
229 + else
230 + return hashtable_insert_handle_to_item_hashtable(dict, handle);
231 +}
232 +
233 +static inline int hashtable_delete_unsafe(DICTIONARY *dict, const char *name, size_t name_len, DICTIONARY_ITEM *item) {
234 + if(dict->options & DICT_OPTION_INDEX_JUDY)
235 + return hashtable_delete_judy(dict, name, name_len, item);
236 + else
237 + return hashtable_delete_hashtable(dict, name, name_len, item);
238 +}
239 +
240 +static inline DICTIONARY_ITEM *hashtable_get_unsafe(DICTIONARY *dict, const char *name, size_t name_len) {
241 + DICTIONARY_STATS_SEARCHES_PLUS1(dict);
242 +
243 + DICTIONARY_ITEM *item;
244 +
245 + if(dict->options & DICT_OPTION_INDEX_JUDY)
246 + item = hashtable_get_judy(dict, name, name_len);
247 + else
248 + item = hashtable_get_hashtable(dict, name, name_len);
249 +
250 + if(item)
251 + pointer_check(dict, item);
252 +
253 + return item;
254 +}
255 +
256 +static inline void hashtable_set_item_unsafe(DICTIONARY *dict, void *handle, DICTIONARY_ITEM *item) {
257 + if(dict->options & DICT_OPTION_INDEX_JUDY)
258 + hashtable_set_item_judy(dict, handle, item);
259 + else
260 + hashtable_set_item_hashtable(dict, handle, item);
261 +}
262 +
263 +#endif //NETDATA_DICTIONARY_HASHTABLE_H
src/libnetdata/dictionary/dictionary-internals.h new
+259
@@ -0,0 +1,259 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_DICTIONARY_INTERNALS_H
4 +#define NETDATA_DICTIONARY_INTERNALS_H
5 +
6 +#define DICTIONARY_INTERNALS
7 +#include "../libnetdata.h"
8 +
9 +// runtime flags of the dictionary - must be checked with atomics
10 +typedef enum __attribute__ ((__packed__)) {
11 + DICT_FLAG_NONE = 0,
12 + DICT_FLAG_DESTROYED = (1 << 0), // this dictionary has been destroyed
13 +} DICT_FLAGS;
14 +
15 +#define dict_flag_check(dict, flag) (__atomic_load_n(&((dict)->flags), __ATOMIC_RELAXED) & (flag))
16 +#define dict_flag_set(dict, flag) __atomic_or_fetch(&((dict)->flags), flag, __ATOMIC_RELAXED)
17 +#define dict_flag_clear(dict, flag) __atomic_and_fetch(&((dict)->flags), ~(flag), __ATOMIC_RELAXED)
18 +
19 +// flags macros
20 +#define is_dictionary_destroyed(dict) dict_flag_check(dict, DICT_FLAG_DESTROYED)
21 +
22 +// configuration options macros
23 +#define is_dictionary_single_threaded(dict) ((dict)->options & DICT_OPTION_SINGLE_THREADED)
24 +#define is_view_dictionary(dict) ((dict)->master)
25 +#define is_master_dictionary(dict) (!is_view_dictionary(dict))
26 +
27 +typedef enum __attribute__ ((__packed__)) item_options {
28 + ITEM_OPTION_NONE = 0,
29 + ITEM_OPTION_ALLOCATED_NAME = (1 << 0), // the name pointer is a STRING
30 +
31 + // IMPORTANT: This is 1-bit - to add more change ITEM_OPTIONS_BITS
32 +} ITEM_OPTIONS;
33 +
34 +typedef enum __attribute__ ((__packed__)) item_flags {
35 + ITEM_FLAG_NONE = 0,
36 + ITEM_FLAG_DELETED = (1 << 0), // this item is marked deleted, so it is not available for traversal (deleted from the index too)
37 + ITEM_FLAG_BEING_CREATED = (1 << 1), // this item is currently being created - this flag is removed when construction finishes
38 +
39 + // IMPORTANT: This is 8-bit
40 +} ITEM_FLAGS;
41 +
42 +#define item_flag_check(item, flag) (__atomic_load_n(&((item)->flags), __ATOMIC_RELAXED) & (flag))
43 +#define item_flag_set(item, flag) __atomic_or_fetch(&((item)->flags), flag, __ATOMIC_RELAXED)
44 +#define item_flag_clear(item, flag) __atomic_and_fetch(&((item)->flags), ~(flag), __ATOMIC_RELAXED)
45 +
46 +#define item_shared_flag_check(item, flag) (__atomic_load_n(&((item)->shared->flags), __ATOMIC_RELAXED) & (flag))
47 +#define item_shared_flag_set(item, flag) __atomic_or_fetch(&((item)->shared->flags), flag, __ATOMIC_RELAXED)
48 +#define item_shared_flag_clear(item, flag) __atomic_and_fetch(&((item)->shared->flags), ~(flag), __ATOMIC_RELAXED)
49 +
50 +#define REFCOUNT_DELETING (-100)
51 +
52 +#define ITEM_FLAGS_TYPE uint8_t
53 +#define KEY_LEN_TYPE uint32_t
54 +#define VALUE_LEN_TYPE uint32_t
55 +
56 +#define ITEM_OPTIONS_BITS 1
57 +#define KEY_LEN_BITS ((sizeof(KEY_LEN_TYPE) * 8) - (sizeof(ITEM_FLAGS_TYPE) * 8) - ITEM_OPTIONS_BITS)
58 +#define KEY_LEN_MAX ((1 << KEY_LEN_BITS) - 1)
59 +
60 +#define VALUE_LEN_BITS ((sizeof(VALUE_LEN_TYPE) * 8) - (sizeof(ITEM_FLAGS_TYPE) * 8))
61 +#define VALUE_LEN_MAX ((1 << VALUE_LEN_BITS) - 1)
62 +
63 +
64 +/*
65 + * Every item in the dictionary has the following structure.
66 + */
67 +
68 +typedef int32_t REFCOUNT;
69 +
70 +typedef struct dictionary_item_shared {
71 + void *value; // the value of the dictionary item
72 +
73 + // the order of the following items is important!
74 + // The total of their storage should be 64-bits
75 +
76 + REFCOUNT links; // how many links this item has
77 + VALUE_LEN_TYPE value_len:VALUE_LEN_BITS; // the size of the value
78 + ITEM_FLAGS_TYPE flags; // shared flags
79 +} DICTIONARY_ITEM_SHARED;
80 +
81 +struct dictionary_item {
82 +#ifdef NETDATA_INTERNAL_CHECKS
83 + DICTIONARY *dict;
84 + pid_t creator_pid;
85 + pid_t deleter_pid;
86 + pid_t ll_adder_pid;
87 + pid_t ll_remover_pid;
88 +#endif
89 +
90 + DICTIONARY_ITEM_SHARED *shared;
91 +
92 + struct dictionary_item *next; // a double linked list to allow fast insertions and deletions
93 + struct dictionary_item *prev;
94 +
95 + union {
96 + STRING *string_name; // the name of the dictionary item
97 + char *caller_name; // the user supplied string pointer
98 + // void *key_ptr; // binary key pointer
99 + };
100 +
101 + // the order of the following items is important!
102 + // The total of their storage should be 64-bits
103 +
104 + REFCOUNT refcount; // the private reference counter
105 +
106 + KEY_LEN_TYPE key_len:KEY_LEN_BITS; // the size of key indexed (for strings, including the null terminator)
107 + // this is (2^23 - 1) = 8.388.607 bytes max key length.
108 +
109 + ITEM_OPTIONS options:ITEM_OPTIONS_BITS; // permanent configuration options
110 + // (no atomic operations on this - they never change)
111 +
112 + ITEM_FLAGS_TYPE flags; // runtime changing flags for this item (atomic operations on this)
113 + // cannot be a bit field because of atomics.
114 +};
115 +
116 +struct dictionary_hooks {
117 + REFCOUNT links;
118 + usec_t last_master_deletion_us;
119 +
120 + dict_cb_insert_t insert_callback;
121 + void *insert_callback_data;
122 +
123 + dict_cb_conflict_t conflict_callback;
124 + void *conflict_callback_data;
125 +
126 + dict_cb_react_t react_callback;
127 + void *react_callback_data;
128 +
129 + dict_cb_delete_t delete_callback;
130 + void *delelte_callback_data;
131 +};
132 +
133 +struct dictionary {
134 +#ifdef NETDATA_INTERNAL_CHECKS
135 + const char *creation_function;
136 + const char *creation_file;
137 + size_t creation_line;
138 + pid_t creation_tid;
139 +#endif
140 +
141 + usec_t last_gc_run_us;
142 + DICT_OPTIONS options; // the configuration flags of the dictionary (they never change - no atomics)
143 + DICT_FLAGS flags; // run time flags for the dictionary (they change all the time - atomics needed)
144 +
145 + ARAL *value_aral;
146 +
147 + struct { // support for multiple indexing engines
148 + Pvoid_t JudyHSArray; // the hash table
149 + RW_SPINLOCK rw_spinlock; // protect the index
150 + } index;
151 +
152 + struct {
153 + DICTIONARY_ITEM *list; // the double linked list of all items in the dictionary
154 + RW_SPINLOCK rw_spinlock; // protect the linked-list
155 + pid_t writer_pid; // the gettid() of the writer
156 + uint32_t writer_depth; // nesting of write locks
157 + } items;
158 +
159 + struct dictionary_hooks *hooks; // pointer to external function callbacks to be called at certain points
160 + struct dictionary_stats *stats; // statistics data, when DICT_OPTION_STATS is set
161 +
162 + DICTIONARY *master; // the master dictionary
163 + DICTIONARY *next; // linked list for delayed destruction (garbage collection of whole dictionaries)
164 +
165 + uint32_t version; // the current version of the dictionary
166 + // it is incremented when:
167 + // - item added
168 + // - item removed
169 + // - item value reset
170 + // - conflict callback returns true
171 + // - function dictionary_version_increment() is called
172 +
173 + int32_t entries; // how many items are currently in the index (the linked list may have more)
174 + int32_t referenced_items; // how many items of the dictionary are currently being used by 3rd parties
175 + int32_t pending_deletion_items; // how many items of the dictionary have been deleted, but have not been removed yet
176 +
177 +#ifdef NETDATA_DICTIONARY_VALIDATE_POINTERS
178 + netdata_mutex_t global_pointer_registry_mutex;
179 + Pvoid_t global_pointer_registry;
180 +#endif
181 +};
182 +
183 +// ----------------------------------------------------------------------------
184 +// forward definitions of functions used in reverse order in the code
185 +
186 +void garbage_collect_pending_deletes(DICTIONARY *dict);
187 +static inline void item_linked_list_remove(DICTIONARY *dict, DICTIONARY_ITEM *item);
188 +static size_t dict_item_free_with_hooks(DICTIONARY *dict, DICTIONARY_ITEM *item);
189 +static inline const char *item_get_name(const DICTIONARY_ITEM *item);
190 +static inline int hashtable_delete_unsafe(DICTIONARY *dict, const char *name, size_t name_len, DICTIONARY_ITEM *item);
191 +static void item_release(DICTIONARY *dict, DICTIONARY_ITEM *item);
192 +static bool dict_item_set_deleted(DICTIONARY *dict, DICTIONARY_ITEM *item);
193 +
194 +#define RC_ITEM_OK ( 0)
195 +#define RC_ITEM_MARKED_FOR_DELETION (-1) // the item is marked for deletion
196 +#define RC_ITEM_IS_CURRENTLY_BEING_DELETED (-2) // the item is currently being deleted
197 +#define RC_ITEM_IS_CURRENTLY_BEING_CREATED (-3) // the item is currently being deleted
198 +#define RC_ITEM_IS_REFERENCED (-4) // the item is currently referenced
199 +#define item_check_and_acquire(dict, item) (item_check_and_acquire_advanced(dict, item, false) == RC_ITEM_OK)
200 +static int item_check_and_acquire_advanced(DICTIONARY *dict, DICTIONARY_ITEM *item, bool having_index_lock);
201 +#define item_is_not_referenced_and_can_be_removed(dict, item) (item_is_not_referenced_and_can_be_removed_advanced(dict, item) == RC_ITEM_OK)
202 +static inline int item_is_not_referenced_and_can_be_removed_advanced(DICTIONARY *dict, DICTIONARY_ITEM *item);
203 +
204 +// ----------------------------------------------------------------------------
205 +// validate each pointer is indexed once - internal checks only
206 +
207 +#ifdef NETDATA_DICTIONARY_VALIDATE_POINTERS
208 +static inline void pointer_index_init(DICTIONARY *dict __maybe_unused) {
209 + netdata_mutex_init(&dict->global_pointer_registry_mutex);
210 +}
211 +
212 +static inline void pointer_destroy_index(DICTIONARY *dict __maybe_unused) {
213 + netdata_mutex_lock(&dict->global_pointer_registry_mutex);
214 + JudyHSFreeArray(&dict->global_pointer_registry, PJE0);
215 + netdata_mutex_unlock(&dict->global_pointer_registry_mutex);
216 +}
217 +static inline void pointer_add(DICTIONARY *dict __maybe_unused, DICTIONARY_ITEM *item __maybe_unused) {
218 + netdata_mutex_lock(&dict->global_pointer_registry_mutex);
219 + Pvoid_t *PValue = JudyHSIns(&dict->global_pointer_registry, &item, sizeof(void *), PJE0);
220 + if(*PValue != NULL)
221 + fatal("pointer already exists in registry");
222 + *PValue = item;
223 + netdata_mutex_unlock(&dict->global_pointer_registry_mutex);
224 +}
225 +
226 +static inline void pointer_check(DICTIONARY *dict __maybe_unused, DICTIONARY_ITEM *item __maybe_unused) {
227 + netdata_mutex_lock(&dict->global_pointer_registry_mutex);
228 + Pvoid_t *PValue = JudyHSGet(dict->global_pointer_registry, &item, sizeof(void *));
229 + if(PValue == NULL)
230 + fatal("pointer is not found in registry");
231 + netdata_mutex_unlock(&dict->global_pointer_registry_mutex);
232 +}
233 +
234 +static inline void pointer_del(DICTIONARY *dict __maybe_unused, DICTIONARY_ITEM *item __maybe_unused) {
235 + netdata_mutex_lock(&dict->global_pointer_registry_mutex);
236 + int ret = JudyHSDel(&dict->global_pointer_registry, &item, sizeof(void *), PJE0);
237 + if(!ret)
238 + fatal("pointer to be deleted does not exist in registry");
239 + netdata_mutex_unlock(&dict->global_pointer_registry_mutex);
240 +}
241 +#else // !NETDATA_DICTIONARY_VALIDATE_POINTERS
242 +#define pointer_index_init(dict) debug_dummy()
243 +#define pointer_destroy_index(dict) debug_dummy()
244 +#define pointer_add(dict, item) debug_dummy()
245 +#define pointer_check(dict, item) debug_dummy()
246 +#define pointer_del(dict, item) debug_dummy()
247 +#endif // !NETDATA_DICTIONARY_VALIDATE_POINTERS
248 +
249 +extern ARAL *dict_items_aral;
250 +extern ARAL *dict_shared_items_aral;
251 +
252 +#include "dictionary-statistics.h"
253 +#include "dictionary-locks.h"
254 +#include "dictionary-refcount.h"
255 +#include "dictionary-hashtable.h"
256 +#include "dictionary-callbacks.h"
257 +#include "dictionary-item.h"
258 +
259 +#endif //NETDATA_DICTIONARY_INTERNALS_H
src/libnetdata/dictionary/dictionary-item.h new
+555
@@ -0,0 +1,555 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_DICTIONARY_ITEM_H
4 +#define NETDATA_DICTIONARY_ITEM_H
5 +
6 +#include "dictionary-internals.h"
7 +
8 +// ----------------------------------------------------------------------------
9 +// ITEM initialization and updates
10 +
11 +static inline size_t item_set_name(DICTIONARY *dict, DICTIONARY_ITEM *item, const char *name, size_t name_len) {
12 + if(likely(dict->options & DICT_OPTION_NAME_LINK_DONT_CLONE)) {
13 + item->caller_name = (char *)name;
14 + item->key_len = name_len;
15 + }
16 + else {
17 + item->string_name = string_strdupz(name);
18 + item->key_len = string_strlen(item->string_name);
19 + item->options |= ITEM_OPTION_ALLOCATED_NAME;
20 + }
21 +
22 + return item->key_len;
23 +}
24 +
25 +static inline size_t item_free_name(DICTIONARY *dict, DICTIONARY_ITEM *item) {
26 + if(likely(!(dict->options & DICT_OPTION_NAME_LINK_DONT_CLONE)))
27 + string_freez(item->string_name);
28 +
29 + return item->key_len;
30 +}
31 +
32 +static inline const char *item_get_name(const DICTIONARY_ITEM *item) {
33 + if(item->options & ITEM_OPTION_ALLOCATED_NAME)
34 + return string2str(item->string_name);
35 + else
36 + return item->caller_name;
37 +}
38 +
39 +static inline size_t item_get_name_len(const DICTIONARY_ITEM *item) {
40 + if(item->options & ITEM_OPTION_ALLOCATED_NAME)
41 + return string_strlen(item->string_name);
42 + else
43 + return strlen(item->caller_name);
44 +}
45 +
46 +// ----------------------------------------------------------------------------
47 +
48 +static inline DICTIONARY_ITEM *dict_item_create(DICTIONARY *dict __maybe_unused, size_t *allocated_bytes, DICTIONARY_ITEM *master_item) {
49 + DICTIONARY_ITEM *item;
50 +
51 + size_t size = sizeof(DICTIONARY_ITEM);
52 + item = aral_mallocz(dict_items_aral);
53 + memset(item, 0, sizeof(DICTIONARY_ITEM));
54 +
55 +#ifdef NETDATA_INTERNAL_CHECKS
56 + item->creator_pid = gettid();
57 +#endif
58 +
59 + item->refcount = 1;
60 + item->flags = ITEM_FLAG_BEING_CREATED;
61 +
62 + *allocated_bytes += size;
63 +
64 + if(master_item) {
65 + item->shared = master_item->shared;
66 +
67 + if(unlikely(__atomic_add_fetch(&item->shared->links, 1, __ATOMIC_ACQUIRE) <= 1))
68 + fatal("DICTIONARY: attempted to link to a shared item structure that had zero references");
69 + }
70 + else {
71 + size = sizeof(DICTIONARY_ITEM_SHARED);
72 + item->shared = aral_mallocz(dict_shared_items_aral);
73 + memset(item->shared, 0, sizeof(DICTIONARY_ITEM_SHARED));
74 +
75 + item->shared->links = 1;
76 + *allocated_bytes += size;
77 + }
78 +
79 +#ifdef NETDATA_INTERNAL_CHECKS
80 + item->dict = dict;
81 +#endif
82 + return item;
83 +}
84 +
85 +static inline void *dict_item_value_mallocz(DICTIONARY *dict, size_t value_len) {
86 + if(dict->value_aral) {
87 + internal_fatal(aral_element_size(dict->value_aral) != value_len,
88 + "DICTIONARY: item value size %zu does not match the configured fixed one %zu",
89 + value_len, aral_element_size(dict->value_aral));
90 + return aral_mallocz(dict->value_aral);
91 + }
92 + else
93 + return mallocz(value_len);
94 +}
95 +
96 +static inline void dict_item_value_freez(DICTIONARY *dict, void *ptr) {
97 + if(dict->value_aral)
98 + aral_freez(dict->value_aral, ptr);
99 + else
100 + freez(ptr);
101 +}
102 +
103 +static inline void *dict_item_value_create(DICTIONARY *dict, void *value, size_t value_len) {
104 + void *ptr = NULL;
105 +
106 + if(likely(value_len)) {
107 + if (likely(value)) {
108 + // a value has been supplied
109 + // copy it
110 + ptr = dict_item_value_mallocz(dict, value_len);
111 + memcpy(ptr, value, value_len);
112 + }
113 + else {
114 + // no value has been supplied
115 + // allocate a clear memory block
116 + ptr = dict_item_value_mallocz(dict, value_len);
117 + memset(ptr, 0, value_len);
118 + }
119 + }
120 + // else
121 + // the caller wants an item without any value
122 +
123 + return ptr;
124 +}
125 +
126 +static inline DICTIONARY_ITEM *dict_item_create_with_hooks(DICTIONARY *dict, const char *name, size_t name_len, void *value, size_t value_len, void *constructor_data, DICTIONARY_ITEM *master_item) {
127 +#ifdef NETDATA_INTERNAL_CHECKS
128 + if(unlikely(name_len > KEY_LEN_MAX))
129 + fatal("DICTIONARY: tried to index a key of size %zu, but the maximum acceptable is %zu", name_len, (size_t)KEY_LEN_MAX);
130 +
131 + if(unlikely(value_len > VALUE_LEN_MAX))
132 + fatal("DICTIONARY: tried to add an item of size %zu, but the maximum acceptable is %zu", value_len, (size_t)VALUE_LEN_MAX);
133 +#endif
134 +
135 + size_t item_size = 0, key_size = 0, value_size = 0;
136 +
137 + DICTIONARY_ITEM *item = dict_item_create(dict, &item_size, master_item);
138 + key_size += item_set_name(dict, item, name, name_len);
139 +
140 + if(unlikely(is_view_dictionary(dict))) {
141 + // we are on a view dictionary
142 + // do not touch the value
143 + ;
144 +
145 +#ifdef NETDATA_INTERNAL_CHECKS
146 + if(unlikely(!master_item))
147 + fatal("DICTIONARY: cannot add an item to a view without a master item.");
148 +#endif
149 + }
150 + else {
151 + // we are on the master dictionary
152 +
153 + if(unlikely(dict->options & DICT_OPTION_VALUE_LINK_DONT_CLONE))
154 + item->shared->value = value;
155 + else
156 + item->shared->value = dict_item_value_create(dict, value, value_len);
157 +
158 + item->shared->value_len = value_len;
159 + value_size += value_len;
160 +
161 + dictionary_execute_insert_callback(dict, item, constructor_data);
162 + }
163 +
164 + DICTIONARY_ENTRIES_PLUS1(dict);
165 + DICTIONARY_STATS_PLUS_MEMORY(dict, key_size, item_size, value_size);
166 +
167 + return item;
168 +}
169 +
170 +static inline void dict_item_reset_value_with_hooks(DICTIONARY *dict, DICTIONARY_ITEM *item, void *value, size_t value_len, void *constructor_data) {
171 + if(unlikely(is_view_dictionary(dict)))
172 + fatal("DICTIONARY: %s() should never be called on views.", __FUNCTION__ );
173 +
174 + netdata_log_debug(D_DICTIONARY, "Dictionary entry with name '%s' found. Changing its value.", item_get_name(item));
175 +
176 + DICTIONARY_VALUE_RESETS_PLUS1(dict);
177 +
178 + if(item->shared->value_len != value_len) {
179 + DICTIONARY_STATS_PLUS_MEMORY(dict, 0, 0, value_len);
180 + DICTIONARY_STATS_MINUS_MEMORY(dict, 0, 0, item->shared->value_len);
181 + }
182 +
183 + dictionary_execute_delete_callback(dict, item);
184 +
185 + if(likely(dict->options & DICT_OPTION_VALUE_LINK_DONT_CLONE)) {
186 + netdata_log_debug(D_DICTIONARY, "Dictionary: linking value to '%s'", item_get_name(item));
187 + item->shared->value = value;
188 + item->shared->value_len = value_len;
189 + }
190 + else {
191 + netdata_log_debug(D_DICTIONARY, "Dictionary: cloning value to '%s'", item_get_name(item));
192 +
193 + void *old_value = item->shared->value;
194 + void *new_value = NULL;
195 + if(value_len) {
196 + new_value = dict_item_value_mallocz(dict, value_len);
197 + if(value) memcpy(new_value, value, value_len);
198 + else memset(new_value, 0, value_len);
199 + }
200 + item->shared->value = new_value;
201 + item->shared->value_len = value_len;
202 +
203 + netdata_log_debug(D_DICTIONARY, "Dictionary: freeing old value of '%s'", item_get_name(item));
204 + dict_item_value_freez(dict, old_value);
205 + }
206 +
207 + dictionary_execute_insert_callback(dict, item, constructor_data);
208 +}
209 +
210 +static inline size_t dict_item_free_with_hooks(DICTIONARY *dict, DICTIONARY_ITEM *item) {
211 + netdata_log_debug(D_DICTIONARY, "Destroying name value entry for name '%s'.", item_get_name(item));
212 +
213 + if(!item_flag_check(item, ITEM_FLAG_DELETED))
214 + DICTIONARY_ENTRIES_MINUS1(dict);
215 +
216 + size_t item_size = 0, key_size = 0, value_size = 0;
217 +
218 + key_size += item->key_len;
219 + if(unlikely(!(dict->options & DICT_OPTION_NAME_LINK_DONT_CLONE)))
220 + item_free_name(dict, item);
221 +
222 + if(item_shared_release_and_check_if_it_can_be_freed(dict, item)) {
223 + dictionary_execute_delete_callback(dict, item);
224 +
225 + if(unlikely(!(dict->options & DICT_OPTION_VALUE_LINK_DONT_CLONE))) {
226 + netdata_log_debug(D_DICTIONARY, "Dictionary freeing value of '%s'", item_get_name(item));
227 + dict_item_value_freez(dict, item->shared->value);
228 + item->shared->value = NULL;
229 + }
230 + value_size += item->shared->value_len;
231 +
232 + aral_freez(dict_shared_items_aral, item->shared);
233 + item->shared = NULL;
234 + item_size += sizeof(DICTIONARY_ITEM_SHARED);
235 + }
236 +
237 + aral_freez(dict_items_aral, item);
238 +
239 + item_size += sizeof(DICTIONARY_ITEM);
240 +
241 + DICTIONARY_STATS_MINUS_MEMORY(dict, key_size, item_size, value_size);
242 +
243 + // we return the memory we actually freed
244 + return item_size + ((dict->options & DICT_OPTION_VALUE_LINK_DONT_CLONE) ? 0 : value_size);
245 +}
246 +
247 +// ----------------------------------------------------------------------------
248 +// linked list management
249 +
250 +static inline void item_linked_list_add(DICTIONARY *dict, DICTIONARY_ITEM *item) {
251 + ll_recursive_lock(dict, DICTIONARY_LOCK_WRITE);
252 +
253 + if(dict->options & DICT_OPTION_ADD_IN_FRONT)
254 + DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(dict->items.list, item, prev, next);
255 + else
256 + DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(dict->items.list, item, prev, next);
257 +
258 +#ifdef NETDATA_INTERNAL_CHECKS
259 + item->ll_adder_pid = gettid();
260 +#endif
261 +
262 + // clear the BEING created flag,
263 + // after it has been inserted into the linked list
264 + item_flag_clear(item, ITEM_FLAG_BEING_CREATED);
265 +
266 + garbage_collect_pending_deletes(dict);
267 + ll_recursive_unlock(dict, DICTIONARY_LOCK_WRITE);
268 +}
269 +
270 +static inline void item_linked_list_remove(DICTIONARY *dict, DICTIONARY_ITEM *item) {
271 + ll_recursive_lock(dict, DICTIONARY_LOCK_WRITE);
272 +
273 + DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(dict->items.list, item, prev, next);
274 +
275 +#ifdef NETDATA_INTERNAL_CHECKS
276 + item->ll_remover_pid = gettid();
277 +#endif
278 +
279 + garbage_collect_pending_deletes(dict);
280 + ll_recursive_unlock(dict, DICTIONARY_LOCK_WRITE);
281 +}
282 +
283 +// ----------------------------------------------------------------------------
284 +// item operations
285 +
286 +static inline void dict_item_shared_set_deleted(DICTIONARY *dict, DICTIONARY_ITEM *item) {
287 + if(is_master_dictionary(dict)) {
288 + item_shared_flag_set(item, ITEM_FLAG_DELETED);
289 +
290 + if(dict->hooks)
291 + __atomic_store_n(&dict->hooks->last_master_deletion_us, now_realtime_usec(), __ATOMIC_RELAXED);
292 + }
293 +}
294 +
295 +// returns true if we set the deleted flag on this item
296 +static inline bool dict_item_set_deleted(DICTIONARY *dict, DICTIONARY_ITEM *item) {
297 + ITEM_FLAGS expected, desired;
298 +
299 + expected = __atomic_load_n(&item->flags, __ATOMIC_RELAXED);
300 +
301 + do {
302 +
303 + if (expected & ITEM_FLAG_DELETED)
304 + return false;
305 +
306 + desired = expected | ITEM_FLAG_DELETED;
307 +
308 + } while(!__atomic_compare_exchange_n(&item->flags, &expected, desired, false, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED));
309 +
310 + DICTIONARY_ENTRIES_MINUS1(dict);
311 + return true;
312 +}
313 +
314 +static inline void dict_item_free_or_mark_deleted(DICTIONARY *dict, DICTIONARY_ITEM *item) {
315 + int rc = item_is_not_referenced_and_can_be_removed_advanced(dict, item);
316 + switch(rc) {
317 + case RC_ITEM_OK:
318 + // the item is ours, refcount set to -100
319 + dict_item_shared_set_deleted(dict, item);
320 + item_linked_list_remove(dict, item);
321 + dict_item_free_with_hooks(dict, item);
322 + break;
323 +
324 + case RC_ITEM_IS_REFERENCED:
325 + case RC_ITEM_IS_CURRENTLY_BEING_CREATED:
326 + // the item is currently referenced by others
327 + dict_item_shared_set_deleted(dict, item);
328 + dict_item_set_deleted(dict, item);
329 + // after this point do not touch the item
330 + break;
331 +
332 + case RC_ITEM_IS_CURRENTLY_BEING_DELETED:
333 + // an item that is currently being deleted by someone else - don't touch it
334 + break;
335 +
336 + default:
337 + internal_error(true, "Hey dev! You forgot to add the new condition here!");
338 + break;
339 + }
340 +}
341 +
342 +// this is used by traversal functions to remove the current item
343 +// if it is deleted, and it has zero references. This will eliminate
344 +// the need for the garbage collector to kick-in later.
345 +// Most deletions happen during traversal, so this is a nice hack
346 +// to speed up everything!
347 +static inline void dict_item_release_and_check_if_it_is_deleted_and_can_be_removed_under_this_lock_mode(DICTIONARY *dict, DICTIONARY_ITEM *item, char rw) {
348 + if(rw == DICTIONARY_LOCK_WRITE) {
349 + bool should_be_deleted = item_flag_check(item, ITEM_FLAG_DELETED);
350 +
351 + item_release(dict, item);
352 +
353 + if(should_be_deleted && item_is_not_referenced_and_can_be_removed(dict, item)) {
354 + // this has to be before removing from the linked list,
355 + // otherwise the garbage collector will also kick in!
356 + DICTIONARY_PENDING_DELETES_MINUS1(dict);
357 +
358 + item_linked_list_remove(dict, item);
359 + dict_item_free_with_hooks(dict, item);
360 + }
361 + }
362 + else {
363 + // we can't do anything under this mode
364 + item_release(dict, item);
365 + }
366 +}
367 +
368 +static inline bool dict_item_del(DICTIONARY *dict, const char *name, ssize_t name_len) {
369 + if(name_len == -1)
370 + name_len = (ssize_t)strlen(name);
371 +
372 + netdata_log_debug(D_DICTIONARY, "DEL dictionary entry with name '%s'.", name);
373 +
374 + // Unfortunately, the JudyHSDel() does not return the value of the
375 + // item that was deleted, so we have to find it before we delete it,
376 + // since we need to release our structures too.
377 +
378 + dictionary_index_lock_wrlock(dict);
379 +
380 + int ret;
381 + DICTIONARY_ITEM *item = hashtable_get_unsafe(dict, name, name_len);
382 + if(unlikely(!item)) {
383 + dictionary_index_wrlock_unlock(dict);
384 + ret = false;
385 + }
386 + else {
387 + if(hashtable_delete_unsafe(dict, name, name_len, item) == 0)
388 + netdata_log_error("DICTIONARY: INTERNAL ERROR: tried to delete item with name '%s', "
389 + "name_len %zd that is not in the index",
390 + name, name_len);
391 + else
392 + pointer_del(dict, item);
393 +
394 + dictionary_index_wrlock_unlock(dict);
395 +
396 + dict_item_free_or_mark_deleted(dict, item);
397 + ret = true;
398 + }
399 +
400 + return ret;
401 +}
402 +
403 +static inline DICTIONARY_ITEM *dict_item_add_or_reset_value_and_acquire(DICTIONARY *dict, const char *name, ssize_t name_len, void *value, size_t value_len, void *constructor_data, DICTIONARY_ITEM *master_item) {
404 + if(unlikely(!name || !*name)) {
405 + internal_error(
406 + true,
407 + "DICTIONARY: attempted to %s() without a name on a dictionary created from %s() %zu@%s.",
408 + __FUNCTION__,
409 + dict->creation_function,
410 + dict->creation_line,
411 + dict->creation_file);
412 + return NULL;
413 + }
414 +
415 + if(unlikely(is_dictionary_destroyed(dict))) {
416 + internal_error(true, "DICTIONARY: attempted to dictionary_set() on a destroyed dictionary");
417 + return NULL;
418 + }
419 +
420 + if(name_len == -1)
421 + name_len = (ssize_t)strlen(name);
422 +
423 + netdata_log_debug(D_DICTIONARY, "SET dictionary entry with name '%s'.", name);
424 +
425 + // DISCUSSION:
426 + // Is it better to gain a read-lock and do a hashtable_get_unsafe()
427 + // before we write lock to do hashtable_insert_unsafe()?
428 + //
429 + // Probably this depends on the use case.
430 + // For statsd for example that does dictionary_set() to update received values,
431 + // it could be beneficial to do a get() before we insert().
432 + //
433 + // But the caller has the option to do this on his/her own.
434 + // So, let's do the fastest here and let the caller decide the flow of calls.
435 +
436 + dictionary_index_lock_wrlock(dict);
437 +
438 + bool added_or_updated = false;
439 + size_t spins = 0;
440 + DICTIONARY_ITEM *item = NULL;
441 + do {
442 + void *handle = hashtable_insert_unsafe(dict, name, name_len);
443 + item = hashtable_insert_handle_to_item_unsafe(dict, handle);
444 + if (likely(item == NULL)) {
445 + // a new item added to the index
446 +
447 + // create the dictionary item
448 + item = dict_item_create_with_hooks(dict, name, name_len, value, value_len, constructor_data, master_item);
449 +
450 + pointer_add(dict, item);
451 +
452 + hashtable_set_item_unsafe(dict, handle, item);
453 +
454 + // unlock the index lock, before we add it to the linked list
455 + // DON'T DO IT THE OTHER WAY AROUND - DO NOT CROSS THE LOCKS!
456 + dictionary_index_wrlock_unlock(dict);
457 +
458 + item_linked_list_add(dict, item);
459 +
460 + added_or_updated = true;
461 + }
462 + else {
463 + pointer_check(dict, item);
464 +
465 + if(item_check_and_acquire_advanced(dict, item, true) != RC_ITEM_OK) {
466 + spins++;
467 + continue;
468 + }
469 +
470 + // the item is already in the index
471 + // so, either we will return the old one
472 + // or overwrite the value, depending on dictionary flags
473 +
474 + // We should not compare the values here!
475 + // even if they are the same, we have to do the whole job
476 + // so that the callbacks will be called.
477 +
478 + if(is_view_dictionary(dict)) {
479 + // view dictionary
480 + // the item is already there and can be used
481 + if(item->shared != master_item->shared)
482 + netdata_log_error("DICTIONARY: changing the master item on a view is not supported. The previous item will remain. To change the key of an item in a view, delete it and add it again.");
483 + }
484 + else {
485 + // master dictionary
486 + // the user wants to reset its value
487 +
488 + if (!(dict->options & DICT_OPTION_DONT_OVERWRITE_VALUE)) {
489 + dict_item_reset_value_with_hooks(dict, item, value, value_len, constructor_data);
490 + added_or_updated = true;
491 + }
492 +
493 + else if (dictionary_execute_conflict_callback(dict, item, value, constructor_data)) {
494 + dictionary_version_increment(dict);
495 + added_or_updated = true;
496 + }
497 +
498 + else {
499 + // conflict callback returned false
500 + // we did really nothing!
501 + ;
502 + }
503 + }
504 +
505 + dictionary_index_wrlock_unlock(dict);
506 + }
507 + } while(!item);
508 +
509 +
510 + if(unlikely(spins > 0))
511 + DICTIONARY_STATS_INSERT_SPINS_PLUS(dict, spins);
512 +
513 + if(is_master_dictionary(dict) && added_or_updated)
514 + dictionary_execute_react_callback(dict, item, constructor_data);
515 +
516 + return item;
517 +}
518 +
519 +static inline DICTIONARY_ITEM *dict_item_find_and_acquire(DICTIONARY *dict, const char *name, ssize_t name_len) {
520 + if(unlikely(!name || !*name)) {
521 + internal_error(
522 + true,
523 + "DICTIONARY: attempted to %s() without a name on a dictionary created from %s() %zu@%s.",
524 + __FUNCTION__,
525 + dict->creation_function,
526 + dict->creation_line,
527 + dict->creation_file);
528 + return NULL;
529 + }
530 +
531 + if(unlikely(is_dictionary_destroyed(dict))) {
532 + internal_error(true, "DICTIONARY: attempted to dictionary_get() on a destroyed dictionary");
533 + return NULL;
534 + }
535 +
536 + if(name_len == -1)
537 + name_len = (ssize_t)strlen(name);
538 +
539 + netdata_log_debug(D_DICTIONARY, "GET dictionary entry with name '%s'.", name);
540 +
541 + dictionary_index_lock_rdlock(dict);
542 +
543 + DICTIONARY_ITEM *item = hashtable_get_unsafe(dict, name, name_len);
544 + if(unlikely(item && !item_check_and_acquire(dict, item))) {
545 + item = NULL;
546 + DICTIONARY_STATS_SEARCH_IGNORES_PLUS1(dict);
547 + }
548 +
549 + dictionary_index_rdlock_unlock(dict);
550 +
551 + return item;
552 +}
553 +
554 +
555 +#endif //NETDATA_DICTIONARY_ITEM_H
src/libnetdata/dictionary/dictionary-locks.h new
+112
@@ -0,0 +1,112 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_DICTIONARY_LOCKS_H
4 +#define NETDATA_DICTIONARY_LOCKS_H
5 +
6 +#include "dictionary-internals.h"
7 +
8 +// ----------------------------------------------------------------------------
9 +// dictionary locks
10 +
11 +static inline size_t dictionary_locks_init(DICTIONARY *dict) {
12 + if(likely(!is_dictionary_single_threaded(dict))) {
13 + rw_spinlock_init(&dict->index.rw_spinlock);
14 + rw_spinlock_init(&dict->items.rw_spinlock);
15 + }
16 +
17 + return 0;
18 +}
19 +
20 +static inline size_t dictionary_locks_destroy(DICTIONARY *dict __maybe_unused) {
21 + return 0;
22 +}
23 +
24 +static inline void ll_recursive_lock_set_thread_as_writer(DICTIONARY *dict) {
25 + pid_t expected = 0, desired = gettid();
26 + if(!__atomic_compare_exchange_n(&dict->items.writer_pid, &expected, desired, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED))
27 + fatal("DICTIONARY: Cannot set thread %d as exclusive writer, expected %d, desired %d, found %d.", gettid(), expected, desired, __atomic_load_n(&dict->items.writer_pid, __ATOMIC_RELAXED));
28 +}
29 +
30 +static inline void ll_recursive_unlock_unset_thread_writer(DICTIONARY *dict) {
31 + pid_t expected = gettid(), desired = 0;
32 + if(!__atomic_compare_exchange_n(&dict->items.writer_pid, &expected, desired, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED))
33 + fatal("DICTIONARY: Cannot unset thread %d as exclusive writer, expected %d, desired %d, found %d.", gettid(), expected, desired, __atomic_load_n(&dict->items.writer_pid, __ATOMIC_RELAXED));
34 +}
35 +
36 +static inline bool ll_recursive_lock_is_thread_the_writer(DICTIONARY *dict) {
37 + pid_t tid = gettid();
38 + return tid > 0 && tid == __atomic_load_n(&dict->items.writer_pid, __ATOMIC_RELAXED);
39 +}
40 +
41 +static inline void ll_recursive_lock(DICTIONARY *dict, char rw) {
42 + if(unlikely(is_dictionary_single_threaded(dict)))
43 + return;
44 +
45 + if(ll_recursive_lock_is_thread_the_writer(dict)) {
46 + dict->items.writer_depth++;
47 + return;
48 + }
49 +
50 + if(rw == DICTIONARY_LOCK_READ || rw == DICTIONARY_LOCK_REENTRANT || rw == 'R') {
51 + // read lock
52 + rw_spinlock_read_lock(&dict->items.rw_spinlock);
53 + }
54 + else {
55 + // write lock
56 + rw_spinlock_write_lock(&dict->items.rw_spinlock);
57 + ll_recursive_lock_set_thread_as_writer(dict);
58 + }
59 +}
60 +
61 +static inline void ll_recursive_unlock(DICTIONARY *dict, char rw) {
62 + if(unlikely(is_dictionary_single_threaded(dict)))
63 + return;
64 +
65 + if(ll_recursive_lock_is_thread_the_writer(dict) && dict->items.writer_depth > 0) {
66 + dict->items.writer_depth--;
67 + return;
68 + }
69 +
70 + if(rw == DICTIONARY_LOCK_READ || rw == DICTIONARY_LOCK_REENTRANT || rw == 'R') {
71 + // read unlock
72 +
73 + rw_spinlock_read_unlock(&dict->items.rw_spinlock);
74 + }
75 + else {
76 + // write unlock
77 +
78 + ll_recursive_unlock_unset_thread_writer(dict);
79 +
80 + rw_spinlock_write_unlock(&dict->items.rw_spinlock);
81 + }
82 +}
83 +
84 +static inline void dictionary_index_lock_rdlock(DICTIONARY *dict) {
85 + if(unlikely(is_dictionary_single_threaded(dict)))
86 + return;
87 +
88 + rw_spinlock_read_lock(&dict->index.rw_spinlock);
89 +}
90 +
91 +static inline void dictionary_index_rdlock_unlock(DICTIONARY *dict) {
92 + if(unlikely(is_dictionary_single_threaded(dict)))
93 + return;
94 +
95 + rw_spinlock_read_unlock(&dict->index.rw_spinlock);
96 +}
97 +
98 +static inline void dictionary_index_lock_wrlock(DICTIONARY *dict) {
99 + if(unlikely(is_dictionary_single_threaded(dict)))
100 + return;
101 +
102 + rw_spinlock_write_lock(&dict->index.rw_spinlock);
103 +}
104 +static inline void dictionary_index_wrlock_unlock(DICTIONARY *dict) {
105 + if(unlikely(is_dictionary_single_threaded(dict)))
106 + return;
107 +
108 + rw_spinlock_write_unlock(&dict->index.rw_spinlock);
109 +}
110 +
111 +
112 +#endif //NETDATA_DICTIONARY_LOCKS_H
src/libnetdata/dictionary/dictionary-refcount.h new
+247
@@ -0,0 +1,247 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_DICTIONARY_REFCOUNT_H
4 +#define NETDATA_DICTIONARY_REFCOUNT_H
5 +
6 +#include "dictionary-internals.h"
7 +
8 +// ----------------------------------------------------------------------------
9 +// reference counters
10 +
11 +static inline size_t reference_counter_init(DICTIONARY *dict __maybe_unused) {
12 + // allocate memory required for reference counters
13 + // return number of bytes
14 + return 0;
15 +}
16 +
17 +static inline size_t reference_counter_free(DICTIONARY *dict __maybe_unused) {
18 + // free memory required for reference counters
19 + // return number of bytes
20 + return 0;
21 +}
22 +
23 +static inline void item_acquire(DICTIONARY *dict, DICTIONARY_ITEM *item) {
24 + REFCOUNT refcount;
25 +
26 + if(unlikely(is_dictionary_single_threaded(dict)))
27 + refcount = ++item->refcount;
28 +
29 + else
30 + // increment the refcount
31 + refcount = __atomic_add_fetch(&item->refcount, 1, __ATOMIC_SEQ_CST);
32 +
33 +
34 + if(refcount <= 0) {
35 + internal_error(
36 + true,
37 + "DICTIONARY: attempted to acquire item which is deleted (refcount = %d): "
38 + "'%s' on dictionary created by %s() (%zu@%s)",
39 + refcount - 1,
40 + item_get_name(item),
41 + dict->creation_function,
42 + dict->creation_line,
43 + dict->creation_file);
44 +
45 + fatal(
46 + "DICTIONARY: request to acquire item '%s', which is deleted (refcount = %d)!",
47 + item_get_name(item),
48 + refcount - 1);
49 + }
50 +
51 + if(refcount == 1) {
52 + // referenced items counts number of unique items referenced
53 + // so, we increase it only when refcount == 1
54 + DICTIONARY_REFERENCED_ITEMS_PLUS1(dict);
55 +
56 + // if this is a deleted item, but the counter increased to 1
57 + // we need to remove it from the pending items to delete
58 + if(item_flag_check(item, ITEM_FLAG_DELETED))
59 + DICTIONARY_PENDING_DELETES_MINUS1(dict);
60 + }
61 +}
62 +
63 +static inline void item_release(DICTIONARY *dict, DICTIONARY_ITEM *item) {
64 + // this function may be called without any lock on the dictionary
65 + // or even when someone else has 'write' lock on the dictionary
66 +
67 + bool is_deleted;
68 + REFCOUNT refcount;
69 +
70 + if(unlikely(is_dictionary_single_threaded(dict))) {
71 + is_deleted = item->flags & ITEM_FLAG_DELETED;
72 + refcount = --item->refcount;
73 + }
74 + else {
75 + // get the flags before decrementing any reference counters
76 + // (the other way around may lead to use-after-free)
77 + is_deleted = item_flag_check(item, ITEM_FLAG_DELETED);
78 +
79 + // decrement the refcount
80 + refcount = __atomic_sub_fetch(&item->refcount, 1, __ATOMIC_RELEASE);
81 + }
82 +
83 + if(refcount < 0) {
84 + internal_error(
85 + true,
86 + "DICTIONARY: attempted to release item without references (refcount = %d): "
87 + "'%s' on dictionary created by %s() (%zu@%s)",
88 + refcount + 1,
89 + item_get_name(item),
90 + dict->creation_function,
91 + dict->creation_line,
92 + dict->creation_file);
93 +
94 + fatal(
95 + "DICTIONARY: attempted to release item '%s' without references (refcount = %d)",
96 + item_get_name(item),
97 + refcount + 1);
98 + }
99 +
100 + if(refcount == 0) {
101 +
102 + if(is_deleted)
103 + DICTIONARY_PENDING_DELETES_PLUS1(dict);
104 +
105 + // referenced items counts number of unique items referenced
106 + // so, we decrease it only when refcount == 0
107 + DICTIONARY_REFERENCED_ITEMS_MINUS1(dict);
108 + }
109 +}
110 +
111 +static inline int item_check_and_acquire_advanced(DICTIONARY *dict, DICTIONARY_ITEM *item, bool having_index_lock) {
112 + size_t spins = 0;
113 + REFCOUNT refcount, desired;
114 +
115 + int ret = RC_ITEM_OK;
116 +
117 + refcount = DICTIONARY_ITEM_REFCOUNT_GET(dict, item);
118 +
119 + do {
120 + spins++;
121 +
122 + if(refcount < 0) {
123 + // we can't use this item
124 + ret = RC_ITEM_IS_CURRENTLY_BEING_DELETED;
125 + break;
126 + }
127 +
128 + if(item_flag_check(item, ITEM_FLAG_DELETED)) {
129 + // we can't use this item
130 + ret = RC_ITEM_MARKED_FOR_DELETION;
131 + break;
132 + }
133 +
134 + desired = refcount + 1;
135 +
136 + } while(!__atomic_compare_exchange_n(&item->refcount, &refcount, desired, false, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED));
137 +
138 + // if ret == ITEM_OK, we acquired the item
139 +
140 + if(ret == RC_ITEM_OK) {
141 + if (unlikely(is_view_dictionary(dict) &&
142 + item_shared_flag_check(item, ITEM_FLAG_DELETED) &&
143 + !item_flag_check(item, ITEM_FLAG_DELETED))) {
144 + // but, we can't use this item
145 +
146 + if (having_index_lock) {
147 + // delete it from the hashtable
148 + if(hashtable_delete_unsafe(dict, item_get_name(item), item->key_len, item) == 0)
149 + netdata_log_error("DICTIONARY: INTERNAL ERROR VIEW: tried to delete item with name '%s', "
150 + "name_len %u that is not in the index",
151 + item_get_name(item), (KEY_LEN_TYPE)(item->key_len));
152 + else
153 + pointer_del(dict, item);
154 +
155 + // mark it in our dictionary as deleted too,
156 + // this is safe to be done here, because we have got
157 + // a reference counter on item
158 + dict_item_set_deleted(dict, item);
159 +
160 + // decrement the refcount we incremented above
161 + if (__atomic_sub_fetch(&item->refcount, 1, __ATOMIC_RELEASE) == 0) {
162 + // this is a deleted item, and we are the last one
163 + DICTIONARY_PENDING_DELETES_PLUS1(dict);
164 + }
165 +
166 + // do not touch the item below this point
167 + } else {
168 + // this is traversal / walkthrough
169 + // decrement the refcount we incremented above
170 + __atomic_sub_fetch(&item->refcount, 1, __ATOMIC_RELEASE);
171 + }
172 +
173 + return RC_ITEM_MARKED_FOR_DELETION;
174 + }
175 +
176 + if(desired == 1)
177 + DICTIONARY_REFERENCED_ITEMS_PLUS1(dict);
178 + }
179 +
180 + if(unlikely(spins > 1))
181 + DICTIONARY_STATS_CHECK_SPINS_PLUS(dict, spins - 1);
182 +
183 + return ret;
184 +}
185 +
186 +// if a dictionary item can be deleted, return true, otherwise return false
187 +// we use the private reference counter
188 +static inline int item_is_not_referenced_and_can_be_removed_advanced(DICTIONARY *dict, DICTIONARY_ITEM *item) {
189 + // if we can set refcount to REFCOUNT_DELETING, we can delete this item
190 +
191 + size_t spins = 0;
192 + REFCOUNT refcount, desired = REFCOUNT_DELETING;
193 +
194 + int ret = RC_ITEM_OK;
195 +
196 + refcount = DICTIONARY_ITEM_REFCOUNT_GET(dict, item);
197 +
198 + do {
199 + spins++;
200 +
201 + if(refcount < 0) {
202 + // we can't use this item
203 + ret = RC_ITEM_IS_CURRENTLY_BEING_DELETED;
204 + break;
205 + }
206 +
207 + if(refcount > 0) {
208 + // we can't delete this
209 + ret = RC_ITEM_IS_REFERENCED;
210 + break;
211 + }
212 +
213 + if(item_flag_check(item, ITEM_FLAG_BEING_CREATED)) {
214 + // we can't use this item
215 + ret = RC_ITEM_IS_CURRENTLY_BEING_CREATED;
216 + break;
217 + }
218 + } while(!__atomic_compare_exchange_n(&item->refcount, &refcount, desired, false, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED));
219 +
220 +#ifdef NETDATA_INTERNAL_CHECKS
221 + if(ret == RC_ITEM_OK)
222 + item->deleter_pid = gettid();
223 +#endif
224 +
225 + if(unlikely(spins > 1))
226 + DICTIONARY_STATS_DELETE_SPINS_PLUS(dict, spins - 1);
227 +
228 + return ret;
229 +}
230 +
231 +// if a dictionary item can be freed, return true, otherwise return false
232 +// we use the shared reference counter
233 +static inline bool item_shared_release_and_check_if_it_can_be_freed(DICTIONARY *dict __maybe_unused, DICTIONARY_ITEM *item) {
234 + // if we can set refcount to REFCOUNT_DELETING, we can delete this item
235 +
236 + REFCOUNT links = __atomic_sub_fetch(&item->shared->links, 1, __ATOMIC_RELEASE);
237 + if(links == 0 && __atomic_compare_exchange_n(&item->shared->links, &links, REFCOUNT_DELETING, false, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) {
238 +
239 + // we can delete it
240 + return true;
241 + }
242 +
243 + // we can't delete it
244 + return false;
245 +}
246 +
247 +#endif //NETDATA_DICTIONARY_REFCOUNT_H
src/libnetdata/dictionary/dictionary-statistics.h new
+246
@@ -0,0 +1,246 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_DICTIONARY_STATISTICS_H
4 +#define NETDATA_DICTIONARY_STATISTICS_H
5 +
6 +#include "dictionary-internals.h"
7 +
8 +// ----------------------------------------------------------------------------
9 +// memory statistics
10 +
11 +#ifdef DICT_WITH_STATS
12 +static inline void DICTIONARY_STATS_PLUS_MEMORY(DICTIONARY *dict, size_t key_size, size_t item_size, size_t value_size) {
13 + if(key_size)
14 + __atomic_fetch_add(&dict->stats->memory.index, (long)JUDYHS_INDEX_SIZE_ESTIMATE(key_size), __ATOMIC_RELAXED);
15 +
16 + if(item_size)
17 + __atomic_fetch_add(&dict->stats->memory.dict, (long)item_size, __ATOMIC_RELAXED);
18 +
19 + if(value_size)
20 + __atomic_fetch_add(&dict->stats->memory.values, (long)value_size, __ATOMIC_RELAXED);
21 +}
22 +
23 +static inline void DICTIONARY_STATS_MINUS_MEMORY(DICTIONARY *dict, size_t key_size, size_t item_size, size_t value_size) {
24 + if(key_size)
25 + __atomic_fetch_sub(&dict->stats->memory.index, (long)JUDYHS_INDEX_SIZE_ESTIMATE(key_size), __ATOMIC_RELAXED);
26 +
27 + if(item_size)
28 + __atomic_fetch_sub(&dict->stats->memory.dict, (long)item_size, __ATOMIC_RELAXED);
29 +
30 + if(value_size)
31 + __atomic_fetch_sub(&dict->stats->memory.values, (long)value_size, __ATOMIC_RELAXED);
32 +}
33 +#else
34 +#define DICTIONARY_STATS_PLUS_MEMORY(dict, key_size, item_size, value_size) do {(void)item_size;} while(0)
35 +#define DICTIONARY_STATS_MINUS_MEMORY(dict, key_size, item_size, value_size) do {;} while(0)
36 +#endif
37 +
38 +// ----------------------------------------------------------------------------
39 +// internal statistics API
40 +
41 +#ifdef DICT_WITH_STATS
42 +static inline void DICTIONARY_STATS_SEARCHES_PLUS1(DICTIONARY *dict) {
43 + __atomic_fetch_add(&dict->stats->ops.searches, 1, __ATOMIC_RELAXED);
44 +}
45 +#else
46 +#define DICTIONARY_STATS_SEARCHES_PLUS1(dict) do {;} while(0)
47 +#endif
48 +
49 +static inline void DICTIONARY_ENTRIES_PLUS1(DICTIONARY *dict) {
50 +#ifdef DICT_WITH_STATS
51 + // statistics
52 + __atomic_fetch_add(&dict->stats->items.entries, 1, __ATOMIC_RELAXED);
53 + __atomic_fetch_add(&dict->stats->items.referenced, 1, __ATOMIC_RELAXED);
54 + __atomic_fetch_add(&dict->stats->ops.inserts, 1, __ATOMIC_RELAXED);
55 +#endif
56 +
57 + if(unlikely(is_dictionary_single_threaded(dict))) {
58 + dict->version++;
59 + dict->entries++;
60 + dict->referenced_items++;
61 +
62 + }
63 + else {
64 + __atomic_fetch_add(&dict->version, 1, __ATOMIC_RELAXED);
65 + __atomic_fetch_add(&dict->entries, 1, __ATOMIC_RELAXED);
66 + __atomic_fetch_add(&dict->referenced_items, 1, __ATOMIC_RELAXED);
67 + }
68 +}
69 +
70 +static inline void DICTIONARY_ENTRIES_MINUS1(DICTIONARY *dict) {
71 +#ifdef DICT_WITH_STATS
72 + // statistics
73 + __atomic_fetch_add(&dict->stats->ops.deletes, 1, __ATOMIC_RELAXED);
74 + __atomic_fetch_sub(&dict->stats->items.entries, 1, __ATOMIC_RELAXED);
75 +#endif
76 +
77 + size_t entries; (void)entries;
78 + if(unlikely(is_dictionary_single_threaded(dict))) {
79 + dict->version++;
80 + entries = dict->entries--;
81 + }
82 + else {
83 + __atomic_fetch_add(&dict->version, 1, __ATOMIC_RELAXED);
84 + entries = __atomic_fetch_sub(&dict->entries, 1, __ATOMIC_RELAXED);
85 + }
86 +
87 + internal_fatal(entries == 0,
88 + "DICT: negative number of entries in dictionary created from %s() (%zu@%s)",
89 + dict->creation_function,
90 + dict->creation_line,
91 + dict->creation_file);
92 +}
93 +
94 +static inline void DICTIONARY_VALUE_RESETS_PLUS1(DICTIONARY *dict) {
95 +#ifdef DICT_WITH_STATS
96 + __atomic_fetch_add(&dict->stats->ops.resets, 1, __ATOMIC_RELAXED);
97 +#endif
98 +
99 + if(unlikely(is_dictionary_single_threaded(dict)))
100 + dict->version++;
101 + else
102 + __atomic_fetch_add(&dict->version, 1, __ATOMIC_RELAXED);
103 +}
104 +
105 +#ifdef DICT_WITH_STATS
106 +static inline void DICTIONARY_STATS_TRAVERSALS_PLUS1(DICTIONARY *dict) {
107 + __atomic_fetch_add(&dict->stats->ops.traversals, 1, __ATOMIC_RELAXED);
108 +}
109 +static inline void DICTIONARY_STATS_WALKTHROUGHS_PLUS1(DICTIONARY *dict) {
110 + __atomic_fetch_add(&dict->stats->ops.walkthroughs, 1, __ATOMIC_RELAXED);
111 +}
112 +static inline void DICTIONARY_STATS_CHECK_SPINS_PLUS(DICTIONARY *dict, size_t count) {
113 + __atomic_fetch_add(&dict->stats->spin_locks.use_spins, count, __ATOMIC_RELAXED);
114 +}
115 +static inline void DICTIONARY_STATS_INSERT_SPINS_PLUS(DICTIONARY *dict, size_t count) {
116 + __atomic_fetch_add(&dict->stats->spin_locks.insert_spins, count, __ATOMIC_RELAXED);
117 +}
118 +static inline void DICTIONARY_STATS_DELETE_SPINS_PLUS(DICTIONARY *dict, size_t count) {
119 + __atomic_fetch_add(&dict->stats->spin_locks.delete_spins, count, __ATOMIC_RELAXED);
120 +}
121 +static inline void DICTIONARY_STATS_SEARCH_IGNORES_PLUS1(DICTIONARY *dict) {
122 + __atomic_fetch_add(&dict->stats->spin_locks.search_spins, 1, __ATOMIC_RELAXED);
123 +}
124 +static inline void DICTIONARY_STATS_CALLBACK_INSERTS_PLUS1(DICTIONARY *dict) {
125 + __atomic_fetch_add(&dict->stats->callbacks.inserts, 1, __ATOMIC_RELEASE);
126 +}
127 +static inline void DICTIONARY_STATS_CALLBACK_CONFLICTS_PLUS1(DICTIONARY *dict) {
128 + __atomic_fetch_add(&dict->stats->callbacks.conflicts, 1, __ATOMIC_RELEASE);
129 +}
130 +static inline void DICTIONARY_STATS_CALLBACK_REACTS_PLUS1(DICTIONARY *dict) {
131 + __atomic_fetch_add(&dict->stats->callbacks.reacts, 1, __ATOMIC_RELEASE);
132 +}
133 +static inline void DICTIONARY_STATS_CALLBACK_DELETES_PLUS1(DICTIONARY *dict) {
134 + __atomic_fetch_add(&dict->stats->callbacks.deletes, 1, __ATOMIC_RELEASE);
135 +}
136 +static inline void DICTIONARY_STATS_GARBAGE_COLLECTIONS_PLUS1(DICTIONARY *dict) {
137 + __atomic_fetch_add(&dict->stats->ops.garbage_collections, 1, __ATOMIC_RELAXED);
138 +}
139 +static inline void DICTIONARY_STATS_DICT_CREATIONS_PLUS1(DICTIONARY *dict) {
140 + __atomic_fetch_add(&dict->stats->dictionaries.active, 1, __ATOMIC_RELAXED);
141 + __atomic_fetch_add(&dict->stats->ops.creations, 1, __ATOMIC_RELAXED);
142 +}
143 +static inline void DICTIONARY_STATS_DICT_DESTRUCTIONS_PLUS1(DICTIONARY *dict) {
144 + __atomic_fetch_sub(&dict->stats->dictionaries.active, 1, __ATOMIC_RELAXED);
145 + __atomic_fetch_add(&dict->stats->ops.destructions, 1, __ATOMIC_RELAXED);
146 +}
147 +static inline void DICTIONARY_STATS_DICT_DESTROY_QUEUED_PLUS1(DICTIONARY *dict) {
148 + __atomic_fetch_add(&dict->stats->dictionaries.deleted, 1, __ATOMIC_RELAXED);
149 +}
150 +static inline void DICTIONARY_STATS_DICT_DESTROY_QUEUED_MINUS1(DICTIONARY *dict) {
151 + __atomic_fetch_sub(&dict->stats->dictionaries.deleted, 1, __ATOMIC_RELAXED);
152 +}
153 +static inline void DICTIONARY_STATS_DICT_FLUSHES_PLUS1(DICTIONARY *dict) {
154 + __atomic_fetch_add(&dict->stats->ops.flushes, 1, __ATOMIC_RELAXED);
155 +}
156 +#else
157 +#define DICTIONARY_STATS_TRAVERSALS_PLUS1(dict) do {;} while(0)
158 +#define DICTIONARY_STATS_WALKTHROUGHS_PLUS1(dict) do {;} while(0)
159 +#define DICTIONARY_STATS_CHECK_SPINS_PLUS(dict, count) do {;} while(0)
160 +#define DICTIONARY_STATS_INSERT_SPINS_PLUS(dict, count) do {;} while(0)
161 +#define DICTIONARY_STATS_DELETE_SPINS_PLUS(dict, count) do {;} while(0)
162 +#define DICTIONARY_STATS_SEARCH_IGNORES_PLUS1(dict) do {;} while(0)
163 +#define DICTIONARY_STATS_CALLBACK_INSERTS_PLUS1(dict) do {;} while(0)
164 +#define DICTIONARY_STATS_CALLBACK_CONFLICTS_PLUS1(dict) do {;} while(0)
165 +#define DICTIONARY_STATS_CALLBACK_REACTS_PLUS1(dict) do {;} while(0)
166 +#define DICTIONARY_STATS_CALLBACK_DELETES_PLUS1(dict) do {;} while(0)
167 +#define DICTIONARY_STATS_GARBAGE_COLLECTIONS_PLUS1(dict) do {;} while(0)
168 +#define DICTIONARY_STATS_DICT_CREATIONS_PLUS1(dict) do {;} while(0)
169 +#define DICTIONARY_STATS_DICT_DESTRUCTIONS_PLUS1(dict) do {;} while(0)
170 +#define DICTIONARY_STATS_DICT_DESTROY_QUEUED_PLUS1(dict) do {;} while(0)
171 +#define DICTIONARY_STATS_DICT_DESTROY_QUEUED_MINUS1(dict) do {;} while(0)
172 +#define DICTIONARY_STATS_DICT_FLUSHES_PLUS1(dict) do {;} while(0)
173 +#endif
174 +
175 +static inline void DICTIONARY_REFERENCED_ITEMS_PLUS1(DICTIONARY *dict) {
176 +#ifdef DICT_WITH_STATS
177 + __atomic_fetch_add(&dict->stats->items.referenced, 1, __ATOMIC_RELAXED);
178 +#endif
179 +
180 + if(unlikely(is_dictionary_single_threaded(dict)))
181 + ++dict->referenced_items;
182 + else
183 + __atomic_add_fetch(&dict->referenced_items, 1, __ATOMIC_RELAXED);
184 +}
185 +
186 +static inline void DICTIONARY_REFERENCED_ITEMS_MINUS1(DICTIONARY *dict) {
187 +#ifdef DICT_WITH_STATS
188 + __atomic_fetch_sub(&dict->stats->items.referenced, 1, __ATOMIC_RELAXED);
189 +#endif
190 +
191 + long int referenced_items; (void)referenced_items;
192 + if(unlikely(is_dictionary_single_threaded(dict)))
193 + referenced_items = --dict->referenced_items;
194 + else
195 + referenced_items = __atomic_sub_fetch(&dict->referenced_items, 1, __ATOMIC_SEQ_CST);
196 +
197 + internal_fatal(referenced_items < 0,
198 + "DICT: negative number of referenced items (%ld) in dictionary created from %s() (%zu@%s)",
199 + referenced_items,
200 + dict->creation_function,
201 + dict->creation_line,
202 + dict->creation_file);
203 +}
204 +
205 +static inline void DICTIONARY_PENDING_DELETES_PLUS1(DICTIONARY *dict) {
206 +#ifdef DICT_WITH_STATS
207 + __atomic_fetch_add(&dict->stats->items.pending_deletion, 1, __ATOMIC_RELAXED);
208 +#endif
209 +
210 + if(unlikely(is_dictionary_single_threaded(dict)))
211 + ++dict->pending_deletion_items;
212 + else
213 + __atomic_add_fetch(&dict->pending_deletion_items, 1, __ATOMIC_RELEASE);
214 +}
215 +
216 +static inline long int DICTIONARY_PENDING_DELETES_MINUS1(DICTIONARY *dict) {
217 +#ifdef DICT_WITH_STATS
218 + __atomic_fetch_sub(&dict->stats->items.pending_deletion, 1, __ATOMIC_RELEASE);
219 +#endif
220 +
221 + if(unlikely(is_dictionary_single_threaded(dict)))
222 + return --dict->pending_deletion_items;
223 + else
224 + return __atomic_sub_fetch(&dict->pending_deletion_items, 1, __ATOMIC_ACQUIRE);
225 +}
226 +
227 +static inline long int DICTIONARY_PENDING_DELETES_GET(DICTIONARY *dict) {
228 + if(unlikely(is_dictionary_single_threaded(dict)))
229 + return dict->pending_deletion_items;
230 + else
231 + return __atomic_load_n(&dict->pending_deletion_items, __ATOMIC_SEQ_CST);
232 +}
233 +
234 +static inline REFCOUNT DICTIONARY_ITEM_REFCOUNT_GET(DICTIONARY *dict, DICTIONARY_ITEM *item) {
235 + if(unlikely(dict && is_dictionary_single_threaded(dict))) // this is an exception, dict can be null
236 + return item->refcount;
237 + else
238 + return (REFCOUNT)__atomic_load_n(&item->refcount, __ATOMIC_ACQUIRE);
239 +}
240 +
241 +static inline REFCOUNT DICTIONARY_ITEM_REFCOUNT_GET_SOLE(DICTIONARY_ITEM *item) {
242 + return (REFCOUNT)__atomic_load_n(&item->refcount, __ATOMIC_ACQUIRE);
243 +}
244 +
245 +
246 +#endif //NETDATA_DICTIONARY_STATISTICS_H
src/libnetdata/dictionary/dictionary-traversal.c new
+268
@@ -0,0 +1,268 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "dictionary-internals.h"
4 +
5 +
6 +// ----------------------------------------------------------------------------
7 +// traversal with loop
8 +
9 +void *dictionary_foreach_start_rw(DICTFE *dfe, DICTIONARY *dict, char rw) {
10 + if(unlikely(!dfe || !dict)) return NULL;
11 +
12 + DICTIONARY_STATS_TRAVERSALS_PLUS1(dict);
13 +
14 + if(unlikely(is_dictionary_destroyed(dict))) {
15 + internal_error(true, "DICTIONARY: attempted to dictionary_foreach_start_rw() on a destroyed dictionary");
16 + dfe->counter = 0;
17 + dfe->item = NULL;
18 + dfe->name = NULL;
19 + dfe->value = NULL;
20 + return NULL;
21 + }
22 +
23 + dfe->counter = 0;
24 + dfe->dict = dict;
25 + dfe->rw = rw;
26 + dfe->locked = true;
27 + ll_recursive_lock(dict, dfe->rw);
28 +
29 + // get the first item from the list
30 + DICTIONARY_ITEM *item = dict->items.list;
31 +
32 + // skip all the deleted items
33 + while(item && !item_check_and_acquire(dict, item))
34 + item = item->next;
35 +
36 + if(likely(item)) {
37 + dfe->item = item;
38 + dfe->name = (char *)item_get_name(item);
39 + dfe->value = item->shared->value;
40 + }
41 + else {
42 + dfe->item = NULL;
43 + dfe->name = NULL;
44 + dfe->value = NULL;
45 + }
46 +
47 + if(unlikely(dfe->rw == DICTIONARY_LOCK_REENTRANT)) {
48 + ll_recursive_unlock(dfe->dict, dfe->rw);
49 + dfe->locked = false;
50 + }
51 +
52 + return dfe->value;
53 +}
54 +
55 +void *dictionary_foreach_next(DICTFE *dfe) {
56 + if(unlikely(!dfe || !dfe->dict)) return NULL;
57 +
58 + if(unlikely(is_dictionary_destroyed(dfe->dict))) {
59 + internal_error(true, "DICTIONARY: attempted to dictionary_foreach_next() on a destroyed dictionary");
60 + dfe->item = NULL;
61 + dfe->name = NULL;
62 + dfe->value = NULL;
63 + return NULL;
64 + }
65 +
66 + if(unlikely(dfe->rw == DICTIONARY_LOCK_REENTRANT) || !dfe->locked) {
67 + ll_recursive_lock(dfe->dict, dfe->rw);
68 + dfe->locked = true;
69 + }
70 +
71 + // the item we just did
72 + DICTIONARY_ITEM *item = dfe->item;
73 +
74 + // get the next item from the list
75 + DICTIONARY_ITEM *item_next = (item) ? item->next : NULL;
76 +
77 + // skip all the deleted items until one that can be acquired is found
78 + while(item_next && !item_check_and_acquire(dfe->dict, item_next))
79 + item_next = item_next->next;
80 +
81 + if(likely(item)) {
82 + dict_item_release_and_check_if_it_is_deleted_and_can_be_removed_under_this_lock_mode(dfe->dict, item, dfe->rw);
83 + // item_release(dfe->dict, item);
84 + }
85 +
86 + item = item_next;
87 + if(likely(item)) {
88 + dfe->item = item;
89 + dfe->name = (char *)item_get_name(item);
90 + dfe->value = item->shared->value;
91 + dfe->counter++;
92 + }
93 + else {
94 + dfe->item = NULL;
95 + dfe->name = NULL;
96 + dfe->value = NULL;
97 + }
98 +
99 + if(unlikely(dfe->rw == DICTIONARY_LOCK_REENTRANT)) {
100 + ll_recursive_unlock(dfe->dict, dfe->rw);
101 + dfe->locked = false;
102 + }
103 +
104 + return dfe->value;
105 +}
106 +
107 +void dictionary_foreach_unlock(DICTFE *dfe) {
108 + if(dfe->locked) {
109 + ll_recursive_unlock(dfe->dict, dfe->rw);
110 + dfe->locked = false;
111 + }
112 +}
113 +
114 +void dictionary_foreach_done(DICTFE *dfe) {
115 + if(unlikely(!dfe || !dfe->dict)) return;
116 +
117 + if(unlikely(is_dictionary_destroyed(dfe->dict))) {
118 + internal_error(true, "DICTIONARY: attempted to dictionary_foreach_next() on a destroyed dictionary");
119 + return;
120 + }
121 +
122 + // the item we just did
123 + DICTIONARY_ITEM *item = dfe->item;
124 +
125 + // release it, so that it can possibly be deleted
126 + if(likely(item)) {
127 + dict_item_release_and_check_if_it_is_deleted_and_can_be_removed_under_this_lock_mode(dfe->dict, item, dfe->rw);
128 + // item_release(dfe->dict, item);
129 + }
130 +
131 + if(likely(dfe->rw != DICTIONARY_LOCK_REENTRANT) && dfe->locked) {
132 + ll_recursive_unlock(dfe->dict, dfe->rw);
133 + dfe->locked = false;
134 + }
135 +
136 + dfe->dict = NULL;
137 + dfe->item = NULL;
138 + dfe->name = NULL;
139 + dfe->value = NULL;
140 + dfe->counter = 0;
141 +}
142 +
143 +// ----------------------------------------------------------------------------
144 +// API - walk through the dictionary.
145 +// The dictionary is locked for reading while this happens
146 +// do not use other dictionary calls while walking the dictionary - deadlock!
147 +
148 +int dictionary_walkthrough_rw(DICTIONARY *dict, char rw, dict_walkthrough_callback_t walkthrough_callback, void *data) {
149 + if(unlikely(!dict || !walkthrough_callback)) return 0;
150 +
151 + if(unlikely(is_dictionary_destroyed(dict))) {
152 + internal_error(true, "DICTIONARY: attempted to dictionary_walkthrough_rw() on a destroyed dictionary");
153 + return 0;
154 + }
155 +
156 + ll_recursive_lock(dict, rw);
157 +
158 + DICTIONARY_STATS_WALKTHROUGHS_PLUS1(dict);
159 +
160 + // written in such a way, that the callback can delete the active element
161 +
162 + int ret = 0;
163 + DICTIONARY_ITEM *item = dict->items.list, *item_next;
164 + while(item) {
165 +
166 + // skip the deleted items
167 + if(unlikely(!item_check_and_acquire(dict, item))) {
168 + item = item->next;
169 + continue;
170 + }
171 +
172 + if(unlikely(rw == DICTIONARY_LOCK_REENTRANT))
173 + ll_recursive_unlock(dict, rw);
174 +
175 + int r = walkthrough_callback(item, item->shared->value, data);
176 +
177 + if(unlikely(rw == DICTIONARY_LOCK_REENTRANT))
178 + ll_recursive_lock(dict, rw);
179 +
180 + // since we have a reference counter, this item cannot be deleted
181 + // until we release the reference counter, so the pointers are there
182 + item_next = item->next;
183 +
184 + dict_item_release_and_check_if_it_is_deleted_and_can_be_removed_under_this_lock_mode(dict, item, rw);
185 + // item_release(dict, item);
186 +
187 + if(unlikely(r < 0)) {
188 + ret = r;
189 + break;
190 + }
191 +
192 + ret += r;
193 +
194 + item = item_next;
195 + }
196 +
197 + ll_recursive_unlock(dict, rw);
198 +
199 + return ret;
200 +}
201 +
202 +// ----------------------------------------------------------------------------
203 +// sorted walkthrough
204 +
205 +typedef int (*qsort_compar)(const void *item1, const void *item2);
206 +
207 +static int dictionary_sort_compar(const void *item1, const void *item2) {
208 + return strcmp(item_get_name((*(DICTIONARY_ITEM **)item1)), item_get_name((*(DICTIONARY_ITEM **)item2)));
209 +}
210 +
211 +int dictionary_sorted_walkthrough_rw(DICTIONARY *dict, char rw, dict_walkthrough_callback_t walkthrough_callback, void *data, dict_item_comparator_t item_comparator) {
212 + if(unlikely(!dict || !walkthrough_callback)) return 0;
213 +
214 + if(unlikely(is_dictionary_destroyed(dict))) {
215 + internal_error(true, "DICTIONARY: attempted to dictionary_sorted_walkthrough_rw() on a destroyed dictionary");
216 + return 0;
217 + }
218 +
219 + DICTIONARY_STATS_WALKTHROUGHS_PLUS1(dict);
220 +
221 + ll_recursive_lock(dict, rw);
222 + size_t entries = __atomic_load_n(&dict->entries, __ATOMIC_RELAXED);
223 + DICTIONARY_ITEM **array = mallocz(sizeof(DICTIONARY_ITEM *) * entries);
224 +
225 + size_t i;
226 + DICTIONARY_ITEM *item;
227 + for(item = dict->items.list, i = 0; item && i < entries; item = item->next) {
228 + if(likely(item_check_and_acquire(dict, item)))
229 + array[i++] = item;
230 + }
231 + ll_recursive_unlock(dict, rw);
232 +
233 + if(unlikely(i != entries))
234 + entries = i;
235 +
236 + if(item_comparator)
237 + qsort(array, entries, sizeof(DICTIONARY_ITEM *), (qsort_compar) item_comparator);
238 + else
239 + qsort(array, entries, sizeof(DICTIONARY_ITEM *), dictionary_sort_compar);
240 +
241 + bool callit = true;
242 + int ret = 0, r;
243 + for(i = 0; i < entries ;i++) {
244 + item = array[i];
245 +
246 + if(callit)
247 + r = walkthrough_callback(item, item->shared->value, data);
248 +
249 + dict_item_release_and_check_if_it_is_deleted_and_can_be_removed_under_this_lock_mode(dict, item, rw);
250 + // item_release(dict, item);
251 +
252 + if(r < 0) {
253 + ret = r;
254 + r = 0;
255 +
256 + // stop calling the callback,
257 + // but we have to continue, to release all the reference counters
258 + callit = false;
259 + }
260 + else
261 + ret += r;
262 + }
263 +
264 + freez(array);
265 +
266 + return ret;
267 +}
268 +
src/libnetdata/dictionary/dictionary-unittest.c new
+1195
@@ -0,0 +1,1195 @@
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 + netdata_thread_t 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() % 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 + return arg;
666 +}
667 +
668 +static int dictionary_unittest_threads() {
669 + time_t seconds_to_run = 5;
670 + int threads_to_create = 2;
671 +
672 + struct thread_unittest tu[threads_to_create];
673 + memset(tu, 0, sizeof(struct thread_unittest) * threads_to_create);
674 +
675 + fprintf(
676 + stderr,
677 + "\nChecking dictionary concurrency with %d threads for %lld seconds...\n",
678 + threads_to_create,
679 + (long long)seconds_to_run);
680 +
681 + // threads testing of dictionary
682 + struct dictionary_stats stats = {};
683 + tu[0].join = 0;
684 + tu[0].dups = 1;
685 + tu[0].dict = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE, &stats, 0);
686 +
687 + for (int i = 0; i < threads_to_create; i++) {
688 + if(i)
689 + tu[i] = tu[0];
690 +
691 + char buf[100 + 1];
692 + snprintf(buf, 100, "dict%d", i);
693 + netdata_thread_create(
694 + &tu[i].thread,
695 + buf,
696 + NETDATA_THREAD_OPTION_DONT_LOG | NETDATA_THREAD_OPTION_JOINABLE,
697 + unittest_dict_thread,
698 + &tu[i]);
699 + }
700 +
701 + sleep_usec(seconds_to_run * USEC_PER_SEC);
702 +
703 + for (int i = 0; i < threads_to_create; i++) {
704 + __atomic_store_n(&tu[i].join, 1, __ATOMIC_RELAXED);
705 +
706 + void *retval;
707 + netdata_thread_join(tu[i].thread, &retval);
708 +
709 + if(i) {
710 + tu[0].stats.ops.inserts += tu[i].stats.ops.inserts;
711 + tu[0].stats.ops.deletes += tu[i].stats.ops.deletes;
712 + tu[0].stats.ops.searches += tu[i].stats.ops.searches;
713 + tu[0].stats.ops.flushes += tu[i].stats.ops.flushes;
714 + tu[0].stats.ops.traversals += tu[i].stats.ops.traversals;
715 + }
716 + }
717 +
718 + fprintf(stderr,
719 + "CALLS : inserts %zu"
720 + ", deletes %zu"
721 + ", searches %zu"
722 + ", traversals %zu"
723 + ", flushes %zu"
724 + "\n",
725 + tu[0].stats.ops.inserts,
726 + tu[0].stats.ops.deletes,
727 + tu[0].stats.ops.searches,
728 + tu[0].stats.ops.traversals,
729 + tu[0].stats.ops.flushes
730 + );
731 +
732 +#ifdef DICT_WITH_STATS
733 + fprintf(stderr,
734 + "ACTUAL: inserts %zu"
735 + ", deletes %zu"
736 + ", searches %zu"
737 + ", traversals %zu"
738 + ", resets %zu"
739 + ", flushes %zu"
740 + ", entries %d"
741 + ", referenced_items %d"
742 + ", pending deletions %d"
743 + ", check spins %zu"
744 + ", insert spins %zu"
745 + ", delete spins %zu"
746 + ", search ignores %zu"
747 + "\n",
748 + stats.ops.inserts,
749 + stats.ops.deletes,
750 + stats.ops.searches,
751 + stats.ops.traversals,
752 + stats.ops.resets,
753 + stats.ops.flushes,
754 + tu[0].dict->entries,
755 + tu[0].dict->referenced_items,
756 + tu[0].dict->pending_deletion_items,
757 + stats.spin_locks.use_spins,
758 + stats.spin_locks.insert_spins,
759 + stats.spin_locks.delete_spins,
760 + stats.spin_locks.search_spins
761 + );
762 +#endif
763 +
764 + dictionary_destroy(tu[0].dict);
765 + return 0;
766 +}
767 +
768 +struct thread_view_unittest {
769 + int join;
770 + DICTIONARY *master;
771 + DICTIONARY *view;
772 + DICTIONARY_ITEM *item_master;
773 + int dups;
774 +};
775 +
776 +static void *unittest_dict_master_thread(void *arg) {
777 + struct thread_view_unittest *tv = arg;
778 +
779 + DICTIONARY_ITEM *item = NULL;
780 + int loops = 0;
781 + while(!__atomic_load_n(&tv->join, __ATOMIC_RELAXED)) {
782 +
783 + if(!item)
784 + item = dictionary_set_and_acquire_item(tv->master, "ITEM1", "123", strlen("123"));
785 +
786 + if(__atomic_load_n(&tv->item_master, __ATOMIC_RELAXED) != NULL) {
787 + dictionary_acquired_item_release(tv->master, item);
788 + dictionary_del(tv->master, "ITEM1");
789 + item = NULL;
790 + loops++;
791 + continue;
792 + }
793 +
794 + dictionary_acquired_item_dup(tv->master, item); // for the view thread
795 + __atomic_store_n(&tv->item_master, item, __ATOMIC_RELAXED);
796 + dictionary_del(tv->master, "ITEM1");
797 +
798 +
799 + for(int i = 0; i < tv->dups + loops ; i++) {
800 + dictionary_acquired_item_dup(tv->master, item);
801 + }
802 +
803 + for(int i = 0; i < tv->dups + loops ; i++) {
804 + dictionary_acquired_item_release(tv->master, item);
805 + }
806 +
807 + dictionary_acquired_item_release(tv->master, item);
808 +
809 + item = NULL;
810 + loops = 0;
811 + }
812 +
813 + return arg;
814 +}
815 +
816 +static void *unittest_dict_view_thread(void *arg) {
817 + struct thread_view_unittest *tv = arg;
818 +
819 + DICTIONARY_ITEM *m_item = NULL;
820 +
821 + while(!__atomic_load_n(&tv->join, __ATOMIC_RELAXED)) {
822 + if(!(m_item = __atomic_load_n(&tv->item_master, __ATOMIC_RELAXED)))
823 + continue;
824 +
825 + DICTIONARY_ITEM *v_item = dictionary_view_set_and_acquire_item(tv->view, "ITEM2", m_item);
826 + dictionary_acquired_item_release(tv->master, m_item);
827 + __atomic_store_n(&tv->item_master, NULL, __ATOMIC_RELAXED);
828 +
829 + for(int i = 0; i < tv->dups ; i++) {
830 + dictionary_acquired_item_dup(tv->view, v_item);
831 + }
832 +
833 + for(int i = 0; i < tv->dups ; i++) {
834 + dictionary_acquired_item_release(tv->view, v_item);
835 + }
836 +
837 + dictionary_del(tv->view, "ITEM2");
838 +
839 + while(!__atomic_load_n(&tv->join, __ATOMIC_RELAXED) && !(m_item = __atomic_load_n(&tv->item_master, __ATOMIC_RELAXED))) {
840 + dictionary_acquired_item_dup(tv->view, v_item);
841 + dictionary_acquired_item_release(tv->view, v_item);
842 + }
843 +
844 + dictionary_acquired_item_release(tv->view, v_item);
845 + }
846 +
847 + return arg;
848 +}
849 +
850 +static int dictionary_unittest_view_threads() {
851 +
852 + struct thread_view_unittest tv = {
853 + .join = 0,
854 + .master = NULL,
855 + .view = NULL,
856 + .item_master = NULL,
857 + .dups = 1,
858 + };
859 +
860 + // threads testing of dictionary
861 + struct dictionary_stats stats_master = {};
862 + struct dictionary_stats stats_view = {};
863 + tv.master = dictionary_create_advanced(DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_DONT_OVERWRITE_VALUE, &stats_master, 0);
864 + tv.view = dictionary_create_view(tv.master);
865 + tv.view->stats = &stats_view;
866 +
867 + time_t seconds_to_run = 5;
868 + fprintf(
869 + stderr,
870 + "\nChecking dictionary concurrency with 1 master and 1 view threads for %lld seconds...\n",
871 + (long long)seconds_to_run);
872 +
873 + netdata_thread_t master_thread, view_thread;
874 + tv.join = 0;
875 +
876 + netdata_thread_create(
877 + &master_thread,
878 + "master",
879 + NETDATA_THREAD_OPTION_DONT_LOG | NETDATA_THREAD_OPTION_JOINABLE,
880 + unittest_dict_master_thread,
881 + &tv);
882 +
883 + netdata_thread_create(
884 + &view_thread,
885 + "view",
886 + NETDATA_THREAD_OPTION_DONT_LOG | NETDATA_THREAD_OPTION_JOINABLE,
887 + unittest_dict_view_thread,
888 + &tv);
889 +
890 + sleep_usec(seconds_to_run * USEC_PER_SEC);
891 +
892 + __atomic_store_n(&tv.join, 1, __ATOMIC_RELAXED);
893 + void *retval;
894 + netdata_thread_join(view_thread, &retval);
895 + netdata_thread_join(master_thread, &retval);
896 +
897 +#ifdef DICT_WITH_STATS
898 + fprintf(stderr,
899 + "MASTER: inserts %zu"
900 + ", deletes %zu"
901 + ", searches %zu"
902 + ", resets %zu"
903 + ", entries %d"
904 + ", referenced_items %d"
905 + ", pending deletions %d"
906 + ", check spins %zu"
907 + ", insert spins %zu"
908 + ", delete spins %zu"
909 + ", search ignores %zu"
910 + "\n",
911 + stats_master.ops.inserts,
912 + stats_master.ops.deletes,
913 + stats_master.ops.searches,
914 + stats_master.ops.resets,
915 + tv.master->entries,
916 + tv.master->referenced_items,
917 + tv.master->pending_deletion_items,
918 + stats_master.spin_locks.use_spins,
919 + stats_master.spin_locks.insert_spins,
920 + stats_master.spin_locks.delete_spins,
921 + stats_master.spin_locks.search_spins
922 + );
923 + fprintf(stderr,
924 + "VIEW : inserts %zu"
925 + ", deletes %zu"
926 + ", searches %zu"
927 + ", resets %zu"
928 + ", entries %d"
929 + ", referenced_items %d"
930 + ", pending deletions %d"
931 + ", check spins %zu"
932 + ", insert spins %zu"
933 + ", delete spins %zu"
934 + ", search ignores %zu"
935 + "\n",
936 + stats_view.ops.inserts,
937 + stats_view.ops.deletes,
938 + stats_view.ops.searches,
939 + stats_view.ops.resets,
940 + tv.view->entries,
941 + tv.view->referenced_items,
942 + tv.view->pending_deletion_items,
943 + stats_view.spin_locks.use_spins,
944 + stats_view.spin_locks.insert_spins,
945 + stats_view.spin_locks.delete_spins,
946 + stats_view.spin_locks.search_spins
947 + );
948 +#endif
949 +
950 + dictionary_destroy(tv.master);
951 + dictionary_destroy(tv.view);
952 +
953 + return 0;
954 +}
955 +
956 +size_t dictionary_unittest_views(void) {
957 + size_t errors = 0;
958 + struct dictionary_stats stats = {};
959 + DICTIONARY *master = dictionary_create_advanced(DICT_OPTION_NONE, &stats, 0);
960 + DICTIONARY *view = dictionary_create_view(master);
961 +
962 + fprintf(stderr, "\n\nChecking dictionary views...\n");
963 +
964 + // Add an item to both master and view, then remove the view first and the master second
965 + fprintf(stderr, "\nPASS 1: Adding 1 item to master:\n");
966 + DICTIONARY_ITEM *item1_on_master = dictionary_set_and_acquire_item(master, "KEY 1", "VALUE1", strlen("VALUE1") + 1);
967 + errors += unittest_check_dictionary("master", master, 1, 1, 0, 1, 0);
968 + errors += unittest_check_item("master", master, item1_on_master, "KEY 1", item1_on_master->shared->value, 1, ITEM_FLAG_NONE, true, true, true);
969 +
970 + fprintf(stderr, "\nPASS 1: Adding master item to view:\n");
971 + DICTIONARY_ITEM *item1_on_view = dictionary_view_set_and_acquire_item(view, "KEY 1 ON VIEW", item1_on_master);
972 + errors += unittest_check_dictionary("view", view, 1, 1, 0, 1, 0);
973 + 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);
974 +
975 + fprintf(stderr, "\nPASS 1: Deleting view item:\n");
976 + dictionary_del(view, "KEY 1 ON VIEW");
977 + errors += unittest_check_dictionary("master", master, 1, 1, 0, 1, 0);
978 + errors += unittest_check_dictionary("view", view, 0, 0, 1, 1, 0);
979 + errors += unittest_check_item("master", master, item1_on_master, "KEY 1", item1_on_master->shared->value, 1, ITEM_FLAG_NONE, true, true, true);
980 + 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);
981 +
982 + fprintf(stderr, "\nPASS 1: Releasing the deleted view item:\n");
983 + dictionary_acquired_item_release(view, item1_on_view);
984 + errors += unittest_check_dictionary("master", master, 1, 1, 0, 1, 0);
985 + errors += unittest_check_dictionary("view", view, 0, 0, 1, 0, 1);
986 + errors += unittest_check_item("master", master, item1_on_master, "KEY 1", item1_on_master->shared->value, 1, ITEM_FLAG_NONE, true, true, true);
987 +
988 + fprintf(stderr, "\nPASS 1: Releasing the acquired master item:\n");
989 + dictionary_acquired_item_release(master, item1_on_master);
990 + errors += unittest_check_dictionary("master", master, 1, 1, 0, 0, 0);
991 + errors += unittest_check_dictionary("view", view, 0, 0, 1, 0, 1);
992 + errors += unittest_check_item("master", master, item1_on_master, "KEY 1", item1_on_master->shared->value, 0, ITEM_FLAG_NONE, true, true, true);
993 +
994 + fprintf(stderr, "\nPASS 1: Deleting the released master item:\n");
995 + dictionary_del(master, "KEY 1");
996 + errors += unittest_check_dictionary("master", master, 0, 0, 0, 0, 0);
997 + errors += unittest_check_dictionary("view", view, 0, 0, 1, 0, 1);
998 +
999 + // The other way now:
1000 + // Add an item to both master and view, then remove the master first and verify it is deleted on the view also
1001 + fprintf(stderr, "\nPASS 2: Adding 1 item to master:\n");
1002 + item1_on_master = dictionary_set_and_acquire_item(master, "KEY 1", "VALUE1", strlen("VALUE1") + 1);
1003 + errors += unittest_check_dictionary("master", master, 1, 1, 0, 1, 0);
1004 + errors += unittest_check_item("master", master, item1_on_master, "KEY 1", item1_on_master->shared->value, 1, ITEM_FLAG_NONE, true, true, true);
1005 +
1006 + fprintf(stderr, "\nPASS 2: Adding master item to view:\n");
1007 + item1_on_view = dictionary_view_set_and_acquire_item(view, "KEY 1 ON VIEW", item1_on_master);
1008 + errors += unittest_check_dictionary("view", view, 1, 1, 0, 1, 0);
1009 + 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);
1010 +
1011 + fprintf(stderr, "\nPASS 2: Deleting master item:\n");
1012 + dictionary_del(master, "KEY 1");
1013 + garbage_collect_pending_deletes(view);
1014 + errors += unittest_check_dictionary("master", master, 0, 0, 1, 1, 0);
1015 + errors += unittest_check_dictionary("view", view, 0, 0, 1, 1, 0);
1016 + errors += unittest_check_item("master", master, item1_on_master, "KEY 1", item1_on_master->shared->value, 1, ITEM_FLAG_DELETED, false, false, true);
1017 + 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);
1018 +
1019 + fprintf(stderr, "\nPASS 2: Releasing the acquired master item:\n");
1020 + dictionary_acquired_item_release(master, item1_on_master);
1021 + errors += unittest_check_dictionary("master", master, 0, 0, 1, 0, 1);
1022 + errors += unittest_check_dictionary("view", view, 0, 0, 1, 1, 0);
1023 + 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);
1024 +
1025 + fprintf(stderr, "\nPASS 2: Releasing the deleted view item:\n");
1026 + dictionary_acquired_item_release(view, item1_on_view);
1027 + errors += unittest_check_dictionary("master", master, 0, 0, 1, 0, 1);
1028 + errors += unittest_check_dictionary("view", view, 0, 0, 1, 0, 1);
1029 +
1030 + dictionary_destroy(master);
1031 + dictionary_destroy(view);
1032 + return errors;
1033 +}
1034 +
1035 +/*
1036 + * FIXME: a dictionary-related leak is reported when running the address
1037 + * sanitizer. Need to investigate if it's introduced by the unit-test itself,
1038 + * or the dictionary implementation.
1039 +*/
1040 +int dictionary_unittest(size_t entries) {
1041 + if(entries < 10) entries = 10;
1042 +
1043 + DICTIONARY *dict;
1044 + size_t errors = 0;
1045 +
1046 + fprintf(stderr, "Generating %zu names and values...\n", entries);
1047 + char **names = dictionary_unittest_generate_names(entries);
1048 + char **values = dictionary_unittest_generate_values(entries);
1049 +
1050 + fprintf(stderr, "\nCreating dictionary single threaded, clone, %zu items\n", entries);
1051 + dict = dictionary_create(DICT_OPTION_SINGLE_THREADED);
1052 + dictionary_unittest_clone(dict, names, values, entries, &errors);
1053 +
1054 + fprintf(stderr, "\nCreating dictionary multi threaded, clone, %zu items\n", entries);
1055 + dict = dictionary_create(DICT_OPTION_NONE);
1056 + dictionary_unittest_clone(dict, names, values, entries, &errors);
1057 +
1058 + fprintf(stderr, "\nCreating dictionary single threaded, non-clone, add-in-front options, %zu items\n", entries);
1059 + dict = dictionary_create(
1060 + DICT_OPTION_SINGLE_THREADED | DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_VALUE_LINK_DONT_CLONE |
1061 + DICT_OPTION_ADD_IN_FRONT);
1062 + dictionary_unittest_nonclone(dict, names, values, entries, &errors);
1063 +
1064 + fprintf(stderr, "\nCreating dictionary multi threaded, non-clone, add-in-front options, %zu items\n", entries);
1065 + dict = dictionary_create(
1066 + DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_VALUE_LINK_DONT_CLONE | DICT_OPTION_ADD_IN_FRONT);
1067 + dictionary_unittest_nonclone(dict, names, values, entries, &errors);
1068 +
1069 + fprintf(stderr, "\nCreating dictionary single-threaded, non-clone, don't overwrite options, %zu items\n", entries);
1070 + dict = dictionary_create(
1071 + DICT_OPTION_SINGLE_THREADED | DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_VALUE_LINK_DONT_CLONE |
1072 + DICT_OPTION_DONT_OVERWRITE_VALUE);
1073 + dictionary_unittest_run_and_measure_time(dict, "adding entries", names, values, entries, &errors, dictionary_unittest_set_nonclone);
1074 + dictionary_unittest_run_and_measure_time(dict, "resetting non-overwrite entries", names, values, entries, &errors, dictionary_unittest_reset_dont_overwrite_nonclone);
1075 + dictionary_unittest_run_and_measure_time(dict, "traverse foreach read loop", names, values, entries, &errors, dictionary_unittest_foreach);
1076 + dictionary_unittest_run_and_measure_time(dict, "walkthrough read callback", names, values, entries, &errors, dictionary_unittest_walkthrough);
1077 + dictionary_unittest_run_and_measure_time(dict, "walkthrough read callback stop", names, values, entries, &errors, dictionary_unittest_walkthrough_stop);
1078 + dictionary_unittest_run_and_measure_time(dict, "destroying full dictionary", names, values, entries, &errors, dictionary_unittest_destroy);
1079 +
1080 + fprintf(stderr, "\nCreating dictionary multi-threaded, non-clone, don't overwrite options, %zu items\n", entries);
1081 + dict = dictionary_create(
1082 + DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_VALUE_LINK_DONT_CLONE | DICT_OPTION_DONT_OVERWRITE_VALUE);
1083 + dictionary_unittest_run_and_measure_time(dict, "adding entries", names, values, entries, &errors, dictionary_unittest_set_nonclone);
1084 + dictionary_unittest_run_and_measure_time(dict, "walkthrough write delete this", names, values, entries, &errors, dictionary_unittest_walkthrough_delete_this);
1085 + dictionary_unittest_run_and_measure_time(dict, "destroying empty dictionary", names, values, entries, &errors, dictionary_unittest_destroy);
1086 +
1087 + fprintf(stderr, "\nCreating dictionary multi-threaded, non-clone, don't overwrite options, %zu items\n", entries);
1088 + dict = dictionary_create(
1089 + DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_VALUE_LINK_DONT_CLONE | DICT_OPTION_DONT_OVERWRITE_VALUE);
1090 + dictionary_unittest_run_and_measure_time(dict, "adding entries", names, values, entries, &errors, dictionary_unittest_set_nonclone);
1091 + dictionary_unittest_run_and_measure_time(dict, "foreach write delete this", names, values, entries, &errors, dictionary_unittest_foreach_delete_this);
1092 + dictionary_unittest_run_and_measure_time(dict, "traverse foreach read loop empty", names, values, 0, &errors, dictionary_unittest_foreach);
1093 + dictionary_unittest_run_and_measure_time(dict, "walkthrough read callback empty", names, values, 0, &errors, dictionary_unittest_walkthrough);
1094 + dictionary_unittest_run_and_measure_time(dict, "destroying empty dictionary", names, values, entries, &errors, dictionary_unittest_destroy);
1095 +
1096 + fprintf(stderr, "\nCreating dictionary single threaded, clone, %zu items\n", entries);
1097 + dict = dictionary_create(DICT_OPTION_SINGLE_THREADED);
1098 + dictionary_unittest_sorting(dict, names, values, entries, &errors);
1099 + dictionary_unittest_run_and_measure_time(dict, "destroying full dictionary", names, values, entries, &errors, dictionary_unittest_destroy);
1100 +
1101 + fprintf(stderr, "\nCreating dictionary single threaded, clone, %zu items\n", entries);
1102 + dict = dictionary_create(DICT_OPTION_SINGLE_THREADED);
1103 + dictionary_unittest_null_dfe(dict, names, values, entries, &errors);
1104 + dictionary_unittest_run_and_measure_time(dict, "destroying full dictionary", names, values, entries, &errors, dictionary_unittest_destroy);
1105 +
1106 + fprintf(stderr, "\nCreating dictionary single threaded, noclone, %zu items\n", entries);
1107 + dict = dictionary_create(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_VALUE_LINK_DONT_CLONE);
1108 + dictionary_unittest_null_dfe(dict, names, values, entries, &errors);
1109 + dictionary_unittest_run_and_measure_time(dict, "destroying full dictionary", names, values, entries, &errors, dictionary_unittest_destroy);
1110 +
1111 + // check reference counters
1112 + {
1113 + fprintf(stderr, "\nTesting reference counters:\n");
1114 + dict = dictionary_create(DICT_OPTION_NONE | DICT_OPTION_NAME_LINK_DONT_CLONE);
1115 + errors += unittest_check_dictionary("", dict, 0, 0, 0, 0, 0);
1116 +
1117 + fprintf(stderr, "\nAdding test item to dictionary and acquiring it\n");
1118 + dictionary_set(dict, "test", "ITEM1", 6);
1119 + DICTIONARY_ITEM *item = (DICTIONARY_ITEM *)dictionary_get_and_acquire_item(dict, "test");
1120 +
1121 + errors += unittest_check_dictionary("", dict, 1, 1, 0, 1, 0);
1122 + errors += unittest_check_item("ACQUIRED", dict, item, "test", "ITEM1", 1, ITEM_FLAG_NONE, true, true, true);
1123 +
1124 + fprintf(stderr, "\nChecking that reference counters are increased:\n");
1125 + void *t;
1126 + dfe_start_read(dict, t) {
1127 + errors += unittest_check_dictionary("", dict, 1, 1, 0, 1, 0);
1128 + errors += unittest_check_item("ACQUIRED TRAVERSAL", dict, item, "test", "ITEM1", 2, ITEM_FLAG_NONE, true, true, true);
1129 + }
1130 + dfe_done(t);
1131 +
1132 + fprintf(stderr, "\nChecking that reference counters are decreased:\n");
1133 + errors += unittest_check_dictionary("", dict, 1, 1, 0, 1, 0);
1134 + errors += unittest_check_item("ACQUIRED TRAVERSAL 2", dict, item, "test", "ITEM1", 1, ITEM_FLAG_NONE, true, true, true);
1135 +
1136 + fprintf(stderr, "\nDeleting the item we have acquired:\n");
1137 + dictionary_del(dict, "test");
1138 +
1139 + errors += unittest_check_dictionary("", dict, 0, 0, 1, 1, 0);
1140 + errors += unittest_check_item("DELETED", dict, item, "test", "ITEM1", 1, ITEM_FLAG_DELETED, false, false, true);
1141 +
1142 + fprintf(stderr, "\nAdding another item with the same name of the item we deleted, while being acquired:\n");
1143 + dictionary_set(dict, "test", "ITEM2", 6);
1144 + errors += unittest_check_dictionary("", dict, 1, 1, 1, 1, 0);
1145 +
1146 + fprintf(stderr, "\nAcquiring the second item:\n");
1147 + DICTIONARY_ITEM *item2 = (DICTIONARY_ITEM *)dictionary_get_and_acquire_item(dict, "test");
1148 + errors += unittest_check_item("FIRST", dict, item, "test", "ITEM1", 1, ITEM_FLAG_DELETED, false, false, true);
1149 + errors += unittest_check_item("SECOND", dict, item2, "test", "ITEM2", 1, ITEM_FLAG_NONE, true, true, true);
1150 + errors += unittest_check_dictionary("", dict, 1, 1, 1, 2, 0);
1151 +
1152 + fprintf(stderr, "\nReleasing the second item (the first is still acquired):\n");
1153 + dictionary_acquired_item_release(dict, (DICTIONARY_ITEM *)item2);
1154 + errors += unittest_check_dictionary("", dict, 1, 1, 1, 1, 0);
1155 + errors += unittest_check_item("FIRST", dict, item, "test", "ITEM1", 1, ITEM_FLAG_DELETED, false, false, true);
1156 + errors += unittest_check_item("SECOND RELEASED", dict, item2, "test", "ITEM2", 0, ITEM_FLAG_NONE, true, true, true);
1157 +
1158 + fprintf(stderr, "\nDeleting the second item (the first is still acquired):\n");
1159 + dictionary_del(dict, "test");
1160 + errors += unittest_check_dictionary("", dict, 0, 0, 1, 1, 0);
1161 + errors += unittest_check_item("ACQUIRED DELETED", dict, item, "test", "ITEM1", 1, ITEM_FLAG_DELETED, false, false, true);
1162 +
1163 + fprintf(stderr, "\nReleasing the first item (which we have already deleted):\n");
1164 + dictionary_acquired_item_release(dict, (DICTIONARY_ITEM *)item);
1165 + dfe_start_write(dict, item) ; dfe_done(item);
1166 + errors += unittest_check_dictionary("", dict, 0, 0, 1, 0, 1);
1167 +
1168 + fprintf(stderr, "\nAdding again the test item to dictionary and acquiring it\n");
1169 + dictionary_set(dict, "test", "ITEM1", 6);
1170 + item = (DICTIONARY_ITEM *)dictionary_get_and_acquire_item(dict, "test");
1171 +
1172 + errors += unittest_check_dictionary("", dict, 1, 1, 0, 1, 0);
1173 + errors += unittest_check_item("RE-ADDITION", dict, item, "test", "ITEM1", 1, ITEM_FLAG_NONE, true, true, true);
1174 +
1175 + fprintf(stderr, "\nDestroying the dictionary while we have acquired an item\n");
1176 + dictionary_destroy(dict);
1177 +
1178 + fprintf(stderr, "Releasing the item (on a destroyed dictionary)\n");
1179 + dictionary_acquired_item_release(dict, (DICTIONARY_ITEM *)item);
1180 + item = NULL;
1181 + dict = NULL;
1182 + }
1183 +
1184 + dictionary_unittest_free_char_pp(names, entries);
1185 + dictionary_unittest_free_char_pp(values, entries);
1186 +
1187 + errors += dictionary_unittest_views();
1188 + errors += dictionary_unittest_threads();
1189 + errors += dictionary_unittest_view_threads();
1190 +
1191 + cleanup_destroyed_dictionaries();
1192 +
1193 + fprintf(stderr, "\n%zu errors found\n", errors);
1194 + return errors ? 1 : 0;
1195 +}
src/libnetdata/dictionary/dictionary.c
+419 -3462
@@ -1,282 +1,24 @@
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 -#define DICTIONARY_INTERNALS
3 +#include "dictionary-internals.h"
4
5 -#include "../libnetdata.h"
6 -
7 -// runtime flags of the dictionary - must be checked with atomics
8 -typedef enum __attribute__ ((__packed__)) {
9 - DICT_FLAG_NONE = 0,
10 - DICT_FLAG_DESTROYED = (1 << 0), // this dictionary has been destroyed
11 -} DICT_FLAGS;
12 -
13 -#define dict_flag_check(dict, flag) (__atomic_load_n(&((dict)->flags), __ATOMIC_RELAXED) & (flag))
14 -#define dict_flag_set(dict, flag) __atomic_or_fetch(&((dict)->flags), flag, __ATOMIC_RELAXED)
15 -#define dict_flag_clear(dict, flag) __atomic_and_fetch(&((dict)->flags), ~(flag), __ATOMIC_RELAXED)
16 -
17 -// flags macros
18 -#define is_dictionary_destroyed(dict) dict_flag_check(dict, DICT_FLAG_DESTROYED)
19 -
20 -// configuration options macros
21 -#define is_dictionary_single_threaded(dict) ((dict)->options & DICT_OPTION_SINGLE_THREADED)
22 -#define is_view_dictionary(dict) ((dict)->master)
23 -#define is_master_dictionary(dict) (!is_view_dictionary(dict))
24 -
25 -typedef enum __attribute__ ((__packed__)) item_options {
26 - ITEM_OPTION_NONE = 0,
27 - ITEM_OPTION_ALLOCATED_NAME = (1 << 0), // the name pointer is a STRING
28 -
29 - // IMPORTANT: This is 1-bit - to add more change ITEM_OPTIONS_BITS
30 -} ITEM_OPTIONS;
31 -
32 -typedef enum __attribute__ ((__packed__)) item_flags {
33 - ITEM_FLAG_NONE = 0,
34 - ITEM_FLAG_DELETED = (1 << 0), // this item is marked deleted, so it is not available for traversal (deleted from the index too)
35 - ITEM_FLAG_BEING_CREATED = (1 << 1), // this item is currently being created - this flag is removed when construction finishes
36 -
37 - // IMPORTANT: This is 8-bit
38 -} ITEM_FLAGS;
39 -
40 -#define item_flag_check(item, flag) (__atomic_load_n(&((item)->flags), __ATOMIC_RELAXED) & (flag))
41 -#define item_flag_set(item, flag) __atomic_or_fetch(&((item)->flags), flag, __ATOMIC_RELAXED)
42 -#define item_flag_clear(item, flag) __atomic_and_fetch(&((item)->flags), ~(flag), __ATOMIC_RELAXED)
43 -
44 -#define item_shared_flag_check(item, flag) (__atomic_load_n(&((item)->shared->flags), __ATOMIC_RELAXED) & (flag))
45 -#define item_shared_flag_set(item, flag) __atomic_or_fetch(&((item)->shared->flags), flag, __ATOMIC_RELAXED)
46 -#define item_shared_flag_clear(item, flag) __atomic_and_fetch(&((item)->shared->flags), ~(flag), __ATOMIC_RELAXED)
47 -
48 -#define REFCOUNT_DELETING (-100)
49 -
50 -#define ITEM_FLAGS_TYPE uint8_t
51 -#define KEY_LEN_TYPE uint32_t
52 -#define VALUE_LEN_TYPE uint32_t
53 -
54 -#define ITEM_OPTIONS_BITS 1
55 -#define KEY_LEN_BITS ((sizeof(KEY_LEN_TYPE) * 8) - (sizeof(ITEM_FLAGS_TYPE) * 8) - ITEM_OPTIONS_BITS)
56 -#define KEY_LEN_MAX ((1 << KEY_LEN_BITS) - 1)
57 -
58 -#define VALUE_LEN_BITS ((sizeof(VALUE_LEN_TYPE) * 8) - (sizeof(ITEM_FLAGS_TYPE) * 8))
59 -#define VALUE_LEN_MAX ((1 << VALUE_LEN_BITS) - 1)
60 -
61 -
62 -/*
63 - * Every item in the dictionary has the following structure.
64 - */
65 -
66 -typedef int32_t REFCOUNT;
67 -
68 -typedef struct dictionary_item_shared {
69 - void *value; // the value of the dictionary item
70 -
71 - // the order of the following items is important!
72 - // The total of their storage should be 64-bits
73 -
74 - REFCOUNT links; // how many links this item has
75 - VALUE_LEN_TYPE value_len:VALUE_LEN_BITS; // the size of the value
76 - ITEM_FLAGS_TYPE flags; // shared flags
77 -} DICTIONARY_ITEM_SHARED;
78 -
79 -struct dictionary_item {
80 -#ifdef NETDATA_INTERNAL_CHECKS
81 - DICTIONARY *dict;
82 - pid_t creator_pid;
83 - pid_t deleter_pid;
84 - pid_t ll_adder_pid;
85 - pid_t ll_remover_pid;
86 -#endif
87 -
88 - DICTIONARY_ITEM_SHARED *shared;
89 -
90 - struct dictionary_item *next; // a double linked list to allow fast insertions and deletions
91 - struct dictionary_item *prev;
92 -
93 - union {
94 - STRING *string_name; // the name of the dictionary item
95 - char *caller_name; // the user supplied string pointer
96 -// void *key_ptr; // binary key pointer
97 - };
98 -
99 - // the order of the following items is important!
100 - // The total of their storage should be 64-bits
101 -
102 - REFCOUNT refcount; // the private reference counter
103 -
104 - KEY_LEN_TYPE key_len:KEY_LEN_BITS; // the size of key indexed (for strings, including the null terminator)
105 - // this is (2^23 - 1) = 8.388.607 bytes max key length.
106 -
107 - ITEM_OPTIONS options:ITEM_OPTIONS_BITS; // permanent configuration options
108 - // (no atomic operations on this - they never change)
109 -
110 - ITEM_FLAGS_TYPE flags; // runtime changing flags for this item (atomic operations on this)
111 - // cannot be a bit field because of atomics.
112 -};
113 -
114 -struct dictionary_hooks {
115 - REFCOUNT links;
116 - usec_t last_master_deletion_us;
117 -
118 - dict_cb_insert_t insert_callback;
119 - void *insert_callback_data;
120 -
121 - dict_cb_conflict_t conflict_callback;
122 - void *conflict_callback_data;
123 -
124 - dict_cb_react_t react_callback;
125 - void *react_callback_data;
126 -
127 - dict_cb_delete_t delete_callback;
128 - void *delelte_callback_data;
129 -};
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
135 -struct dictionary {
136 -#ifdef NETDATA_INTERNAL_CHECKS
137 - const char *creation_function;
138 - const char *creation_file;
139 - size_t creation_line;
140 - pid_t creation_tid;
141 -#endif
142 -
143 - usec_t last_gc_run_us;
144 - DICT_OPTIONS options; // the configuration flags of the dictionary (they never change - no atomics)
145 - DICT_FLAGS flags; // run time flags for the dictionary (they change all the time - atomics needed)
146 -
147 - ARAL *value_aral;
148 -
149 - struct { // support for multiple indexing engines
150 - Pvoid_t JudyHSArray; // the hash table
151 - RW_SPINLOCK rw_spinlock; // protect the index
152 - } index;
153 -
154 - struct {
155 - DICTIONARY_ITEM *list; // the double linked list of all items in the dictionary
156 - RW_SPINLOCK rw_spinlock; // protect the linked-list
157 - pid_t writer_pid; // the gettid() of the writer
158 - uint32_t writer_depth; // nesting of write locks
159 - } items;
160 -
161 - struct dictionary_hooks *hooks; // pointer to external function callbacks to be called at certain points
162 - struct dictionary_stats *stats; // statistics data, when DICT_OPTION_STATS is set
163 -
164 - DICTIONARY *master; // the master dictionary
165 - DICTIONARY *next; // linked list for delayed destruction (garbage collection of whole dictionaries)
166 -
167 - uint32_t version; // the current version of the dictionary
168 - // it is incremented when:
169 - // - item added
170 - // - item removed
171 - // - item value reset
172 - // - conflict callback returns true
173 - // - function dictionary_version_increment() is called
174 -
175 - int32_t entries; // how many items are currently in the index (the linked list may have more)
176 - int32_t referenced_items; // how many items of the dictionary are currently being used by 3rd parties
177 - int32_t pending_deletion_items; // how many items of the dictionary have been deleted, but have not been removed yet
178 -
179 -#ifdef NETDATA_DICTIONARY_VALIDATE_POINTERS
180 - netdata_mutex_t global_pointer_registry_mutex;
181 - Pvoid_t global_pointer_registry;
182 -#endif
183 -};
184 -
185 -// ----------------------------------------------------------------------------
186 -// forward definitions of functions used in reverse order in the code
187 -
188 -static void garbage_collect_pending_deletes(DICTIONARY *dict);
189 -static inline void item_linked_list_remove(DICTIONARY *dict, DICTIONARY_ITEM *item);
190 -static size_t dict_item_free_with_hooks(DICTIONARY *dict, DICTIONARY_ITEM *item);
191 -static inline const char *item_get_name(const DICTIONARY_ITEM *item);
192 -static inline int hashtable_delete_unsafe(DICTIONARY *dict, const char *name, size_t name_len, void *item);
193 -static void item_release(DICTIONARY *dict, DICTIONARY_ITEM *item);
194 -static bool dict_item_set_deleted(DICTIONARY *dict, DICTIONARY_ITEM *item);
195 -
196 -#define RC_ITEM_OK ( 0)
197 -#define RC_ITEM_MARKED_FOR_DELETION (-1) // the item is marked for deletion
198 -#define RC_ITEM_IS_CURRENTLY_BEING_DELETED (-2) // the item is currently being deleted
199 -#define RC_ITEM_IS_CURRENTLY_BEING_CREATED (-3) // the item is currently being deleted
200 -#define RC_ITEM_IS_REFERENCED (-4) // the item is currently referenced
201 -#define item_check_and_acquire(dict, item) (item_check_and_acquire_advanced(dict, item, false) == RC_ITEM_OK)
202 -static int item_check_and_acquire_advanced(DICTIONARY *dict, DICTIONARY_ITEM *item, bool having_index_lock);
203 -#define item_is_not_referenced_and_can_be_removed(dict, item) (item_is_not_referenced_and_can_be_removed_advanced(dict, item) == RC_ITEM_OK)
204 -static inline int item_is_not_referenced_and_can_be_removed_advanced(DICTIONARY *dict, DICTIONARY_ITEM *item);
205 -
206 -// ----------------------------------------------------------------------------
207 -// validate each pointer is indexed once - internal checks only
208 -
209 -#ifdef NETDATA_DICTIONARY_VALIDATE_POINTERS
210 -static inline void pointer_index_init(DICTIONARY *dict __maybe_unused) {
211 - netdata_mutex_init(&dict->global_pointer_registry_mutex);
212 -}
213 -
214 -static inline void pointer_destroy_index(DICTIONARY *dict __maybe_unused) {
215 - netdata_mutex_lock(&dict->global_pointer_registry_mutex);
216 - JudyHSFreeArray(&dict->global_pointer_registry, PJE0);
217 - netdata_mutex_unlock(&dict->global_pointer_registry_mutex);
218 -}
219 -static inline void pointer_add(DICTIONARY *dict __maybe_unused, DICTIONARY_ITEM *item __maybe_unused) {
220 - netdata_mutex_lock(&dict->global_pointer_registry_mutex);
221 - Pvoid_t *PValue = JudyHSIns(&dict->global_pointer_registry, &item, sizeof(void *), PJE0);
222 - if(*PValue != NULL)
223 - fatal("pointer already exists in registry");
224 - *PValue = item;
225 - netdata_mutex_unlock(&dict->global_pointer_registry_mutex);
226 -}
227 -
228 -static inline void pointer_check(DICTIONARY *dict __maybe_unused, DICTIONARY_ITEM *item __maybe_unused) {
229 - netdata_mutex_lock(&dict->global_pointer_registry_mutex);
230 - Pvoid_t *PValue = JudyHSGet(dict->global_pointer_registry, &item, sizeof(void *));
231 - if(PValue == NULL)
232 - fatal("pointer is not found in registry");
233 - netdata_mutex_unlock(&dict->global_pointer_registry_mutex);
234 -}
235 -
236 -static inline void pointer_del(DICTIONARY *dict __maybe_unused, DICTIONARY_ITEM *item __maybe_unused) {
237 - netdata_mutex_lock(&dict->global_pointer_registry_mutex);
238 - int ret = JudyHSDel(&dict->global_pointer_registry, &item, sizeof(void *), PJE0);
239 - if(!ret)
240 - fatal("pointer to be deleted does not exist in registry");
241 - netdata_mutex_unlock(&dict->global_pointer_registry_mutex);
242 -}
243 -#else // !NETDATA_DICTIONARY_VALIDATE_POINTERS
244 -#define pointer_index_init(dict) debug_dummy()
245 -#define pointer_destroy_index(dict) debug_dummy()
246 -#define pointer_add(dict, item) debug_dummy()
247 -#define pointer_check(dict, item) debug_dummy()
248 -#define pointer_del(dict, item) debug_dummy()
249 -#endif // !NETDATA_DICTIONARY_VALIDATE_POINTERS
250 -
12 // ----------------------------------------------------------------------------
252 -// memory statistics
253 -
254 -#ifdef DICT_WITH_STATS
255 -static inline void DICTIONARY_STATS_PLUS_MEMORY(DICTIONARY *dict, size_t key_size, size_t item_size, size_t value_size) {
256 - if(key_size)
257 - __atomic_fetch_add(&dict->stats->memory.index, (long)JUDYHS_INDEX_SIZE_ESTIMATE(key_size), __ATOMIC_RELAXED);
13 +// public locks API
14
259 - if(item_size)
260 - __atomic_fetch_add(&dict->stats->memory.dict, (long)item_size, __ATOMIC_RELAXED);
261 -
262 - if(value_size)
263 - __atomic_fetch_add(&dict->stats->memory.values, (long)value_size, __ATOMIC_RELAXED);
15 +inline void dictionary_write_lock(DICTIONARY *dict) {
16 + ll_recursive_lock(dict, DICTIONARY_LOCK_WRITE);
17 }
18
266 -static inline void DICTIONARY_STATS_MINUS_MEMORY(DICTIONARY *dict, size_t key_size, size_t item_size, size_t value_size) {
267 - if(key_size)
268 - __atomic_fetch_sub(&dict->stats->memory.index, (long)JUDYHS_INDEX_SIZE_ESTIMATE(key_size), __ATOMIC_RELAXED);
269 -
270 - if(item_size)
271 - __atomic_fetch_sub(&dict->stats->memory.dict, (long)item_size, __ATOMIC_RELAXED);
272 -
273 - if(value_size)
274 - __atomic_fetch_sub(&dict->stats->memory.values, (long)value_size, __ATOMIC_RELAXED);
19 +inline void dictionary_write_unlock(DICTIONARY *dict) {
20 + ll_recursive_unlock(dict, DICTIONARY_LOCK_WRITE);
21 }
276 -#else
277 -#define DICTIONARY_STATS_PLUS_MEMORY(dict, key_size, item_size, value_size) do {;} while(0)
278 -#define DICTIONARY_STATS_MINUS_MEMORY(dict, key_size, item_size, value_size) do {;} while(0)
279 -#endif
22
23 // ----------------------------------------------------------------------------
24 // callbacks registration
@@ -380,411 +122,10 @@ void dictionary_version_increment(DICTIONARY *dict) {
122 __atomic_fetch_add(&dict->version, 1, __ATOMIC_RELAXED);
123 }
124
383 -// ----------------------------------------------------------------------------
384 -// internal statistics API
385 -
386 -#ifdef DICT_WITH_STATS
387 -static inline void DICTIONARY_STATS_SEARCHES_PLUS1(DICTIONARY *dict) {
388 - __atomic_fetch_add(&dict->stats->ops.searches, 1, __ATOMIC_RELAXED);
389 -}
390 -#else
391 -#define DICTIONARY_STATS_SEARCHES_PLUS1(dict) do {;} while(0)
392 -#endif
393 -
394 -static inline void DICTIONARY_ENTRIES_PLUS1(DICTIONARY *dict) {
395 -#ifdef DICT_WITH_STATS
396 - // statistics
397 - __atomic_fetch_add(&dict->stats->items.entries, 1, __ATOMIC_RELAXED);
398 - __atomic_fetch_add(&dict->stats->items.referenced, 1, __ATOMIC_RELAXED);
399 - __atomic_fetch_add(&dict->stats->ops.inserts, 1, __ATOMIC_RELAXED);
400 -#endif
401 -
402 - if(unlikely(is_dictionary_single_threaded(dict))) {
403 - dict->version++;
404 - dict->entries++;
405 - dict->referenced_items++;
406 -
407 - }
408 - else {
409 - __atomic_fetch_add(&dict->version, 1, __ATOMIC_RELAXED);
410 - __atomic_fetch_add(&dict->entries, 1, __ATOMIC_RELAXED);
411 - __atomic_fetch_add(&dict->referenced_items, 1, __ATOMIC_RELAXED);
412 - }
413 -}
414 -
415 -static inline void DICTIONARY_ENTRIES_MINUS1(DICTIONARY *dict) {
416 -#ifdef DICT_WITH_STATS
417 - // statistics
418 - __atomic_fetch_add(&dict->stats->ops.deletes, 1, __ATOMIC_RELAXED);
419 - __atomic_fetch_sub(&dict->stats->items.entries, 1, __ATOMIC_RELAXED);
420 -#endif
421 -
422 - size_t entries; (void)entries;
423 - if(unlikely(is_dictionary_single_threaded(dict))) {
424 - dict->version++;
425 - entries = dict->entries--;
426 - }
427 - else {
428 - __atomic_fetch_add(&dict->version, 1, __ATOMIC_RELAXED);
429 - entries = __atomic_fetch_sub(&dict->entries, 1, __ATOMIC_RELAXED);
430 - }
431 -
432 - internal_fatal(entries == 0,
433 - "DICT: negative number of entries in dictionary created from %s() (%zu@%s)",
434 - dict->creation_function,
435 - dict->creation_line,
436 - dict->creation_file);
437 -}
438 -
439 -static inline void DICTIONARY_VALUE_RESETS_PLUS1(DICTIONARY *dict) {
440 -#ifdef DICT_WITH_STATS
441 - __atomic_fetch_add(&dict->stats->ops.resets, 1, __ATOMIC_RELAXED);
442 -#endif
443 -
444 - if(unlikely(is_dictionary_single_threaded(dict)))
445 - dict->version++;
446 - else
447 - __atomic_fetch_add(&dict->version, 1, __ATOMIC_RELAXED);
448 -}
449 -
450 -#ifdef DICT_WITH_STATS
451 -static inline void DICTIONARY_STATS_TRAVERSALS_PLUS1(DICTIONARY *dict) {
452 - __atomic_fetch_add(&dict->stats->ops.traversals, 1, __ATOMIC_RELAXED);
453 -}
454 -static inline void DICTIONARY_STATS_WALKTHROUGHS_PLUS1(DICTIONARY *dict) {
455 - __atomic_fetch_add(&dict->stats->ops.walkthroughs, 1, __ATOMIC_RELAXED);
456 -}
457 -static inline void DICTIONARY_STATS_CHECK_SPINS_PLUS(DICTIONARY *dict, size_t count) {
458 - __atomic_fetch_add(&dict->stats->spin_locks.use_spins, count, __ATOMIC_RELAXED);
459 -}
460 -static inline void DICTIONARY_STATS_INSERT_SPINS_PLUS(DICTIONARY *dict, size_t count) {
461 - __atomic_fetch_add(&dict->stats->spin_locks.insert_spins, count, __ATOMIC_RELAXED);
462 -}
463 -static inline void DICTIONARY_STATS_DELETE_SPINS_PLUS(DICTIONARY *dict, size_t count) {
464 - __atomic_fetch_add(&dict->stats->spin_locks.delete_spins, count, __ATOMIC_RELAXED);
465 -}
466 -static inline void DICTIONARY_STATS_SEARCH_IGNORES_PLUS1(DICTIONARY *dict) {
467 - __atomic_fetch_add(&dict->stats->spin_locks.search_spins, 1, __ATOMIC_RELAXED);
468 -}
469 -static inline void DICTIONARY_STATS_CALLBACK_INSERTS_PLUS1(DICTIONARY *dict) {
470 - __atomic_fetch_add(&dict->stats->callbacks.inserts, 1, __ATOMIC_RELEASE);
471 -}
472 -static inline void DICTIONARY_STATS_CALLBACK_CONFLICTS_PLUS1(DICTIONARY *dict) {
473 - __atomic_fetch_add(&dict->stats->callbacks.conflicts, 1, __ATOMIC_RELEASE);
474 -}
475 -static inline void DICTIONARY_STATS_CALLBACK_REACTS_PLUS1(DICTIONARY *dict) {
476 - __atomic_fetch_add(&dict->stats->callbacks.reacts, 1, __ATOMIC_RELEASE);
477 -}
478 -static inline void DICTIONARY_STATS_CALLBACK_DELETES_PLUS1(DICTIONARY *dict) {
479 - __atomic_fetch_add(&dict->stats->callbacks.deletes, 1, __ATOMIC_RELEASE);
480 -}
481 -static inline void DICTIONARY_STATS_GARBAGE_COLLECTIONS_PLUS1(DICTIONARY *dict) {
482 - __atomic_fetch_add(&dict->stats->ops.garbage_collections, 1, __ATOMIC_RELAXED);
483 -}
484 -static inline void DICTIONARY_STATS_DICT_CREATIONS_PLUS1(DICTIONARY *dict) {
485 - __atomic_fetch_add(&dict->stats->dictionaries.active, 1, __ATOMIC_RELAXED);
486 - __atomic_fetch_add(&dict->stats->ops.creations, 1, __ATOMIC_RELAXED);
487 -}
488 -static inline void DICTIONARY_STATS_DICT_DESTRUCTIONS_PLUS1(DICTIONARY *dict) {
489 - __atomic_fetch_sub(&dict->stats->dictionaries.active, 1, __ATOMIC_RELAXED);
490 - __atomic_fetch_add(&dict->stats->ops.destructions, 1, __ATOMIC_RELAXED);
491 -}
492 -static inline void DICTIONARY_STATS_DICT_DESTROY_QUEUED_PLUS1(DICTIONARY *dict) {
493 - __atomic_fetch_add(&dict->stats->dictionaries.deleted, 1, __ATOMIC_RELAXED);
494 -}
495 -static inline void DICTIONARY_STATS_DICT_DESTROY_QUEUED_MINUS1(DICTIONARY *dict) {
496 - __atomic_fetch_sub(&dict->stats->dictionaries.deleted, 1, __ATOMIC_RELAXED);
497 -}
498 -static inline void DICTIONARY_STATS_DICT_FLUSHES_PLUS1(DICTIONARY *dict) {
499 - __atomic_fetch_add(&dict->stats->ops.flushes, 1, __ATOMIC_RELAXED);
500 -}
501 -#else
502 -#define DICTIONARY_STATS_TRAVERSALS_PLUS1(dict) do {;} while(0)
503 -#define DICTIONARY_STATS_WALKTHROUGHS_PLUS1(dict) do {;} while(0)
504 -#define DICTIONARY_STATS_CHECK_SPINS_PLUS(dict, count) do {;} while(0)
505 -#define DICTIONARY_STATS_INSERT_SPINS_PLUS(dict, count) do {;} while(0)
506 -#define DICTIONARY_STATS_DELETE_SPINS_PLUS(dict, count) do {;} while(0)
507 -#define DICTIONARY_STATS_SEARCH_IGNORES_PLUS1(dict) do {;} while(0)
508 -#define DICTIONARY_STATS_CALLBACK_INSERTS_PLUS1(dict) do {;} while(0)
509 -#define DICTIONARY_STATS_CALLBACK_CONFLICTS_PLUS1(dict) do {;} while(0)
510 -#define DICTIONARY_STATS_CALLBACK_REACTS_PLUS1(dict) do {;} while(0)
511 -#define DICTIONARY_STATS_CALLBACK_DELETES_PLUS1(dict) do {;} while(0)
512 -#define DICTIONARY_STATS_GARBAGE_COLLECTIONS_PLUS1(dict) do {;} while(0)
513 -#define DICTIONARY_STATS_DICT_CREATIONS_PLUS1(dict) do {;} while(0)
514 -#define DICTIONARY_STATS_DICT_DESTRUCTIONS_PLUS1(dict) do {;} while(0)
515 -#define DICTIONARY_STATS_DICT_DESTROY_QUEUED_PLUS1(dict) do {;} while(0)
516 -#define DICTIONARY_STATS_DICT_DESTROY_QUEUED_MINUS1(dict) do {;} while(0)
517 -#define DICTIONARY_STATS_DICT_FLUSHES_PLUS1(dict) do {;} while(0)
518 -#endif
519 -
520 -static inline void DICTIONARY_REFERENCED_ITEMS_PLUS1(DICTIONARY *dict) {
521 -#ifdef DICT_WITH_STATS
522 - __atomic_fetch_add(&dict->stats->items.referenced, 1, __ATOMIC_RELAXED);
523 -#endif
524 -
525 - if(unlikely(is_dictionary_single_threaded(dict)))
526 - ++dict->referenced_items;
527 - else
528 - __atomic_add_fetch(&dict->referenced_items, 1, __ATOMIC_RELAXED);
529 -}
530 -
531 -static inline void DICTIONARY_REFERENCED_ITEMS_MINUS1(DICTIONARY *dict) {
532 -#ifdef DICT_WITH_STATS
533 - __atomic_fetch_sub(&dict->stats->items.referenced, 1, __ATOMIC_RELAXED);
534 -#endif
535 -
536 - long int referenced_items; (void)referenced_items;
537 - if(unlikely(is_dictionary_single_threaded(dict)))
538 - referenced_items = --dict->referenced_items;
539 - else
540 - referenced_items = __atomic_sub_fetch(&dict->referenced_items, 1, __ATOMIC_SEQ_CST);
541 -
542 - internal_fatal(referenced_items < 0,
543 - "DICT: negative number of referenced items (%ld) in dictionary created from %s() (%zu@%s)",
544 - referenced_items,
545 - dict->creation_function,
546 - dict->creation_line,
547 - dict->creation_file);
548 -}
549 -
550 -static inline void DICTIONARY_PENDING_DELETES_PLUS1(DICTIONARY *dict) {
551 -#ifdef DICT_WITH_STATS
552 - __atomic_fetch_add(&dict->stats->items.pending_deletion, 1, __ATOMIC_RELAXED);
553 -#endif
554 -
555 - if(unlikely(is_dictionary_single_threaded(dict)))
556 - ++dict->pending_deletion_items;
557 - else
558 - __atomic_add_fetch(&dict->pending_deletion_items, 1, __ATOMIC_RELEASE);
559 -}
560 -
561 -static inline long int DICTIONARY_PENDING_DELETES_MINUS1(DICTIONARY *dict) {
562 -#ifdef DICT_WITH_STATS
563 - __atomic_fetch_sub(&dict->stats->items.pending_deletion, 1, __ATOMIC_RELEASE);
564 -#endif
565 -
566 - if(unlikely(is_dictionary_single_threaded(dict)))
567 - return --dict->pending_deletion_items;
568 - else
569 - return __atomic_sub_fetch(&dict->pending_deletion_items, 1, __ATOMIC_ACQUIRE);
570 -}
571 -
572 -static inline long int DICTIONARY_PENDING_DELETES_GET(DICTIONARY *dict) {
573 - if(unlikely(is_dictionary_single_threaded(dict)))
574 - return dict->pending_deletion_items;
575 - else
576 - return __atomic_load_n(&dict->pending_deletion_items, __ATOMIC_SEQ_CST);
577 -}
578 -
579 -static inline REFCOUNT DICTIONARY_ITEM_REFCOUNT_GET(DICTIONARY *dict, DICTIONARY_ITEM *item) {
580 - if(unlikely(dict && is_dictionary_single_threaded(dict))) // this is an exception, dict can be null
581 - return item->refcount;
582 - else
583 - return (REFCOUNT)__atomic_load_n(&item->refcount, __ATOMIC_ACQUIRE);
584 -}
585 -
586 -static inline REFCOUNT DICTIONARY_ITEM_REFCOUNT_GET_SOLE(DICTIONARY_ITEM *item) {
587 - return (REFCOUNT)__atomic_load_n(&item->refcount, __ATOMIC_ACQUIRE);
588 -}
589 -
590 -// ----------------------------------------------------------------------------
591 -// callbacks execution
592 -
593 -static void dictionary_execute_insert_callback(DICTIONARY *dict, DICTIONARY_ITEM *item, void *constructor_data) {
594 - if(likely(!dict->hooks || !dict->hooks->insert_callback))
595 - return;
596 -
597 - if(unlikely(is_view_dictionary(dict)))
598 - fatal("DICTIONARY: called %s() on a view.", __FUNCTION__ );
599 -
600 - internal_error(false,
601 - "DICTIONARY: Running insert callback on item '%s' of dictionary created from %s() %zu@%s.",
602 - item_get_name(item),
603 - dict->creation_function,
604 - dict->creation_line,
605 - dict->creation_file);
606 -
607 - dict->hooks->insert_callback(item, item->shared->value, constructor_data?constructor_data:dict->hooks->insert_callback_data);
608 - DICTIONARY_STATS_CALLBACK_INSERTS_PLUS1(dict);
609 -}
610 -
611 -static bool dictionary_execute_conflict_callback(DICTIONARY *dict, DICTIONARY_ITEM *item, void *new_value, void *constructor_data) {
612 - if(likely(!dict->hooks || !dict->hooks->conflict_callback))
613 - return false;
614 -
615 - if(unlikely(is_view_dictionary(dict)))
616 - fatal("DICTIONARY: called %s() on a view.", __FUNCTION__ );
617 -
618 - internal_error(false,
619 - "DICTIONARY: Running conflict callback on item '%s' of dictionary created from %s() %zu@%s.",
620 - item_get_name(item),
621 - dict->creation_function,
622 - dict->creation_line,
623 - dict->creation_file);
624 -
625 - bool ret = dict->hooks->conflict_callback(
626 - item, item->shared->value, new_value,
627 - constructor_data ? constructor_data : dict->hooks->conflict_callback_data);
628 -
629 - DICTIONARY_STATS_CALLBACK_CONFLICTS_PLUS1(dict);
630 -
631 - return ret;
632 -}
633 -
634 -static void dictionary_execute_react_callback(DICTIONARY *dict, DICTIONARY_ITEM *item, void *constructor_data) {
635 - if(likely(!dict->hooks || !dict->hooks->react_callback))
636 - return;
637 -
638 - if(unlikely(is_view_dictionary(dict)))
639 - fatal("DICTIONARY: called %s() on a view.", __FUNCTION__ );
640 -
641 - internal_error(false,
642 - "DICTIONARY: Running react callback on item '%s' of dictionary created from %s() %zu@%s.",
643 - item_get_name(item),
644 - dict->creation_function,
645 - dict->creation_line,
646 - dict->creation_file);
647 -
648 - dict->hooks->react_callback(item, item->shared->value,
649 - constructor_data?constructor_data:dict->hooks->react_callback_data);
650 -
651 - DICTIONARY_STATS_CALLBACK_REACTS_PLUS1(dict);
652 -}
653 -
654 -static void dictionary_execute_delete_callback(DICTIONARY *dict, DICTIONARY_ITEM *item) {
655 - if(likely(!dict->hooks || !dict->hooks->delete_callback))
656 - return;
657 -
658 - // We may execute delete callback on items deleted from a view,
659 - // because we may have references to it, after the master is gone
660 - // so, the shared structure will remain until the last reference is released.
661 -
662 - internal_error(false,
663 - "DICTIONARY: Running delete callback on item '%s' of dictionary created from %s() %zu@%s.",
664 - item_get_name(item),
665 - dict->creation_function,
666 - dict->creation_line,
667 - dict->creation_file);
668 -
669 - dict->hooks->delete_callback(item, item->shared->value, dict->hooks->delelte_callback_data);
670 -
671 - DICTIONARY_STATS_CALLBACK_DELETES_PLUS1(dict);
672 -}
673 -
674 -// ----------------------------------------------------------------------------
675 -// dictionary locks
676 -
677 -static inline size_t dictionary_locks_init(DICTIONARY *dict) {
678 - if(likely(!is_dictionary_single_threaded(dict))) {
679 - rw_spinlock_init(&dict->index.rw_spinlock);
680 - rw_spinlock_init(&dict->items.rw_spinlock);
681 - }
682 -
683 - return 0;
684 -}
685 -
686 -static inline size_t dictionary_locks_destroy(DICTIONARY *dict __maybe_unused) {
687 - return 0;
688 -}
689 -
690 -static inline void ll_recursive_lock_set_thread_as_writer(DICTIONARY *dict) {
691 - pid_t expected = 0, desired = gettid();
692 - if(!__atomic_compare_exchange_n(&dict->items.writer_pid, &expected, desired, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED))
693 - fatal("DICTIONARY: Cannot set thread %d as exclusive writer, expected %d, desired %d, found %d.", gettid(), expected, desired, __atomic_load_n(&dict->items.writer_pid, __ATOMIC_RELAXED));
694 -}
695 -
696 -static inline void ll_recursive_unlock_unset_thread_writer(DICTIONARY *dict) {
697 - pid_t expected = gettid(), desired = 0;
698 - if(!__atomic_compare_exchange_n(&dict->items.writer_pid, &expected, desired, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED))
699 - fatal("DICTIONARY: Cannot unset thread %d as exclusive writer, expected %d, desired %d, found %d.", gettid(), expected, desired, __atomic_load_n(&dict->items.writer_pid, __ATOMIC_RELAXED));
700 -}
701 -
702 -static inline bool ll_recursive_lock_is_thread_the_writer(DICTIONARY *dict) {
703 - pid_t tid = gettid();
704 - return tid > 0 && tid == __atomic_load_n(&dict->items.writer_pid, __ATOMIC_RELAXED);
705 -}
706 -
707 -static inline void ll_recursive_lock(DICTIONARY *dict, char rw) {
708 - if(unlikely(is_dictionary_single_threaded(dict)))
709 - return;
710 -
711 - if(ll_recursive_lock_is_thread_the_writer(dict)) {
712 - dict->items.writer_depth++;
713 - return;
714 - }
715 -
716 - if(rw == DICTIONARY_LOCK_READ || rw == DICTIONARY_LOCK_REENTRANT || rw == 'R') {
717 - // read lock
718 - rw_spinlock_read_lock(&dict->items.rw_spinlock);
719 - }
720 - else {
721 - // write lock
722 - rw_spinlock_write_lock(&dict->items.rw_spinlock);
723 - ll_recursive_lock_set_thread_as_writer(dict);
724 - }
725 -}
726 -
727 -static inline void ll_recursive_unlock(DICTIONARY *dict, char rw) {
728 - if(unlikely(is_dictionary_single_threaded(dict)))
729 - return;
730 -
731 - if(ll_recursive_lock_is_thread_the_writer(dict) && dict->items.writer_depth > 0) {
732 - dict->items.writer_depth--;
733 - return;
734 - }
735 -
736 - if(rw == DICTIONARY_LOCK_READ || rw == DICTIONARY_LOCK_REENTRANT || rw == 'R') {
737 - // read unlock
738 -
739 - rw_spinlock_read_unlock(&dict->items.rw_spinlock);
740 - }
741 - else {
742 - // write unlock
743 -
744 - ll_recursive_unlock_unset_thread_writer(dict);
745 -
746 - rw_spinlock_write_unlock(&dict->items.rw_spinlock);
747 - }
748 -}
749 -
750 -inline void dictionary_write_lock(DICTIONARY *dict) {
751 - ll_recursive_lock(dict, DICTIONARY_LOCK_WRITE);
752 -}
753 -inline void dictionary_write_unlock(DICTIONARY *dict) {
754 - ll_recursive_unlock(dict, DICTIONARY_LOCK_WRITE);
755 -}
756 -
757 -static inline void dictionary_index_lock_rdlock(DICTIONARY *dict) {
758 - if(unlikely(is_dictionary_single_threaded(dict)))
759 - return;
760 -
761 - rw_spinlock_read_lock(&dict->index.rw_spinlock);
762 -}
763 -
764 -static inline void dictionary_index_rdlock_unlock(DICTIONARY *dict) {
765 - if(unlikely(is_dictionary_single_threaded(dict)))
766 - return;
767 -
768 - rw_spinlock_read_unlock(&dict->index.rw_spinlock);
769 -}
770 -
771 -static inline void dictionary_index_lock_wrlock(DICTIONARY *dict) {
772 - if(unlikely(is_dictionary_single_threaded(dict)))
773 - return;
774 -
775 - rw_spinlock_write_lock(&dict->index.rw_spinlock);
776 -}
777 -static inline void dictionary_index_wrlock_unlock(DICTIONARY *dict) {
778 - if(unlikely(is_dictionary_single_threaded(dict)))
779 - return;
780 -
781 - rw_spinlock_write_unlock(&dict->index.rw_spinlock);
782 -}
783 -
125 // ----------------------------------------------------------------------------
126 // items garbage collector
127
787 -static void garbage_collect_pending_deletes(DICTIONARY *dict) {
128 +void garbage_collect_pending_deletes(DICTIONARY *dict) {
129 usec_t last_master_deletion_us = dict->hooks?__atomic_load_n(&dict->hooks->last_master_deletion_us, __ATOMIC_RELAXED):0;
130 usec_t last_gc_run_us = __atomic_load_n(&dict->last_gc_run_us, __ATOMIC_RELAXED);
131
@@ -856,2955 +197,571 @@ void dictionary_garbage_collect(DICTIONARY *dict) {
197 }
198
199 // ----------------------------------------------------------------------------
859 -// reference counters
860 -
861 -static inline size_t reference_counter_init(DICTIONARY *dict __maybe_unused) {
862 - // allocate memory required for reference counters
863 - // return number of bytes
864 - return 0;
865 -}
866 -
867 -static inline size_t reference_counter_free(DICTIONARY *dict __maybe_unused) {
868 - // free memory required for reference counters
869 - // return number of bytes
870 - return 0;
871 -}
200
873 -static void item_acquire(DICTIONARY *dict, DICTIONARY_ITEM *item) {
874 - REFCOUNT refcount;
875 -
876 - if(unlikely(is_dictionary_single_threaded(dict)))
877 - refcount = ++item->refcount;
201 +void dictionary_static_items_aral_init(void) {
202 + static SPINLOCK spinlock;
203
879 - else
880 - // increment the refcount
881 - refcount = __atomic_add_fetch(&item->refcount, 1, __ATOMIC_SEQ_CST);
204 + if(unlikely(!dict_items_aral || !dict_shared_items_aral)) {
205 + spinlock_lock(&spinlock);
206
207 + // we have to check again
208 + if(!dict_items_aral)
209 + dict_items_aral = aral_create(
210 + "dict-items",
211 + sizeof(DICTIONARY_ITEM),
212 + 0,
213 + 65536,
214 + aral_by_size_statistics(),
215 + NULL, NULL, false, false);
216
884 - if(refcount <= 0) {
885 - internal_error(
886 - true,
887 - "DICTIONARY: attempted to acquire item which is deleted (refcount = %d): "
888 - "'%s' on dictionary created by %s() (%zu@%s)",
889 - refcount - 1,
890 - item_get_name(item),
891 - dict->creation_function,
892 - dict->creation_line,
893 - dict->creation_file);
894 -
895 - fatal(
896 - "DICTIONARY: request to acquire item '%s', which is deleted (refcount = %d)!",
897 - item_get_name(item),
898 - refcount - 1);
899 - }
900 -
901 - if(refcount == 1) {
902 - // referenced items counts number of unique items referenced
903 - // so, we increase it only when refcount == 1
904 - DICTIONARY_REFERENCED_ITEMS_PLUS1(dict);
217 + // we have to check again
218 + if(!dict_shared_items_aral)
219 + dict_shared_items_aral = aral_create(
220 + "dict-shared-items",
221 + sizeof(DICTIONARY_ITEM_SHARED),
222 + 0,
223 + 65536,
224 + aral_by_size_statistics(),
225 + NULL, NULL, false, false);
226
906 - // if this is a deleted item, but the counter increased to 1
907 - // we need to remove it from the pending items to delete
908 - if(item_flag_check(item, ITEM_FLAG_DELETED))
909 - DICTIONARY_PENDING_DELETES_MINUS1(dict);
227 + spinlock_unlock(&spinlock);
228 }
229 }
230
913 -static void item_release(DICTIONARY *dict, DICTIONARY_ITEM *item) {
914 - // this function may be called without any lock on the dictionary
915 - // or even when someone else has 'write' lock on the dictionary
916 -
917 - bool is_deleted;
918 - REFCOUNT refcount;
919 -
920 - if(unlikely(is_dictionary_single_threaded(dict))) {
921 - is_deleted = item->flags & ITEM_FLAG_DELETED;
922 - refcount = --item->refcount;
923 - }
924 - else {
925 - // get the flags before decrementing any reference counters
926 - // (the other way around may lead to use-after-free)
927 - is_deleted = item_flag_check(item, ITEM_FLAG_DELETED);
928 -
929 - // decrement the refcount
930 - refcount = __atomic_sub_fetch(&item->refcount, 1, __ATOMIC_RELEASE);
931 - }
932 -
933 - if(refcount < 0) {
934 - internal_error(
935 - true,
936 - "DICTIONARY: attempted to release item without references (refcount = %d): "
937 - "'%s' on dictionary created by %s() (%zu@%s)",
938 - refcount + 1,
939 - item_get_name(item),
940 - dict->creation_function,
941 - dict->creation_line,
942 - dict->creation_file);
231 +// ----------------------------------------------------------------------------
232 +// delayed destruction of dictionaries
233
944 - fatal(
945 - "DICTIONARY: attempted to release item '%s' without references (refcount = %d)",
946 - item_get_name(item),
947 - refcount + 1);
948 - }
234 +static bool dictionary_free_all_resources(DICTIONARY *dict, size_t *mem, bool force) {
235 + if(mem)
236 + *mem = 0;
237
950 - if(refcount == 0) {
238 + if(!force && dictionary_referenced_items(dict))
239 + return false;
240
952 - if(is_deleted)
953 - DICTIONARY_PENDING_DELETES_PLUS1(dict);
241 + size_t dict_size = 0, counted_items = 0, item_size = 0, index_size = 0;
242 + (void)counted_items;
243
955 - // referenced items counts number of unique items referenced
956 - // so, we decrease it only when refcount == 0
957 - DICTIONARY_REFERENCED_ITEMS_MINUS1(dict);
958 - }
959 -}
244 +#ifdef NETDATA_INTERNAL_CHECKS
245 + long int entries = dict->entries;
246 + long int referenced_items = dict->referenced_items;
247 + long int pending_deletion_items = dict->pending_deletion_items;
248 + const char *creation_function = dict->creation_function;
249 + const char *creation_file = dict->creation_file;
250 + size_t creation_line = dict->creation_line;
251 +#endif
252
961 -static int item_check_and_acquire_advanced(DICTIONARY *dict, DICTIONARY_ITEM *item, bool having_index_lock) {
962 - size_t spins = 0;
963 - REFCOUNT refcount, desired;
253 + // destroy the index
254 + dictionary_index_lock_wrlock(dict);
255 + index_size += hashtable_destroy_unsafe(dict);
256 + dictionary_index_wrlock_unlock(dict);
257
965 - int ret = RC_ITEM_OK;
258 + ll_recursive_lock(dict, DICTIONARY_LOCK_WRITE);
259 + DICTIONARY_ITEM *item = dict->items.list;
260 + while (item) {
261 + // cache item->next
262 + // because we are going to free item
263 + DICTIONARY_ITEM *item_next = item->next;
264
967 - refcount = DICTIONARY_ITEM_REFCOUNT_GET(dict, item);
265 + item_size += dict_item_free_with_hooks(dict, item);
266 + item = item_next;
267
969 - do {
970 - spins++;
268 + // to speed up destruction, we don't unlink the item
269 + // from the linked-list here
270
972 - if(refcount < 0) {
973 - // we can't use this item
974 - ret = RC_ITEM_IS_CURRENTLY_BEING_DELETED;
975 - break;
976 - }
271 + counted_items++;
272 + }
273 + dict->items.list = NULL;
274 + ll_recursive_unlock(dict, DICTIONARY_LOCK_WRITE);
275
978 - if(item_flag_check(item, ITEM_FLAG_DELETED)) {
979 - // we can't use this item
980 - ret = RC_ITEM_MARKED_FOR_DELETION;
981 - break;
982 - }
276 + dict_size += dictionary_locks_destroy(dict);
277 + dict_size += reference_counter_free(dict);
278 + dict_size += dictionary_hooks_free(dict);
279 + dict_size += sizeof(DICTIONARY);
280 + DICTIONARY_STATS_MINUS_MEMORY(dict, 0, sizeof(DICTIONARY), 0);
281
984 - desired = refcount + 1;
985 -
986 - } while(!__atomic_compare_exchange_n(&item->refcount, &refcount, desired, false, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED));
987 -
988 - // if ret == ITEM_OK, we acquired the item
989 -
990 - if(ret == RC_ITEM_OK) {
991 - if (unlikely(is_view_dictionary(dict) &&
992 - item_shared_flag_check(item, ITEM_FLAG_DELETED) &&
993 - !item_flag_check(item, ITEM_FLAG_DELETED))) {
994 - // but, we can't use this item
995 -
996 - if (having_index_lock) {
997 - // delete it from the hashtable
998 - if(hashtable_delete_unsafe(dict, item_get_name(item), item->key_len, item) == 0)
999 - netdata_log_error("DICTIONARY: INTERNAL ERROR VIEW: tried to delete item with name '%s', "
1000 - "name_len %u that is not in the index",
1001 - item_get_name(item), (KEY_LEN_TYPE)(item->key_len));
1002 - else
1003 - pointer_del(dict, item);
1004 -
1005 - // mark it in our dictionary as deleted too,
1006 - // this is safe to be done here, because we have got
1007 - // a reference counter on item
1008 - dict_item_set_deleted(dict, item);
1009 -
1010 - // decrement the refcount we incremented above
1011 - if (__atomic_sub_fetch(&item->refcount, 1, __ATOMIC_RELEASE) == 0) {
1012 - // this is a deleted item, and we are the last one
1013 - DICTIONARY_PENDING_DELETES_PLUS1(dict);
1014 - }
1015 -
1016 - // do not touch the item below this point
1017 - } else {
1018 - // this is traversal / walkthrough
1019 - // decrement the refcount we incremented above
1020 - __atomic_sub_fetch(&item->refcount, 1, __ATOMIC_RELEASE);
1021 - }
282 + if(dict->value_aral)
283 + aral_by_size_release(dict->value_aral);
284
1023 - return RC_ITEM_MARKED_FOR_DELETION;
1024 - }
285 + freez(dict);
286
1026 - if(desired == 1)
1027 - DICTIONARY_REFERENCED_ITEMS_PLUS1(dict);
1028 - }
287 + internal_error(
288 + false,
289 + "DICTIONARY: Freed dictionary created from %s() %zu@%s, having %ld (counted %zu) entries, %ld referenced, %ld pending deletion, total freed memory: %zu bytes (sizeof(dict) = %zu, sizeof(item) = %zu).",
290 + creation_function,
291 + creation_line,
292 + creation_file,
293 + entries, counted_items, referenced_items, pending_deletion_items,
294 + dict_size + item_size, sizeof(DICTIONARY), sizeof(DICTIONARY_ITEM) + sizeof(DICTIONARY_ITEM_SHARED));
295
1030 - if(unlikely(spins > 1))
1031 - DICTIONARY_STATS_CHECK_SPINS_PLUS(dict, spins - 1);
296 + if(mem)
297 + *mem = dict_size + item_size + index_size;
298
1033 - return ret;
299 + return true;
300 }
301
1036 -// if a dictionary item can be deleted, return true, otherwise return false
1037 -// we use the private reference counter
1038 -static inline int item_is_not_referenced_and_can_be_removed_advanced(DICTIONARY *dict, DICTIONARY_ITEM *item) {
1039 - // if we can set refcount to REFCOUNT_DELETING, we can delete this item
302 +netdata_mutex_t dictionaries_waiting_to_be_destroyed_mutex = NETDATA_MUTEX_INITIALIZER;
303 +static DICTIONARY *dictionaries_waiting_to_be_destroyed = NULL;
304
1041 - size_t spins = 0;
1042 - REFCOUNT refcount, desired = REFCOUNT_DELETING;
305 +static void dictionary_queue_for_destruction(DICTIONARY *dict) {
306 + if(is_dictionary_destroyed(dict))
307 + return;
308
1044 - int ret = RC_ITEM_OK;
309 + DICTIONARY_STATS_DICT_DESTROY_QUEUED_PLUS1(dict);
310 + dict_flag_set(dict, DICT_FLAG_DESTROYED);
311
1046 - refcount = DICTIONARY_ITEM_REFCOUNT_GET(dict, item);
312 + netdata_mutex_lock(&dictionaries_waiting_to_be_destroyed_mutex);
313
1048 - do {
1049 - spins++;
314 + dict->next = dictionaries_waiting_to_be_destroyed;
315 + dictionaries_waiting_to_be_destroyed = dict;
316
1051 - if(refcount < 0) {
1052 - // we can't use this item
1053 - ret = RC_ITEM_IS_CURRENTLY_BEING_DELETED;
1054 - break;
1055 - }
317 + netdata_mutex_unlock(&dictionaries_waiting_to_be_destroyed_mutex);
318 +}
319
1057 - if(refcount > 0) {
1058 - // we can't delete this
1059 - ret = RC_ITEM_IS_REFERENCED;
1060 - break;
1061 - }
320 +void cleanup_destroyed_dictionaries(void) {
321 + if(!dictionaries_waiting_to_be_destroyed)
322 + return;
323
1063 - if(item_flag_check(item, ITEM_FLAG_BEING_CREATED)) {
1064 - // we can't use this item
1065 - ret = RC_ITEM_IS_CURRENTLY_BEING_CREATED;
1066 - break;
1067 - }
1068 - } while(!__atomic_compare_exchange_n(&item->refcount, &refcount, desired, false, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED));
324 + netdata_mutex_lock(&dictionaries_waiting_to_be_destroyed_mutex);
325 +
326 + DICTIONARY *dict, *last = NULL, *next = NULL;
327 + for(dict = dictionaries_waiting_to_be_destroyed; dict ; dict = next) {
328 + next = dict->next;
329
330 #ifdef NETDATA_INTERNAL_CHECKS
1071 - if(ret == RC_ITEM_OK)
1072 - item->deleter_pid = gettid();
331 + size_t line = dict->creation_line;
332 + const char *file = dict->creation_file;
333 + const char *function = dict->creation_function;
334 + pid_t pid = dict->creation_tid;
335 #endif
336
1075 - if(unlikely(spins > 1))
1076 - DICTIONARY_STATS_DELETE_SPINS_PLUS(dict, spins - 1);
337 + DICTIONARY_STATS_DICT_DESTROY_QUEUED_MINUS1(dict);
338 + if(dictionary_free_all_resources(dict, NULL, false)) {
339
1078 - return ret;
1079 -}
340 + internal_error(
341 + true,
342 + "DICTIONARY: freed dictionary with delayed destruction, created from %s() %zu@%s pid %d.",
343 + function, line, file, pid);
344
1081 -// if a dictionary item can be freed, return true, otherwise return false
1082 -// we use the shared reference counter
1083 -static inline bool item_shared_release_and_check_if_it_can_be_freed(DICTIONARY *dict __maybe_unused, DICTIONARY_ITEM *item) {
1084 - // if we can set refcount to REFCOUNT_DELETING, we can delete this item
345 + if(last) last->next = next;
346 + else dictionaries_waiting_to_be_destroyed = next;
347 + }
348 + else {
349
1086 - REFCOUNT links = __atomic_sub_fetch(&item->shared->links, 1, __ATOMIC_RELEASE);
1087 - if(links == 0 && __atomic_compare_exchange_n(&item->shared->links, &links, REFCOUNT_DELETING, false, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) {
350 + internal_error(
351 + true,
352 + "DICTIONARY: cannot free dictionary with delayed destruction, created from %s() %zu@%s pid %d.",
353 + function, line, file, pid);
354
1089 - // we can delete it
1090 - return true;
355 + DICTIONARY_STATS_DICT_DESTROY_QUEUED_PLUS1(dict);
356 + last = dict;
357 + }
358 }
359
1093 - // we can't delete it
1094 - return false;
360 + netdata_mutex_unlock(&dictionaries_waiting_to_be_destroyed_mutex);
361 }
362
1097 -
363 // ----------------------------------------------------------------------------
1099 -// hash table operations
1100 -
1101 -static size_t hashtable_init_unsafe(DICTIONARY *dict) {
1102 - dict->index.JudyHSArray = NULL;
1103 - return 0;
1104 -}
1105 -
1106 -static size_t hashtable_destroy_unsafe(DICTIONARY *dict) {
1107 - if(unlikely(!dict->index.JudyHSArray)) return 0;
1108 -
1109 - pointer_destroy_index(dict);
364 +// API internal checks
365
1111 - JError_t J_Error;
1112 - Word_t ret = JudyHSFreeArray(&dict->index.JudyHSArray, &J_Error);
1113 - if(unlikely(ret == (Word_t) JERR)) {
1114 - netdata_log_error("DICTIONARY: Cannot destroy JudyHS, JU_ERRNO_* == %u, ID == %d",
1115 - JU_ERRNO(&J_Error), JU_ERRID(&J_Error));
366 +#ifdef NETDATA_INTERNAL_CHECKS
367 +#define api_internal_check(dict, item, allow_null_dict, allow_null_item) api_internal_check_with_trace(dict, item, __FUNCTION__, allow_null_dict, allow_null_item)
368 +static inline void api_internal_check_with_trace(DICTIONARY *dict, DICTIONARY_ITEM *item, const char *function, bool allow_null_dict, bool allow_null_item) {
369 + if(!allow_null_dict && !dict) {
370 + internal_error(
371 + item,
372 + "DICTIONARY: attempted to %s() with a NULL dictionary, passing an item created from %s() %zu@%s.",
373 + function,
374 + item->dict->creation_function,
375 + item->dict->creation_line,
376 + item->dict->creation_file);
377 + fatal("DICTIONARY: attempted to %s() but dict is NULL", function);
378 }
379
1118 - netdata_log_debug(D_DICTIONARY, "Dictionary: hash table freed %lu bytes", ret);
1119 -
1120 - dict->index.JudyHSArray = NULL;
1121 - return (size_t)ret;
1122 -}
1123 -
1124 -static inline void **hashtable_insert_unsafe(DICTIONARY *dict, const char *name, size_t name_len) {
1125 - JError_t J_Error;
1126 - Pvoid_t *Rc = JudyHSIns(&dict->index.JudyHSArray, (void *)name, name_len, &J_Error);
1127 - if (unlikely(Rc == PJERR)) {
1128 - netdata_log_error("DICTIONARY: Cannot insert entry with name '%s' to JudyHS, JU_ERRNO_* == %u, ID == %d",
1129 - name, JU_ERRNO(&J_Error), JU_ERRID(&J_Error));
380 + if(!allow_null_item && !item) {
381 + internal_error(
382 + true,
383 + "DICTIONARY: attempted to %s() without an item on a dictionary created from %s() %zu@%s.",
384 + function,
385 + dict?dict->creation_function:"unknown",
386 + dict?dict->creation_line:0,
387 + dict?dict->creation_file:"unknown");
388 + fatal("DICTIONARY: attempted to %s() but item is NULL", function);
389 }
390
1132 - // if *Rc == 0, new item added to the array
1133 - // otherwise the existing item value is returned in *Rc
1134 -
1135 - // we return a pointer to a pointer, so that the caller can
1136 - // put anything needed at the value of the index.
1137 - // The pointer to pointer we return has to be used before
1138 - // any other operation that may change the index (insert/delete).
1139 - return Rc;
1140 -}
1141 -
1142 -static inline int hashtable_delete_unsafe(DICTIONARY *dict, const char *name, size_t name_len, void *item) {
1143 - (void)item;
1144 - if(unlikely(!dict->index.JudyHSArray)) return 0;
1145 -
1146 - JError_t J_Error;
1147 - int ret = JudyHSDel(&dict->index.JudyHSArray, (void *)name, name_len, &J_Error);
1148 - if(unlikely(ret == JERR)) {
1149 - netdata_log_error("DICTIONARY: Cannot delete entry with name '%s' from JudyHS, JU_ERRNO_* == %u, ID == %d",
1150 - name,
1151 - JU_ERRNO(&J_Error), JU_ERRID(&J_Error));
1152 - return 0;
391 + if(dict && item && dict != item->dict) {
392 + internal_error(
393 + true,
394 + "DICTIONARY: attempted to %s() an item on a dictionary created from %s() %zu@%s, but the item belongs to the dictionary created from %s() %zu@%s.",
395 + function,
396 + dict->creation_function,
397 + dict->creation_line,
398 + dict->creation_file,
399 + item->dict->creation_function,
400 + item->dict->creation_line,
401 + item->dict->creation_file
402 + );
403 + fatal("DICTIONARY: %s(): item does not belong to this dictionary.", function);
404 }
405
1155 - // Hey, this is problematic! We need the value back, not just an int with a status!
1156 - // https://sourceforge.net/p/judy/feature-requests/23/
1157 -
1158 - if(unlikely(ret == 0)) {
1159 - // not found in the dictionary
1160 - return 0;
1161 - }
1162 - else {
1163 - // found and deleted from the dictionary
1164 - return 1;
406 + if(item) {
407 + REFCOUNT refcount = DICTIONARY_ITEM_REFCOUNT_GET(dict, item);
408 + if (unlikely(refcount <= 0)) {
409 + internal_error(
410 + true,
411 + "DICTIONARY: attempted to %s() of an item with reference counter = %d on a dictionary created from %s() %zu@%s",
412 + function,
413 + refcount,
414 + item->dict->creation_function,
415 + item->dict->creation_line,
416 + item->dict->creation_file);
417 + fatal("DICTIONARY: attempted to %s but item is having refcount = %d", function, refcount);
418 + }
419 }
420 }
421 +#else
422 +#define api_internal_check(dict, item, allow_null_dict, allow_null_item) debug_dummy()
423 +#endif
424
1168 -static inline DICTIONARY_ITEM *hashtable_get_unsafe(DICTIONARY *dict, const char *name, size_t name_len) {
1169 - if(unlikely(!dict->index.JudyHSArray)) return NULL;
1170 -
1171 - DICTIONARY_STATS_SEARCHES_PLUS1(dict);
1172 -
1173 - Pvoid_t *Rc;
1174 - Rc = JudyHSGet(dict->index.JudyHSArray, (void *)name, name_len);
1175 - if(likely(Rc)) {
1176 - // found in the hash table
1177 - pointer_check(dict, (DICTIONARY_ITEM *)*Rc);
1178 - return (DICTIONARY_ITEM *)*Rc;
425 +#define api_is_name_good(dict, name, name_len) api_is_name_good_with_trace(dict, name, name_len, __FUNCTION__)
426 +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) {
427 + if(unlikely(!name)) {
428 + internal_error(
429 + true,
430 + "DICTIONARY: attempted to %s() with name = NULL on a dictionary created from %s() %zu@%s.",
431 + function,
432 + dict?dict->creation_function:"unknown",
433 + dict?dict->creation_line:0,
434 + dict?dict->creation_file:"unknown");
435 + return false;
436 }
1180 - else {
1181 - // not found in the hash table
1182 - return NULL;
437 +
438 + if(unlikely(!*name)) {
439 + internal_error(
440 + true,
441 + "DICTIONARY: attempted to %s() with empty name on a dictionary created from %s() %zu@%s.",
442 + function,
443 + dict?dict->creation_function:"unknown",
444 + dict?dict->creation_line:0,
445 + dict?dict->creation_file:"unknown");
446 + return false;
447 }
1184 -}
448
1186 -static inline void hashtable_inserted_item_unsafe(DICTIONARY *dict, void *item) {
1187 - (void)dict;
1188 - (void)item;
449 + internal_error(
450 + name_len > 0 && name_len != (ssize_t)strlen(name),
451 + "DICTIONARY: attempted to %s() with a name of '%s', having length of %zu, "
452 + "but the supplied name_len = %ld, on a dictionary created from %s() %zu@%s.",
453 + function,
454 + name,
455 + strlen(name),
456 + (long int) name_len,
457 + dict?dict->creation_function:"unknown",
458 + dict?dict->creation_line:0,
459 + dict?dict->creation_file:"unknown");
460
1190 - // this is called just after an item is successfully inserted to the hashtable
1191 - // we don't need this for judy, but we may need it if we integrate more hash tables
461 + internal_error(
462 + name_len <= 0 && name_len != -1,
463 + "DICTIONARY: attempted to %s() with a name of '%s', having length of %zu, "
464 + "but the supplied name_len = %ld, on a dictionary created from %s() %zu@%s.",
465 + function,
466 + name,
467 + strlen(name),
468 + (long int) name_len,
469 + dict?dict->creation_function:"unknown",
470 + dict?dict->creation_line:0,
471 + dict?dict->creation_file:"unknown");
472
1193 - ;
473 + return true;
474 }
475
476 // ----------------------------------------------------------------------------
1197 -// linked list management
477 +// API - dictionary management
478
1199 -static inline void item_linked_list_add(DICTIONARY *dict, DICTIONARY_ITEM *item) {
1200 - ll_recursive_lock(dict, DICTIONARY_LOCK_WRITE);
479 +static DICTIONARY *dictionary_create_internal(DICT_OPTIONS options, struct dictionary_stats *stats, size_t fixed_size) {
480 + cleanup_destroyed_dictionaries();
481
1202 - if(dict->options & DICT_OPTION_ADD_IN_FRONT)
1203 - DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(dict->items.list, item, prev, next);
1204 - else
1205 - DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(dict->items.list, item, prev, next);
482 + DICTIONARY *dict = callocz(1, sizeof(DICTIONARY));
483 + dict->options = options;
484 + dict->stats = stats;
485
1207 -#ifdef NETDATA_INTERNAL_CHECKS
1208 - item->ll_adder_pid = gettid();
1209 -#endif
1210 -
1211 - // clear the BEING created flag,
1212 - // after it has been inserted into the linked list
1213 - item_flag_clear(item, ITEM_FLAG_BEING_CREATED);
1214 -
1215 - garbage_collect_pending_deletes(dict);
1216 - ll_recursive_unlock(dict, DICTIONARY_LOCK_WRITE);
1217 -}
1218 -
1219 -static inline void item_linked_list_remove(DICTIONARY *dict, DICTIONARY_ITEM *item) {
1220 - ll_recursive_lock(dict, DICTIONARY_LOCK_WRITE);
1221 -
1222 - DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(dict->items.list, item, prev, next);
1223 -
1224 -#ifdef NETDATA_INTERNAL_CHECKS
1225 - item->ll_remover_pid = gettid();
1226 -#endif
1227 -
1228 - garbage_collect_pending_deletes(dict);
1229 - ll_recursive_unlock(dict, DICTIONARY_LOCK_WRITE);
1230 -}
1231 -
1232 -// ----------------------------------------------------------------------------
1233 -// ITEM initialization and updates
1234 -
1235 -static inline size_t item_set_name(DICTIONARY *dict, DICTIONARY_ITEM *item, const char *name, size_t name_len) {
1236 - if(likely(dict->options & DICT_OPTION_NAME_LINK_DONT_CLONE)) {
1237 - item->caller_name = (char *)name;
1238 - item->key_len = name_len;
1239 - }
1240 - else {
1241 - item->string_name = string_strdupz(name);
1242 - item->key_len = string_strlen(item->string_name);
1243 - item->options |= ITEM_OPTION_ALLOCATED_NAME;
1244 - }
1245 -
1246 - return item->key_len;
1247 -}
1248 -
1249 -static inline size_t item_free_name(DICTIONARY *dict, DICTIONARY_ITEM *item) {
1250 - if(likely(!(dict->options & DICT_OPTION_NAME_LINK_DONT_CLONE)))
1251 - string_freez(item->string_name);
1252 -
1253 - return item->key_len;
1254 -}
1255 -
1256 -static inline const char *item_get_name(const DICTIONARY_ITEM *item) {
1257 - if(item->options & ITEM_OPTION_ALLOCATED_NAME)
1258 - return string2str(item->string_name);
1259 - else
1260 - return item->caller_name;
1261 -}
1262 -
1263 -static inline size_t item_get_name_len(const DICTIONARY_ITEM *item) {
1264 - if(item->options & ITEM_OPTION_ALLOCATED_NAME)
1265 - return string_strlen(item->string_name);
1266 - else
1267 - return strlen(item->caller_name);
1268 -}
1269 -
1270 -static ARAL *dict_items_aral = NULL;
1271 -static ARAL *dict_shared_items_aral = NULL;
1272 -
1273 -void dictionary_static_items_aral_init(void) {
1274 - static SPINLOCK spinlock;
1275 -
1276 - if(unlikely(!dict_items_aral || !dict_shared_items_aral)) {
1277 - spinlock_lock(&spinlock);
1278 -
1279 - // we have to check again
1280 - if(!dict_items_aral)
1281 - dict_items_aral = aral_create(
1282 - "dict-items",
1283 - sizeof(DICTIONARY_ITEM),
1284 - 0,
1285 - 65536,
1286 - aral_by_size_statistics(),
1287 - NULL, NULL, false, false);
1288 -
1289 - // we have to check again
1290 - if(!dict_shared_items_aral)
1291 - dict_shared_items_aral = aral_create(
1292 - "dict-shared-items",
1293 - sizeof(DICTIONARY_ITEM_SHARED),
1294 - 0,
1295 - 65536,
1296 - aral_by_size_statistics(),
1297 - NULL, NULL, false, false);
1298 -
1299 - spinlock_unlock(&spinlock);
1300 - }
1301 -}
1302 -
1303 -static DICTIONARY_ITEM *dict_item_create(DICTIONARY *dict __maybe_unused, size_t *allocated_bytes, DICTIONARY_ITEM *master_item) {
1304 - DICTIONARY_ITEM *item;
1305 -
1306 - size_t size = sizeof(DICTIONARY_ITEM);
1307 - item = aral_mallocz(dict_items_aral);
1308 - memset(item, 0, sizeof(DICTIONARY_ITEM));
1309 -
1310 -#ifdef NETDATA_INTERNAL_CHECKS
1311 - item->creator_pid = gettid();
1312 -#endif
1313 -
1314 - item->refcount = 1;
1315 - item->flags = ITEM_FLAG_BEING_CREATED;
1316 -
1317 - *allocated_bytes += size;
1318 -
1319 - if(master_item) {
1320 - item->shared = master_item->shared;
1321 -
1322 - if(unlikely(__atomic_add_fetch(&item->shared->links, 1, __ATOMIC_ACQUIRE) <= 1))
1323 - fatal("DICTIONARY: attempted to link to a shared item structure that had zero references");
1324 - }
1325 - else {
1326 - size = sizeof(DICTIONARY_ITEM_SHARED);
1327 - item->shared = aral_mallocz(dict_shared_items_aral);
1328 - memset(item->shared, 0, sizeof(DICTIONARY_ITEM_SHARED));
1329 -
1330 - item->shared->links = 1;
1331 - *allocated_bytes += size;
1332 - }
1333 -
1334 -#ifdef NETDATA_INTERNAL_CHECKS
1335 - item->dict = dict;
1336 -#endif
1337 - return item;
1338 -}
1339 -
1340 -static inline void *dict_item_value_mallocz(DICTIONARY *dict, size_t value_len) {
1341 - if(dict->value_aral) {
1342 - internal_fatal(aral_element_size(dict->value_aral) != value_len,
1343 - "DICTIONARY: item value size %zu does not match the configured fixed one %zu",
1344 - value_len, aral_element_size(dict->value_aral));
1345 - return aral_mallocz(dict->value_aral);
1346 - }
1347 - else
1348 - return mallocz(value_len);
1349 -}
1350 -
1351 -static inline void dict_item_value_freez(DICTIONARY *dict, void *ptr) {
1352 - if(dict->value_aral)
1353 - aral_freez(dict->value_aral, ptr);
1354 - else
1355 - freez(ptr);
1356 -}
1357 -
1358 -static void *dict_item_value_create(DICTIONARY *dict, void *value, size_t value_len) {
1359 - void *ptr = NULL;
1360 -
1361 - if(likely(value_len)) {
1362 - if (likely(value)) {
1363 - // a value has been supplied
1364 - // copy it
1365 - ptr = dict_item_value_mallocz(dict, value_len);
1366 - memcpy(ptr, value, value_len);
1367 - }
1368 - else {
1369 - // no value has been supplied
1370 - // allocate a clear memory block
1371 - ptr = dict_item_value_mallocz(dict, value_len);
1372 - memset(ptr, 0, value_len);
1373 - }
1374 - }
1375 - // else
1376 - // the caller wants an item without any value
1377 -
1378 - return ptr;
1379 -}
1380 -
1381 -static DICTIONARY_ITEM *dict_item_create_with_hooks(DICTIONARY *dict, const char *name, size_t name_len, void *value, size_t value_len, void *constructor_data, DICTIONARY_ITEM *master_item) {
1382 -#ifdef NETDATA_INTERNAL_CHECKS
1383 - if(unlikely(name_len > KEY_LEN_MAX))
1384 - fatal("DICTIONARY: tried to index a key of size %zu, but the maximum acceptable is %zu", name_len, (size_t)KEY_LEN_MAX);
1385 -
1386 - if(unlikely(value_len > VALUE_LEN_MAX))
1387 - fatal("DICTIONARY: tried to add an item of size %zu, but the maximum acceptable is %zu", value_len, (size_t)VALUE_LEN_MAX);
1388 -#endif
1389 -
1390 - size_t item_size = 0, key_size = 0, value_size = 0;
1391 -
1392 - DICTIONARY_ITEM *item = dict_item_create(dict, &item_size, master_item);
1393 - key_size += item_set_name(dict, item, name, name_len);
1394 -
1395 - if(unlikely(is_view_dictionary(dict))) {
1396 - // we are on a view dictionary
1397 - // do not touch the value
1398 - ;
1399 -
1400 -#ifdef NETDATA_INTERNAL_CHECKS
1401 - if(unlikely(!master_item))
1402 - fatal("DICTIONARY: cannot add an item to a view without a master item.");
1403 -#endif
1404 - }
1405 - else {
1406 - // we are on the master dictionary
1407 -
1408 - if(unlikely(dict->options & DICT_OPTION_VALUE_LINK_DONT_CLONE))
1409 - item->shared->value = value;
1410 - else
1411 - item->shared->value = dict_item_value_create(dict, value, value_len);
1412 -
1413 - item->shared->value_len = value_len;
1414 - value_size += value_len;
1415 -
1416 - dictionary_execute_insert_callback(dict, item, constructor_data);
1417 - }
1418 -
1419 - DICTIONARY_ENTRIES_PLUS1(dict);
1420 - DICTIONARY_STATS_PLUS_MEMORY(dict, key_size, item_size, value_size);
1421 -
1422 - return item;
1423 -}
1424 -
1425 -static void dict_item_reset_value_with_hooks(DICTIONARY *dict, DICTIONARY_ITEM *item, void *value, size_t value_len, void *constructor_data) {
1426 - if(unlikely(is_view_dictionary(dict)))
1427 - fatal("DICTIONARY: %s() should never be called on views.", __FUNCTION__ );
1428 -
1429 - netdata_log_debug(D_DICTIONARY, "Dictionary entry with name '%s' found. Changing its value.", item_get_name(item));
1430 -
1431 - DICTIONARY_VALUE_RESETS_PLUS1(dict);
1432 -
1433 - if(item->shared->value_len != value_len) {
1434 - DICTIONARY_STATS_PLUS_MEMORY(dict, 0, 0, value_len);
1435 - DICTIONARY_STATS_MINUS_MEMORY(dict, 0, 0, item->shared->value_len);
1436 - }
1437 -
1438 - dictionary_execute_delete_callback(dict, item);
1439 -
1440 - if(likely(dict->options & DICT_OPTION_VALUE_LINK_DONT_CLONE)) {
1441 - netdata_log_debug(D_DICTIONARY, "Dictionary: linking value to '%s'", item_get_name(item));
1442 - item->shared->value = value;
1443 - item->shared->value_len = value_len;
1444 - }
1445 - else {
1446 - netdata_log_debug(D_DICTIONARY, "Dictionary: cloning value to '%s'", item_get_name(item));
1447 -
1448 - void *old_value = item->shared->value;
1449 - void *new_value = NULL;
1450 - if(value_len) {
1451 - new_value = dict_item_value_mallocz(dict, value_len);
1452 - if(value) memcpy(new_value, value, value_len);
1453 - else memset(new_value, 0, value_len);
1454 - }
1455 - item->shared->value = new_value;
1456 - item->shared->value_len = value_len;
1457 -
1458 - netdata_log_debug(D_DICTIONARY, "Dictionary: freeing old value of '%s'", item_get_name(item));
1459 - dict_item_value_freez(dict, old_value);
1460 - }
1461 -
1462 - dictionary_execute_insert_callback(dict, item, constructor_data);
1463 -}
1464 -
1465 -static size_t dict_item_free_with_hooks(DICTIONARY *dict, DICTIONARY_ITEM *item) {
1466 - netdata_log_debug(D_DICTIONARY, "Destroying name value entry for name '%s'.", item_get_name(item));
1467 -
1468 - if(!item_flag_check(item, ITEM_FLAG_DELETED))
1469 - DICTIONARY_ENTRIES_MINUS1(dict);
1470 -
1471 - size_t item_size = 0, key_size = 0, value_size = 0;
1472 -
1473 - key_size += item->key_len;
1474 - if(unlikely(!(dict->options & DICT_OPTION_NAME_LINK_DONT_CLONE)))
1475 - item_free_name(dict, item);
1476 -
1477 - if(item_shared_release_and_check_if_it_can_be_freed(dict, item)) {
1478 - dictionary_execute_delete_callback(dict, item);
1479 -
1480 - if(unlikely(!(dict->options & DICT_OPTION_VALUE_LINK_DONT_CLONE))) {
1481 - netdata_log_debug(D_DICTIONARY, "Dictionary freeing value of '%s'", item_get_name(item));
1482 - dict_item_value_freez(dict, item->shared->value);
1483 - item->shared->value = NULL;
1484 - }
1485 - value_size += item->shared->value_len;
1486 -
1487 - aral_freez(dict_shared_items_aral, item->shared);
1488 - item->shared = NULL;
1489 - item_size += sizeof(DICTIONARY_ITEM_SHARED);
1490 - }
1491 -
1492 - aral_freez(dict_items_aral, item);
1493 -
1494 - item_size += sizeof(DICTIONARY_ITEM);
1495 -
1496 - DICTIONARY_STATS_MINUS_MEMORY(dict, key_size, item_size, value_size);
1497 -
1498 - // we return the memory we actually freed
1499 - return item_size + ((dict->options & DICT_OPTION_VALUE_LINK_DONT_CLONE) ? 0 : value_size);
1500 -}
1501 -
1502 -// ----------------------------------------------------------------------------
1503 -// item operations
1504 -
1505 -static void dict_item_shared_set_deleted(DICTIONARY *dict, DICTIONARY_ITEM *item) {
1506 - if(is_master_dictionary(dict)) {
1507 - item_shared_flag_set(item, ITEM_FLAG_DELETED);
1508 -
1509 - if(dict->hooks)
1510 - __atomic_store_n(&dict->hooks->last_master_deletion_us, now_realtime_usec(), __ATOMIC_RELAXED);
1511 - }
1512 -}
1513 -
1514 -// returns true if we set the deleted flag on this item
1515 -static bool dict_item_set_deleted(DICTIONARY *dict, DICTIONARY_ITEM *item) {
1516 - ITEM_FLAGS expected, desired;
1517 -
1518 - expected = __atomic_load_n(&item->flags, __ATOMIC_RELAXED);
1519 -
1520 - do {
1521 -
1522 - if (expected & ITEM_FLAG_DELETED)
1523 - return false;
1524 -
1525 - desired = expected | ITEM_FLAG_DELETED;
1526 -
1527 - } while(!__atomic_compare_exchange_n(&item->flags, &expected, desired, false, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED));
1528 -
1529 - DICTIONARY_ENTRIES_MINUS1(dict);
1530 - return true;
1531 -}
1532 -
1533 -static inline void dict_item_free_or_mark_deleted(DICTIONARY *dict, DICTIONARY_ITEM *item) {
1534 - int rc = item_is_not_referenced_and_can_be_removed_advanced(dict, item);
1535 - switch(rc) {
1536 - case RC_ITEM_OK:
1537 - // the item is ours, refcount set to -100
1538 - dict_item_shared_set_deleted(dict, item);
1539 - item_linked_list_remove(dict, item);
1540 - dict_item_free_with_hooks(dict, item);
1541 - break;
1542 -
1543 - case RC_ITEM_IS_REFERENCED:
1544 - case RC_ITEM_IS_CURRENTLY_BEING_CREATED:
1545 - // the item is currently referenced by others
1546 - dict_item_shared_set_deleted(dict, item);
1547 - dict_item_set_deleted(dict, item);
1548 - // after this point do not touch the item
1549 - break;
1550 -
1551 - case RC_ITEM_IS_CURRENTLY_BEING_DELETED:
1552 - // an item that is currently being deleted by someone else - don't touch it
1553 - break;
1554 -
1555 - default:
1556 - internal_error(true, "Hey dev! You forgot to add the new condition here!");
1557 - break;
1558 - }
1559 -}
1560 -
1561 -// this is used by traversal functions to remove the current item
1562 -// if it is deleted, and it has zero references. This will eliminate
1563 -// the need for the garbage collector to kick-in later.
1564 -// Most deletions happen during traversal, so this is a nice hack
1565 -// to speed up everything!
1566 -static inline void dict_item_release_and_check_if_it_is_deleted_and_can_be_removed_under_this_lock_mode(DICTIONARY *dict, DICTIONARY_ITEM *item, char rw) {
1567 - if(rw == DICTIONARY_LOCK_WRITE) {
1568 - bool should_be_deleted = item_flag_check(item, ITEM_FLAG_DELETED);
1569 -
1570 - item_release(dict, item);
1571 -
1572 - if(should_be_deleted && item_is_not_referenced_and_can_be_removed(dict, item)) {
1573 - // this has to be before removing from the linked list,
1574 - // otherwise the garbage collector will also kick in!
1575 - DICTIONARY_PENDING_DELETES_MINUS1(dict);
1576 -
1577 - item_linked_list_remove(dict, item);
1578 - dict_item_free_with_hooks(dict, item);
1579 - }
1580 - }
1581 - else {
1582 - // we can't do anything under this mode
1583 - item_release(dict, item);
1584 - }
1585 -}
1586 -
1587 -static bool dict_item_del(DICTIONARY *dict, const char *name, ssize_t name_len) {
1588 - if(name_len == -1)
1589 - name_len = (ssize_t)strlen(name);
1590 -
1591 - netdata_log_debug(D_DICTIONARY, "DEL dictionary entry with name '%s'.", name);
1592 -
1593 - // Unfortunately, the JudyHSDel() does not return the value of the
1594 - // item that was deleted, so we have to find it before we delete it,
1595 - // since we need to release our structures too.
1596 -
1597 - dictionary_index_lock_wrlock(dict);
1598 -
1599 - int ret;
1600 - DICTIONARY_ITEM *item = hashtable_get_unsafe(dict, name, name_len);
1601 - if(unlikely(!item)) {
1602 - dictionary_index_wrlock_unlock(dict);
1603 - ret = false;
1604 - }
1605 - else {
1606 - if(hashtable_delete_unsafe(dict, name, name_len, item) == 0)
1607 - netdata_log_error("DICTIONARY: INTERNAL ERROR: tried to delete item with name '%s', "
1608 - "name_len %zd that is not in the index",
1609 - name, name_len);
1610 - else
1611 - pointer_del(dict, item);
1612 -
1613 - dictionary_index_wrlock_unlock(dict);
1614 -
1615 - dict_item_free_or_mark_deleted(dict, item);
1616 - ret = true;
1617 - }
1618 -
1619 - return ret;
1620 -}
1621 -
1622 -static DICTIONARY_ITEM *dict_item_add_or_reset_value_and_acquire(DICTIONARY *dict, const char *name, ssize_t name_len, void *value, size_t value_len, void *constructor_data, DICTIONARY_ITEM *master_item) {
1623 - if(unlikely(!name || !*name)) {
1624 - internal_error(
1625 - true,
1626 - "DICTIONARY: attempted to %s() without a name on a dictionary created from %s() %zu@%s.",
1627 - __FUNCTION__,
1628 - dict->creation_function,
1629 - dict->creation_line,
1630 - dict->creation_file);
1631 - return NULL;
1632 - }
1633 -
1634 - if(unlikely(is_dictionary_destroyed(dict))) {
1635 - internal_error(true, "DICTIONARY: attempted to dictionary_set() on a destroyed dictionary");
1636 - return NULL;
1637 - }
1638 -
1639 - if(name_len == -1)
1640 - name_len = (ssize_t)strlen(name);
1641 -
1642 - netdata_log_debug(D_DICTIONARY, "SET dictionary entry with name '%s'.", name);
1643 -
1644 - // DISCUSSION:
1645 - // Is it better to gain a read-lock and do a hashtable_get_unsafe()
1646 - // before we write lock to do hashtable_insert_unsafe()?
1647 - //
1648 - // Probably this depends on the use case.
1649 - // For statsd for example that does dictionary_set() to update received values,
1650 - // it could be beneficial to do a get() before we insert().
1651 - //
1652 - // But the caller has the option to do this on his/her own.
1653 - // So, let's do the fastest here and let the caller decide the flow of calls.
1654 -
1655 - dictionary_index_lock_wrlock(dict);
1656 -
1657 - bool added_or_updated = false;
1658 - size_t spins = 0;
1659 - DICTIONARY_ITEM *item = NULL;
1660 - do {
1661 - DICTIONARY_ITEM **item_pptr = (DICTIONARY_ITEM **)hashtable_insert_unsafe(dict, name, name_len);
1662 - if (likely(*item_pptr == NULL)) {
1663 - // a new item added to the index
1664 -
1665 - // create the dictionary item
1666 - item = *item_pptr =
1667 - dict_item_create_with_hooks(dict, name, name_len, value, value_len, constructor_data, master_item);
1668 -
1669 - pointer_add(dict, item);
1670 -
1671 - // call the hashtable react
1672 - hashtable_inserted_item_unsafe(dict, item);
1673 -
1674 - // unlock the index lock, before we add it to the linked list
1675 - // DON'T DO IT THE OTHER WAY AROUND - DO NOT CROSS THE LOCKS!
1676 - dictionary_index_wrlock_unlock(dict);
1677 -
1678 - item_linked_list_add(dict, item);
1679 -
1680 - added_or_updated = true;
1681 - }
1682 - else {
1683 - pointer_check(dict, *item_pptr);
1684 -
1685 - if(item_check_and_acquire_advanced(dict, *item_pptr, true) != RC_ITEM_OK) {
1686 - spins++;
1687 - continue;
1688 - }
1689 -
1690 - // the item is already in the index
1691 - // so, either we will return the old one
1692 - // or overwrite the value, depending on dictionary flags
1693 -
1694 - // We should not compare the values here!
1695 - // even if they are the same, we have to do the whole job
1696 - // so that the callbacks will be called.
1697 -
1698 - item = *item_pptr;
1699 -
1700 - if(is_view_dictionary(dict)) {
1701 - // view dictionary
1702 - // the item is already there and can be used
1703 - if(item->shared != master_item->shared)
1704 - netdata_log_error("DICTIONARY: changing the master item on a view is not supported. The previous item will remain. To change the key of an item in a view, delete it and add it again.");
1705 - }
1706 - else {
1707 - // master dictionary
1708 - // the user wants to reset its value
1709 -
1710 - if (!(dict->options & DICT_OPTION_DONT_OVERWRITE_VALUE)) {
1711 - dict_item_reset_value_with_hooks(dict, item, value, value_len, constructor_data);
1712 - added_or_updated = true;
1713 - }
1714 -
1715 - else if (dictionary_execute_conflict_callback(dict, item, value, constructor_data)) {
1716 - dictionary_version_increment(dict);
1717 - added_or_updated = true;
1718 - }
1719 -
1720 - else {
1721 - // conflict callback returned false
1722 - // we did really nothing!
1723 - ;
1724 - }
1725 - }
1726 -
1727 - dictionary_index_wrlock_unlock(dict);
1728 - }
1729 - } while(!item);
1730 -
1731 -
1732 - if(unlikely(spins > 0))
1733 - DICTIONARY_STATS_INSERT_SPINS_PLUS(dict, spins);
1734 -
1735 - if(is_master_dictionary(dict) && added_or_updated)
1736 - dictionary_execute_react_callback(dict, item, constructor_data);
1737 -
1738 - return item;
1739 -}
1740 -
1741 -static DICTIONARY_ITEM *dict_item_find_and_acquire(DICTIONARY *dict, const char *name, ssize_t name_len) {
1742 - if(unlikely(!name || !*name)) {
1743 - internal_error(
1744 - true,
1745 - "DICTIONARY: attempted to %s() without a name on a dictionary created from %s() %zu@%s.",
1746 - __FUNCTION__,
1747 - dict->creation_function,
1748 - dict->creation_line,
1749 - dict->creation_file);
1750 - return NULL;
1751 - }
1752 -
1753 - if(unlikely(is_dictionary_destroyed(dict))) {
1754 - internal_error(true, "DICTIONARY: attempted to dictionary_get() on a destroyed dictionary");
1755 - return NULL;
1756 - }
1757 -
1758 - if(name_len == -1)
1759 - name_len = (ssize_t)strlen(name);
1760 -
1761 - netdata_log_debug(D_DICTIONARY, "GET dictionary entry with name '%s'.", name);
1762 -
1763 - dictionary_index_lock_rdlock(dict);
1764 -
1765 - DICTIONARY_ITEM *item = hashtable_get_unsafe(dict, name, name_len);
1766 - if(unlikely(item && !item_check_and_acquire(dict, item))) {
1767 - item = NULL;
1768 - DICTIONARY_STATS_SEARCH_IGNORES_PLUS1(dict);
1769 - }
1770 -
1771 - dictionary_index_rdlock_unlock(dict);
1772 -
1773 - return item;
1774 -}
1775 -
1776 -// ----------------------------------------------------------------------------
1777 -// delayed destruction of dictionaries
1778 -
1779 -static bool dictionary_free_all_resources(DICTIONARY *dict, size_t *mem, bool force) {
1780 - if(mem)
1781 - *mem = 0;
1782 -
1783 - if(!force && dictionary_referenced_items(dict))
1784 - return false;
1785 -
1786 - size_t dict_size = 0, counted_items = 0, item_size = 0, index_size = 0;
1787 - (void)counted_items;
1788 -
1789 -#ifdef NETDATA_INTERNAL_CHECKS
1790 - long int entries = dict->entries;
1791 - long int referenced_items = dict->referenced_items;
1792 - long int pending_deletion_items = dict->pending_deletion_items;
1793 - const char *creation_function = dict->creation_function;
1794 - const char *creation_file = dict->creation_file;
1795 - size_t creation_line = dict->creation_line;
1796 -#endif
1797 -
1798 - // destroy the index
1799 - dictionary_index_lock_wrlock(dict);
1800 - index_size += hashtable_destroy_unsafe(dict);
1801 - dictionary_index_wrlock_unlock(dict);
1802 -
1803 - ll_recursive_lock(dict, DICTIONARY_LOCK_WRITE);
1804 - DICTIONARY_ITEM *item = dict->items.list;
1805 - while (item) {
1806 - // cache item->next
1807 - // because we are going to free item
1808 - DICTIONARY_ITEM *item_next = item->next;
1809 -
1810 - item_size += dict_item_free_with_hooks(dict, item);
1811 - item = item_next;
1812 -
1813 - // to speed up destruction, we don't
1814 - // unlink item from the linked-list here
1815 -
1816 - counted_items++;
1817 - }
1818 - dict->items.list = NULL;
1819 - ll_recursive_unlock(dict, DICTIONARY_LOCK_WRITE);
1820 -
1821 - dict_size += dictionary_locks_destroy(dict);
1822 - dict_size += reference_counter_free(dict);
1823 - dict_size += dictionary_hooks_free(dict);
1824 - dict_size += sizeof(DICTIONARY);
1825 - DICTIONARY_STATS_MINUS_MEMORY(dict, 0, sizeof(DICTIONARY), 0);
1826 -
1827 - if(dict->value_aral)
1828 - aral_by_size_release(dict->value_aral);
1829 -
1830 - freez(dict);
1831 -
1832 - internal_error(
1833 - false,
1834 - "DICTIONARY: Freed dictionary created from %s() %zu@%s, having %ld (counted %zu) entries, %ld referenced, %ld pending deletion, total freed memory: %zu bytes (sizeof(dict) = %zu, sizeof(item) = %zu).",
1835 - creation_function,
1836 - creation_line,
1837 - creation_file,
1838 - entries, counted_items, referenced_items, pending_deletion_items,
1839 - dict_size + item_size, sizeof(DICTIONARY), sizeof(DICTIONARY_ITEM) + sizeof(DICTIONARY_ITEM_SHARED));
1840 -
1841 - if(mem)
1842 - *mem = dict_size + item_size + index_size;
1843 -
1844 - return true;
1845 -}
1846 -
1847 -netdata_mutex_t dictionaries_waiting_to_be_destroyed_mutex = NETDATA_MUTEX_INITIALIZER;
1848 -static DICTIONARY *dictionaries_waiting_to_be_destroyed = NULL;
1849 -
1850 -void dictionary_queue_for_destruction(DICTIONARY *dict) {
1851 - if(is_dictionary_destroyed(dict))
1852 - return;
1853 -
1854 - DICTIONARY_STATS_DICT_DESTROY_QUEUED_PLUS1(dict);
1855 - dict_flag_set(dict, DICT_FLAG_DESTROYED);
1856 -
1857 - netdata_mutex_lock(&dictionaries_waiting_to_be_destroyed_mutex);
1858 -
1859 - dict->next = dictionaries_waiting_to_be_destroyed;
1860 - dictionaries_waiting_to_be_destroyed = dict;
1861 -
1862 - netdata_mutex_unlock(&dictionaries_waiting_to_be_destroyed_mutex);
1863 -}
1864 -
1865 -void cleanup_destroyed_dictionaries(void) {
1866 - if(!dictionaries_waiting_to_be_destroyed)
1867 - return;
1868 -
1869 - netdata_mutex_lock(&dictionaries_waiting_to_be_destroyed_mutex);
1870 -
1871 - DICTIONARY *dict, *last = NULL, *next = NULL;
1872 - for(dict = dictionaries_waiting_to_be_destroyed; dict ; dict = next) {
1873 - next = dict->next;
1874 -
1875 -#ifdef NETDATA_INTERNAL_CHECKS
1876 - size_t line = dict->creation_line;
1877 - const char *file = dict->creation_file;
1878 - const char *function = dict->creation_function;
1879 - pid_t pid = dict->creation_tid;
1880 -#endif
1881 -
1882 - DICTIONARY_STATS_DICT_DESTROY_QUEUED_MINUS1(dict);
1883 - if(dictionary_free_all_resources(dict, NULL, false)) {
1884 -
1885 - internal_error(
1886 - true,
1887 - "DICTIONARY: freed dictionary with delayed destruction, created from %s() %zu@%s pid %d.",
1888 - function, line, file, pid);
1889 -
1890 - if(last) last->next = next;
1891 - else dictionaries_waiting_to_be_destroyed = next;
1892 - }
1893 - else {
1894 -
1895 - internal_error(
1896 - true,
1897 - "DICTIONARY: cannot free dictionary with delayed destruction, created from %s() %zu@%s pid %d.",
1898 - function, line, file, pid);
1899 -
1900 - DICTIONARY_STATS_DICT_DESTROY_QUEUED_PLUS1(dict);
1901 - last = dict;
1902 - }
1903 - }
1904 -
1905 - netdata_mutex_unlock(&dictionaries_waiting_to_be_destroyed_mutex);
1906 -}
1907 -
1908 -// ----------------------------------------------------------------------------
1909 -// API internal checks
1910 -
1911 -#ifdef NETDATA_INTERNAL_CHECKS
1912 -#define api_internal_check(dict, item, allow_null_dict, allow_null_item) api_internal_check_with_trace(dict, item, __FUNCTION__, allow_null_dict, allow_null_item)
1913 -static inline void api_internal_check_with_trace(DICTIONARY *dict, DICTIONARY_ITEM *item, const char *function, bool allow_null_dict, bool allow_null_item) {
1914 - if(!allow_null_dict && !dict) {
1915 - internal_error(
1916 - item,
1917 - "DICTIONARY: attempted to %s() with a NULL dictionary, passing an item created from %s() %zu@%s.",
1918 - function,
1919 - item->dict->creation_function,
1920 - item->dict->creation_line,
1921 - item->dict->creation_file);
1922 - fatal("DICTIONARY: attempted to %s() but dict is NULL", function);
1923 - }
1924 -
1925 - if(!allow_null_item && !item) {
1926 - internal_error(
1927 - true,
1928 - "DICTIONARY: attempted to %s() without an item on a dictionary created from %s() %zu@%s.",
1929 - function,
1930 - dict?dict->creation_function:"unknown",
1931 - dict?dict->creation_line:0,
1932 - dict?dict->creation_file:"unknown");
1933 - fatal("DICTIONARY: attempted to %s() but item is NULL", function);
1934 - }
1935 -
1936 - if(dict && item && dict != item->dict) {
1937 - internal_error(
1938 - true,
1939 - "DICTIONARY: attempted to %s() an item on a dictionary created from %s() %zu@%s, but the item belongs to the dictionary created from %s() %zu@%s.",
1940 - function,
1941 - dict->creation_function,
1942 - dict->creation_line,
1943 - dict->creation_file,
1944 - item->dict->creation_function,
1945 - item->dict->creation_line,
1946 - item->dict->creation_file
1947 - );
1948 - fatal("DICTIONARY: %s(): item does not belong to this dictionary.", function);
1949 - }
1950 -
1951 - if(item) {
1952 - REFCOUNT refcount = DICTIONARY_ITEM_REFCOUNT_GET(dict, item);
1953 - if (unlikely(refcount <= 0)) {
1954 - internal_error(
1955 - true,
1956 - "DICTIONARY: attempted to %s() of an item with reference counter = %d on a dictionary created from %s() %zu@%s",
1957 - function,
1958 - refcount,
1959 - item->dict->creation_function,
1960 - item->dict->creation_line,
1961 - item->dict->creation_file);
1962 - fatal("DICTIONARY: attempted to %s but item is having refcount = %d", function, refcount);
1963 - }
1964 - }
1965 -}
1966 -#else
1967 -#define api_internal_check(dict, item, allow_null_dict, allow_null_item) debug_dummy()
1968 -#endif
1969 -
1970 -#define api_is_name_good(dict, name, name_len) api_is_name_good_with_trace(dict, name, name_len, __FUNCTION__)
1971 -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) {
1972 - if(unlikely(!name)) {
1973 - internal_error(
1974 - true,
1975 - "DICTIONARY: attempted to %s() with name = NULL on a dictionary created from %s() %zu@%s.",
1976 - function,
1977 - dict?dict->creation_function:"unknown",
1978 - dict?dict->creation_line:0,
1979 - dict?dict->creation_file:"unknown");
1980 - return false;
1981 - }
1982 -
1983 - if(unlikely(!*name)) {
1984 - internal_error(
1985 - true,
1986 - "DICTIONARY: attempted to %s() with empty name on a dictionary created from %s() %zu@%s.",
1987 - function,
1988 - dict?dict->creation_function:"unknown",
1989 - dict?dict->creation_line:0,
1990 - dict?dict->creation_file:"unknown");
1991 - return false;
1992 - }
1993 -
1994 - internal_error(
1995 - name_len > 0 && name_len != (ssize_t)strlen(name),
1996 - "DICTIONARY: attempted to %s() with a name of '%s', having length of %zu, "
1997 - "but the supplied name_len = %ld, on a dictionary created from %s() %zu@%s.",
1998 - function,
1999 - name,
2000 - strlen(name),
2001 - (long int) name_len,
2002 - dict?dict->creation_function:"unknown",
2003 - dict?dict->creation_line:0,
2004 - dict?dict->creation_file:"unknown");
2005 -
2006 - internal_error(
2007 - name_len <= 0 && name_len != -1,
2008 - "DICTIONARY: attempted to %s() with a name of '%s', having length of %zu, "
2009 - "but the supplied name_len = %ld, on a dictionary created from %s() %zu@%s.",
2010 - function,
2011 - name,
2012 - strlen(name),
2013 - (long int) name_len,
2014 - dict?dict->creation_function:"unknown",
2015 - dict?dict->creation_line:0,
2016 - dict?dict->creation_file:"unknown");
2017 -
2018 - return true;
2019 -}
2020 -
2021 -// ----------------------------------------------------------------------------
2022 -// API - dictionary management
2023 -
2024 -static DICTIONARY *dictionary_create_internal(DICT_OPTIONS options, struct dictionary_stats *stats, size_t fixed_size) {
2025 - cleanup_destroyed_dictionaries();
2026 -
2027 - DICTIONARY *dict = callocz(1, sizeof(DICTIONARY));
2028 - dict->options = options;
2029 - dict->stats = stats;
2030 -
2031 - if((dict->options & DICT_OPTION_FIXED_SIZE) && !fixed_size) {
2032 - dict->options &= ~DICT_OPTION_FIXED_SIZE;
2033 - internal_fatal(true, "DICTIONARY: requested fixed size dictionary, without setting the size");
2034 - }
2035 - if(!(dict->options & DICT_OPTION_FIXED_SIZE) && fixed_size) {
2036 - dict->options |= DICT_OPTION_FIXED_SIZE;
2037 - internal_fatal(true, "DICTIONARY: set a fixed size for the items, without setting DICT_OPTION_FIXED_SIZE flag");
2038 - }
2039 -
2040 - if(dict->options & DICT_OPTION_FIXED_SIZE)
2041 - dict->value_aral = aral_by_size_acquire(fixed_size);
2042 - else
2043 - dict->value_aral = NULL;
2044 -
2045 - size_t dict_size = 0;
2046 - dict_size += sizeof(DICTIONARY);
2047 - dict_size += dictionary_locks_init(dict);
2048 - dict_size += reference_counter_init(dict);
2049 - dict_size += hashtable_init_unsafe(dict);
2050 -
2051 - dictionary_static_items_aral_init();
2052 - pointer_index_init(dict);
2053 -
2054 - DICTIONARY_STATS_PLUS_MEMORY(dict, 0, dict_size, 0);
2055 -
2056 - return dict;
2057 -}
2058 -
2059 -#ifdef NETDATA_INTERNAL_CHECKS
2060 -DICTIONARY *dictionary_create_advanced_with_trace(DICT_OPTIONS options, struct dictionary_stats *stats, size_t fixed_size, const char *function, size_t line, const char *file) {
2061 -#else
2062 -DICTIONARY *dictionary_create_advanced(DICT_OPTIONS options, struct dictionary_stats *stats, size_t fixed_size) {
2063 -#endif
2064 -
2065 - DICTIONARY *dict = dictionary_create_internal(options, stats?stats:&dictionary_stats_category_other, fixed_size);
2066 -
2067 -#ifdef NETDATA_INTERNAL_CHECKS
2068 - dict->creation_function = function;
2069 - dict->creation_file = file;
2070 - dict->creation_line = line;
2071 -#endif
2072 -
2073 - DICTIONARY_STATS_DICT_CREATIONS_PLUS1(dict);
2074 - return dict;
2075 -}
2076 -
2077 -#ifdef NETDATA_INTERNAL_CHECKS
2078 -DICTIONARY *dictionary_create_view_with_trace(DICTIONARY *master, const char *function, size_t line, const char *file) {
2079 -#else
2080 -DICTIONARY *dictionary_create_view(DICTIONARY *master) {
2081 -#endif
2082 -
2083 - DICTIONARY *dict = dictionary_create_internal(master->options, master->stats,
2084 - master->value_aral ? aral_element_size(master->value_aral) : 0);
2085 -
2086 - dict->master = master;
2087 -
2088 - dictionary_hooks_allocate(master);
2089 -
2090 - if(unlikely(__atomic_load_n(&master->hooks->links, __ATOMIC_RELAXED)) < 1)
2091 - fatal("DICTIONARY: attempted to create a view that has %d links", master->hooks->links);
2092 -
2093 - dict->hooks = master->hooks;
2094 - __atomic_add_fetch(&master->hooks->links, 1, __ATOMIC_ACQUIRE);
2095 -
2096 -#ifdef NETDATA_INTERNAL_CHECKS
2097 - dict->creation_function = function;
2098 - dict->creation_file = file;
2099 - dict->creation_line = line;
2100 - dict->creation_tid = gettid();
2101 -#endif
2102 -
2103 - DICTIONARY_STATS_DICT_CREATIONS_PLUS1(dict);
2104 - return dict;
2105 -}
2106 -
2107 -void dictionary_flush(DICTIONARY *dict) {
2108 - if(unlikely(!dict))
2109 - return;
2110 -
2111 - ll_recursive_lock(dict, DICTIONARY_LOCK_WRITE);
2112 -
2113 - DICTIONARY_ITEM *item, *next = NULL;
2114 - for(item = dict->items.list; item ;item = next) {
2115 - next = item->next;
2116 - dict_item_del(dict, item_get_name(item), (ssize_t)item_get_name_len(item));
2117 - }
2118 -
2119 - ll_recursive_unlock(dict, DICTIONARY_LOCK_WRITE);
2120 -
2121 - DICTIONARY_STATS_DICT_FLUSHES_PLUS1(dict);
2122 -}
2123 -
2124 -size_t dictionary_destroy(DICTIONARY *dict) {
2125 - cleanup_destroyed_dictionaries();
2126 -
2127 - if(!dict) return 0;
2128 -
2129 - ll_recursive_lock(dict, DICTIONARY_LOCK_WRITE);
2130 -
2131 - dict_flag_set(dict, DICT_FLAG_DESTROYED);
2132 - DICTIONARY_STATS_DICT_DESTRUCTIONS_PLUS1(dict);
2133 -
2134 - size_t referenced_items = dictionary_referenced_items(dict);
2135 - if(referenced_items) {
2136 - dictionary_flush(dict);
2137 - dictionary_queue_for_destruction(dict);
2138 -
2139 - internal_error(
2140 - true,
2141 - "DICTIONARY: delaying destruction of dictionary created from %s() %zu@%s, because it has %d referenced items in it (%d total).",
2142 - dict->creation_function,
2143 - dict->creation_line,
2144 - dict->creation_file,
2145 - dict->referenced_items,
2146 - dict->entries);
2147 -
2148 - ll_recursive_unlock(dict, DICTIONARY_LOCK_WRITE);
2149 - return 0;
2150 - }
2151 -
2152 - ll_recursive_unlock(dict, DICTIONARY_LOCK_WRITE);
2153 -
2154 - size_t freed;
2155 - dictionary_free_all_resources(dict, &freed, true);
2156 -
2157 - return freed;
2158 -}
2159 -
2160 -// ----------------------------------------------------------------------------
2161 -// SET an item to the dictionary
2162 -
2163 -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) {
2164 - if(unlikely(!api_is_name_good(dict, name, name_len)))
2165 - return NULL;
2166 -
2167 - api_internal_check(dict, NULL, false, true);
2168 -
2169 - if(unlikely(is_view_dictionary(dict)))
2170 - fatal("DICTIONARY: this dictionary is a view, you cannot add items other than the ones from the master dictionary.");
2171 -
2172 - DICTIONARY_ITEM *item =
2173 - dict_item_add_or_reset_value_and_acquire(dict, name, name_len, value, value_len, constructor_data, NULL);
2174 - api_internal_check(dict, item, false, false);
2175 - return item;
2176 -}
2177 -
2178 -void *dictionary_set_advanced(DICTIONARY *dict, const char *name, ssize_t name_len, void *value, size_t value_len, void *constructor_data) {
2179 - DICTIONARY_ITEM *item = dictionary_set_and_acquire_item_advanced(dict, name, name_len, value, value_len, constructor_data);
2180 -
2181 - if(likely(item)) {
2182 - void *v = item->shared->value;
2183 - item_release(dict, item);
2184 - return v;
2185 - }
2186 -
2187 - return NULL;
2188 -}
2189 -
2190 -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) {
2191 - if(unlikely(!api_is_name_good(dict, name, name_len)))
2192 - return NULL;
2193 -
2194 - api_internal_check(dict, NULL, false, true);
2195 -
2196 - if(unlikely(is_master_dictionary(dict)))
2197 - fatal("DICTIONARY: this dictionary is a master, you cannot add items from other dictionaries.");
2198 -
2199 - garbage_collect_pending_deletes(dict);
2200 -
2201 - dictionary_acquired_item_dup(dict->master, master_item);
2202 - DICTIONARY_ITEM *item = dict_item_add_or_reset_value_and_acquire(dict, name, name_len, NULL, 0, NULL, master_item);
2203 - dictionary_acquired_item_release(dict->master, master_item);
2204 -
2205 - api_internal_check(dict, item, false, false);
2206 - return item;
2207 -}
2208 -
2209 -void *dictionary_view_set_advanced(DICTIONARY *dict, const char *name, ssize_t name_len, DICTIONARY_ITEM *master_item) {
2210 - DICTIONARY_ITEM *item = dictionary_view_set_and_acquire_item_advanced(dict, name, name_len, master_item);
2211 -
2212 - if(likely(item)) {
2213 - void *v = item->shared->value;
2214 - item_release(dict, item);
2215 - return v;
2216 - }
2217 -
2218 - return NULL;
2219 -}
2220 -
2221 -// ----------------------------------------------------------------------------
2222 -// GET an item from the dictionary
2223 -
2224 -DICT_ITEM_CONST DICTIONARY_ITEM *dictionary_get_and_acquire_item_advanced(DICTIONARY *dict, const char *name, ssize_t name_len) {
2225 - if(unlikely(!api_is_name_good(dict, name, name_len)))
2226 - return NULL;
2227 -
2228 - api_internal_check(dict, NULL, false, true);
2229 - DICTIONARY_ITEM *item = dict_item_find_and_acquire(dict, name, name_len);
2230 - api_internal_check(dict, item, false, true);
2231 - return item;
2232 -}
2233 -
2234 -void *dictionary_get_advanced(DICTIONARY *dict, const char *name, ssize_t name_len) {
2235 - DICTIONARY_ITEM *item = dictionary_get_and_acquire_item_advanced(dict, name, name_len);
2236 -
2237 - if(likely(item)) {
2238 - void *v = item->shared->value;
2239 - item_release(dict, item);
2240 - return v;
2241 - }
2242 -
2243 - return NULL;
2244 -}
2245 -
2246 -// ----------------------------------------------------------------------------
2247 -// DUP/REL an item (increase/decrease its reference counter)
2248 -
2249 -DICT_ITEM_CONST DICTIONARY_ITEM *dictionary_acquired_item_dup(DICTIONARY *dict, DICT_ITEM_CONST DICTIONARY_ITEM *item) {
2250 - // we allow the item to be NULL here
2251 - api_internal_check(dict, item, false, true);
2252 -
2253 - if(likely(item)) {
2254 - item_acquire(dict, item);
2255 - api_internal_check(dict, item, false, false);
2256 - }
2257 -
2258 - return item;
2259 -}
2260 -
2261 -void dictionary_acquired_item_release(DICTIONARY *dict, DICT_ITEM_CONST DICTIONARY_ITEM *item) {
2262 - // we allow the item to be NULL here
2263 - api_internal_check(dict, item, false, true);
2264 -
2265 - // no need to get a lock here
2266 - // we pass the last parameter to reference_counter_release() as true
2267 - // so that the release may get a write-lock if required to clean up
2268 -
2269 - if(likely(item))
2270 - item_release(dict, item);
2271 -}
2272 -
2273 -// ----------------------------------------------------------------------------
2274 -// get the name/value of an item
2275 -
2276 -const char *dictionary_acquired_item_name(DICT_ITEM_CONST DICTIONARY_ITEM *item) {
2277 - return item_get_name(item);
2278 -}
2279 -
2280 -void *dictionary_acquired_item_value(DICT_ITEM_CONST DICTIONARY_ITEM *item) {
2281 - if(likely(item))
2282 - return item->shared->value;
2283 -
2284 - return NULL;
2285 -}
2286 -
2287 -size_t dictionary_acquired_item_references(DICT_ITEM_CONST DICTIONARY_ITEM *item) {
2288 - if(likely(item))
2289 - return DICTIONARY_ITEM_REFCOUNT_GET_SOLE(item);
2290 -
2291 - return 0;
2292 -}
2293 -
2294 -// ----------------------------------------------------------------------------
2295 -// DEL an item
2296 -
2297 -bool dictionary_del_advanced(DICTIONARY *dict, const char *name, ssize_t name_len) {
2298 - if(unlikely(!api_is_name_good(dict, name, name_len)))
2299 - return false;
2300 -
2301 - api_internal_check(dict, NULL, false, true);
2302 -
2303 - if(unlikely(is_dictionary_destroyed(dict))) {
2304 - internal_error(true, "DICTIONARY: attempted to delete item on a destroyed dictionary");
2305 - return false;
2306 - }
2307 -
2308 - return dict_item_del(dict, name, name_len);
2309 -}
2310 -
2311 -// ----------------------------------------------------------------------------
2312 -// traversal with loop
2313 -
2314 -void *dictionary_foreach_start_rw(DICTFE *dfe, DICTIONARY *dict, char rw) {
2315 - if(unlikely(!dfe || !dict)) return NULL;
2316 -
2317 - DICTIONARY_STATS_TRAVERSALS_PLUS1(dict);
2318 -
2319 - if(unlikely(is_dictionary_destroyed(dict))) {
2320 - internal_error(true, "DICTIONARY: attempted to dictionary_foreach_start_rw() on a destroyed dictionary");
2321 - dfe->counter = 0;
2322 - dfe->item = NULL;
2323 - dfe->name = NULL;
2324 - dfe->value = NULL;
2325 - return NULL;
2326 - }
2327 -
2328 - dfe->counter = 0;
2329 - dfe->dict = dict;
2330 - dfe->rw = rw;
2331 - dfe->locked = true;
2332 - ll_recursive_lock(dict, dfe->rw);
2333 -
2334 - // get the first item from the list
2335 - DICTIONARY_ITEM *item = dict->items.list;
2336 -
2337 - // skip all the deleted items
2338 - while(item && !item_check_and_acquire(dict, item))
2339 - item = item->next;
2340 -
2341 - if(likely(item)) {
2342 - dfe->item = item;
2343 - dfe->name = (char *)item_get_name(item);
2344 - dfe->value = item->shared->value;
2345 - }
2346 - else {
2347 - dfe->item = NULL;
2348 - dfe->name = NULL;
2349 - dfe->value = NULL;
2350 - }
2351 -
2352 - if(unlikely(dfe->rw == DICTIONARY_LOCK_REENTRANT)) {
2353 - ll_recursive_unlock(dfe->dict, dfe->rw);
2354 - dfe->locked = false;
2355 - }
2356 -
2357 - return dfe->value;
2358 -}
2359 -
2360 -void *dictionary_foreach_next(DICTFE *dfe) {
2361 - if(unlikely(!dfe || !dfe->dict)) return NULL;
2362 -
2363 - if(unlikely(is_dictionary_destroyed(dfe->dict))) {
2364 - internal_error(true, "DICTIONARY: attempted to dictionary_foreach_next() on a destroyed dictionary");
2365 - dfe->item = NULL;
2366 - dfe->name = NULL;
2367 - dfe->value = NULL;
2368 - return NULL;
2369 - }
2370 -
2371 - if(unlikely(dfe->rw == DICTIONARY_LOCK_REENTRANT) || !dfe->locked) {
2372 - ll_recursive_lock(dfe->dict, dfe->rw);
2373 - dfe->locked = true;
2374 - }
2375 -
2376 - // the item we just did
2377 - DICTIONARY_ITEM *item = dfe->item;
2378 -
2379 - // get the next item from the list
2380 - DICTIONARY_ITEM *item_next = (item) ? item->next : NULL;
2381 -
2382 - // skip all the deleted items until one that can be acquired is found
2383 - while(item_next && !item_check_and_acquire(dfe->dict, item_next))
2384 - item_next = item_next->next;
2385 -
2386 - if(likely(item)) {
2387 - dict_item_release_and_check_if_it_is_deleted_and_can_be_removed_under_this_lock_mode(dfe->dict, item, dfe->rw);
2388 - // item_release(dfe->dict, item);
2389 - }
2390 -
2391 - item = item_next;
2392 - if(likely(item)) {
2393 - dfe->item = item;
2394 - dfe->name = (char *)item_get_name(item);
2395 - dfe->value = item->shared->value;
2396 - dfe->counter++;
2397 - }
2398 - else {
2399 - dfe->item = NULL;
2400 - dfe->name = NULL;
2401 - dfe->value = NULL;
2402 - }
2403 -
2404 - if(unlikely(dfe->rw == DICTIONARY_LOCK_REENTRANT)) {
2405 - ll_recursive_unlock(dfe->dict, dfe->rw);
2406 - dfe->locked = false;
2407 - }
2408 -
2409 - return dfe->value;
2410 -}
2411 -
2412 -void dictionary_foreach_unlock(DICTFE *dfe) {
2413 - if(dfe->locked) {
2414 - ll_recursive_unlock(dfe->dict, dfe->rw);
2415 - dfe->locked = false;
2416 - }
2417 -}
2418 -
2419 -void dictionary_foreach_done(DICTFE *dfe) {
2420 - if(unlikely(!dfe || !dfe->dict)) return;
2421 -
2422 - if(unlikely(is_dictionary_destroyed(dfe->dict))) {
2423 - internal_error(true, "DICTIONARY: attempted to dictionary_foreach_next() on a destroyed dictionary");
2424 - return;
2425 - }
2426 -
2427 - // the item we just did
2428 - DICTIONARY_ITEM *item = dfe->item;
2429 -
2430 - // release it, so that it can possibly be deleted
2431 - if(likely(item)) {
2432 - dict_item_release_and_check_if_it_is_deleted_and_can_be_removed_under_this_lock_mode(dfe->dict, item, dfe->rw);
2433 - // item_release(dfe->dict, item);
2434 - }
2435 -
2436 - if(likely(dfe->rw != DICTIONARY_LOCK_REENTRANT) && dfe->locked) {
2437 - ll_recursive_unlock(dfe->dict, dfe->rw);
2438 - dfe->locked = false;
2439 - }
2440 -
2441 - dfe->dict = NULL;
2442 - dfe->item = NULL;
2443 - dfe->name = NULL;
2444 - dfe->value = NULL;
2445 - dfe->counter = 0;
2446 -}
2447 -
2448 -// ----------------------------------------------------------------------------
2449 -// API - walk through the dictionary.
2450 -// The dictionary is locked for reading while this happens
2451 -// do not use other dictionary calls while walking the dictionary - deadlock!
2452 -
2453 -int dictionary_walkthrough_rw(DICTIONARY *dict, char rw, dict_walkthrough_callback_t walkthrough_callback, void *data) {
2454 - if(unlikely(!dict || !walkthrough_callback)) return 0;
2455 -
2456 - if(unlikely(is_dictionary_destroyed(dict))) {
2457 - internal_error(true, "DICTIONARY: attempted to dictionary_walkthrough_rw() on a destroyed dictionary");
2458 - return 0;
2459 - }
2460 -
2461 - ll_recursive_lock(dict, rw);
2462 -
2463 - DICTIONARY_STATS_WALKTHROUGHS_PLUS1(dict);
2464 -
2465 - // written in such a way, that the callback can delete the active element
2466 -
2467 - int ret = 0;
2468 - DICTIONARY_ITEM *item = dict->items.list, *item_next;
2469 - while(item) {
2470 -
2471 - // skip the deleted items
2472 - if(unlikely(!item_check_and_acquire(dict, item))) {
2473 - item = item->next;
2474 - continue;
2475 - }
2476 -
2477 - if(unlikely(rw == DICTIONARY_LOCK_REENTRANT))
2478 - ll_recursive_unlock(dict, rw);
2479 -
2480 - int r = walkthrough_callback(item, item->shared->value, data);
2481 -
2482 - if(unlikely(rw == DICTIONARY_LOCK_REENTRANT))
2483 - ll_recursive_lock(dict, rw);
2484 -
2485 - // since we have a reference counter, this item cannot be deleted
2486 - // until we release the reference counter, so the pointers are there
2487 - item_next = item->next;
2488 -
2489 - dict_item_release_and_check_if_it_is_deleted_and_can_be_removed_under_this_lock_mode(dict, item, rw);
2490 - // item_release(dict, item);
2491 -
2492 - if(unlikely(r < 0)) {
2493 - ret = r;
2494 - break;
2495 - }
2496 -
2497 - ret += r;
2498 -
2499 - item = item_next;
2500 - }
2501 -
2502 - ll_recursive_unlock(dict, rw);
2503 -
2504 - return ret;
2505 -}
2506 -
2507 -// ----------------------------------------------------------------------------
2508 -// sorted walkthrough
2509 -
2510 -typedef int (*qsort_compar)(const void *item1, const void *item2);
2511 -
2512 -static int dictionary_sort_compar(const void *item1, const void *item2) {
2513 - return strcmp(item_get_name((*(DICTIONARY_ITEM **)item1)), item_get_name((*(DICTIONARY_ITEM **)item2)));
2514 -}
2515 -
2516 -int dictionary_sorted_walkthrough_rw(DICTIONARY *dict, char rw, dict_walkthrough_callback_t walkthrough_callback, void *data, dict_item_comparator_t item_comparator) {
2517 - if(unlikely(!dict || !walkthrough_callback)) return 0;
2518 -
2519 - if(unlikely(is_dictionary_destroyed(dict))) {
2520 - internal_error(true, "DICTIONARY: attempted to dictionary_sorted_walkthrough_rw() on a destroyed dictionary");
2521 - return 0;
486 + if((dict->options & DICT_OPTION_FIXED_SIZE) && !fixed_size) {
487 + dict->options &= ~DICT_OPTION_FIXED_SIZE;
488 + internal_fatal(true, "DICTIONARY: requested fixed size dictionary, without setting the size");
489 }
2523 -
2524 - DICTIONARY_STATS_WALKTHROUGHS_PLUS1(dict);
2525 -
2526 - ll_recursive_lock(dict, rw);
2527 - size_t entries = __atomic_load_n(&dict->entries, __ATOMIC_RELAXED);
2528 - DICTIONARY_ITEM **array = mallocz(sizeof(DICTIONARY_ITEM *) * entries);
2529 -
2530 - size_t i;
2531 - DICTIONARY_ITEM *item;
2532 - for(item = dict->items.list, i = 0; item && i < entries; item = item->next) {
2533 - if(likely(item_check_and_acquire(dict, item)))
2534 - array[i++] = item;
490 + if(!(dict->options & DICT_OPTION_FIXED_SIZE) && fixed_size) {
491 + dict->options |= DICT_OPTION_FIXED_SIZE;
492 + internal_fatal(true, "DICTIONARY: set a fixed size for the items, without setting DICT_OPTION_FIXED_SIZE flag");
493 }
2536 - ll_recursive_unlock(dict, rw);
2537 -
2538 - if(unlikely(i != entries))
2539 - entries = i;
494
2541 - if(item_comparator)
2542 - qsort(array, entries, sizeof(DICTIONARY_ITEM *), (qsort_compar) item_comparator);
495 + if(dict->options & DICT_OPTION_FIXED_SIZE)
496 + dict->value_aral = aral_by_size_acquire(fixed_size);
497 else
2544 - qsort(array, entries, sizeof(DICTIONARY_ITEM *), dictionary_sort_compar);
2545 -
2546 - bool callit = true;
2547 - int ret = 0, r;
2548 - for(i = 0; i < entries ;i++) {
2549 - item = array[i];
2550 -
2551 - if(callit)
2552 - r = walkthrough_callback(item, item->shared->value, data);
2553 -
2554 - dict_item_release_and_check_if_it_is_deleted_and_can_be_removed_under_this_lock_mode(dict, item, rw);
2555 - // item_release(dict, item);
2556 -
2557 - if(r < 0) {
2558 - ret = r;
2559 - r = 0;
2560 -
2561 - // stop calling the callback,
2562 - // but we have to continue, to release all the reference counters
2563 - callit = false;
2564 - }
2565 - else
2566 - ret += r;
2567 - }
2568 -
2569 - freez(array);
2570 -
2571 - return ret;
2572 -}
2573 -
2574 -// ----------------------------------------------------------------------------
2575 -// THREAD_CACHE
2576 -
2577 -static __thread Pvoid_t thread_cache_judy_array = NULL;
2578 -
2579 -void *thread_cache_entry_get_or_set(void *key,
2580 - ssize_t key_length,
2581 - void *value,
2582 - void *(*transform_the_value_before_insert)(void *key, size_t key_length, void *value)
2583 - ) {
2584 - if(unlikely(!key || !key_length)) return NULL;
2585 -
2586 - if(key_length == -1)
2587 - key_length = (ssize_t)strlen((char *)key);
2588 -
2589 - JError_t J_Error;
2590 - Pvoid_t *Rc = JudyHSIns(&thread_cache_judy_array, key, key_length, &J_Error);
2591 - if (unlikely(Rc == PJERR)) {
2592 - fatal("THREAD_CACHE: Cannot insert entry to JudyHS, JU_ERRNO_* == %u, ID == %d",
2593 - JU_ERRNO(&J_Error), JU_ERRID(&J_Error));
2594 - }
2595 -
2596 - if(*Rc == 0) {
2597 - // new item added
2598 -
2599 - *Rc = (transform_the_value_before_insert) ? transform_the_value_before_insert(key, key_length, value) : value;
2600 - }
2601 -
2602 - return *Rc;
2603 -}
2604 -
2605 -void thread_cache_destroy(void) {
2606 - if(unlikely(!thread_cache_judy_array)) return;
2607 -
2608 - JError_t J_Error;
2609 - Word_t ret = JudyHSFreeArray(&thread_cache_judy_array, &J_Error);
2610 - if(unlikely(ret == (Word_t) JERR)) {
2611 - netdata_log_error("THREAD_CACHE: Cannot destroy JudyHS, JU_ERRNO_* == %u, ID == %d",
2612 - JU_ERRNO(&J_Error), JU_ERRID(&J_Error));
2613 - }
2614 -
2615 - internal_error(true, "THREAD_CACHE: hash table freed %lu bytes", ret);
2616 -
2617 - thread_cache_judy_array = NULL;
2618 -}
2619 -
2620 -// ----------------------------------------------------------------------------
2621 -// unit test
2622 -
2623 -static void dictionary_unittest_free_char_pp(char **pp, size_t entries) {
2624 - for(size_t i = 0; i < entries ;i++)
2625 - freez(pp[i]);
2626 -
2627 - freez(pp);
2628 -}
2629 -
2630 -static char **dictionary_unittest_generate_names(size_t entries) {
2631 - char **names = mallocz(sizeof(char *) * entries);
2632 - for(size_t i = 0; i < entries ;i++) {
2633 - char buf[25 + 1] = "";
2634 - snprintfz(buf, sizeof(buf), "name.%zu.0123456789.%zu!@#$%%^&*(),./[]{}\\|~`", i, entries / 2 + i);
2635 - names[i] = strdupz(buf);
2636 - }
2637 - return names;
2638 -}
2639 -
2640 -static char **dictionary_unittest_generate_values(size_t entries) {
2641 - char **values = mallocz(sizeof(char *) * entries);
2642 - for(size_t i = 0; i < entries ;i++) {
2643 - char buf[25 + 1] = "";
2644 - snprintfz(buf, sizeof(buf), "value-%zu-0987654321.%zu%%^&*(),. \t !@#$/[]{}\\|~`", i, entries / 2 + i);
2645 - values[i] = strdupz(buf);
2646 - }
2647 - return values;
2648 -}
2649 -
2650 -static size_t dictionary_unittest_set_clone(DICTIONARY *dict, char **names, char **values, size_t entries) {
2651 - size_t errors = 0;
2652 - for(size_t i = 0; i < entries ;i++) {
2653 - size_t vallen = strlen(values[i]);
2654 - char *val = (char *)dictionary_set(dict, names[i], values[i], vallen);
2655 - if(val == values[i]) { fprintf(stderr, ">>> %s() returns reference to value\n", __FUNCTION__); errors++; }
2656 - if(!val || memcmp(val, values[i], vallen) != 0) { fprintf(stderr, ">>> %s() returns invalid value\n", __FUNCTION__); errors++; }
2657 - }
2658 - return errors;
2659 -}
2660 -
2661 -static size_t dictionary_unittest_set_null(DICTIONARY *dict, char **names, char **values, size_t entries) {
2662 - (void)values;
2663 - size_t errors = 0;
2664 - size_t i = 0;
2665 - for(; i < entries ;i++) {
2666 - void *val = dictionary_set(dict, names[i], NULL, 0);
2667 - if(val != NULL) { fprintf(stderr, ">>> %s() returns a non NULL value\n", __FUNCTION__); errors++; }
2668 - }
2669 - if(dictionary_entries(dict) != i) {
2670 - fprintf(stderr, ">>> %s() dictionary items do not match\n", __FUNCTION__);
2671 - errors++;
2672 - }
2673 - return errors;
2674 -}
2675 -
2676 -
2677 -static size_t dictionary_unittest_set_nonclone(DICTIONARY *dict, char **names, char **values, size_t entries) {
2678 - size_t errors = 0;
2679 - for(size_t i = 0; i < entries ;i++) {
2680 - size_t vallen = strlen(values[i]);
2681 - char *val = (char *)dictionary_set(dict, names[i], values[i], vallen);
2682 - if(val != values[i]) { fprintf(stderr, ">>> %s() returns invalid pointer to value\n", __FUNCTION__); errors++; }
2683 - }
2684 - return errors;
2685 -}
2686 -
2687 -static size_t dictionary_unittest_get_clone(DICTIONARY *dict, char **names, char **values, size_t entries) {
2688 - size_t errors = 0;
2689 - for(size_t i = 0; i < entries ;i++) {
2690 - size_t vallen = strlen(values[i]);
2691 - char *val = (char *)dictionary_get(dict, names[i]);
2692 - if(val == values[i]) { fprintf(stderr, ">>> %s() returns reference to value\n", __FUNCTION__); errors++; }
2693 - if(!val || memcmp(val, values[i], vallen) != 0) { fprintf(stderr, ">>> %s() returns invalid value\n", __FUNCTION__); errors++; }
2694 - }
2695 - return errors;
2696 -}
2697 -
2698 -static size_t dictionary_unittest_get_nonclone(DICTIONARY *dict, char **names, char **values, size_t entries) {
2699 - size_t errors = 0;
2700 - for(size_t i = 0; i < entries ;i++) {
2701 - char *val = (char *)dictionary_get(dict, names[i]);
2702 - if(val != values[i]) { fprintf(stderr, ">>> %s() returns invalid pointer to value\n", __FUNCTION__); errors++; }
2703 - }
2704 - return errors;
2705 -}
2706 -
2707 -static size_t dictionary_unittest_get_nonexisting(DICTIONARY *dict, char **names, char **values, size_t entries) {
2708 - (void)names;
2709 - size_t errors = 0;
2710 - for(size_t i = 0; i < entries ;i++) {
2711 - char *val = (char *)dictionary_get(dict, values[i]);
2712 - if(val) { fprintf(stderr, ">>> %s() returns non-existing item\n", __FUNCTION__); errors++; }
2713 - }
2714 - return errors;
2715 -}
2716 -
2717 -static size_t dictionary_unittest_del_nonexisting(DICTIONARY *dict, char **names, char **values, size_t entries) {
2718 - (void)names;
2719 - size_t errors = 0;
2720 - for(size_t i = 0; i < entries ;i++) {
2721 - bool ret = dictionary_del(dict, values[i]);
2722 - if(ret) { fprintf(stderr, ">>> %s() deleted non-existing item\n", __FUNCTION__); errors++; }
2723 - }
2724 - return errors;
2725 -}
2726 -
2727 -static size_t dictionary_unittest_del_existing(DICTIONARY *dict, char **names, char **values, size_t entries) {
2728 - (void)values;
2729 - size_t errors = 0;
2730 -
2731 - size_t forward_from = 0, forward_to = entries / 3;
2732 - size_t middle_from = forward_to, middle_to = entries * 2 / 3;
2733 - size_t backward_from = middle_to, backward_to = entries;
2734 -
2735 - for(size_t i = forward_from; i < forward_to ;i++) {
2736 - bool ret = dictionary_del(dict, names[i]);
2737 - if(!ret) { fprintf(stderr, ">>> %s() didn't delete (forward) existing item\n", __FUNCTION__); errors++; }
2738 - }
2739 -
2740 - for(size_t i = middle_to - 1; i >= middle_from ;i--) {
2741 - bool ret = dictionary_del(dict, names[i]);
2742 - if(!ret) { fprintf(stderr, ">>> %s() didn't delete (middle) existing item\n", __FUNCTION__); errors++; }
2743 - }
2744 -
2745 - for(size_t i = backward_to - 1; i >= backward_from ;i--) {
2746 - bool ret = dictionary_del(dict, names[i]);
2747 - if(!ret) { fprintf(stderr, ">>> %s() didn't delete (backward) existing item\n", __FUNCTION__); errors++; }
2748 - }
2749 -
2750 - return errors;
2751 -}
2752 -
2753 -static size_t dictionary_unittest_reset_clone(DICTIONARY *dict, char **names, char **values, size_t entries) {
2754 - (void)values;
2755 - // set the name as value too
2756 - size_t errors = 0;
2757 - for(size_t i = 0; i < entries ;i++) {
2758 - size_t vallen = strlen(names[i]);
2759 - char *val = (char *)dictionary_set(dict, names[i], names[i], vallen);
2760 - if(val == names[i]) { fprintf(stderr, ">>> %s() returns reference to value\n", __FUNCTION__); errors++; }
2761 - if(!val || memcmp(val, names[i], vallen) != 0) { fprintf(stderr, ">>> %s() returns invalid value\n", __FUNCTION__); errors++; }
2762 - }
2763 - return errors;
2764 -}
2765 -
2766 -static size_t dictionary_unittest_reset_nonclone(DICTIONARY *dict, char **names, char **values, size_t entries) {
2767 - (void)values;
2768 - // set the name as value too
2769 - size_t errors = 0;
2770 - for(size_t i = 0; i < entries ;i++) {
2771 - size_t vallen = strlen(names[i]);
2772 - char *val = (char *)dictionary_set(dict, names[i], names[i], vallen);
2773 - if(val != names[i]) { fprintf(stderr, ">>> %s() returns invalid pointer to value\n", __FUNCTION__); errors++; }
2774 - if(!val) { fprintf(stderr, ">>> %s() returns invalid value\n", __FUNCTION__); errors++; }
2775 - }
2776 - return errors;
2777 -}
2778 -
2779 -static size_t dictionary_unittest_reset_dont_overwrite_nonclone(DICTIONARY *dict, char **names, char **values, size_t entries) {
2780 - // set the name as value too
2781 - size_t errors = 0;
2782 - for(size_t i = 0; i < entries ;i++) {
2783 - size_t vallen = strlen(names[i]);
2784 - char *val = (char *)dictionary_set(dict, names[i], names[i], vallen);
2785 - if(val != values[i]) { fprintf(stderr, ">>> %s() returns invalid pointer to value\n", __FUNCTION__); errors++; }
2786 - }
2787 - return errors;
2788 -}
2789 -
2790 -static int dictionary_unittest_walkthrough_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value __maybe_unused, void *data __maybe_unused) {
2791 - return 1;
2792 -}
2793 -
2794 -static size_t dictionary_unittest_walkthrough(DICTIONARY *dict, char **names, char **values, size_t entries) {
2795 - (void)names;
2796 - (void)values;
2797 - int sum = dictionary_walkthrough_read(dict, dictionary_unittest_walkthrough_callback, NULL);
2798 - if(sum < (int)entries) return entries - sum;
2799 - else return sum - entries;
2800 -}
2801 -
2802 -static int dictionary_unittest_walkthrough_delete_this_callback(const DICTIONARY_ITEM *item, void *value __maybe_unused, void *data) {
2803 - const char *name = dictionary_acquired_item_name((DICTIONARY_ITEM *)item);
2804 -
2805 - if(!dictionary_del((DICTIONARY *)data, name))
2806 - return 0;
2807 -
2808 - return 1;
2809 -}
2810 -
2811 -static size_t dictionary_unittest_walkthrough_delete_this(DICTIONARY *dict, char **names, char **values, size_t entries) {
2812 - (void)names;
2813 - (void)values;
2814 - int sum = dictionary_walkthrough_write(dict, dictionary_unittest_walkthrough_delete_this_callback, dict);
2815 - if(sum < (int)entries) return entries - sum;
2816 - else return sum - entries;
2817 -}
2818 -
2819 -static int dictionary_unittest_walkthrough_stop_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value __maybe_unused, void *data __maybe_unused) {
2820 - return -1;
2821 -}
2822 -
2823 -static size_t dictionary_unittest_walkthrough_stop(DICTIONARY *dict, char **names, char **values, size_t entries) {
2824 - (void)names;
2825 - (void)values;
2826 - (void)entries;
2827 - int sum = dictionary_walkthrough_read(dict, dictionary_unittest_walkthrough_stop_callback, NULL);
2828 - if(sum != -1) return 1;
2829 - return 0;
2830 -}
2831 -
2832 -static size_t dictionary_unittest_foreach(DICTIONARY *dict, char **names, char **values, size_t entries) {
2833 - (void)names;
2834 - (void)values;
2835 - (void)entries;
2836 - size_t count = 0;
2837 - char *item;
2838 - dfe_start_read(dict, item)
2839 - count++;
2840 - dfe_done(item);
2841 -
2842 - if(count > entries) return count - entries;
2843 - return entries - count;
2844 -}
2845 -
2846 -static size_t dictionary_unittest_foreach_delete_this(DICTIONARY *dict, char **names, char **values, size_t entries) {
2847 - (void)names;
2848 - (void)values;
2849 - (void)entries;
2850 - size_t count = 0;
2851 - char *item;
2852 - dfe_start_write(dict, item)
2853 - if(dictionary_del(dict, item_dfe.name)) count++;
2854 - dfe_done(item);
2855 -
2856 - if(count > entries) return count - entries;
2857 - return entries - count;
2858 -}
2859 -
2860 -static size_t dictionary_unittest_destroy(DICTIONARY *dict, char **names, char **values, size_t entries) {
2861 - (void)names;
2862 - (void)values;
2863 - (void)entries;
2864 - size_t bytes = dictionary_destroy(dict);
2865 - fprintf(stderr, " %s() freed %zu bytes,", __FUNCTION__, bytes);
2866 - return 0;
2867 -}
2868 -
2869 -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)) {
2870 - fprintf(stderr, "%40s ... ", message);
2871 -
2872 - usec_t started = now_realtime_usec();
2873 - size_t errs = callback(dict, names, values, entries);
2874 - usec_t ended = now_realtime_usec();
2875 - usec_t dt = ended - started;
2876 -
2877 - if(callback == dictionary_unittest_destroy) dict = NULL;
2878 -
2879 - long int found_ok = 0, found_deleted = 0, found_referenced = 0;
2880 - if(dict) {
2881 - DICTIONARY_ITEM *item;
2882 - DOUBLE_LINKED_LIST_FOREACH_FORWARD(dict->items.list, item, prev, next) {
2883 - if(item->refcount >= 0 && !(item ->flags & ITEM_FLAG_DELETED))
2884 - found_ok++;
2885 - else
2886 - found_deleted++;
2887 -
2888 - if(item->refcount > 0)
2889 - found_referenced++;
2890 - }
2891 - }
2892 -
2893 - fprintf(stderr, " %zu errors, %d (found %ld) items in dictionary, %d (found %ld) referenced, %d (found %ld) deleted, %"PRIu64" usec \n",
2894 - errs, dict?dict->entries:0, found_ok, dict?dict->referenced_items:0, found_referenced, dict?dict->pending_deletion_items:0, found_deleted, dt);
2895 - *errors += errs;
2896 - return dt;
2897 -}
2898 -
2899 -static void dictionary_unittest_clone(DICTIONARY *dict, char **names, char **values, size_t entries, size_t *errors) {
2900 - dictionary_unittest_run_and_measure_time(dict, "adding entries", names, values, entries, errors, dictionary_unittest_set_clone);
2901 - dictionary_unittest_run_and_measure_time(dict, "getting entries", names, values, entries, errors, dictionary_unittest_get_clone);
2902 - dictionary_unittest_run_and_measure_time(dict, "getting non-existing entries", names, values, entries, errors, dictionary_unittest_get_nonexisting);
2903 - dictionary_unittest_run_and_measure_time(dict, "resetting entries", names, values, entries, errors, dictionary_unittest_reset_clone);
2904 - dictionary_unittest_run_and_measure_time(dict, "deleting non-existing entries", names, values, entries, errors, dictionary_unittest_del_nonexisting);
2905 - dictionary_unittest_run_and_measure_time(dict, "traverse foreach read loop", names, values, entries, errors, dictionary_unittest_foreach);
2906 - dictionary_unittest_run_and_measure_time(dict, "walkthrough read callback", names, values, entries, errors, dictionary_unittest_walkthrough);
2907 - dictionary_unittest_run_and_measure_time(dict, "walkthrough read callback stop", names, values, entries, errors, dictionary_unittest_walkthrough_stop);
2908 - dictionary_unittest_run_and_measure_time(dict, "deleting existing entries", names, values, entries, errors, dictionary_unittest_del_existing);
2909 - dictionary_unittest_run_and_measure_time(dict, "walking through empty", names, values, 0, errors, dictionary_unittest_walkthrough);
2910 - dictionary_unittest_run_and_measure_time(dict, "traverse foreach empty", names, values, 0, errors, dictionary_unittest_foreach);
2911 - dictionary_unittest_run_and_measure_time(dict, "destroying empty dictionary", names, values, entries, errors, dictionary_unittest_destroy);
2912 -}
498 + dict->value_aral = NULL;
499
2914 -static void dictionary_unittest_nonclone(DICTIONARY *dict, char **names, char **values, size_t entries, size_t *errors) {
2915 - dictionary_unittest_run_and_measure_time(dict, "adding entries", names, values, entries, errors, dictionary_unittest_set_nonclone);
2916 - dictionary_unittest_run_and_measure_time(dict, "getting entries", names, values, entries, errors, dictionary_unittest_get_nonclone);
2917 - dictionary_unittest_run_and_measure_time(dict, "getting non-existing entries", names, values, entries, errors, dictionary_unittest_get_nonexisting);
2918 - dictionary_unittest_run_and_measure_time(dict, "resetting entries", names, values, entries, errors, dictionary_unittest_reset_nonclone);
2919 - dictionary_unittest_run_and_measure_time(dict, "deleting non-existing entries", names, values, entries, errors, dictionary_unittest_del_nonexisting);
2920 - dictionary_unittest_run_and_measure_time(dict, "traverse foreach read loop", names, values, entries, errors, dictionary_unittest_foreach);
2921 - dictionary_unittest_run_and_measure_time(dict, "walkthrough read callback", names, values, entries, errors, dictionary_unittest_walkthrough);
2922 - dictionary_unittest_run_and_measure_time(dict, "walkthrough read callback stop", names, values, entries, errors, dictionary_unittest_walkthrough_stop);
2923 - dictionary_unittest_run_and_measure_time(dict, "deleting existing entries", names, values, entries, errors, dictionary_unittest_del_existing);
2924 - dictionary_unittest_run_and_measure_time(dict, "walking through empty", names, values, 0, errors, dictionary_unittest_walkthrough);
2925 - dictionary_unittest_run_and_measure_time(dict, "traverse foreach empty", names, values, 0, errors, dictionary_unittest_foreach);
2926 - dictionary_unittest_run_and_measure_time(dict, "destroying empty dictionary", names, values, entries, errors, dictionary_unittest_destroy);
2927 -}
500 + if(!(dict->options & (DICT_OPTION_INDEX_JUDY|DICT_OPTION_INDEX_HASHTABLE)))
501 + dict->options |= DICT_OPTION_INDEX_JUDY;
502
2929 -struct dictionary_unittest_sorting {
2930 - const char *old_name;
2931 - const char *old_value;
2932 - size_t count;
2933 -};
503 + size_t dict_size = 0;
504 + dict_size += sizeof(DICTIONARY);
505 + dict_size += dictionary_locks_init(dict);
506 + dict_size += reference_counter_init(dict);
507 + dict_size += hashtable_init_unsafe(dict);
508
2935 -static int dictionary_unittest_sorting_callback(const DICTIONARY_ITEM *item, void *value, void *data) {
2936 - const char *name = dictionary_acquired_item_name((DICTIONARY_ITEM *)item);
2937 - struct dictionary_unittest_sorting *t = (struct dictionary_unittest_sorting *)data;
2938 - const char *v = (const char *)value;
509 + dictionary_static_items_aral_init();
510 + pointer_index_init(dict);
511
2940 - int ret = 0;
2941 - if(t->old_name && strcmp(t->old_name, name) > 0) {
2942 - fprintf(stderr, "name '%s' should be after '%s'\n", t->old_name, name);
2943 - ret = 1;
2944 - }
2945 - t->count++;
2946 - t->old_name = name;
2947 - t->old_value = v;
512 + DICTIONARY_STATS_PLUS_MEMORY(dict, 0, dict_size, 0);
513
2949 - return ret;
514 + return dict;
515 }
516
2952 -static size_t dictionary_unittest_sorted_walkthrough(DICTIONARY *dict, char **names, char **values, size_t entries) {
2953 - (void)names;
2954 - (void)values;
2955 - struct dictionary_unittest_sorting tmp = { .old_name = NULL, .old_value = NULL, .count = 0 };
2956 - size_t errors;
2957 - errors = dictionary_sorted_walkthrough_read(dict, dictionary_unittest_sorting_callback, &tmp);
517 +#ifdef NETDATA_INTERNAL_CHECKS
518 +DICTIONARY *dictionary_create_advanced_with_trace(DICT_OPTIONS options, struct dictionary_stats *stats, size_t fixed_size, const char *function, size_t line, const char *file) {
519 +#else
520 +DICTIONARY *dictionary_create_advanced(DICT_OPTIONS options, struct dictionary_stats *stats, size_t fixed_size) {
521 +#endif
522
2959 - if(tmp.count != entries) {
2960 - fprintf(stderr, "Expected %zu entries, counted %zu\n", entries, tmp.count);
2961 - errors++;
2962 - }
2963 - return errors;
2964 -}
523 + DICTIONARY *dict = dictionary_create_internal(options, stats?stats:&dictionary_stats_category_other, fixed_size);
524
2966 -static void dictionary_unittest_sorting(DICTIONARY *dict, char **names, char **values, size_t entries, size_t *errors) {
2967 - dictionary_unittest_run_and_measure_time(dict, "adding entries", names, values, entries, errors, dictionary_unittest_set_clone);
2968 - dictionary_unittest_run_and_measure_time(dict, "sorted walkthrough", names, values, entries, errors, dictionary_unittest_sorted_walkthrough);
2969 -}
525 +#ifdef NETDATA_INTERNAL_CHECKS
526 + dict->creation_function = function;
527 + dict->creation_file = file;
528 + dict->creation_line = line;
529 +#endif
530
2971 -static void dictionary_unittest_null_dfe(DICTIONARY *dict, char **names, char **values, size_t entries, size_t *errors) {
2972 - dictionary_unittest_run_and_measure_time(dict, "adding null value entries", names, values, entries, errors, dictionary_unittest_set_null);
2973 - dictionary_unittest_run_and_measure_time(dict, "traverse foreach read loop", names, values, entries, errors, dictionary_unittest_foreach);
531 + DICTIONARY_STATS_DICT_CREATIONS_PLUS1(dict);
532 + return dict;
533 }
534
535 +#ifdef NETDATA_INTERNAL_CHECKS
536 +DICTIONARY *dictionary_create_view_with_trace(DICTIONARY *master, const char *function, size_t line, const char *file) {
537 +#else
538 +DICTIONARY *dictionary_create_view(DICTIONARY *master) {
539 +#endif
540
2977 -static int unittest_check_dictionary_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value __maybe_unused, void *data __maybe_unused) {
2978 - return 1;
2979 -}
541 + DICTIONARY *dict = dictionary_create_internal(master->options, master->stats,
542 + master->value_aral ? aral_element_size(master->value_aral) : 0);
543
2981 -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) {
2982 - size_t errors = 0;
544 + dict->master = master;
545
2984 - size_t ll = 0;
2985 - void *t;
2986 - dfe_start_read(dict, t)
2987 - ll++;
2988 - dfe_done(t);
546 + dictionary_hooks_allocate(master);
547
2990 - fprintf(stderr, "DICT %-20s: dictionary foreach entries %zu, expected %zu...\t\t\t\t\t",
2991 - label, ll, traversable);
2992 - if(ll != traversable) {
2993 - fprintf(stderr, "FAILED\n");
2994 - errors++;
2995 - }
2996 - else
2997 - fprintf(stderr, "OK\n");
2998 -
2999 - ll = dictionary_walkthrough_read(dict, unittest_check_dictionary_callback, NULL);
3000 - fprintf(stderr, "DICT %-20s: dictionary walkthrough entries %zu, expected %zu...\t\t\t\t",
3001 - label, ll, traversable);
3002 - if(ll != traversable) {
3003 - fprintf(stderr, "FAILED\n");
3004 - errors++;
3005 - }
3006 - else
3007 - fprintf(stderr, "OK\n");
3008 -
3009 - ll = dictionary_sorted_walkthrough_read(dict, unittest_check_dictionary_callback, NULL);
3010 - fprintf(stderr, "DICT %-20s: dictionary sorted walkthrough entries %zu, expected %zu...\t\t\t",
3011 - label, ll, traversable);
3012 - if(ll != traversable) {
3013 - fprintf(stderr, "FAILED\n");
3014 - errors++;
3015 - }
3016 - else
3017 - fprintf(stderr, "OK\n");
548 + if(unlikely(__atomic_load_n(&master->hooks->links, __ATOMIC_RELAXED)) < 1)
549 + fatal("DICTIONARY: attempted to create a view that has %d links", master->hooks->links);
550
3019 - DICTIONARY_ITEM *item;
3020 - size_t active = 0, deleted = 0, referenced = 0, pending = 0;
3021 - for(item = dict->items.list; item; item = item->next) {
3022 - if(!(item->flags & ITEM_FLAG_DELETED) && !(item->shared->flags & ITEM_FLAG_DELETED))
3023 - active++;
3024 - else {
3025 - deleted++;
551 + dict->hooks = master->hooks;
552 + __atomic_add_fetch(&master->hooks->links, 1, __ATOMIC_ACQUIRE);
553
3027 - if(item->refcount == 0)
3028 - pending++;
3029 - }
554 +#ifdef NETDATA_INTERNAL_CHECKS
555 + dict->creation_function = function;
556 + dict->creation_file = file;
557 + dict->creation_line = line;
558 + dict->creation_tid = gettid();
559 +#endif
560
3031 - if(item->refcount > 0)
3032 - referenced++;
3033 - }
561 + DICTIONARY_STATS_DICT_CREATIONS_PLUS1(dict);
562 + return dict;
563 +}
564
3035 - fprintf(stderr, "DICT %-20s: dictionary active items reported %d, counted %zu, expected %zu...\t\t\t",
3036 - label, dict->entries, active, active_items);
3037 - if(active != active_items || active != (size_t)dict->entries) {
3038 - fprintf(stderr, "FAILED\n");
3039 - errors++;
3040 - }
3041 - else
3042 - fprintf(stderr, "OK\n");
565 +void dictionary_flush(DICTIONARY *dict) {
566 + if(unlikely(!dict))
567 + return;
568
3044 - fprintf(stderr, "DICT %-20s: dictionary deleted items counted %zu, expected %zu...\t\t\t\t",
3045 - label, deleted, deleted_items);
3046 - if(deleted != deleted_items) {
3047 - fprintf(stderr, "FAILED\n");
3048 - errors++;
3049 - }
3050 - else
3051 - fprintf(stderr, "OK\n");
569 + ll_recursive_lock(dict, DICTIONARY_LOCK_WRITE);
570
3053 - fprintf(stderr, "DICT %-20s: dictionary referenced items reported %d, counted %zu, expected %zu...\t\t",
3054 - label, dict->referenced_items, referenced, referenced_items);
3055 - if(referenced != referenced_items || dict->referenced_items != (long int)referenced) {
3056 - fprintf(stderr, "FAILED\n");
3057 - errors++;
571 + DICTIONARY_ITEM *item, *next = NULL;
572 + for(item = dict->items.list; item ;item = next) {
573 + next = item->next;
574 + dict_item_del(dict, item_get_name(item), (ssize_t)item_get_name_len(item));
575 }
3059 - else
3060 - fprintf(stderr, "OK\n");
576
3062 - fprintf(stderr, "DICT %-20s: dictionary pending deletion items reported %d, counted %zu, expected %zu...\t",
3063 - label, dict->pending_deletion_items, pending, pending_deletion);
3064 - if(pending != pending_deletion || pending != (size_t)dict->pending_deletion_items) {
3065 - fprintf(stderr, "FAILED\n");
3066 - errors++;
3067 - }
3068 - else
3069 - fprintf(stderr, "OK\n");
577 + ll_recursive_unlock(dict, DICTIONARY_LOCK_WRITE);
578
3071 - return errors;
579 + DICTIONARY_STATS_DICT_FLUSHES_PLUS1(dict);
580 }
581
3074 -static int check_item_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data) {
3075 - return value == data;
3076 -}
582 +size_t dictionary_destroy(DICTIONARY *dict) {
583 + cleanup_destroyed_dictionaries();
584
3078 -static size_t unittest_check_item(const char *label, DICTIONARY *dict,
3079 - DICTIONARY_ITEM *item, const char *name, const char *value, int refcount,
3080 - ITEM_FLAGS deleted_flags, bool searchable, bool browsable, bool linked) {
3081 - size_t errors = 0;
585 + if(!dict) return 0;
586
3083 - fprintf(stderr, "ITEM %-20s: name is '%s', expected '%s'...\t\t\t\t\t\t", label, item_get_name(item), name);
3084 - if(strcmp(item_get_name(item), name) != 0) {
3085 - fprintf(stderr, "FAILED\n");
3086 - errors++;
3087 - }
3088 - else
3089 - fprintf(stderr, "OK\n");
587 + ll_recursive_lock(dict, DICTIONARY_LOCK_WRITE);
588
3091 - fprintf(stderr, "ITEM %-20s: value is '%s', expected '%s'...\t\t\t\t\t", label, (const char *)item->shared->value, value);
3092 - if(strcmp((const char *)item->shared->value, value) != 0) {
3093 - fprintf(stderr, "FAILED\n");
3094 - errors++;
3095 - }
3096 - else
3097 - fprintf(stderr, "OK\n");
589 + dict_flag_set(dict, DICT_FLAG_DESTROYED);
590 + DICTIONARY_STATS_DICT_DESTRUCTIONS_PLUS1(dict);
591
3099 - fprintf(stderr, "ITEM %-20s: refcount is %d, expected %d...\t\t\t\t\t\t\t", label, item->refcount, refcount);
3100 - if (item->refcount != refcount) {
3101 - fprintf(stderr, "FAILED\n");
3102 - errors++;
3103 - }
3104 - else
3105 - fprintf(stderr, "OK\n");
592 + size_t referenced_items = dictionary_referenced_items(dict);
593 + if(referenced_items) {
594 + dictionary_flush(dict);
595 + dictionary_queue_for_destruction(dict);
596
3107 - fprintf(stderr, "ITEM %-20s: deleted flag is %s, expected %s...\t\t\t\t\t", label,
3108 - (item->flags & ITEM_FLAG_DELETED || item->shared->flags & ITEM_FLAG_DELETED)?"true":"false",
3109 - (deleted_flags & ITEM_FLAG_DELETED)?"true":"false");
597 + internal_error(
598 + true,
599 + "DICTIONARY: delaying destruction of dictionary created from %s() %zu@%s, because it has %d referenced items in it (%d total).",
600 + dict->creation_function,
601 + dict->creation_line,
602 + dict->creation_file,
603 + dict->referenced_items,
604 + dict->entries);
605
3111 - if ((item->flags & ITEM_FLAG_DELETED || item->shared->flags & ITEM_FLAG_DELETED) != (deleted_flags & ITEM_FLAG_DELETED)) {
3112 - fprintf(stderr, "FAILED\n");
3113 - errors++;
3114 - }
3115 - else
3116 - fprintf(stderr, "OK\n");
3117 -
3118 - void *v = dictionary_get(dict, name);
3119 - bool found = v == item->shared->value;
3120 - fprintf(stderr, "ITEM %-20s: searchable %5s, expected %5s...\t\t\t\t\t\t", label,
3121 - found?"true":"false", searchable?"true":"false");
3122 - if(found != searchable) {
3123 - fprintf(stderr, "FAILED\n");
3124 - errors++;
606 + ll_recursive_unlock(dict, DICTIONARY_LOCK_WRITE);
607 + return 0;
608 }
3126 - else
3127 - fprintf(stderr, "OK\n");
609
3129 - found = false;
3130 - void *t;
3131 - dfe_start_read(dict, t) {
3132 - if(t == item->shared->value) found = true;
3133 - }
3134 - dfe_done(t);
610 + ll_recursive_unlock(dict, DICTIONARY_LOCK_WRITE);
611
3136 - fprintf(stderr, "ITEM %-20s: dfe browsable %5s, expected %5s...\t\t\t\t\t", label,
3137 - found?"true":"false", browsable?"true":"false");
3138 - if(found != browsable) {
3139 - fprintf(stderr, "FAILED\n");
3140 - errors++;
3141 - }
3142 - else
3143 - fprintf(stderr, "OK\n");
3144 -
3145 - found = dictionary_walkthrough_read(dict, check_item_callback, item->shared->value);
3146 - fprintf(stderr, "ITEM %-20s: walkthrough browsable %5s, expected %5s...\t\t\t\t", label,
3147 - found?"true":"false", browsable?"true":"false");
3148 - if(found != browsable) {
3149 - fprintf(stderr, "FAILED\n");
3150 - errors++;
3151 - }
3152 - else
3153 - fprintf(stderr, "OK\n");
3154 -
3155 - found = dictionary_sorted_walkthrough_read(dict, check_item_callback, item->shared->value);
3156 - fprintf(stderr, "ITEM %-20s: sorted walkthrough browsable %5s, expected %5s...\t\t\t", label,
3157 - found?"true":"false", browsable?"true":"false");
3158 - if(found != browsable) {
3159 - fprintf(stderr, "FAILED\n");
3160 - errors++;
3161 - }
3162 - else
3163 - fprintf(stderr, "OK\n");
3164 -
3165 - found = false;
3166 - DICTIONARY_ITEM *n;
3167 - for(n = dict->items.list; n ;n = n->next)
3168 - if(n == item) found = true;
3169 -
3170 - fprintf(stderr, "ITEM %-20s: linked %5s, expected %5s...\t\t\t\t\t\t", label,
3171 - found?"true":"false", linked?"true":"false");
3172 - if(found != linked) {
3173 - fprintf(stderr, "FAILED\n");
3174 - errors++;
3175 - }
3176 - else
3177 - fprintf(stderr, "OK\n");
612 + size_t freed;
613 + dictionary_free_all_resources(dict, &freed, true);
614
3179 - return errors;
615 + return freed;
616 }
617
3182 -struct thread_unittest {
3183 - int join;
3184 - DICTIONARY *dict;
3185 - int dups;
3186 -
3187 - netdata_thread_t thread;
3188 - struct dictionary_stats stats;
3189 -};
3190 -
3191 -static void *unittest_dict_thread(void *arg) {
3192 - struct thread_unittest *tu = arg;
3193 - for(; 1 ;) {
3194 - if(__atomic_load_n(&tu->join, __ATOMIC_RELAXED))
3195 - break;
618 +// ----------------------------------------------------------------------------
619 +// SET an item to the dictionary
620
3197 - DICT_ITEM_CONST DICTIONARY_ITEM *item =
3198 - dictionary_set_and_acquire_item_advanced(tu->dict, "dict thread checking 1234567890",
3199 - -1, NULL, 0, NULL);
3200 - tu->stats.ops.inserts++;
621 +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) {
622 + if(unlikely(!api_is_name_good(dict, name, name_len)))
623 + return NULL;
624
3202 - dictionary_get(tu->dict, dictionary_acquired_item_name(item));
3203 - tu->stats.ops.searches++;
625 + api_internal_check(dict, NULL, false, true);
626
3205 - void *t1;
3206 - dfe_start_write(tu->dict, t1) {
627 + if(unlikely(is_view_dictionary(dict)))
628 + fatal("DICTIONARY: this dictionary is a view, you cannot add items other than the ones from the master dictionary.");
629
3208 - // this should delete the referenced item
3209 - dictionary_del(tu->dict, t1_dfe.name);
3210 - tu->stats.ops.deletes++;
630 + DICTIONARY_ITEM *item =
631 + dict_item_add_or_reset_value_and_acquire(dict, name, name_len, value, value_len, constructor_data, NULL);
632 + api_internal_check(dict, item, false, false);
633 + return item;
634 +}
635
3212 - void *t2;
3213 - dfe_start_write(tu->dict, t2) {
3214 - // this should add another
3215 - dictionary_set(tu->dict, t2_dfe.name, NULL, 0);
3216 - tu->stats.ops.inserts++;
636 +void *dictionary_set_advanced(DICTIONARY *dict, const char *name, ssize_t name_len, void *value, size_t value_len, void *constructor_data) {
637 + DICTIONARY_ITEM *item = dictionary_set_and_acquire_item_advanced(dict, name, name_len, value, value_len, constructor_data);
638
3218 - dictionary_get(tu->dict, dictionary_acquired_item_name(item));
3219 - tu->stats.ops.searches++;
639 + if(likely(item)) {
640 + void *v = item->shared->value;
641 + item_release(dict, item);
642 + return v;
643 + }
644
3221 - // and this should delete it again
3222 - dictionary_del(tu->dict, t2_dfe.name);
3223 - tu->stats.ops.deletes++;
3224 - }
3225 - dfe_done(t2);
3226 - tu->stats.ops.traversals++;
645 + return NULL;
646 +}
647
3228 - // this should fail to add it
3229 - dictionary_set(tu->dict, t1_dfe.name, NULL, 0);
3230 - tu->stats.ops.inserts++;
648 +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) {
649 + if(unlikely(!api_is_name_good(dict, name, name_len)))
650 + return NULL;
651
3232 - dictionary_del(tu->dict, t1_dfe.name);
3233 - tu->stats.ops.deletes++;
3234 - }
3235 - dfe_done(t1);
3236 - tu->stats.ops.traversals++;
652 + api_internal_check(dict, NULL, false, true);
653
3238 - for(int i = 0; i < tu->dups ; i++) {
3239 - dictionary_acquired_item_dup(tu->dict, item);
3240 - dictionary_get(tu->dict, dictionary_acquired_item_name(item));
3241 - tu->stats.ops.searches++;
3242 - }
654 + if(unlikely(is_master_dictionary(dict)))
655 + fatal("DICTIONARY: this dictionary is a master, you cannot add items from other dictionaries.");
656
3244 - for(int i = 0; i < tu->dups ; i++) {
3245 - dictionary_acquired_item_release(tu->dict, item);
3246 - dictionary_del(tu->dict, dictionary_acquired_item_name(item));
3247 - tu->stats.ops.deletes++;
3248 - }
657 + garbage_collect_pending_deletes(dict);
658
3250 - dictionary_acquired_item_release(tu->dict, item);
3251 - dictionary_del(tu->dict, "dict thread checking 1234567890");
3252 - tu->stats.ops.deletes++;
3253 -
3254 - // test concurrent deletions and flushes
3255 - {
3256 - if(gettid() % 2) {
3257 - char buf [256 + 1];
3258 -
3259 - for (int i = 0; i < 1000; i++) {
3260 - snprintfz(buf, sizeof(buf), "del/flush test %d", i);
3261 - dictionary_set(tu->dict, buf, NULL, 0);
3262 - tu->stats.ops.inserts++;
3263 - }
3264 -
3265 - for (int i = 0; i < 1000; i++) {
3266 - snprintfz(buf, sizeof(buf), "del/flush test %d", i);
3267 - dictionary_del(tu->dict, buf);
3268 - tu->stats.ops.deletes++;
3269 - }
3270 - }
3271 - else {
3272 - for (int i = 0; i < 10; i++) {
3273 - dictionary_flush(tu->dict);
3274 - tu->stats.ops.flushes++;
3275 - }
3276 - }
3277 - }
3278 - }
659 + dictionary_acquired_item_dup(dict->master, master_item);
660 + DICTIONARY_ITEM *item = dict_item_add_or_reset_value_and_acquire(dict, name, name_len, NULL, 0, NULL, master_item);
661 + dictionary_acquired_item_release(dict->master, master_item);
662
3280 - return arg;
663 + api_internal_check(dict, item, false, false);
664 + return item;
665 }
666
3283 -static int dictionary_unittest_threads() {
3284 - time_t seconds_to_run = 5;
3285 - int threads_to_create = 2;
3286 -
3287 - struct thread_unittest tu[threads_to_create];
3288 - memset(tu, 0, sizeof(struct thread_unittest) * threads_to_create);
3289 -
3290 - fprintf(
3291 - stderr,
3292 - "\nChecking dictionary concurrency with %d threads for %lld seconds...\n",
3293 - threads_to_create,
3294 - (long long)seconds_to_run);
3295 -
3296 - // threads testing of dictionary
3297 - struct dictionary_stats stats = {};
3298 - tu[0].join = 0;
3299 - tu[0].dups = 1;
3300 - tu[0].dict = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE, &stats, 0);
3301 -
3302 - for (int i = 0; i < threads_to_create; i++) {
3303 - if(i)
3304 - tu[i] = tu[0];
3305 -
3306 - char buf[100 + 1];
3307 - snprintf(buf, 100, "dict%d", i);
3308 - netdata_thread_create(
3309 - &tu[i].thread,
3310 - buf,
3311 - NETDATA_THREAD_OPTION_DONT_LOG | NETDATA_THREAD_OPTION_JOINABLE,
3312 - unittest_dict_thread,
3313 - &tu[i]);
3314 - }
3315 -
3316 - sleep_usec(seconds_to_run * USEC_PER_SEC);
3317 -
3318 - for (int i = 0; i < threads_to_create; i++) {
3319 - __atomic_store_n(&tu[i].join, 1, __ATOMIC_RELAXED);
3320 -
3321 - void *retval;
3322 - netdata_thread_join(tu[i].thread, &retval);
667 +void *dictionary_view_set_advanced(DICTIONARY *dict, const char *name, ssize_t name_len, DICTIONARY_ITEM *master_item) {
668 + DICTIONARY_ITEM *item = dictionary_view_set_and_acquire_item_advanced(dict, name, name_len, master_item);
669
3324 - if(i) {
3325 - tu[0].stats.ops.inserts += tu[i].stats.ops.inserts;
3326 - tu[0].stats.ops.deletes += tu[i].stats.ops.deletes;
3327 - tu[0].stats.ops.searches += tu[i].stats.ops.searches;
3328 - tu[0].stats.ops.flushes += tu[i].stats.ops.flushes;
3329 - tu[0].stats.ops.traversals += tu[i].stats.ops.traversals;
3330 - }
670 + if(likely(item)) {
671 + void *v = item->shared->value;
672 + item_release(dict, item);
673 + return v;
674 }
675
3333 - fprintf(stderr,
3334 - "CALLS : inserts %zu"
3335 - ", deletes %zu"
3336 - ", searches %zu"
3337 - ", traversals %zu"
3338 - ", flushes %zu"
3339 - "\n",
3340 - tu[0].stats.ops.inserts,
3341 - tu[0].stats.ops.deletes,
3342 - tu[0].stats.ops.searches,
3343 - tu[0].stats.ops.traversals,
3344 - tu[0].stats.ops.flushes
3345 - );
3346 -
3347 -#ifdef DICT_WITH_STATS
3348 - fprintf(stderr,
3349 - "ACTUAL: inserts %zu"
3350 - ", deletes %zu"
3351 - ", searches %zu"
3352 - ", traversals %zu"
3353 - ", resets %zu"
3354 - ", flushes %zu"
3355 - ", entries %d"
3356 - ", referenced_items %d"
3357 - ", pending deletions %d"
3358 - ", check spins %zu"
3359 - ", insert spins %zu"
3360 - ", delete spins %zu"
3361 - ", search ignores %zu"
3362 - "\n",
3363 - stats.ops.inserts,
3364 - stats.ops.deletes,
3365 - stats.ops.searches,
3366 - stats.ops.traversals,
3367 - stats.ops.resets,
3368 - stats.ops.flushes,
3369 - tu[0].dict->entries,
3370 - tu[0].dict->referenced_items,
3371 - tu[0].dict->pending_deletion_items,
3372 - stats.spin_locks.use_spins,
3373 - stats.spin_locks.insert_spins,
3374 - stats.spin_locks.delete_spins,
3375 - stats.spin_locks.search_spins
3376 - );
3377 -#endif
3378 -
3379 - dictionary_destroy(tu[0].dict);
3380 - return 0;
676 + return NULL;
677 }
678
3383 -struct thread_view_unittest {
3384 - int join;
3385 - DICTIONARY *master;
3386 - DICTIONARY *view;
3387 - DICTIONARY_ITEM *item_master;
3388 - int dups;
3389 -};
3390 -
3391 -static void *unittest_dict_master_thread(void *arg) {
3392 - struct thread_view_unittest *tv = arg;
3393 -
3394 - DICTIONARY_ITEM *item = NULL;
3395 - int loops = 0;
3396 - while(!__atomic_load_n(&tv->join, __ATOMIC_RELAXED)) {
679 +// ----------------------------------------------------------------------------
680 +// GET an item from the dictionary
681
3398 - if(!item)
3399 - item = dictionary_set_and_acquire_item(tv->master, "ITEM1", "123", strlen("123"));
682 +DICT_ITEM_CONST DICTIONARY_ITEM *dictionary_get_and_acquire_item_advanced(DICTIONARY *dict, const char *name, ssize_t name_len) {
683 + if(unlikely(!api_is_name_good(dict, name, name_len)))
684 + return NULL;
685
3401 - if(__atomic_load_n(&tv->item_master, __ATOMIC_RELAXED) != NULL) {
3402 - dictionary_acquired_item_release(tv->master, item);
3403 - dictionary_del(tv->master, "ITEM1");
3404 - item = NULL;
3405 - loops++;
3406 - continue;
3407 - }
686 + api_internal_check(dict, NULL, false, true);
687 + DICTIONARY_ITEM *item = dict_item_find_and_acquire(dict, name, name_len);
688 + api_internal_check(dict, item, false, true);
689 + return item;
690 +}
691
3409 - dictionary_acquired_item_dup(tv->master, item); // for the view thread
3410 - __atomic_store_n(&tv->item_master, item, __ATOMIC_RELAXED);
3411 - dictionary_del(tv->master, "ITEM1");
692 +void *dictionary_get_advanced(DICTIONARY *dict, const char *name, ssize_t name_len) {
693 + DICTIONARY_ITEM *item = dictionary_get_and_acquire_item_advanced(dict, name, name_len);
694
695 + if(likely(item)) {
696 + void *v = item->shared->value;
697 + item_release(dict, item);
698 + return v;
699 + }
700
3414 - for(int i = 0; i < tv->dups + loops ; i++) {
3415 - dictionary_acquired_item_dup(tv->master, item);
3416 - }
701 + return NULL;
702 +}
703
3418 - for(int i = 0; i < tv->dups + loops ; i++) {
3419 - dictionary_acquired_item_release(tv->master, item);
3420 - }
704 +// ----------------------------------------------------------------------------
705 +// DUP/REL an item (increase/decrease its reference counter)
706
3422 - dictionary_acquired_item_release(tv->master, item);
707 +DICT_ITEM_CONST DICTIONARY_ITEM *dictionary_acquired_item_dup(DICTIONARY *dict, DICT_ITEM_CONST DICTIONARY_ITEM *item) {
708 + // we allow the item to be NULL here
709 + api_internal_check(dict, item, false, true);
710
3424 - item = NULL;
3425 - loops = 0;
711 + if(likely(item)) {
712 + item_acquire(dict, item);
713 + api_internal_check(dict, item, false, false);
714 }
715
3428 - return arg;
716 + return item;
717 }
718
3431 -static void *unittest_dict_view_thread(void *arg) {
3432 - struct thread_view_unittest *tv = arg;
3433 -
3434 - DICTIONARY_ITEM *m_item = NULL;
3435 -
3436 - while(!__atomic_load_n(&tv->join, __ATOMIC_RELAXED)) {
3437 - if(!(m_item = __atomic_load_n(&tv->item_master, __ATOMIC_RELAXED)))
3438 - continue;
3439 -
3440 - DICTIONARY_ITEM *v_item = dictionary_view_set_and_acquire_item(tv->view, "ITEM2", m_item);
3441 - dictionary_acquired_item_release(tv->master, m_item);
3442 - __atomic_store_n(&tv->item_master, NULL, __ATOMIC_RELAXED);
719 +void dictionary_acquired_item_release(DICTIONARY *dict, DICT_ITEM_CONST DICTIONARY_ITEM *item) {
720 + // we allow the item to be NULL here
721 + api_internal_check(dict, item, false, true);
722
3444 - for(int i = 0; i < tv->dups ; i++) {
3445 - dictionary_acquired_item_dup(tv->view, v_item);
3446 - }
723 + // no need to get a lock here
724 + // we pass the last parameter to reference_counter_release() as true
725 + // so that the release may get a write-lock if required to clean up
726
3448 - for(int i = 0; i < tv->dups ; i++) {
3449 - dictionary_acquired_item_release(tv->view, v_item);
3450 - }
727 + if(likely(item))
728 + item_release(dict, item);
729 +}
730
3452 - dictionary_del(tv->view, "ITEM2");
731 +// ----------------------------------------------------------------------------
732 +// get the name/value of an item
733
3454 - while(!__atomic_load_n(&tv->join, __ATOMIC_RELAXED) && !(m_item = __atomic_load_n(&tv->item_master, __ATOMIC_RELAXED))) {
3455 - dictionary_acquired_item_dup(tv->view, v_item);
3456 - dictionary_acquired_item_release(tv->view, v_item);
3457 - }
734 +const char *dictionary_acquired_item_name(DICT_ITEM_CONST DICTIONARY_ITEM *item) {
735 + return item_get_name(item);
736 +}
737
3459 - dictionary_acquired_item_release(tv->view, v_item);
3460 - }
738 +void *dictionary_acquired_item_value(DICT_ITEM_CONST DICTIONARY_ITEM *item) {
739 + if(likely(item))
740 + return item->shared->value;
741
3462 - return arg;
742 + return NULL;
743 }
744
3465 -static int dictionary_unittest_view_threads() {
3466 -
3467 - struct thread_view_unittest tv = {
3468 - .join = 0,
3469 - .master = NULL,
3470 - .view = NULL,
3471 - .item_master = NULL,
3472 - .dups = 1,
3473 - };
3474 -
3475 - // threads testing of dictionary
3476 - struct dictionary_stats stats_master = {};
3477 - struct dictionary_stats stats_view = {};
3478 - tv.master = dictionary_create_advanced(DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_DONT_OVERWRITE_VALUE, &stats_master, 0);
3479 - tv.view = dictionary_create_view(tv.master);
3480 - tv.view->stats = &stats_view;
3481 -
3482 - time_t seconds_to_run = 5;
3483 - fprintf(
3484 - stderr,
3485 - "\nChecking dictionary concurrency with 1 master and 1 view threads for %lld seconds...\n",
3486 - (long long)seconds_to_run);
3487 -
3488 - netdata_thread_t master_thread, view_thread;
3489 - tv.join = 0;
3490 -
3491 - netdata_thread_create(
3492 - &master_thread,
3493 - "master",
3494 - NETDATA_THREAD_OPTION_DONT_LOG | NETDATA_THREAD_OPTION_JOINABLE,
3495 - unittest_dict_master_thread,
3496 - &tv);
3497 -
3498 - netdata_thread_create(
3499 - &view_thread,
3500 - "view",
3501 - NETDATA_THREAD_OPTION_DONT_LOG | NETDATA_THREAD_OPTION_JOINABLE,
3502 - unittest_dict_view_thread,
3503 - &tv);
3504 -
3505 - sleep_usec(seconds_to_run * USEC_PER_SEC);
3506 -
3507 - __atomic_store_n(&tv.join, 1, __ATOMIC_RELAXED);
3508 - void *retval;
3509 - netdata_thread_join(view_thread, &retval);
3510 - netdata_thread_join(master_thread, &retval);
3511 -
3512 -#ifdef DICT_WITH_STATS
3513 - fprintf(stderr,
3514 - "MASTER: inserts %zu"
3515 - ", deletes %zu"
3516 - ", searches %zu"
3517 - ", resets %zu"
3518 - ", entries %d"
3519 - ", referenced_items %d"
3520 - ", pending deletions %d"
3521 - ", check spins %zu"
3522 - ", insert spins %zu"
3523 - ", delete spins %zu"
3524 - ", search ignores %zu"
3525 - "\n",
3526 - stats_master.ops.inserts,
3527 - stats_master.ops.deletes,
3528 - stats_master.ops.searches,
3529 - stats_master.ops.resets,
3530 - tv.master->entries,
3531 - tv.master->referenced_items,
3532 - tv.master->pending_deletion_items,
3533 - stats_master.spin_locks.use_spins,
3534 - stats_master.spin_locks.insert_spins,
3535 - stats_master.spin_locks.delete_spins,
3536 - stats_master.spin_locks.search_spins
3537 - );
3538 - fprintf(stderr,
3539 - "VIEW : inserts %zu"
3540 - ", deletes %zu"
3541 - ", searches %zu"
3542 - ", resets %zu"
3543 - ", entries %d"
3544 - ", referenced_items %d"
3545 - ", pending deletions %d"
3546 - ", check spins %zu"
3547 - ", insert spins %zu"
3548 - ", delete spins %zu"
3549 - ", search ignores %zu"
3550 - "\n",
3551 - stats_view.ops.inserts,
3552 - stats_view.ops.deletes,
3553 - stats_view.ops.searches,
3554 - stats_view.ops.resets,
3555 - tv.view->entries,
3556 - tv.view->referenced_items,
3557 - tv.view->pending_deletion_items,
3558 - stats_view.spin_locks.use_spins,
3559 - stats_view.spin_locks.insert_spins,
3560 - stats_view.spin_locks.delete_spins,
3561 - stats_view.spin_locks.search_spins
3562 - );
3563 -#endif
3564 -
3565 - dictionary_destroy(tv.master);
3566 - dictionary_destroy(tv.view);
745 +size_t dictionary_acquired_item_references(DICT_ITEM_CONST DICTIONARY_ITEM *item) {
746 + if(likely(item))
747 + return DICTIONARY_ITEM_REFCOUNT_GET_SOLE(item);
748
749 return 0;
750 }
751
3571 -size_t dictionary_unittest_views(void) {
3572 - size_t errors = 0;
3573 - struct dictionary_stats stats = {};
3574 - DICTIONARY *master = dictionary_create_advanced(DICT_OPTION_NONE, &stats, 0);
3575 - DICTIONARY *view = dictionary_create_view(master);
3576 -
3577 - fprintf(stderr, "\n\nChecking dictionary views...\n");
3578 -
3579 - // Add an item to both master and view, then remove the view first and the master second
3580 - fprintf(stderr, "\nPASS 1: Adding 1 item to master:\n");
3581 - DICTIONARY_ITEM *item1_on_master = dictionary_set_and_acquire_item(master, "KEY 1", "VALUE1", strlen("VALUE1") + 1);
3582 - errors += unittest_check_dictionary("master", master, 1, 1, 0, 1, 0);
3583 - errors += unittest_check_item("master", master, item1_on_master, "KEY 1", item1_on_master->shared->value, 1, ITEM_FLAG_NONE, true, true, true);
3584 -
3585 - fprintf(stderr, "\nPASS 1: Adding master item to view:\n");
3586 - DICTIONARY_ITEM *item1_on_view = dictionary_view_set_and_acquire_item(view, "KEY 1 ON VIEW", item1_on_master);
3587 - errors += unittest_check_dictionary("view", view, 1, 1, 0, 1, 0);
3588 - 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);
3589 -
3590 - fprintf(stderr, "\nPASS 1: Deleting view item:\n");
3591 - dictionary_del(view, "KEY 1 ON VIEW");
3592 - errors += unittest_check_dictionary("master", master, 1, 1, 0, 1, 0);
3593 - errors += unittest_check_dictionary("view", view, 0, 0, 1, 1, 0);
3594 - errors += unittest_check_item("master", master, item1_on_master, "KEY 1", item1_on_master->shared->value, 1, ITEM_FLAG_NONE, true, true, true);
3595 - 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);
3596 -
3597 - fprintf(stderr, "\nPASS 1: Releasing the deleted view item:\n");
3598 - dictionary_acquired_item_release(view, item1_on_view);
3599 - errors += unittest_check_dictionary("master", master, 1, 1, 0, 1, 0);
3600 - errors += unittest_check_dictionary("view", view, 0, 0, 1, 0, 1);
3601 - errors += unittest_check_item("master", master, item1_on_master, "KEY 1", item1_on_master->shared->value, 1, ITEM_FLAG_NONE, true, true, true);
3602 -
3603 - fprintf(stderr, "\nPASS 1: Releasing the acquired master item:\n");
3604 - dictionary_acquired_item_release(master, item1_on_master);
3605 - errors += unittest_check_dictionary("master", master, 1, 1, 0, 0, 0);
3606 - errors += unittest_check_dictionary("view", view, 0, 0, 1, 0, 1);
3607 - errors += unittest_check_item("master", master, item1_on_master, "KEY 1", item1_on_master->shared->value, 0, ITEM_FLAG_NONE, true, true, true);
3608 -
3609 - fprintf(stderr, "\nPASS 1: Deleting the released master item:\n");
3610 - dictionary_del(master, "KEY 1");
3611 - errors += unittest_check_dictionary("master", master, 0, 0, 0, 0, 0);
3612 - errors += unittest_check_dictionary("view", view, 0, 0, 1, 0, 1);
3613 -
3614 - // The other way now:
3615 - // Add an item to both master and view, then remove the master first and verify it is deleted on the view also
3616 - fprintf(stderr, "\nPASS 2: Adding 1 item to master:\n");
3617 - item1_on_master = dictionary_set_and_acquire_item(master, "KEY 1", "VALUE1", strlen("VALUE1") + 1);
3618 - errors += unittest_check_dictionary("master", master, 1, 1, 0, 1, 0);
3619 - errors += unittest_check_item("master", master, item1_on_master, "KEY 1", item1_on_master->shared->value, 1, ITEM_FLAG_NONE, true, true, true);
3620 -
3621 - fprintf(stderr, "\nPASS 2: Adding master item to view:\n");
3622 - item1_on_view = dictionary_view_set_and_acquire_item(view, "KEY 1 ON VIEW", item1_on_master);
3623 - errors += unittest_check_dictionary("view", view, 1, 1, 0, 1, 0);
3624 - 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);
3625 -
3626 - fprintf(stderr, "\nPASS 2: Deleting master item:\n");
3627 - dictionary_del(master, "KEY 1");
3628 - garbage_collect_pending_deletes(view);
3629 - errors += unittest_check_dictionary("master", master, 0, 0, 1, 1, 0);
3630 - errors += unittest_check_dictionary("view", view, 0, 0, 1, 1, 0);
3631 - errors += unittest_check_item("master", master, item1_on_master, "KEY 1", item1_on_master->shared->value, 1, ITEM_FLAG_DELETED, false, false, true);
3632 - 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);
3633 -
3634 - fprintf(stderr, "\nPASS 2: Releasing the acquired master item:\n");
3635 - dictionary_acquired_item_release(master, item1_on_master);
3636 - errors += unittest_check_dictionary("master", master, 0, 0, 1, 0, 1);
3637 - errors += unittest_check_dictionary("view", view, 0, 0, 1, 1, 0);
3638 - 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);
3639 -
3640 - fprintf(stderr, "\nPASS 2: Releasing the deleted view item:\n");
3641 - dictionary_acquired_item_release(view, item1_on_view);
3642 - errors += unittest_check_dictionary("master", master, 0, 0, 1, 0, 1);
3643 - errors += unittest_check_dictionary("view", view, 0, 0, 1, 0, 1);
3644 -
3645 - dictionary_destroy(master);
3646 - dictionary_destroy(view);
3647 - return errors;
3648 -}
3649 -
3650 -/*
3651 - * FIXME: a dictionary-related leak is reported when running the address
3652 - * sanitizer. Need to investigate if it's introduced by the unit-test itself,
3653 - * or the dictionary implementation.
3654 -*/
3655 -int dictionary_unittest(size_t entries) {
3656 - if(entries < 10) entries = 10;
3657 -
3658 - DICTIONARY *dict;
3659 - size_t errors = 0;
3660 -
3661 - fprintf(stderr, "Generating %zu names and values...\n", entries);
3662 - char **names = dictionary_unittest_generate_names(entries);
3663 - char **values = dictionary_unittest_generate_values(entries);
3664 -
3665 - fprintf(stderr, "\nCreating dictionary single threaded, clone, %zu items\n", entries);
3666 - dict = dictionary_create(DICT_OPTION_SINGLE_THREADED);
3667 - dictionary_unittest_clone(dict, names, values, entries, &errors);
3668 -
3669 - fprintf(stderr, "\nCreating dictionary multi threaded, clone, %zu items\n", entries);
3670 - dict = dictionary_create(DICT_OPTION_NONE);
3671 - dictionary_unittest_clone(dict, names, values, entries, &errors);
3672 -
3673 - fprintf(stderr, "\nCreating dictionary single threaded, non-clone, add-in-front options, %zu items\n", entries);
3674 - dict = dictionary_create(
3675 - DICT_OPTION_SINGLE_THREADED | DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_VALUE_LINK_DONT_CLONE |
3676 - DICT_OPTION_ADD_IN_FRONT);
3677 - dictionary_unittest_nonclone(dict, names, values, entries, &errors);
3678 -
3679 - fprintf(stderr, "\nCreating dictionary multi threaded, non-clone, add-in-front options, %zu items\n", entries);
3680 - dict = dictionary_create(
3681 - DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_VALUE_LINK_DONT_CLONE | DICT_OPTION_ADD_IN_FRONT);
3682 - dictionary_unittest_nonclone(dict, names, values, entries, &errors);
3683 -
3684 - fprintf(stderr, "\nCreating dictionary single-threaded, non-clone, don't overwrite options, %zu items\n", entries);
3685 - dict = dictionary_create(
3686 - DICT_OPTION_SINGLE_THREADED | DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_VALUE_LINK_DONT_CLONE |
3687 - DICT_OPTION_DONT_OVERWRITE_VALUE);
3688 - dictionary_unittest_run_and_measure_time(dict, "adding entries", names, values, entries, &errors, dictionary_unittest_set_nonclone);
3689 - dictionary_unittest_run_and_measure_time(dict, "resetting non-overwrite entries", names, values, entries, &errors, dictionary_unittest_reset_dont_overwrite_nonclone);
3690 - dictionary_unittest_run_and_measure_time(dict, "traverse foreach read loop", names, values, entries, &errors, dictionary_unittest_foreach);
3691 - dictionary_unittest_run_and_measure_time(dict, "walkthrough read callback", names, values, entries, &errors, dictionary_unittest_walkthrough);
3692 - dictionary_unittest_run_and_measure_time(dict, "walkthrough read callback stop", names, values, entries, &errors, dictionary_unittest_walkthrough_stop);
3693 - dictionary_unittest_run_and_measure_time(dict, "destroying full dictionary", names, values, entries, &errors, dictionary_unittest_destroy);
3694 -
3695 - fprintf(stderr, "\nCreating dictionary multi-threaded, non-clone, don't overwrite options, %zu items\n", entries);
3696 - dict = dictionary_create(
3697 - DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_VALUE_LINK_DONT_CLONE | DICT_OPTION_DONT_OVERWRITE_VALUE);
3698 - dictionary_unittest_run_and_measure_time(dict, "adding entries", names, values, entries, &errors, dictionary_unittest_set_nonclone);
3699 - dictionary_unittest_run_and_measure_time(dict, "walkthrough write delete this", names, values, entries, &errors, dictionary_unittest_walkthrough_delete_this);
3700 - dictionary_unittest_run_and_measure_time(dict, "destroying empty dictionary", names, values, entries, &errors, dictionary_unittest_destroy);
3701 -
3702 - fprintf(stderr, "\nCreating dictionary multi-threaded, non-clone, don't overwrite options, %zu items\n", entries);
3703 - dict = dictionary_create(
3704 - DICT_OPTION_NAME_LINK_DONT_CLONE | DICT_OPTION_VALUE_LINK_DONT_CLONE | DICT_OPTION_DONT_OVERWRITE_VALUE);
3705 - dictionary_unittest_run_and_measure_time(dict, "adding entries", names, values, entries, &errors, dictionary_unittest_set_nonclone);
3706 - dictionary_unittest_run_and_measure_time(dict, "foreach write delete this", names, values, entries, &errors, dictionary_unittest_foreach_delete_this);
3707 - dictionary_unittest_run_and_measure_time(dict, "traverse foreach read loop empty", names, values, 0, &errors, dictionary_unittest_foreach);
3708 - dictionary_unittest_run_and_measure_time(dict, "walkthrough read callback empty", names, values, 0, &errors, dictionary_unittest_walkthrough);
3709 - dictionary_unittest_run_and_measure_time(dict, "destroying empty dictionary", names, values, entries, &errors, dictionary_unittest_destroy);
3710 -
3711 - fprintf(stderr, "\nCreating dictionary single threaded, clone, %zu items\n", entries);
3712 - dict = dictionary_create(DICT_OPTION_SINGLE_THREADED);
3713 - dictionary_unittest_sorting(dict, names, values, entries, &errors);
3714 - dictionary_unittest_run_and_measure_time(dict, "destroying full dictionary", names, values, entries, &errors, dictionary_unittest_destroy);
3715 -
3716 - fprintf(stderr, "\nCreating dictionary single threaded, clone, %zu items\n", entries);
3717 - dict = dictionary_create(DICT_OPTION_SINGLE_THREADED);
3718 - dictionary_unittest_null_dfe(dict, names, values, entries, &errors);
3719 - dictionary_unittest_run_and_measure_time(dict, "destroying full dictionary", names, values, entries, &errors, dictionary_unittest_destroy);
3720 -
3721 - fprintf(stderr, "\nCreating dictionary single threaded, noclone, %zu items\n", entries);
3722 - dict = dictionary_create(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_VALUE_LINK_DONT_CLONE);
3723 - dictionary_unittest_null_dfe(dict, names, values, entries, &errors);
3724 - dictionary_unittest_run_and_measure_time(dict, "destroying full dictionary", names, values, entries, &errors, dictionary_unittest_destroy);
3725 -
3726 - // check reference counters
3727 - {
3728 - fprintf(stderr, "\nTesting reference counters:\n");
3729 - dict = dictionary_create(DICT_OPTION_NONE | DICT_OPTION_NAME_LINK_DONT_CLONE);
3730 - errors += unittest_check_dictionary("", dict, 0, 0, 0, 0, 0);
3731 -
3732 - fprintf(stderr, "\nAdding test item to dictionary and acquiring it\n");
3733 - dictionary_set(dict, "test", "ITEM1", 6);
3734 - DICTIONARY_ITEM *item = (DICTIONARY_ITEM *)dictionary_get_and_acquire_item(dict, "test");
3735 -
3736 - errors += unittest_check_dictionary("", dict, 1, 1, 0, 1, 0);
3737 - errors += unittest_check_item("ACQUIRED", dict, item, "test", "ITEM1", 1, ITEM_FLAG_NONE, true, true, true);
3738 -
3739 - fprintf(stderr, "\nChecking that reference counters are increased:\n");
3740 - void *t;
3741 - dfe_start_read(dict, t) {
3742 - errors += unittest_check_dictionary("", dict, 1, 1, 0, 1, 0);
3743 - errors += unittest_check_item("ACQUIRED TRAVERSAL", dict, item, "test", "ITEM1", 2, ITEM_FLAG_NONE, true, true, true);
3744 - }
3745 - dfe_done(t);
3746 -
3747 - fprintf(stderr, "\nChecking that reference counters are decreased:\n");
3748 - errors += unittest_check_dictionary("", dict, 1, 1, 0, 1, 0);
3749 - errors += unittest_check_item("ACQUIRED TRAVERSAL 2", dict, item, "test", "ITEM1", 1, ITEM_FLAG_NONE, true, true, true);
3750 -
3751 - fprintf(stderr, "\nDeleting the item we have acquired:\n");
3752 - dictionary_del(dict, "test");
3753 -
3754 - errors += unittest_check_dictionary("", dict, 0, 0, 1, 1, 0);
3755 - errors += unittest_check_item("DELETED", dict, item, "test", "ITEM1", 1, ITEM_FLAG_DELETED, false, false, true);
3756 -
3757 - fprintf(stderr, "\nAdding another item with the same name of the item we deleted, while being acquired:\n");
3758 - dictionary_set(dict, "test", "ITEM2", 6);
3759 - errors += unittest_check_dictionary("", dict, 1, 1, 1, 1, 0);
3760 -
3761 - fprintf(stderr, "\nAcquiring the second item:\n");
3762 - DICTIONARY_ITEM *item2 = (DICTIONARY_ITEM *)dictionary_get_and_acquire_item(dict, "test");
3763 - errors += unittest_check_item("FIRST", dict, item, "test", "ITEM1", 1, ITEM_FLAG_DELETED, false, false, true);
3764 - errors += unittest_check_item("SECOND", dict, item2, "test", "ITEM2", 1, ITEM_FLAG_NONE, true, true, true);
3765 - errors += unittest_check_dictionary("", dict, 1, 1, 1, 2, 0);
3766 -
3767 - fprintf(stderr, "\nReleasing the second item (the first is still acquired):\n");
3768 - dictionary_acquired_item_release(dict, (DICTIONARY_ITEM *)item2);
3769 - errors += unittest_check_dictionary("", dict, 1, 1, 1, 1, 0);
3770 - errors += unittest_check_item("FIRST", dict, item, "test", "ITEM1", 1, ITEM_FLAG_DELETED, false, false, true);
3771 - errors += unittest_check_item("SECOND RELEASED", dict, item2, "test", "ITEM2", 0, ITEM_FLAG_NONE, true, true, true);
3772 -
3773 - fprintf(stderr, "\nDeleting the second item (the first is still acquired):\n");
3774 - dictionary_del(dict, "test");
3775 - errors += unittest_check_dictionary("", dict, 0, 0, 1, 1, 0);
3776 - errors += unittest_check_item("ACQUIRED DELETED", dict, item, "test", "ITEM1", 1, ITEM_FLAG_DELETED, false, false, true);
3777 -
3778 - fprintf(stderr, "\nReleasing the first item (which we have already deleted):\n");
3779 - dictionary_acquired_item_release(dict, (DICTIONARY_ITEM *)item);
3780 - dfe_start_write(dict, item) ; dfe_done(item);
3781 - errors += unittest_check_dictionary("", dict, 0, 0, 1, 0, 1);
3782 -
3783 - fprintf(stderr, "\nAdding again the test item to dictionary and acquiring it\n");
3784 - dictionary_set(dict, "test", "ITEM1", 6);
3785 - item = (DICTIONARY_ITEM *)dictionary_get_and_acquire_item(dict, "test");
3786 -
3787 - errors += unittest_check_dictionary("", dict, 1, 1, 0, 1, 0);
3788 - errors += unittest_check_item("RE-ADDITION", dict, item, "test", "ITEM1", 1, ITEM_FLAG_NONE, true, true, true);
3789 -
3790 - fprintf(stderr, "\nDestroying the dictionary while we have acquired an item\n");
3791 - dictionary_destroy(dict);
3792 -
3793 - fprintf(stderr, "Releasing the item (on a destroyed dictionary)\n");
3794 - dictionary_acquired_item_release(dict, (DICTIONARY_ITEM *)item);
3795 - item = NULL;
3796 - dict = NULL;
3797 - }
752 +// ----------------------------------------------------------------------------
753 +// DEL an item
754
3799 - dictionary_unittest_free_char_pp(names, entries);
3800 - dictionary_unittest_free_char_pp(values, entries);
755 +bool dictionary_del_advanced(DICTIONARY *dict, const char *name, ssize_t name_len) {
756 + if(unlikely(!api_is_name_good(dict, name, name_len)))
757 + return false;
758
3802 - errors += dictionary_unittest_views();
3803 - errors += dictionary_unittest_threads();
3804 - errors += dictionary_unittest_view_threads();
759 + api_internal_check(dict, NULL, false, true);
760
3806 - cleanup_destroyed_dictionaries();
761 + if(unlikely(is_dictionary_destroyed(dict))) {
762 + internal_error(true, "DICTIONARY: attempted to delete item on a destroyed dictionary");
763 + return false;
764 + }
765
3808 - fprintf(stderr, "\n%zu errors found\n", errors);
3809 - return errors ? 1 : 0;
766 + return dict_item_del(dict, name, name_len);
767 }
src/libnetdata/dictionary/dictionary.h
+2 -10
@@ -58,6 +58,8 @@ typedef enum __attribute__((packed)) dictionary_options {
58 DICT_OPTION_DONT_OVERWRITE_VALUE = (1 << 3), // don't overwrite values of dictionary items (default: overwrite)
59 DICT_OPTION_ADD_IN_FRONT = (1 << 4), // add dictionary items at the front of the linked list (default: at the end)
60 DICT_OPTION_FIXED_SIZE = (1 << 5), // the items of the dictionary have a fixed size
61 + DICT_OPTION_INDEX_JUDY = (1 << 6), // the default, if no other indexing is set
62 + DICT_OPTION_INDEX_HASHTABLE = (1 << 7), // use SIMPLE_HASHTABLE for indexing
63 } DICT_OPTIONS;
64
65 struct dictionary_stats {
@@ -328,14 +330,4 @@ extern struct dictionary_stats dictionary_stats_category_other;
330
331 int dictionary_unittest(size_t entries);
332
331 -// ----------------------------------------------------------------------------
332 -// THREAD CACHE
333 -
334 -void *thread_cache_entry_get_or_set(void *key,
335 - ssize_t key_length,
336 - void *value,
337 - void *(*transform_the_value_before_insert)(void *key, size_t key_length, void *value));
338 -
339 -void thread_cache_destroy(void);
340 -
333 #endif /* NETDATA_DICTIONARY_H */
src/libnetdata/dictionary/thread-cache.c new
+47
@@ -0,0 +1,47 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "thread-cache.h"
4 +
5 +static __thread Pvoid_t thread_cache_judy_array = NULL;
6 +
7 +void *thread_cache_entry_get_or_set(void *key,
8 + ssize_t key_length,
9 + void *value,
10 + void *(*transform_the_value_before_insert)(void *key, size_t key_length, void *value)
11 +) {
12 + if(unlikely(!key || !key_length)) return NULL;
13 +
14 + if(key_length == -1)
15 + key_length = (ssize_t)strlen((char *)key);
16 +
17 + JError_t J_Error;
18 + Pvoid_t *Rc = JudyHSIns(&thread_cache_judy_array, key, key_length, &J_Error);
19 + if (unlikely(Rc == PJERR)) {
20 + fatal("THREAD_CACHE: Cannot insert entry to JudyHS, JU_ERRNO_* == %u, ID == %d",
21 + JU_ERRNO(&J_Error), JU_ERRID(&J_Error));
22 + }
23 +
24 + if(*Rc == 0) {
25 + // new item added
26 +
27 + *Rc = (transform_the_value_before_insert) ? transform_the_value_before_insert(key, key_length, value) : value;
28 + }
29 +
30 + return *Rc;
31 +}
32 +
33 +void thread_cache_destroy(void) {
34 + if(unlikely(!thread_cache_judy_array)) return;
35 +
36 + JError_t J_Error;
37 + Word_t ret = JudyHSFreeArray(&thread_cache_judy_array, &J_Error);
38 + if(unlikely(ret == (Word_t) JERR)) {
39 + netdata_log_error("THREAD_CACHE: Cannot destroy JudyHS, JU_ERRNO_* == %u, ID == %d",
40 + JU_ERRNO(&J_Error), JU_ERRID(&J_Error));
41 + }
42 +
43 + internal_error(true, "THREAD_CACHE: hash table freed %lu bytes", ret);
44 +
45 + thread_cache_judy_array = NULL;
46 +}
47 +
src/libnetdata/dictionary/thread-cache.h new
+15
@@ -0,0 +1,15 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_THREAD_CACHE_H
4 +#define NETDATA_THREAD_CACHE_H
5 +
6 +#include "../libnetdata.h"
7 +
8 +void *thread_cache_entry_get_or_set(void *key,
9 + ssize_t key_length,
10 + void *value,
11 + void *(*transform_the_value_before_insert)(void *key, size_t key_length, void *value));
12 +
13 +void thread_cache_destroy(void);
14 +
15 +#endif //NETDATA_THREAD_CACHE_H
src/libnetdata/libnetdata.h
+1
@@ -737,6 +737,7 @@ extern char *netdata_configured_host_prefix;
737 #include "procfile/procfile.h"
738 #include "string/string.h"
739 #include "dictionary/dictionary.h"
740 +#include "dictionary/thread-cache.h"
741 #if defined(HAVE_LIBBPF) && !defined(__cplusplus)
742 #include "ebpf/ebpf.h"
743 #endif