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